Contents

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
01:             mystery10
02: 2D E9 70 48   PUSH.W   {R4R6,R11,LR}
03: 0D F2 0C 0B   ADDW     R11, SP, #0xC
04: 37 F0 CC F9   BL       __security_push_cookie
05: 84 B0         SUB      SP, SP, #0x10
06: 0D 46         MOV      R5, R1
07: 00 24         MOVS     R4, #0
08: 10 2D         CMP      R5, #0x10
09: 16 46         MOV      R6, R2
10: 0C D3         BCC      loc_1010786
11: 1A 4B         LDR      R3, =__imp_GetSystemTime
12: 68 46         MOV      R0, SP
13: 1B 68         LDR      R3, [R3]
14: 98 47         BLX      R3
15: 00 9B         LDR      R3, [SP,#0x1C+var_1C]
16: 10 24         MOVS     R4, #0x10
17: 33 60         STR      R3, [R6]
18: 01 9B         LDR      R3, [SP,#0x1C+var_18]
19: 73 60         STR      R3, [R6,#4]
20: 02 9B         LDR      R3, [SP,#0x1C+var_14]
21: B3 60         STR      R3, [R6,#8]
22: 03 9B         LDR      R3, [SP,#0x1C+var_10]
23: F3 60         STR      R3, [R6,#0xC]

24:      loc_1010786
25: 2B 1B         SUBS     R3, R5, R4
26: 04 2B         CMP      R3, #4
27: 04 D3         BCC      loc_1010796
28: 11 4B         LDR      R3, =__imp_GetCurrentProcessId
29: 1B 68         LDR      R3, [R3]
30: 98 47         BLX      R3
31: 30 51         STR      R0, [R6,R4]
32: 04 34         ADDS     R4, #4

33:      loc_1010796
34: 2B 1B         SUBS     R3, R5, R4
35: 04 2B         CMP      R3, #4
36: 04 D3         BCC      loc_10107A6
37: 0C 4B         LDR      R3, =__imp_GetTickCount
38: 1B 68         LDR      R3, [R3]
39: 98 47         BLX      R3
40: 30 51         STR      R0, [R6,R4]
41: 04 34         ADDS     R4, #4

42:      loc_10107A6
43: 2B 1B         SUBS     R3, R5, R4
44: 08 2B         CMP      R3, #8
45: 09 D3         BCC      loc_10107C0
46: 07 4B         LDR      R3, =__imp_QueryPerformanceCounter
47: 68 46         MOV      R0, SP
48: 1B 68         LDR      R3, [R3]
49: 98 47         BLX      R3
50: 00 9B         LDR      R3, [SP,#0x1C+var_1C]
51: 32 19         ADDS     R2, R6, R4
52: 33 51         STR      R3, [R6,R4]
53: 01 9B         LDR      R3, [SP,#0x1C+var_18]
54: 08 34         ADDS     R4, #8
55: 53 60         STR      R3, [R2,#4]

56:      loc_10107C0
57: 20 46         MOV      R0, R4
58: 04 B0         ADD      SP, SP, #0x10
59: 37 F0 A4 F9   BL              __security_pop_cookie
60: BD E8 70 88   POP.W    {R4R6,R11,PC}
61:             ; End of function 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:

1
2
3
void WINAPI GetSystemTime(
  _Out_ LPSYSTEMTIME lpSystemTime
);

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kd> dt _systemtime
uxtheme!_SYSTEMTIME
   +0x000 wYear            : Uint2B
   +0x002 wMonth           : Uint2B
   +0x004 wDayOfWeek       : Uint2B
   +0x006 wDay             : Uint2B
   +0x008 wHour            : Uint2B
   +0x00a wMinute          : Uint2B
   +0x00c wSecond          : Uint2B
   +0x00e wMilliseconds    : Uint2B

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:

1
DWORD WINAPI GetCurrentProcessId(void);

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:

1
DWORD WINAPI GetTickCount(void);

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:

1
2
3
BOOL WINAPI QueryPerformanceCounter(
  _Out_ LARGE_INTEGER *lpPerformanceCount
);

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:

1
uint32 mystery10 (void* arg1, uint32 arg2, unknownStruct* arg3) ;

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
uint32 mystery10 (void* r0, uint32 arg2, unknownStruct* arg3) {
 uint32 returnValue = 0; //line 7
 
 if (checkValue > 15) { //line 8
  SYSTEMTIME currentTime;
  GetSystemTime(currentTime);
  
  memcpy(arg3,&currentTime,sizeof(SYSTEMTIME)); // lines 17 - 23
  
  returnValue = 16; // line 16
 }
 
 if (arg2 - returnValue > 3) {
  uint32 processId = GetCurrentProcessId(); // lines 28-30
  *(arg3 + returnValue) = processId; // line 31
  returnValue += 4;
 }
 
 if (arg2 - returnValue > 3) {
  uint32 tickCount = GetTickCount(); // lines 37-39
  *(arg3 + returnValue) = tickCount; // line 40
  returnValue += 4; // line 41
 }

 if (arg2 - returnValue > 7) {
  _LARGE_INTEGER  perfCount = QueryPerformanceCounter();
  *(arg3 + returnValue) = perfCount->HighPart; // line 52
  *(arg3 + returnValue + 4) = perfCount->LowPart; // line 55
  returnValue += 8; // line 54
 } 
 
 return returnValue;
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
uint32 retrieveTimeInfo (void* r0, uint32 size, unknownStruct* dest) {
 uint32 bytesWritten = 0; //line 7
 
 if (checkValue > 15) { //line 8
  SYSTEMTIME currentTime;
  GetSystemTime(currentTime);
  
  memcpy(dest,&currentTime,sizeof(SYSTEMTIME)); // lines 17 - 23
  
  bytesWritten = 16; // line 16
 }
 
 if (size - bytesWritten > 3) {
  uint32 processId = GetCurrentProcessId(); // lines 28-30
  *(dest + bytesWritten) = processId; // line 31
  bytesWritten += 4;
 }
 
 if (size - bytesWritten > 3) {
  uint32 tickCount = GetTickCount(); // lines 37-39
  *(dest + bytesWritten) = tickCount; // line 40
  bytesWritten += 4; // line 41
 }

 if (size - bytesWritten > 7) {
  _LARGE_INTEGER  perfCount = QueryPerformanceCounter();
  *(dest + bytesWritten) = perfCount->HighPart; // line 52
  *(dest + bytesWritten + 4) = perfCount->LowPart; // line 55
  bytesWritten += 8; // line 54
 } 
 
 return bytesWritten;
}