Practical Reverse Engineering Exercise Solutions: Page 78 / Exercise 1
This is the first blog post to a series of ARM challenges from the book Practical Reverse Engineering. In addition to the official ARM manual, the following web page turned out to be very helpful when solving the exercises, as it describes the different ARM instructions in great detail.
https://www.heyrick.co.uk/armwiki/Main_Page
Without further ado, let us explore the first function. The extract below shows the ARM disassembly of a function named mystery1, which we are supposed to decompile into C code.
|
|
Disassembly Analysis
First of all, we notice that each instruction of the disassembly has a width of 32 bits, which is a strong indicator for ARM state rather than Thumb state. Furthermore, the contents of the registers r4-r8
are stored on the stack with the STMFD
command rather than with a PUSH
, as in the case of Thumb state.
The very first instruction is STMFD SP!, {R4-R8}
.
STMFD
is a synonym for STMDB
, meaning to store multiple register values in memory in DB mode (decrement before). That is, the last location of the data written to memory will be stored 4 bytes below the previous value of SP
. Due to the exclamation mark after SP
, writeback is enabled and SP
will point to the first location of the data written to memory.
The instruction LDREQB R3, [R0,#1]!
deserves to be mentioned as well. It performs a load operation of a single unsigned byte from memory into the R3
register. The addressing mode is pre-indexed, meaning R0
(i.e., the base register) will be modified. The value will be retrieved from the memory location [R0+1]
and R0
will be afterwards updated to R0+1
.
LDRB R2, [R3],#1
is a load operation with the post-index address mode. It loads the data at memory address R3
into R2
and afterwards sets R3
to R3+1
.
UMULL R2, R3, R4, R8
multiplies two 32-bit unsigned values to produce a 64 bit result. In particular, it will multiply the values stored in registers R4
and R8
and store the lower and upper 32 bits of the result in R2
and R3
respectively.
SUBS R7, R7, #0x30
performs the substraction R7 = R7 - 0x30
and updates the arithmetic conditional flags depending on the result.
SBC R3, R5, R6,ASR#31
is another instruction worth of explaining. SBC
stands for Substract with Carry and in the most basic scenario would perform the operation Rd = Rn - Op2 - !C
, where C stands for the carry flag. In this case, however, we have an additional shift operation to perform, namely an arithmetic right shift operation of 31 bits with carry output. To summarize, the operation means the following: R3 = R5 - (R6 >> 31) - !C
BMI loc_B318
means that a branch to address loc_b318
is performed when the minus/negative flag is set, i.e., the N flag has the value 1.
MLA R3, R8, R5, R3
has the following meaning: It multiplies the values in registers R8
and R5
and adds the value of R3
. The lower 32 bits of the result are saved into register R3
.
BGT loc_B318
performs a branch to loc_B318
when the Z flag has the value 0 and the N (negative) flag matches the V (overflow) flag.
RSBS R4, R4, #0
carries out a Reverse Substract. More precisely, the register value is substracted from an immediate and the result is written to the destination register. In particular, R4
is substracted from 0 and written back to R4
. The conditional flags in CPSR
will be updated due to the S suffix of the instruction.
RSC R5, R5, #0
is actually very similar to RSB
(reverse substract). The only difference is that RSC
performs a reverse substract with carry. It substracts the R5 value and the value of NOT (Carry flag) from the immediate value #0. The result is written back to R5: R5 = 0 - R5 - !(Carry flag)
STR R4, [R1]
is the basic Store Register operation to store a value from a register to memory. It takes the value from R4 and stores it in the memory area pointed to by the address in R1.
ADC R5, R3, R7,ASR#31
performs an addition with the Carry flag and stores the result in the destination register. Here, a arithmetic shift to the right (ASR) is contained as well. The equivalent of this instruction is as follows: R5 = R3 + (R7 >> 31) + Carry flag
.
Function Analysis:
There are two possible values of R0
when the function mystery1()
exits, namely 0 and 1. Thus, we deduce that the data type of the function’s return value is of type BOOL
.
Current function prototype:
|
|
In the exercise description, we are already told mystery1()
takes two arguments. The first argument is passed in R0
and accessed right at the start of the function in line 03. The second argument, however, is not accessed before line 54. Both of the arguments contain addresses, i.e., they are pointers to some kind of structs.
Current function prototype:
|
|
What is more, the return value of the function is 1 and data stored to the address at R1
(arg2) if and only if the block in line 53 is executed. Otherwise, the return value is 0 and R1
is not being accessed. This means that the return value should indicate to the caller whether a value was stored in the address contained in arg2. As a consequence, we modify the method name to reflect this behaviour:
Current function prototype:
|
|
We notice that all load operations (LDR
) of the function only load single byte values and the original load address is taken from the first argument. The loaded byte values are being compared to fixed values such as 0x2d, 0x2b, 0x30
, whose ASCII character representation is -, + and 0 respectively. Furthermore, the value of R0
is increased in multiple locations by 1 (e.g., lines 08, 58, 12, 15). This strongly suggests the data type of argument1 is in fact a string or an array of characters.
Current function prototype:
|
|
The first blocks of the function check whether the first byte of arg1 matches the value 0x2d
(-) or 0x2b
(+). Depending on the outcome, the program sets the register r6
with a value of 1 in the case of a “-” and 0 otherwise. When looking at the other parts of the program, it turns out r6
can only assume the values 0 and 1, thus we deduce it is of type BOOL
.
The program continues in line 9 to examine the second byte of arg1 for a value of 0x30
(0). The program continues to retrieve successive bytes from arg1 in a loop as long as the 0-character is read.
Up to line 18, we can make already a first attempt at the decompilation:
|
|
Up to now, we know that the input string matches the following regular expression:
"(+|-)0*"
Beginning with line 18, the disassembly becomes more involved. It seems the function checks whether the subsequent bytes are in the range [0x30-0x39]
. The plot thickens: Their ASCII representation are the numbers 0 to 9. Line 36 ensures that at most 10 of these characters are read from arg1.
In register R8
, there is a constant value 0xA
, whose decimal value is 10. This value is used in the multiplication operation in line 30.
We can add these new insights to our decompiled function:
|
|
As soon as the input string contains no further valid numeric characters, we arrive in the block of line 43. The result in r4
is decremented by the minus character value and written to a different register. Afterwards, the modified result is compared to the hexadecimal value 0x8000000
, which is the hexadecimal representation of 2^31. When the modified result is greater or equal than this value, the function aborts and returns 0. Notice that the range of signed 32 bit integers is [-2^31; 2^31 - 1]
.
When the minus character is set, the result value is negated (line 51: R4 = 0 - R4
). Finally, the result value is written to the memory address contained in R1
(line 54) and 1 is returned (line 55). We can now complete the function decompilation:
|
|