RPI Binary Bomb

blevy

2018/08/27

Categories: reverse-engineering

Binary

bomb

Reversed source code

bomb.c

// Functions that are not relevant to defusing the bomb are marked "STUB"

static void libc_start_main(void *arg)
{
	// STUB
}

static void disarm_handler(void)
{
	// STUB
}

// Setting all wires to 0 defuses the bomb
static int wire_yellow = 1;
static int wire_green = 1;
static int wire_blue = 1;
static int wire_red = 1;

/*
 * This function gets the amount of time left to display in timer. Since this
 * lab is over (it was originally given as a lab in a class at RPI), the timer
 * is blank.
 */
static int gettimeleft(int *arg1, int *arg2, int *arg3)
{
	// STUB
}

// Print the menu
static void menu(void)
{
	// STUB
}

/*
 * All of the functions with names ending in "_preflight" except
 * green_preflight() use this to read in input.
 */
static char buffer[24];

static void yellow_preflight(void)
{
	printf("ENTER_UNLOCK_PASSWORD_1:");
	fgets(buffer, 10, stdin);
}

/*
 * Password: "84371065"
 *
 * This one is just a simple check. :)
 */
static void yellow(void)
{
	yellow_preflight();
	if (strncmp(buffer, "84371065", 10) == 0) {
		puts("UNLOCK_PASSWORD_1_ACCEPTED__LOCK_DISENGAGED");
		wire_yellow = 0;
		return;
	}
	wire_yellow <<= 0xa;
}

static void green_preflight(char *buf)
{
	printf("ENTER_UNLOCK_PASSWORD_2:");
	fgets(buf, 0x14, stdin); // UNSAFE
}

/*
 * Password: "dcaotdaeAAAAb"
 *
 * The fgets() in green_preflight() allows buf to overflow into mystery. Since
 * it's being bitwise and'ed with 1, the overwritten value needs to be even for
 * the mystery == 0 at the end to be true. Dividing by 2 changes the default
 * value 1 to 0. Additionally, the strncmp() requires that the first 8
 * characters match "dcaotdae". The "AAAA" overwrites the full length of the
 * buffer. Since x86 is little-endian, the first byte is least-significant and
 * determines if mystery is even. The ASCII value of 'b' is even, which makes
 * the bitwise and with 1 set mystery to zero. This function has stack smash
 * protection enabled, and the stack guard is directly after mystery. Care must
 * be taken to ensure that it is not overwritten. The fgets() inserts a newline
 * and null byte at the end of the input, which sets the first 3 bytes of
 * mystery to "b\n\0". Since mystery is 4 bytes, the buffer does not overflow
 * into the stack guard.
 */
static void green(void) {
	char buf[0xc];
	int mystery = 1;
	green_preflight(buf);
	if (strncmp("dcaotdae", buf, 8) == 0) {
		// [gh]
		puts("UNLOCK_PASSWORD_2_ACCEPTED__LOCK_DISENGAGED");
		mystery &= 1;
		usleep(0x7a120);
		puts("ACTION_OVERRIDDEN_FROM_USER_NOIZEV__LOCK_RE_ENGAGED");
		// Oh no I'm doomed!
		mystery &= 1;
	} else {
		// [gc]
		wire_green *= 2;
	}
	// [gg]
	if (mystery == 0) {
		// [gj]
		wire_green /= 2;
	}
}

static void blue_preflight(void)
{
	printf("ENTER CIRCUIT TRAVERSAL PATH:");
	fgets(buffer, 0x10, stdin);
}

struct node {
	struct node *next;
};

// sizeof(graph) = 192
// sizeof(graph) / sizeof(node) = 48
struct node graph[48] = {
	0x0804c19c, 0x47bbfa96,
	0x0804c178, 0x0804c214,
	0x50171a6e, 0x0804c1b4,
	0x0804c1d8, 0x23daf3f1,
	0x0804c1a8, 0x0804c19c,
	0x634284d3, 0x0804c1c0,
	0x0804c1f0, 0x344c4eb1,
	0x0804c1fc, 0x0804c1cc,
	0x0c4079ef, 0x0804c214,
	0x0804c178, 0x425ebd95,
	0x0804c184, 0x0804c1cc,
	0x07ace749, 0x0804c1a8,
	0x0804c1e4, 0x237a3a88,
	0x0804c184, 0x0804c1f0,
	0x4b846cb6, 0x0804c184,
	0x0804c214, 0x1fba9a98,
	0x0804c1c0, 0x0804c19c,
	0x3a4ad3ff, 0x0804c1c0,
	0x0804c184, 0x16848c16,
	0x0804c178, 0x0804c190,
	0x499ee4ce, 0x0804c1b4,
	0x0804c1c0, 0x261af8fb,
	0x0804c184, 0x0804c1cc,
	0x770ea82a, 0x0804c1fc
};

static uint32_t solution = 0x40475194;

/*
 * Password: "LLRR"
 *
 * The goal is to make berry == solution by xoring it with successive values
 * using L and R commands. I implemented the checker in python and brute
 * forced. See solve_blue.py.
 */
static void blue(void)
{
	blue_preflight();
	struct node *node = graph;
	struct node *berry = node + 1;
	int letter;
	bool end;
	for (int i = 0; i <= 0xe; i++) {
		// [ge]
		end = false;
		letter = buffer[i];
		if (letter == 'L') {
			// [gd]
			node = node->next;
		} else if (letter == 'R') {
			// [gf]
			node = (node + 2)->next;
			// goto [gl]
		} else if (letter == '\n') {
			// [gh]
			end = true;
		} else {
			// [gk]
			// [gj]
			end = true;
			puts("boom");
		}
		// [gl]
		if (end) {
			break;
		}
		// [go]
		berry ^= node->berry;
	}
	// [gn]
	printf("PROGRAMMING GATE ARRAY...");
	fflush(stdout);
	sleep(1);
	puts("SUCCEEDED");
	usleep(0x7a120);
	if (berry == solution) {
		puts("VOLTAGE_REROUTED_FROM_REMOTE_DETONATION_RECEIVER");
		wire_blue--;
	} else {
		// [gt]
		wire_blue++;
	}
}

static uint32_t r[3];

/*
 * In addition to the usual fgets() into the buffer, this function populates
 * and prints the r[3] array with random values. Running this several times,
 * you can see that the values are the same each time, which means that an
 * earlier function called srand() with a constant seed to make the randomness
 * deterministic.
 */
static void red_preflight(void)
{
	r[0] = rand() & 0x7fffffff; // 0x6B8B4567
	r[1] = rand(); // 0x327B23C6
	r[2] = rand(); // 0x643C9869
	for (int i = 0; i <= 2; i++) {
		// [gf]
		printf("CLOCK SYNC %08X\n", r[i]);
		usleep(0x7a120);
	}
	// [gh]
	printf("ENTER_CLOCK_RESYNCHRONIZATION_SEQUENCE:");
	fgets(buffer, 0x15, stdin);
}

/*
 * Password: "KDG3DU32D38EVVXJM64"
 *
 * Since each character of the buffer is checked individually, this algorithm
 * can be modified from a checker to a generator. See solve_red.py for an
 * example generator.
 */
static void red(void)
{
	red_preflight();
	const char *alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
	for (int i = 0; i <= 0x12; i++) {
		// [ge]
		if (buffer[i] != (alphabet[r[2] & 0x1f])) {
			wire_red++;
			return;
		}
		// [gd]
		r[2] = (r[2] >> 5) | (r[1] << 0x1b);
		r[1] = (r[1] >> 5) | (r[0] << 0x1b);
		r[0] = r[0] >> 5;
	}
	wire_red = 0;
	return;
}

int main(void)
{
	pthread_t thread;
	FILE *input;

	pthread_create(&thread, NULL, libc_start_main, NULL);
	signal(SIGUSR1, disarm_handler);
	bool running = true;
	for (;;) {
		if (running) {
			menu();
		}
		printf("MENU SELECTION: ");
		input = stdin;
		fgets(buf, 0x14, stdin);
		if (strcmp(buf, "1\n") == 0
				|| strcasecmp(buf, "yellow\n") == 0) {
			yellow();
		} else if (strcmp(buf, "2\n") == 0
				|| strcasecmp(buf, "green\n") == 0) {
			green();
		} else if (strcmp(buf, "3\n") == 0
				|| strcasecmp(buf, "blue\n") == 0) {
			blue();
		} else if (strcmp(buf, "4\n") == 0
				|| strcasecmp(buf, "red\n") == 0) {
			red();
		} else if (strcasecmp(buf, "exit\n") == 0
				|| strcasecmp(buf, "quit\n") == 0) {
			return;
		} else if (strcasecmp(buf, "disarm\n") == 0) {
			raise(SIGUSR1);
		} else if (buf[0] == '\n') {
			running = false;
			continue;
		} else {
			wire_green = 2;
		}
		printf("PRESS ENTER TO RETURN TO MENU");
		getchar();
		running = true;
	}
}

Correct input

input.txt

1
84371065

2
dcaotdaeAAAAb

3
LLRR

4
KDG3DU32D38EVVXJM64

disarm