RyanHub - file viewer
filename: game.c
branch: main
back to repo
/* game.c - turn based text game 'governors mansion'
 * compile:
 *   gcc -o game game.c -Wall
 * vulnerablility:
 *   when run in debug mode, the debug inspect function contains
 *   an oportunity for a 'format string attack'
 * how to solve:
 *   either use objdump or read description to spot that there is a debug function
 *   test common debug flags until --debug yeilds the debug mode
 *   observe the debug function and notice the malformed print from it
 *   strings can be tested at program start or upon death(anywhere you see debug run)
 *   test values like %p %p %p %p and see what they print out
 *   eventually determine and verify the memory location/printf positional info of governor
 *   construct a string to write a 1 to the governor byte
 *   go to the governors mansion and interact with it in order to get the flag.
 * notes:
 *   challenges are finding debug mode, verifing memory layout, and writing the governor byte
 *   providing insight on any of those makes this easier
 * platforms:
 *   this is designed for and written in linux(ubuntu to be specific)
 *   while this should have no issues compiling elsewhere, i cant gaurentee the same functionality
 *   i know for sure that some windows command lines will not interpret strings the same way...
 */

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

#define BUFSIZE 32

#define MAP_W 25
#define MAP_H 11

#define MAP_STEP 5
#define POI_G_POS 1
#define POI_B_POS 2
#define POI_S_POS 3

// storing key as a string or in a print would allow searching the binary for text with flag, this should prevent that
static unsigned char enc_flag[] = {
    0x14, 0x13, 0x19, 0x19, 0x21, 0x12, 0x1F, 0x16, 0x16, 0x15, 0x1D, 0x15, 0x0C, 0x1F, 0x08, 0x14, 0x1F, 0x08, 0x27
};
// XOR with 0x5A = "NICC{HELLOGOVERNER}"

// global state
static int pos = 0;
static int turn = 0;
// copies of command line args
static int argc_g;
static char **argv_g;

// the layout of player is important to solution
typedef struct Player {
    char name[BUFSIZE];
    int health;
    int governor;
} Player;

typedef void (*ActionFn)(Player *);
typedef int (*AvailFn)(void);

struct Action {
    char key;
    const char *desc;
    ActionFn fn;
    AvailFn avail;
};

static void debug_inspect(Player *p, char *buf) { // vulnerable function

    // a quick glance and this isnt destructive... i just messed up the syntax, its debug only anyways
    // however, when this runs, buf expands into the format string, 
    // this allows for us to print arbitrary values or even write over them

    // this function should be shared in some way so that people know to find a way to get debug to run and then fool with format strings

    if (argc_g > 1 && strcmp(argv_g[1], "--debug") == 0) {

        printf("\nDEBUG\n");
        //printf("buf  &name  governor  &governor\n");
        printf("printf(buf, &p->name, p->governor, &p->governor)\n"); // just to make it more clear whats running
        printf(buf, &p->name, p->governor, &p->governor);
        printf("\n\n");
    }

    // some examples of how to exploit this
    // A%3$hhn  <-  solution string, sets the lowest index of the governor byte to 1
    // %1$p %3$p  <-  prints address of name and address of governor, intermediate step to verify location of governer byte

}

Player *init() {

    // sets up player object, does some prints, takes input for name

    Player *p = malloc(sizeof(Player));
    // dynamic to avoid arbitrary stack nonsense
    if (!p) { perror("cant allocate player"); exit(1); }
    memset(p, 0, sizeof(*p));
    p->health = 100;
    p->governor = 0;

    printf("-- welcome to governors mansion --\n");
    printf("your mission is to get into the governors mansion\n");
    printf("you may explore the world using the controls listed\n");
    printf("good luck and have fun....\n");
    printf(" - ryan (find me at [email protected])\n\n");

    printf("enter your name and you may begin [max %d chars]: ", BUFSIZE);
    if (fgets(p->name, sizeof(p->name), stdin)) {
        p->name[strcspn(p->name, "\n")] = '\0';
    } else {
        p->name[0] = '\0';
    }

    // this is the first oportunity for modifying the governor bit
    debug_inspect(p, p->name);
    // enter something in name and see what happens....

    printf("\n");

    return p;
}

static void map() {
    
    // just a pretty little print function for a map with some little frills

    char grid[MAP_H][MAP_W];
    int mid = MAP_H / 2;

    // grass or something
    for (int y = 0; y < MAP_H; ++y) {
        for (int x = 0; x < MAP_W; ++x) {
            if (y < mid - 1 || y > mid + 1) {
                grid[y][x] = ((x + y) % 7 == 0) ? '"' : ' ';
            } else {
                grid[y][x] = ' ';
            }
        }
    }

    // main trail
    for (int x = 0; x < MAP_W; ++x) {
        grid[mid][x] = '-';
    }

    // waypoints
    int pos_x[MAP_STEP];
    for (int i = 0; i < MAP_STEP; ++i) {
        pos_x[i] = (MAP_STEP == 1) ? 0 : (i * (MAP_W - 1)) / (MAP_STEP - 1);
        grid[mid][pos_x[i]] = '+';
    }

    int gx = pos_x[POI_G_POS];
    int bx = pos_x[POI_B_POS];
    int sx = pos_x[POI_S_POS];

    // governor
    if (mid > 0) {
        grid[mid - 1][gx] = '[';
        if (gx + 1 < MAP_W) grid[mid - 1][gx + 1] = 'G';
        if (gx + 2 < MAP_W) grid[mid - 1][gx + 2] = ']';
    }

    // buffalos
    if (mid + 1 < MAP_H) {
        if (bx > 0) grid[mid + 1][bx - 1] = '(';
        grid[mid + 1][bx] = 'B';
        if (bx + 1 < MAP_W) grid[mid + 1][bx + 1] = ')';
    }

    // saloon
    if (mid > 1) {
        if (sx > 0) grid[mid - 1][sx - 1] = '{';
        grid[mid - 1][sx] = 'S';
        if (sx + 1 < MAP_W) grid[mid - 1][sx + 1] = '}';
    }

    // player
    int px = pos < 0 ? pos_x[0] : (pos >= MAP_STEP ? pos_x[MAP_STEP - 1] : pos_x[pos]);
    grid[mid][px] = 'P';

    // border
    for (int x = 0; x < MAP_W + 2; ++x) putchar('-');
    putchar('\n');

    for (int y = 0; y < MAP_H; ++y) {
        putchar('|');
        for (int x = 0; x < MAP_W; ++x) putchar(grid[y][x]);
        putchar('|');
        putchar('\n');
    }

    for (int x = 0; x < MAP_W + 2; ++x) putchar('-');
    putchar('\n');

    // legend
    printf("P=you  G=governor  B=buffalos  S=saloon\n");
}

// helpers
static int avail_always(void) { return 1; }
static int avail_left(void) { return pos > 0; }
static int avail_right(void) { return pos < MAP_STEP - 1; }
static int avail_g(void) { return pos == POI_G_POS; }
static int avail_b(void) { return pos == POI_B_POS; }
static int avail_s(void) { return pos == POI_S_POS; }
void print_flag() {
    
    // prints the flag so that we dont have to worry about plaintext 'flag' in the binary

    int n = sizeof(enc_flag);
    unsigned char key = 0x5A;
    
    char *flag = malloc(n);
    if (!flag) return;

    for (size_t i = 0; i < n; ++i) flag[i] = enc_flag[i] ^ key;
    printf("- here is your flag: %s -\n", flag);
    
    // free
    memset(flag, 0, n); 
    free(flag);
}

// actions
static void act_quit(Player *p) {
    (void)p;
    printf("goodbye...\n");
    exit(0);
}
static void act_die(Player *p) {
    (void)p;
    printf("\nany last words [max 256]? \n");

    char tomb[256];
    if (fgets(tomb, sizeof(tomb), stdin)) {
        size_t n = strlen(tomb);
        if (n && tomb[n-1] == '\n') { tomb[n-1] = '\0'; --n; }
    } else {
        tomb[0] = '\0';
        clearerr(stdin);
    }

    // second oportunity to win the challenge
    debug_inspect(p, tomb);
    // enter some nice string in the tombstone message

    printf("\n------------------\n");
    printf("HERE LIES %s\n", p->name);
    printf("survived for %d turns\n", turn);
    printf("final words: \n%s\n", tomb);
    printf("------------------\n\n");

    if (p->governor == 1) printf("looks like he did somehow become the governor, interesting...\n");
    else if (p->governor) printf("bizzare, he did modify the value of the governor field, but it must be = 1...\n");
    else printf("he never did become the governor...\n");

    printf("hit 'q' to exit or any other key to continue on... ");
    char rbuf[8];
    if (fgets(rbuf, sizeof(rbuf), stdin))
        if (rbuf[0] == 'q') act_quit(p);
    printf("\nstarting again...\n\n");

}
static void act_left(Player *p) {
    (void)p;
    if (pos > 0) {
        pos--;
        printf("- you moved left -\n");
    } else {
        printf("- you cant move left -\n");
    }
}
static void act_right(Player *p) {
    (void)p;
    if (pos < MAP_STEP - 1) {
        pos++;
        printf("- you moved right -\n");
    } else {
        printf("- you cant move right -\n");
    }
}

static void act_governor(Player *p) {
    (void)p;
    if (p->governor == 1) {
        printf("- welcome back governor! -\n");
	print_flag();
    } else {
        printf("- you dont look like the governor... -\n");
    }
}
static void act_buffalos(Player *p) {
    (void)p;
    printf("- you come upon a herd of buffalos! -\n");

    int r = rand() % 100;
    if (r < 30) {
        printf("- the buffalos stampede and kill you -\n");
        act_die(p);
    } else if (r < 70) {
        p->health += 10;
        printf("- you shoot a buffalo and eat it to gain 10 health, good for you -\n");
    } else {
        printf("- the herd passes by uneventfully, arent they pretty -\n");
    }
}
static void act_saloon(Player *p) {
    (void)p;
    printf("- you step into the saloon -\n");

    int r = rand() % 100;
    if (r < 20) {
        printf("- you get too drunk and die -\n");
        act_die(p);
    } else if (r < 80) {
        p->health += 10;
        printf("- you enjoy a nice water and gain 10 health, lovely -\n");
    } else {
        printf("- you have a drink and make some friends, aww, thats sweet -\n");
    }
}

// action table
static struct Action actions[] = {
    { 'q', "quit", act_quit, avail_always },
    { 'l', "left", act_left, avail_left },
    { 'r', "right", act_right, avail_right },
    { 'g', "enter governors mansion", act_governor, avail_g },
    { 'b', "go see the buffalos", act_buffalos, avail_b },
    { 's', "enter the saloon", act_saloon, avail_s },
};
int option_count = sizeof(actions) / sizeof(actions[0]);

int main(int argc, char **argv) {

    // sets up and then runs input=>action loop

    if (argc > 1) {
        argc_g = argc;
        argv_g = argv;
        if(strcmp(argv_g[1], "--debug") == 0) printf("debug mode enabled\n\n");
    }

    Player *me = init();
    srand((unsigned)time(NULL));
    char buf[BUFSIZE];

    while (1) {
        turn++;
        map();
        printf("you are at tile: %d, health: %d\n", pos, me->health);

        for (int i = 0; i < option_count; ++i) {
            if (actions[i].avail()) {
                printf("%c) %s\n", actions[i].key, actions[i].desc);
            }
        }

        printf("choose an option: ");
        if (!fgets(buf, sizeof(buf), stdin)) break;
        buf[strcspn(buf, "\n")] = '\0';
        if (strlen(buf) != 1) { printf("unknown command.\n"); continue; }

        char first = buf[0];
        int handled = 0;
        for (int i = 0; i < option_count; ++i) {
            if (actions[i].avail() && actions[i].key == first) {
                actions[i].fn(me);
                handled = 1;
                break;
            }
        }
        if (!handled) printf("unknown command.\n");
    }

    free(me);
    return 0;
}