Practical Reverse Engineering Exercise Solutions: Page 79 / Exercise 10
Exercise 10 on page 79 of the book Practical Reverse Engineering specifies the following ARM disassembly of a function called mystery10
:
|
|
Although the function looks complicated at first, we notice it does not contain any kind of loops and only executes sequentially with a couple of conditionals.
Presumably, the security cookie push and pop operations are responsible for inserting and removing a stack cookie, which will protect the function from memory corruption vulnerabilities that aim at overwriting the function’s return address [msdn].
mystery10
also invokes the following built-in Windows routines:
GetSystemTime [msdn]:
GetSystemTime “retrieves the current system date and time. The system time is expressed in Coordinated Universal Time (UTC).”
Its function prototype is as follows:
|
|
It takes one parameter, which is a pointer to memory where the corresponding SYSTEMTIME structure will be stored. As we can see from the definition, all struct field are of type WORD, which means they are 16-bit unsigned integers. For our purposes, having the relative offsets of its members facilitates decompiling the function:
|
|
The pointer passed to the function as an argument is contained in register R0
, which in turn contains the stack pointer (line 12). Thus, the SYSTEMTIME
structure will be saved to the current stack location.
GetCurrentProcessId [msdn]
The Windows routine GetCurrentProcessId “retrieves the process identifier of the calling process.".
Its function prototype is as follows:
|
|
It takes no parameter and returns an unsigned 32-bit integer value.
GetTickCount [msdn]:
The Windows routine GetTickCount “retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days.”
Its function prototype is:
|
|
It takes no parameter and returns an unsigned 32-bit integer value.
QueryPerformanceCounter [msdn]:
The Windows routine QueryPerformanceCounter “retrieves the current value of the performance counter, which is a high resolution (<1us) time stamp that can be used for time-interval measurements.”
Its function prototype is:
|
|
It takes one parameter, which is a pointer to a _LARGE_INTEGER
variable, which represents a 64-bit signed integer value.
Function Analysis
After having discussed the different invoked Windows routines, we have a look at the characteristics of the function mystery10
again.
The disassembly is executed in Thumb mode, as we can infer from the 16-bit instructions and the typical PUSH.W / POP.W
instructions.
As far as the instructions are concerned, there is an interesting branch instruction named BCC
. BCC
stands for Branch Carry Clear and accordingly will perform a branch to the destination when the Carry flag is not set, i.e. CF == 0
. Note that the carry flag is only set when an arithmetic operation between two unsigned values overflows.
mystery10
apparently takes three arguments in R0
, R1
and R2
. Although only the values in R1
and R2
are read (lines 6 und 9 respectively), there has to be a third parameter as otherwise the previously mentioned values would be stored in R0
and R1
.
The second argument in R1
seems to be a 32-bit unsigned integer, as it is used in CMP instructions (e.g. lines 8 and 26) followed by a BCC
instruction. As already mentioned, BCC
instructions check the Carry flag, which is set only when an arithmetic operation of unsigned values overflows.
R2 contains a pointer to an unknown memory structure.
The return value of mystery10
is an unsigned 32 bit integer since it is initialized to 0 (line 7) and only addition operations are performed on it (e.g. lines 16, 32 and 41).
Our provisional function prototype is as follows:
|
|
From the function’s control flow, we can see that the size of arg2 determines whether the Windows routines are called or not. It is repeatedly compared to R4
, which is initialized with 0 and increased for every called Windows routine. Rather than translating the disassembly directly to pseudocode / C, we examine the different input ranges of arg2 that trigger the invocation of Windows routines:
[0;3]
: No routine called[4;7]
: GetCurrentProcessId[8;15]
:GetCurrentProcessId, GetTickCount[16-19]
: GetSystemTime[20-23]
: GetSystemTime, GetCurrentProcessId[24-31]
: GetSystemTime, GetCurrentProcessId, GetTickCount[32-[
: GetSystemTime, GetCurrentProcessId, GetTickCount, QueryPerformanceCounter
For each called Windows routine, its result value is written to the third argument arg3. Moreover, the number of bytes written to arg2 is added to the current value of R4
, which is finally returned by the function (line 57).
We are now ready to create a first draft of mystery10
in C:
|
|
Effectively, the function returns the overall number of bytes written to the structure passed in arg3. The second argument arg2 can be considered a limit, up to which it is allowed to write bytes to arg3. Hence, it probably describes the size of the memory structure pointed to by arg3.
Finally, we provide a function with more descriptive names:
|
|