Infosec Scribbles

August 4, 2019

Crackmes Series: ARM, part 2

Recently, I decided to put my reversing skills to a test by solving some of the public crackmes and writing down my solutions. This is the second post of the series.

For work, I do some very basic Android, TEE, RASP and IoT reversing, but most of those tasks are too shallow and repetitive. You can’t get much depth when reversing is an “if there’s time to waste” part of an engagement with overall length of a week and bottomless scope, so this series is about a monkey learning to dive deeper.


I decided to source my first batch of challenges from Crackmes.One. Second challenge is of difficulty level 2 and is targeting ARM/Linux.

Running it

First, figure out what the binary is:

$ file diff2_bin 
diff2_bin: ELF 32-bit LSB executable, ARM, EABI4 version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, with debug_info, not stripped
$ readelf -a diff2_bin
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x8150
  Start of program headers:          52 (bytes into file)
  Start of section headers:          496100 (bytes into file)
  Flags:                             0x4000002, Version4 EABI, <unknown>
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           40 (bytes)
  Number of section headers:         34
  Section header string table index: 31

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .note.ABI-tag     NOTE            00008114 000114 000020 00   A  0   0  4
  [ 2] .init             PROGBITS        00008134 000134 000014 00  AX  0   0  4
  [ 3] .text             PROGBITS        00008150 000150 05d3a8 00  AX  0   0 16
  [ 4] __libc_freeres_fn PROGBITS        000654f8 05d4f8 000cac 00  AX  0   0  4
  [ 5] .fini             PROGBITS        000661a4 05e1a4 000010 00  AX  0   0  4
  [ 6] .rodata           PROGBITS        000661b8 05e1b8 0144e0 00   A  0   0  8
  [ 7] __libc_atexit     PROGBITS        0007a698 072698 000004 00   A  0   0  4
  [ 8] __libc_subfreeres PROGBITS        0007a69c 07269c 00002c 00   A  0   0  4
  [ 9] .ARM.extab        PROGBITS        0007a6c8 0726c8 00030c 00   A  0   0  4
  [10] .ARM.exidx        ARM_EXIDX       0007a9d4 0729d4 000768 00  AL  3   0  4
  [11] .eh_frame         PROGBITS        0007b13c 07313c 000080 00   A  0   0  4
  [12] .tdata            PROGBITS        00083fb4 073fb4 000010 00 WAT  0   0  4
  [13] .tbss             NOBITS          00083fc4 073fc4 000018 00 WAT  0   0  4
  [14] .init_array       INIT_ARRAY      00083fc4 073fc4 000004 00  WA  0   0  4
  [15] .fini_array       FINI_ARRAY      00083fc8 073fc8 000008 00  WA  0   0  4
  [16] .jcr              PROGBITS        00083fd0 073fd0 000004 00  WA  0   0  4
  [17] .data.rel.ro      PROGBITS        00083fd4 073fd4 00002c 00  WA  0   0  4
  [18] .got              PROGBITS        00084000 074000 00006c 04  WA  0   0  4
  [19] .data             PROGBITS        00084070 074070 0006e4 00  WA  0   0  8
  [20] .bss              NOBITS          00084758 074754 001880 00  WA  0   0  8
  [21] __libc_freeres_pt NOBITS          00085fd8 074754 000014 00  WA  0   0  4
  [22] .comment          PROGBITS        00000000 074754 004472 00      0   0  1
  [23] .debug_aranges    PROGBITS        00000000 078bc8 000058 00      0   0  8
  [24] .debug_pubnames   PROGBITS        00000000 078c20 000025 00      0   0  1
  [25] .debug_info       PROGBITS        00000000 078c45 00017b 00      0   0  1
  [26] .debug_abbrev     PROGBITS        00000000 078dc0 00006f 00      0   0  1
  [27] .debug_line       PROGBITS        00000000 078e2f 000130 00      0   0  1
  [28] .debug_str        PROGBITS        00000000 078f5f 0000a4 01  MS  0   0  1
  [29] .debug_ranges     PROGBITS        00000000 079008 000048 00      0   0  8
  [30] .ARM.attributes   ARM_ATTRIBUTES  00000000 079050 00002d 00      0   0  1
  [31] .shstrtab         STRTAB          00000000 07907d 000167 00      0   0  1
  [32] .symtab           SYMTAB          00000000 079734 00bf00 10     33 1925  4
  [33] .strtab           STRTAB          00000000 085634 006394 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (purecode), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x0729d4 0x0007a9d4 0x0007a9d4 0x00768 0x00768 R   0x4
  LOAD           0x000000 0x00008000 0x00008000 0x731bc 0x731bc R E 0x8000
  LOAD           0x073fb4 0x00083fb4 0x00083fb4 0x007a0 0x02038 RW  0x8000
  NOTE           0x000114 0x00008114 0x00008114 0x00020 0x00020 R   0x4
  TLS            0x073fb4 0x00083fb4 0x00083fb4 0x00010 0x00028 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x073fb4 0x00083fb4 0x00083fb4 0x0004c 0x0004c R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   01     .note.ABI-tag .init .text __libc_freeres_fn .fini .rodata __libc_atexit __libc_subfreeres .ARM.extab .ARM.exidx .eh_frame 
   02     .tdata .init_array .fini_array .jcr .data.rel.ro .got .data .bss __libc_freeres_ptrs 
   03     .note.ABI-tag 
   04     .tdata .tbss 
   05     
   06     .tdata .init_array .fini_array .jcr .data.rel.ro 

There is no dynamic section in this file.

There are no relocations in this file.

Unwind section '.ARM.exidx' at offset 0x729d4 contains 237 entries:
 ...skip...

Symbol table '.symtab' contains 3056 entries:
 ...skip...

No version information found in this file.

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.16
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "ARM10TDMI"
  Tag_CPU_arch: v5TE
  Tag_ARM_ISA_use: Yes
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_enum_size: int

This is a 32-bit ARM v5TE binary, built for Linux Kernel 2.6.16. Once again, I didn’t have a compatible system handy, so I used the same QEMU image as in part 1. ARMv7-A is backwards compatible with ARMv5TE for my purposes, so this will work.

This time, the binary has a lot of symbols, which could mean that it will be easier to reverse or that they were added by an obfuscator. After all, it’s in excess of 3000.

Upon running the binary, it exits with code 255:

user@debian-armhf:~$ ./diff2_bin
user@debian-armhf:~$ echo $?
255

This could indicate further issues with my QEMU setup or it could be by design. The code will reveal which case it is.

Step 1 complete: get the binary to run.

Reversing

At a glance, it seems that this binary has everything it needs statically compiled into it. Symbols include a lot of standard library functions and there are no imports. At 559KB compared to 5.5KB of the previous challenge, this is quite believable.

Inside main(), I see this theory confirmed:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // r1
  int v7; // [sp+14h] [bp-20h]
  int v8; // [sp+18h] [bp-1Ch]
  int v9; // [sp+18h] [bp-1Ch]
  int i; // [sp+18h] [bp-1Ch]
  signed int v11; // [sp+1Ch] [bp-18h]

  if ( argc <= 1 )
    return -1;
  v8 = 0;
  v7 = xmalloc(32, argv, envp);
  while ( v8 != 8 )
  {
    *(v7 + 4 * v8) = xmalloc(32, v3, 4 * v8);
    memset(*(v7 + 4 * v8++), 10, 32);
  }
  *(v7 + 32) = 0;
  v9 = 0;
  v11 = 65;
  while ( v9 != 31 )
    *(*(v7 + 12) + v9++) = v11++;
  *(*(v7 + 12) + 31) = 0;
  for ( i = 0; argv[1][i]; ++i )
  {
    if ( argv[1][i] != *(*(v7 + 12) + i) )
      return -1;
  }
  return 1337;
}

One thing that sticks out is the usage of xmalloc() function, which is not in standard C library. I did a quick search and found the following references:

Looks like a) publib may be involved and b) the only thing special about the function is that it exits if memory allocation fails. IDA’s decompilation output seems wrong, because the signature to expect is void *xmalloc(size_t bytes). Upon closer inspection of functions called from within xmalloc(), it seems that I can safely fix that up in IDA by editing the function signature.

It looks like signatures for some standard library function definitions have not been picked up by IDA either. I don’t know of an automated way to get the definitions in, so I just edit these in IDA manually whenever the need arises.

In this function, I see that it expects some arguments:

  if ( argc <= 1 )
    return -1;

The way argc and argv work is best represented by the following snippet from documentation:

Invocation: ./a.out indatafile outdatafile
argc = 3
argv[0] --> ./a.out
argv[1] --> indatafile
argv[2] --> outdatafile
argv[argc] = (nil)

Therefore, if the condition is argc > 1, it means there has to be at least 1 argument:

Invocation: ./a.out indatafile
argc = 2
argv[0] --> ./a.out
argv[1] --> indatafile
argv[argc] = (nil)

Fail cases all return -1 exit code, which will show up in the terminal as 255, so my QEMU setup is correct.

After the check in the binary, a 32 byte buffer is allocated. Next, it is filled with 8 more pointers to buffers of 32 bytes, each of which gets initialized with 0xA times 32, so I created a corresponding structure in IDA:

idx1 = 0;
buf1 = xmalloc(0x20u);
while ( idx1 != 8 )
{
    *(&buf1->field_0 + idx1) = xmalloc(0x20u);
    memset(*(&buf1->field_0 + idx1++), 10, 0x20u);
}
buf1[1].field_0 = 0;

The first line after the loop doesn’t make sense though: buf1[1].field_0 points beyond the end of the allocated buffer, which could cause memory corruption. Time to check correctness of this line in assembly:

loop_exit               ; r3 has idx
LDR     R3, [R11,#idx1]
MOV     R2, R3,LSL#2    ; r2 has idx << 2, which evals to 8<<2, meaning 32
LDR     R3, [R11,#buf1] ; retrieve a ptr to buf1
ADD     R2, R3, R2      ; r2 = r3+32; r2 = buf1+32;
MOV     R3, #0          ; [buf1+32] = 0
STR     R3, [R2]        ; Store to Memory
MOV     R3, #0          ; Rd = Op2
STR     R3, [R11,#idx1] ; idx=0
MOV     R3, #0x41 ; 'A' ; Rd = Op2
STR     R3, [R11,#Achar] ; Store to Memory
B       loc_839C        ; Branch

Indeed, it looks like there is an off-by-one bug here and 4 bytes extra after the 32 byte structure will be overwritten with nulls.

Either way, the next few lines look very simple and promising:

idx2 = 0;
Achar = 'A';
while ( idx2 != 31 )
  *(buf1->field_C + idx2++) = Achar++;
*(buf1->field_C + 31) = 0;
for ( idx3 = 0; argv[1][idx3]; ++idx3 )
{
  if ( argv[1][idx3] != *(buf1->field_C + idx3) )
    return -1;
}
return 1337;

A field in the struct is filled with 31 characters following A, and a null-byte terminator is added after that.

Second loop then compares bytes in the first argument to bytes at corresponding index in this string inside a struct. It seems that the solution code should be:

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char Achar = 'A';

    void * field_C = malloc(32);

    int idx2 = 0;
    while (idx2 != 31) {
        *((char *)field_C + idx2++) = Achar++;
    }

    *((char *)field_C + 31) = 0;

    printf("%s\n", field_C);

    return 1337;
}

And the answer is:

user@debian-armhf:~$ ./diff2_bin "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_"
user@debian-armhf:~$ echo $?
57

The off-by-one memory corruption bug was not needed after all :)