ddaa's blog
Write-ups for CTF.
ddaa's blog

HITCON 2015 PWN 200 blinkroot

HITCON CTF , dl_resolve

Share Tweet Share

這次也是一題都沒解出來
大概是沒天份吧
不過其實後來知道怎麼偽造 link_map 以後
比賽期間寫的 payload 一度已經很接近了...
只是後來方向錯了 Orz...


這題的程式非常簡單
pseudo code 長這樣:

char data[1024];
int main()
{
    if (recvlen(0, data, 1024) == 1024) {
        close(0);
        close(1);
        close(1);
        data[(int)data] = (int128)(0x1000000000 | data[8]);
        puts(data[16]);
    }
    exit(0);
}

前八個 byte 可以任意控制
所以會造成任意寫值的問題 這題不知道用什麼方式
組合語言是透過 xmm0 寫值
能寫的位置一定要對齊 16 byte (addr & 0xf == 0)
而且前 8 byte 還固定成 0x10
所以不能單純靠改 .dynamic 來解這題
那這題的作法是偽造 link_map 以後做 dl_resolve
目標讓呼叫 puts(data[16]) 變成解出 system[data[16]]

dl_resolve 是 ELF 有做 lazy binding 的時候
function call 不會直接跳進 libc
而是透過 got.plt 得到 function 的 index 後
跳到 PLT0 才解析出 function 在 libc 中的位置
簡單來說大概就是做這樣的事情
dl_runtime_resolve (link_map,index)
dl_resolve 裡面還會 call _dl_fixup
_dl_fixup 才是真正去查 libc address 的地方

以前考過的 dl_resolve 的做法
是透過偽造 index
__fix_up 去解 symbol 時落在我們偽造的 SYMTAB 上面
再讓查 st_name 時落在我們想要執行的 function 名稱
這題的沒辦法去控制 index
所以變成只能從偽造 link_map 下手

根據我比賽時的整整 12 個小時的嘗試...
完整的偽造 link_map 是不可能做到的 T__T
原因是 link_map 中有一個 l_scope 的 member
_dl_fixup 內部的 _dl_lookup_symbol_x 會用上
l_scope 會指向 link_map 本身
link_map 的結構是一個 linked_list
每個 node 保存 elf 和有使用到的 shared library symbol
_dl_lookup_symbol_x 比對所有 shared library 的 symbol
試著找出目前 function call 的這個 symbol
我們無法得知 glibc 的 link_map ... 所以不可能偽造成功 QQ

dl_resolve 還有一個利用方式是:
如果 function 已經被解析過
dl-runtime.c:90

if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) { ... }
else { ... }

會直接進入 else, 不會進入 _dl_lookup_symbols_x
直接透過 link_map->l_addr + sym->st_value 得到結果
這兩個值都可以透過偽造 link_map 來控制
如果在已知 libc 版本的情況下
我們可以讓 l_addrst_value 其中一個是以解析過的 function
另一個則透過 libc 算出適當的 offset
就可以跳到任意函式了
還有一個要注意的是
原本 dl_resolve 解析完會將結果寫回 GOT 上
但是 offset 亂掉了結果可能會是一個不能寫的區段
所以還要偽造 JMPREL 結果能寫回去才行
至於要寫到哪裡就隨意了 反正之後不會用上

這題我先嘗試讓 link_map 落在 __libc_start_main 的 GOT
這樣 l_addr 就會是 __libc_start_main 的 address
再偽造 STMTABJMPREL 得到 st_value 並算出 system 的位置
結果是成功的...但是這題有個問題是
如果這樣子偽造, link_map 會在 data - 0x48 的位置
但是 SYMTAB 的位置在 link_map + 0x68 == data + 0x20
puts 的參數卻是 data[0x10]...
所以能執行的指令就變成不能超過 16 byte XD
對於一般的題目倒也沒差
但是這題把 fd 都關了所以只能把執行結果送回來而已
16 byte 根本不夠用 Orz

第二次的做法就變成讓 link_map 完整的落在 data[512] 上
l_addr 可以隨意控制
再將 SYMTAB 偽造到 GOT 上
滿足 st_other != 0st_value == libc address
一樣要偽造 JMPREL 讓結果可以寫回去
就可以解出任意的 libc function 了
後來想想第二種的做法似乎限制比較少
更好利用

總結一下透過 st_other 的利用條件:

  1. 已經有 glibc 可以算 offset
  2. 有大約 0x140 以上的 buffer 可以偽造 link_map
  3. 取決於 function index, 越後面所需空間越大
  4. 可以 return 到 plt 上, 或是可以改 got 上的 link_map
  5. 要已知可寫的 address ... 所以開 PIE 這招大概還是不能用 Orz

flag: hitcon{81inkr0Qt I$ #B|InK1n9#}