Reversing ARM Binaries

by @bellis1000

In this beginner-friendly tutorial we will take a look at the process of reverse engineering a simple ARM binary and patching it. The specific binary we will look at is named 'patchme1' and is a small program designed specifically for the purpose of this tutorial. Executing the program with zero arguments gives us a short 'usage' message that lets us know that we will need some kind of key in order to use this app.

Executing it again, this time with a random key as the first argument, gives us a red error message telling us that we have entered an incorrect key.

So where do we go from here? Natural instinct for anyone with any kind of reversing knowlegde would be to scan the binary for strings to see if the key happens to be stored in plain ASCII text. We can do so by opening the patchme1 binary in Hopper disassembler for OS X (or another disassembler tool such as IDA or radare2) and navigating to the 'strings' section.

Unfortunately for us, the key is not stored in plain ASCII. The only strings we can see are the 'usage' message, the 'invalid key' message, the path to /bin/sh and a 'You're in!' message. So what now? It is clear that this challenge will not be as simple as finding a string and using it to get through the check. We will need to look deeper into how this program works, and we'll start by disassembling main().

We will start with the first chunk of instructions, up until the point of the first conditional branch.

The first few instructions aren't too interesting. These instructions just make up the generic function prologue that contains the standard things you'd see in most ARM assembly functions, such as setting up the stack for later returning.

Below that, we have a series of memory access instructions (LDRs and STRs) and then a CMP instruction, comparing the value of R0 to 0x2. This is simply checking the number of arguments the user supplied when executing the program. The BGE (Branch if Greater than or Equal to) instruction following this comparison then determines whether we are given the 'usage' message or whether we proceed to the next part of the program. We don't care too much about the 'usage' thing, so we'll continue on to the next stage of the program.

There are some interesting things happening here. Firstly, R0 is being loaded with user-supplied key and then a function 'hash' is BL(Branch with Link)'d to. The BL instruction is the equivalent of a CALL type instruction. It jumps or 'branches' to a new location in memory (in this case the entry point of the hash function) and then it returns to its caller.

Following the call to the hash function, there is a comparison between R0 and a very specific value, 0x203623b1. If the two are not equal, we branch to 0xbef0 which displays the 'invalid key' message and then exits. However, if they are equal then we continue to the restricted area of the program which, by looking at the disassembly, gives us a welcome message and then spawns a shell.

So how do we get that very specific value into R0? On ARM, function return values are passed in R0. This makes it pretty clear and obvious that the hash function is what is generating this number (if you didn't already guess).

Looking at the disassembly for the hash function would likely overwhelm beginners (who of which this post is targetted at) so instead we will cheat and have a sneak peak at the source code instead ;).

From the above code snippet we can understand that this 'hash' function takes in an array of chars (data[]) and returns an unsigned integer. The actual body of this function contains a loop that iterates through the characters in the char array an XOR(Exclusive OR)s each of them with the random number 83, adding the result to an integer variable named 'hash' and then multiplying the value of this by another random value, 839286441.

So in summary, this function implements a very basic hashing algorithm that generates a hash of the data passed to it and returns this hash to the caller.

If you know anything about hashing algorithms, you'll know that even simple ones like this are very tedious to reverse (find some specific data that returns a specific hash) so it would be easier for us to just patch this simple binary instead.

There are a few ways we could patch this specific binary, but we'll go with the simplest way which would be to remove or 'NOP out' this instruction:

But what is a NOP? NOP stands for No-Operation and it is essentially an instruction that when executed, has no effect on the program or the state of the registers. In other words, it does nothing. We can use this to our advantage by placing a NOP where the conditional branch instruction would be, thus causing execution to automatically follow on to the chunk of code that executes the shell.

In the ARMv7 instruction set, there is no dedicated NOP instruction, but we can easily create our own. Something like 'MOV R0, R0' acts as a NOP instruction. It moves the value of R0 into R0, leaving the registers unchanged. So it does nothing.

To actually replace the instruction with our NOP we could use Hopper's built-in 'NOP Region' feature, however we will not be able to export the binary on the free version. Instead, I will assume that we don't have access to the full/paid version of Hopper and patch the instruction manually.

We first need the encoding of our target instruction. This can be found in the right side bar in Hopper.

Now, using your favourite Hex editor application (I'm using iHex), open the patchme1 binary and search for this specific byte combination inside the binary file.

Once you've found it you can replace these 4 bytes with the bytes representing your chosen NOP instruction. In my case I will use 'MOV R0, R0' which has the instruction encoding of 00 00 A0 E1.

Save the file, and you're done! You've successfully patched the program by replacing a single instruction.

To test that the patch works as expected, execute the new patchme1 binary and see what happens.

As the above screenshot shows, we are given access to the shell no matter what data we enter for the key. Challenge complete!

Hopefully you enjoyed this short tutorial! If you're new to ARM assembly then I highly recommend Azeria's set of tutorials. If you want to learn more on the topic of reverse engineering and exploit development on the ARM platform check out my book as well as my set of 'ARM Exploit Exercises' available on my Github. Thanks for reading! Feel free to tweet me @bellis1000 if you have any questions :)

Copyright (c) 2018 ZygoSec