/* 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;
}