Internetwache CTF 2016: SeverfARM

This is the ServerfARM task from the Internetwache CTF 2016, I got it from their official repository.

The provided file is an ELF binary for ARM7. We're asked to extract a key from it. It looks like binja has some problems with ARM; looking at the main function reveals

int32_t main(int32_t argc, char** argv, char** envp)
    char* r0
    mstate r1
    r0, r1 = malloc(bytes: 0x65)
    char* var_c = nullptr
    mstate r2_1
    while (true)
        r2_1 = argc - 1
        if (r2_1 s<= var_c)
            break
        printf("Enter Solution for task %d:", format: var_c, r2_1, var_c, argv, argc, r0, var_c)
        r1 = handle_task(var_c, r0, __isoc99_scanf(&string_format, format: r0))
        var_c = &var_c[1]  // wrong? var_c += 1 (00010704)
    free(mem: r0, r1, r2_1, var_c)
    return 0

I have added a comment to the line I think is wrongly decompiled. Why do I think it's wrong? Take a look at the disassembly:

ldr     r3, [r11,  #-0x8] {var_c}
add     r3, r3, #0x1
str     r3, [r11,  #-0x8] {var_c} ; (00010704)

Clearly, var_c is incremented by one, right? LLIL and MLIL seem to agree with me:

  26 @ 000106fc  r3 = [r11 - 8 {var_c}].d
  27 @ 00010700  r3 = r3 + 1
  28 @ 00010704  [r11 - 8 {var_c}].d = r3
  
  18 @ 000106fc  r3_1 = var_c
  19 @ 00010700  r3_2 = r3_1 + 1
  20 @ 00010704  var_c = r3_2

But HLIL messes up for some reason. Anyways, with that mental correction to the HLIL code we see that handle_task() is called until our loop counter var_c is >= argc - 1.

The handle_task function is quite big, but it's important to remember that we just want to extract a string (that is presumably printed out under some condition). We don't need to care too much about the inner workings of the program. So, with that out of the way, notice that handle_task is called with the loop counter as the first argument. The function is basically a big switch case that does entirely different things based on the loop counter.

So let us go through the possible cases and take a look at what is being printed.

For 0:

// the switch case for 0 is effectively empty but at the end of 
// the function we find:
if (arg1 == 0)
    if (r0_5 s<= 0x23)
        printf(&string_format, format: &data_10e70)  // IW{
        putchar(c: 'S')
        arg1 = printf("%c%c\n", format: '.', 'E')
    else
        arg1 = puts(str: "I{WAQ3")

It is either IW{S.E. or I{WAQ3.

For 1:

printf(&string_format, format: "Here's your 2. block:", arg3, arg1, var_20, arg1)
if (__aeabi_idivmod(zx.d(*var_20), zx.d(var_20[1])) != 'A')
    arg1 = puts(str: "WI{QA3")
else
    printf(&string_format, format: &data_10e94)
    putchar(c: 'V')
    arg1 = printf("%c%c\n", format: '.', 'E')

it is either WI{QA3 or .R.V.E

For 2:

printf(&string_format, format: "Here's your 3. block:", arg3, arg1, var_20, arg1)
if (strcmp(p1: var_20, p2: "1337") != 0)
    arg1 = printf("%c%s%c\n", format: '.', 0x10ec8, '!')  {"Q.D.Q"}
else
    arg1 = puts(str: ".R>=F:")

it is either .Q.D.Q! or .R>=F:.

For 3:

if (zx.d(*var_20) != 0)
    arg1 = printf("%c%s%c\n", format: 'A', 0x10ed8, '}', var_20, arg1)  {":R:M"}

it is A:R:M}.

Notice that exactly one combination spells out an English word, I think it is safe to say that we found the required key.

Conclusion

I'm somewhat disappointed I didn't have to debug this, an ARM debugging setup would probably have been interesting. That aside it was a fun, short challenge.