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.

root@raspberrykoma:/# ./disarming.arm
To disarm, you need a secret code and some validators!
*** Error in `./disarming.arm': free(): invalid next size (fast): 0x555aa430 ***
Please enter secret code: Aborted
.text:099E  PUSH {R1-R3}
.text:09A0  PUSH {R7,LR}
.text:09A2  SUB SP, SP, #0x2C
.text:09A4  ADD R7, SP, #0
.text:09A6  STR R0, [R7,#0x2C+var_28]
.text:09A8  LDR R3, [R7,#0x2C+var_28]
.text:09AA  MOV R0, R3
.text:09AC  BL sub_E68
.text:09B0  MOV R3, R0
.text:09B2  AND.W R3, R3, #1
.text:09B6  STR R3, [R7,#0x2C+var_28]
.text:09B8  ADD.W R3, R7, #0x38
.text:09BC  STR R3, [R7,#0x2C+arg]
.text:09BE  LDR R0, [R7,#0x2C+varg_r1]
.text:09C0  BLX strdup                 ; first allocation
.text:09C4  MOV R3, R0
.text:09C6  STR R3, [R7,#0x2C+s]
.text:09C8  LDR R0, [R7,#0x2C+s]
.text:09CA  BLX strlen                 ; size passed to malloc()
.text:09CE  MOV R3, R0
.text:09D0  MOV R0, R3
.text:09D2  BLX malloc                 ; second allocation
.text:09D6  MOV R3, R0
.text:09D8  STR R3, [R7,#0x2C+ptr]
.text:09DA  LDR R3, [R7,#0x2C+ptr]
.text:09DC  STR R3, [R7,#0x2C+var_C]
.text:09DE  LDR R0, [R7,#0x2C+varg_r1]
.text:09E0  BLX strlen
.text:09E4  MOV R3, R0
.text:09E6  ADDS R3, #3
.text:09E8  LSRS R3, R3, #2
.text:09EA  STR R3, [R7,#0x2C+var_8]
.text:09EC  B loc_A18
.text:0A76  LDR R0, [R7,#0x2C+s]
.text:0A78  BLX free
.text:0A7C  LDR R0, [R7,#0x2C+ptr]     ; crash!
.text:0A7E  BLX free

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:

root@raspberrykoma:/# ./disarming.arm
Please enter secret code: test
Please enter first validator: test

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.

digit   xlate[] index

1       N/A
4       N/A
5       60  '<'
6       116	't'
7       66  'B'
8       122 'z'
9       N/A

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

digit   xlate[] index

0       50  '2'
2       80  'P'
2       103 'g'
3       78  'N'
5       60  '<'
6       116 't'
7       66  'B'

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.

char    xlate[] index
F       33  '!'
l       39  '\''
g       114 'r'
G       118 'v'

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:

Please enter secret code: !'rvBttBNg<2XXXX
Please enter first validator: -1c537989
Good enough: FlgG76673250