DisARMing a Raspberry Pi - BSides San Francisco CTF 2017


Before we start, you should grab a copy of the challenge file from the CTF write-ups 2017 repository page.

The executable we are going to analyze is an ELF for ARM architecture; I took this chance to go and fetch my Raspberry Pi from my dusty drawers to put it to good use. I have installed the Ubuntu MATE for ARM image, but you can probably use whichever distribution you like.

In order to debug the program, I have used the ARM debug server that comes with IDA Pro (dbgsrv/armlinux_server) to connect to the Raspberry Pi; in case you don't have it, take a look at either Voltron or radare2.

Let's get started! Or so I thought; the executable crashes right after the first line of printed text - this is actually not a good start.

This function allocates two buffers, the first one using strdup() and the second one using malloc(). The crash happens when attempting to release the latter, clearly indicating a fault in how the program handles its allocations. Specifically, the indexes being used here are outside the boundaries of the memory it has requested, causing vital heap information being overwritten by the instruction at address 0x09F2. The buffer in question is not really used for anything useful, so I decided to patch the STR instruction with a NOP opcode (0x00, 0xBF) to avoid the issue.

I'm not going to spend more time on this function, as the program is now be able to run just fine; should you wish to investigate why the printed strings are not visible with your hexeditor, this is where you have to start looking at.

We can finally start the program and see what it asks for:

Both parameters are read inside the main() function, using two fgets() call at addresses 0x0AE4 and 0x0B50. One thing to keep in mind is that this function will stop at the newline character, including it in the returned data (take a look at the code at address 0x0B0E).

Nothing here suggests how long the input values should be, as the stack-allocated buffer used to collect the input is pretty big; this means we'll have to find another way to determine the size of the parameters.

// 0x0B54 - 0x0B6C
validator = strtol(validator_string, strlen(secret_code_string));

// 0x0B74 - 0x0B76
counter = 0;

// 0x0BA8 - 0x0BBC
while (strlen(secretcode) > counter)
{
    // 0x0B7C - 0x0B8E
    r1 = secretcode[counter];

    // 0x0B90 - 0x0B94
    r3 = fibs[r1];

    // 0x0B98 - 0x0B9A
    validator -= fibs[r1];

    // 0x0B9E - 0x0BA4
    counter++;
}

The validator value is first interpreted using the strtol() function, and then transformed using an array named fibs which happens to contain the fibonacci sequence.

Notice how the length of the secret code is used as the conversion base; popular choices indicate that we will probably have to enter 8, 10 or 16 characters for the secret code. This is an important clue, as we now know that the validator must be a number.

// 0x0BBE - 0x0BCE
dword_0AC = validator;

// 0x0BD0 - 0x0BF4
dword_0AC = xlate[secret_code[0]] ^ 'F' | dword_0AC;

// 0x0BF6 - 0x0C1C
dword_0AC = xlate[secret_code[1]] ^ 'l' | dword_0AC;

// 0x0C1E - 0x0C44
dword_0AC = xlate[secret_code[2]] ^ 'g' | dword_0AC;

// 0x0C46 - 0x0C6C
dword_0AC = xlate[secret_code[3]] ^ 'G' | dword_0AC;

The first four characters of the secret code are used as indexes in yet another sequence, this time named xlate; you will need a debugger if you want to inspect those values, as they are initialized during startup before the call to the main() entry point.

We can already guess what we need to enter here; we just have to find which indexes into the xlate array contains the characters used in the xor operation, but we'll look into this matter later, when we have a better overview of what is happening inside this function.

// 0xC6E - 0xC76
if (dword_0AC == 0)
{
    // 0xC78 - 0xC86
    r3 = strlen(secret_code);
}
else
{
    // 0x0C88
    r3 = 8;
}

// 0x0C8A - 0x0C94
counter = r3 - 4;

// 0x0C98 - 0x0CA4
read_buffer[counter] = 0;

// 0x0CDC - 0x0CE4
while (counter > 0)
{
    // 0x0CA8 - 0x0CB8
    r2 = secret_code[counter + 4];

    // 0x0CBA - 0x0CC2
    r1 = xlate[r2];

    // 0x0CC4 - 0x0CD0
    r3 = read_buffer[counter] = r1;

    // 0x0CD2 - 0x0CD8
    counter--;
}

It should be pretty clear by now that the dword_0AC variable should always evaluate to zero; the initial value for counter will then be based on the secret code length.

The read_buffer was initially used to get the parameters from the user, so it will still contains the last value read by the fgets() call which happens to be the validator.

// 0x0CE6 - 0x0CEE
if (dword_0AC == 0)
{
    // 0x0CF0 - 0x0CFE
    r2 = strlen(secret_code_string);
}
else
{
    // 0x0D02
    r2 = 8;
}

// 0x0D04 - 0x0D14
converted_read_buffer = strtoull(read_buffer, r2);

If everything works as expected, we will once again be using the secret code length as the conversion base for the strtoull() call. We don't know what's the correct secret code yet, but we at least know that it must contains what is needed for the previous transformation loop to build a number.

// 0x0D18 - 0x0D24
r3 = 0x76673250 ^ converted_read_buffer;

// 0x0D26 - 0x0D2E
r2 = r3 | dword_0AC;

// 0x0D30 - 0x0D34
dword_0AC = r2;

// 0x0D36 - 0x0D3E
if (dword_0AC == 0)
{
    // 0x0D5A - 0x0D64
    return 0;
}
else
{
    // 0x0D40 - 0x0D46
    printf("Sorry, no flag for you!");

    // 0x0D4A
    sub_994(); // this just calls exit(1);
}

The transformed read buffer must evaluate to 0x76673250, as it's the only way to keep the dword_0AC variable to zero; this last clue finally allows us to reconstruct the secret code and the validator value needed to get the flag.

Since the conversion base can be controlled by changing the length of the secret code, we have to decide how to express the value needed by the xor operation. One thing to keep in mind is that the digits we are going to use must be present inside the xlate sequence, or we will not be able to emit them during the transformation loop.

The first attempt went with the decimal base (1986474576) which requires the following digits: 1456789.

Not having much luck with that, I tried again with base 16:

The hexadecimal encoding is not only capable of expressing the value we need, but also gives us two different indexes to choose from for the digit ‘2’! I have choosen to go with the letter ‘g’, and built the final string: BttBNg<2. Having decided to go with base 16 means that the secret code will need to be exactly 16 bytes long.

We now need to retrieve the first four characters; this is not going to be hard, as they are clearly visible on the right hand side of the xor operators at addresses 0x0BD0 - 0x0C6C.

We have found another portion of the secret code, and we now have 12 bytes; the issue is that they are not enough if we want the strtol()/strtoull() functions to work correctly. I added some ‘X’ characters on the right, and here's the final string: !‘rvBttBNg<2XXXX.

The validator is the last missing piece of the puzzle; the initial loop will set it to the value entered by the user, but it is then transformed using the fibonacci sequence along with the secret code characters; this means that you will need to enter another validator if you have used a different padding than mine. The same also applies if you have used the ‘P’ character in the xlate transformation.

The value we need in this case should be 0xE3AC8677 but the strtol() function used to convert the validator works on signed 32-bit integers; this means that we can't use it as the function will return with LONG_MAX value. We can work around this issue by expressing the value we need as a negative integer: -1c537989.

We have found everything we need, and we should now able to get the flag: