Ground Zero: Part 3 – Reverse Engineering Basics – Linux on ARM64

Prologue

As you might already be aware that ARM powers a variety of low-powered devices around us, including but not limited to, phones, routers, IoT devices.etc. Therefore, it is only logical to dig into this architecture and understand how it differs from x86 and x64 architectures. For this blog post, we will focus on 64bit ARM CPU as currently it is most commonly used. Our setup includes Ubuntu 16.04 on ARM Cortex-A53 CPU, which supports both 32bit and 64bit instruction set.

In the previous posts, we reversed a C++ binary in x64 Linux and Windows. In this blog however, we will reverse the same program, re-written in C.

Compile the program :

$ gcc crack_me.c -o crack_me

Binary Information :

Disassembly

Now let’s fire up GDB with the binary and start analyzing. Note that i am using GEF (https://github.com/hugsy/gef) with GDB so my prompt will look like gef> and not gdb>. Let’s start by disassembling the main function.

$ gdb ./crack_me

gef> disas main

Our attention goes directly to <check_pass> function at <main+64>, but before going there, you may want to take a moment and understand what these instructions mean. You can read up more about these on ARM’s documentation (https://developer.arm.com/docs/100069/latest/a64-general-instructions).

The following are some instructions which are of importance to our analysis.

b – branch to label, similar to jmp statement
bl – branch with link to label, similar to call statement
b.ne – branch to label if not equal, similar to jne statement
b.eq – branch to label if equal, similar to je statement

Let’s dive into the assembly code. The numbering refers to the sections highlighted in the gdb disassembly output.

  1. In address 0x4007bc, <main+4>, the stack pointer (sp) register is mov‘ed to register x29. Then we notice the main function arguments being accessed from x29 register. Note that offset 28 of x29 register contains argc, and offset 16 contains argv (which is our input password). Upon comparing the argc value, if it is equal to 0x2, we branch (b.eq – branch if equal) to <main+52>.
  2. The next three lines <main+52>, <main+56> and <main+60> expand the length of the size of argv string from 16 to 24 (16+0x8=24) and is referenced by x0 register.
  3. Then we call (bl – branch with link) to <check_pass> function.

    Let’s disassemble the <check_pass> function.

    gef> disas check_pass

  4. In the address 0x400738, <check_pass+8>, the newly sized argv string is copied from x0 register to x29 register with offset 24. Then we see some stack canary operations, from <check_pass+12> till <check_pass+24>, something is being stored on x29 register with offset 56 from address 0x411048, which is later compared at the end of function from <check_pass+96> onwards till <check_pass+124>.
  5. Coming back to the body of <check_pass> function, we see that from <check_pass+32> onwards, something is being accessed from 0x4008d0 and stored into x29 register with offset 0x28 (40), probably the secret password ?.
  6. Then from <check_pass+60> onwards, x1 register points to the newly copied data from 0x4008d0 and x0 register points to the argv string in x29 register with offset 24, followed by a call to strcmp (string compare between x0 & x1) function. The return value of strcmp function is stored in 16bit general purpose w0 register. If the strings are equal, the w0 is set to 0x0, else it is set to 0x1.

    Coming back to <main> function…

  7. The return value of <check_pass> function is stored in w0 register, which is copied to x29 register with offset 44. Then at <main+76> we compare the value of w0 register to see if it is equal to 0x1. If not, we jump (b.ne – branch if not equal) to <main+100>, which will lead us to a success message, and finally exit the program.

We will now start the program with a wrong password. But before that, we must add breakpoint, at the compare statement at <main+76>.

gef> break *0x400804

gef> run pass123

We have hit the breakpoint at the compare statement at 0x400804, <main+76>. Also, observe that the value of x0 register is 0x1. Since, x0 pointer is nothing but w0 register + 32 extra bits, x0 contains the return value of <check_pass> function. From the source code, we know that the program will check if return value of check_pass function is 1 or not, to display the “Incorrect Password” message. Therefore, this value should be anything but 0x1 for the program to display us a success message.

Let us change it’s value…

gef> set $x0=0x0

Now let us continue with the execution.

gef> continue

Epilogue

Turns out our hypothesis was correct. Changing the value of x0 from 0x1 to 0x0 did the trick. Which means it will always check if w0 is set to 0x1 to display the incorrect message, we know this from the source code of our program. Therefore, going back to <check_pass> function, we had noticed something being copied from address 0x4008d0. Let’s inspect that for a bit.

This does not look like any valid assembly instruction, but the repetitions of 53 is suspicious, also 41 is ‘A’ in hexadecimal. This definitely looks like a constant string. Let’s look deeper. Dumping 10 more lines from our address 0x4008d0

Looking at 0x4008d0 and 0x4008d4, we can make out it is little-endian 8 bit string. Let’s try to decode it…

So, here we have the original password “PASSWORD”.

This was just another example of analyzing binaries with gdb but in a different architecture. Going ahead, we will deal with more complex programs, uncommon architectures, and weirder binaries.

As for now, this is it. Do comment with your queries, if any.
Tweet me at @4c1dk3rn3l

The post Ground Zero: Part 3 – Reverse Engineering Basics – Linux on ARM64 appeared first on ScriptDotSh.

Article Link: https://scriptdotsh.com/index.php/2018/04/26/ground-zero-part-3-reverse-engineering-basics-linux-on-arm64/