For the complete documentation index, see llms.txt. This page is also available as Markdown.

Sunshine Factory

Buffer Overflow + Partial Overwrite

Analysis

We are given source code, which reveals a nested structure design where a sunshine struct contains a pointer to a checking_struct.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

struct checking_struct {
    char canary[8];
    void (*check_fail_fn)(void);
};

struct sunshine {
    void* ptr;
    struct checking_struct* checker;
};

void check_heap_fail() {
    puts("Heap smashing detected!");
    _exit(0);
}

void win() {
    system("/bin/sh");
}

void banner() {
    puts("Choose an option:");
    puts("1. Allocate sunshine");
    puts("2. Free sunshine");
    printf("> ");
}

int main() {
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    char heap_canary[8];
    int fd = open("/dev/urandom", O_RDONLY);
    read(fd, heap_canary, 8);
    struct sunshine* main_sunshine = NULL;
    while (true) {
        banner();
        int option;
        scanf("%d", &option);
        getchar();
        if (option == 1) { 
            printf("Enter sunshine size needed: ");
            ssize_t mem_size_needed;
            scanf("%lu", &mem_size_needed);
            getchar();
            if (mem_size_needed <= 0) {
                puts("Invalid sunshine size requested!");
                continue;
            }
            void* ptr = calloc(1, mem_size_needed);
            struct checking_struct* curchecker = calloc(1, sizeof(struct checking_struct));
            memcpy(curchecker->canary, heap_canary, 8);
            curchecker->check_fail_fn = check_heap_fail;
            struct sunshine* cursunshine = calloc(1, sizeof(struct sunshine));
            cursunshine->ptr = ptr;
            cursunshine->checker = curchecker;
            printf("Enter sunshine content: ");
            read(0, ptr, 0x100);
            puts("Sunshine created!");
            main_sunshine = cursunshine;
        } else if (option == 2) {
            if (main_sunshine == NULL) {
                puts("You need to create a sunshine first!");
                continue;
            }
            if (strncmp(main_sunshine->checker->canary, heap_canary, 8) != 0) {
                main_sunshine->checker->check_fail_fn();
            } else {
                free(main_sunshine->ptr);
                free(main_sunshine->checker);
                free(main_sunshine);
                main_sunshine = NULL;
            }
        } else {
            puts("Invalid choice!");
            continue;
        }
    }
}  

There is canary check as well, which redirects to check_fail_fn if the canary in the structure does not match the canary on the stack.

We also get a controlled buffer overflow as we control the size of the sunshine structure that we request but read takes in 0x100 bytes.

Piecing it together, we can do the following

  1. Request a small sunshine of 8 bytes

  2. Overwrite the canary so that it fails the check and triggers check_fail_fn

  3. Overwrite check_fail_fn to win

However there is PIE enabled which means that the addresses are randomized upon runtime. However, the last 12 bits (3 hex characters) are always the same.

Using this information, we can perform a partial overwrite, overwriting the last nibble to 0xCA and we should be able to trigger win.

We can find the offset to overwrite the last nibble with a cyclic payload and setting a breakpoint at call rax which corresponds to main_sunshine->checker->check_fail_fn();

We can see that we need 40 bytes before it starts overwriting check_fn_fail. Thus, we can simply append the least-significant byte of win to a 40 byte payload, which will get us the flag!

Solve Script

Last updated