Practical Reverse Engineering Solutions – Page 35 (Part I)my go at the exercises 1 to 4 on page 35

This blog post presents my solutions to exercises from the book Practical Reverse Engineering by Bruce Dang, Alexandre Gazet and Elias Bachaalany (ISBN: 1118787315). The book is my first contact with reverse engineering, so take my statements with a grain of salt. All code snippets are on GitHub.

The walk-through in the book has a few minor typos:

  • page 31, last listing: 0x80047400h won’t work, needs to be 0x80047400 (no trailing h).
  • page 33, middle: Line 42 tests the return value of Process32Next should read Line 42 tests the return value of Process32First.
  • page 34, while-listing: "explorer".exe" should read "explorer.exe".
  • page 34, top: continue execution at 37 should read continue execution at 73.
  • page 34, top: is also a jump target in line 43 should read is also a jump target in line 51.

Furthermore, there is a check for th32ParentProcessID == th32ProcessID not mentioned in the book.

Exercise 1

Repeat the walk-through by yourself. Draw the stack layout, including parameters and local variables.

The function uses the STDCALL convention, hence, all three function parameters are put on the stack before calling it:

The first few lines are:

push ebp
mov ebp, esp
sub esp, 130h
push edi
sidt fword ptr [ebp-8]
mov eax, [ebp-6]
cmp eax, 8003F400h
jbe short loc_10001C88 (line 18)
cmp eax, 80047400h
jnb short loc_10001C88 (line 18)

This contains the function prologue in lines 3 and 4, the creation of a stack frame for local variables in line 5, and pushing edi on the stack:

I assume that one of the two jumps is taken. At loc_10001C88 we find the following assembly code:

loc_10001C88:
xor eax, eax
mov ecx, 49h
lea edi, [ebp-12Ch]
mov dword ptr [ebp-130h], 0
push eax
push 2
rep stosd
call CreateToolhelp32Snapshot
mov edi, eax
cmp edi, 0FFFFFFFFh
jnz short loc_10001CB9 (line 35)

The first few lines initialize tagPROCESSENTRY32. The structure has 296 bytes, 260 of which are for the szExeFile member. Here’s where the members of the structure are located on the stack:

Lines 23 and 24 push arguments for CreateToolhelp32Snapshot on the stack. Since this function uses the STDCALL convention, the callee cleans up the stack:

I assume the jump in line 29 is taken. The lines at loc_10001CB9 are as follows:

loc_10001CB9:
lea eax, [ebp-130h]
push esi
push eax
push edi
mov dword ptr [ebp-130h], 128h
call Process32First
test eax, eax
jz short loc_10001D24 (line 70)
mov esi, ds:_stricmp
lea ecx, [ebp-10Ch]
push 10007C50h
push ecx
call esi ; _stricmp
add esp, 8
test eax, eax
jz short loc_10001D16 (line 66)

I assume the jump in 43 is not taken. In this snippet we have two function calls. Process32First uses the STDCALL convention. stricmp on the other hand uses CDECL. In the latter case, the stack pointer is adjusted by the caller:

If the jump in line 51 is not taken, the above procedure will basically be repeated as long as "explorer.exe" matches the process name or the call to Process32Next fails. The only difference in lines 53 to line 65 is the call to Process32Next instead of Process32First. The stack picture will look the same; I therefore take the jump to loc_10001D16.

loc_10001D16:
mov eax, [ebp-118h]
mov ecx, [ebp-128h]
jmp short loc_10001D2A (line 73)

The above snippet doesn’t change the stack.

loc_10001D2A:
cmp eax, ecx
pop esi
jnz short loc_10001D38 (line 82)

Line 75 restores ESI. Let’s assume we jump to line 82:

loc_10001D38:
mov eax, [ebp+0Ch]
dec eax
jnz short loc_10001D53 (line 93)
push 0
push 0
push 0
push 100032D0h
push 0
push 0
call ds:CreateThread

I don’t take the jump in line 85. The stack for lines starting at line 66 should look like this:

The only remaining lines set the return value and clean up the stack:

loc_10001D53:
mov eax, 1
pop edi
mov esp, ebp
pop ebp
retn 0Ch

Exercise 2

In the example walk-through, we did a nearly one-to-one translation of the assembly code to C. As an exercise, re-decompile this whole function so that it looks more natural. What can you say about the developer’s skill level/experience? Explain your reasons. Can you do a better job?

Here’s my decompiled version with references to the assembly lines:

#include <windows.h> 
#include <TlHelp32.h> 
#include <intrin.h> 

typedef struct _IDTR {
	DWORD base;
	SHORT limit;
} IDTR, *PIDTR;

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD ul_reason_for_call,
	LPVOID lpReserved
	) // line 1
{
	// line 2 ---
	IDTR idtr;
	__sidt(&idtr);	
	if (idtr.base > 0x8003F400 && idtr.base < 0x80047400) {
		return FALSE;
	}
	// --- line 17
	// line 19 ---
	PROCESSENTRY32 procentry;
	memset(&procentry, 0, sizeof(PROCESSENTRY32));
	procentry.dwSize = sizeof(procentry); // 0x128
	HANDLE h;	
	h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (h == INVALID_HANDLE_VALUE)
		return FALSE;
	// --- line 34
	// line 36 ---
	int ret = Process32First(h, &procentry);
	while (ret) {
		// line 44 - line 51 AND line 59 - line 65
		if (!wcscmp(procentry.szExeFile, L"explorer.exe")) {						
			break;
		}
		ret = Process32Next(h, &procentry);
	}
	// --- line 65
	// line 66 --
	if (ret)		
		if (procentry.th32ParentProcessID == procentry.th32ProcessID)
			return FALSE;	
	// --- line 81
	// line 70 ---
	else 
	    /*if (ul_reason_for_call == DLL_PROCESS_DETACH)
	        return FALSE; (no such check, error in book */
            return FALSE;
	// --- line 81

	// line 82
	if (ul_reason_for_call == DLL_PROCESS_ATTACH)
		CreateThread(0, 0, (LPTHREAD_START_ROUTINE)0x100032D0, 0, 0, 0);
	return TRUE;
}

Exercise 3

In some of the assembly listings, the function name has a @ prefix followed by a number. Explain when and why this decoration exists.

According to this source:

Names with _ prefix and @h postfix indicate the __stdcall calling convention. It is the default for Windows dll`s. The callee has to clean-up the stack. The number n in the postfix says how many bytes are used for function parameters. Our _DllMain@12 therefore uses 12 Bytes as parameters, i.e., one byte for each of the three parameters.

Exercise 4

You can find the full examples, including how to use the function, on my GitHub page. All functions use the CDECL calling conventions.

strlen

Declaration:

size_t strlen(const char *str)

My assembly x86 implementation:

strlen:
push ebp
mov ebp, esp
mov edi, [ebp+8]     ; get first parameter
mov edx, edi         ; copy address to start of string
xor eax, eax         ; set eax to null byte
mov ecx, -1          ; make sure ecx does not become zero 
repne scasb          ; search null byte
sub edi, edx         ; substract start address from end address 
dec edi              ; decrement difference to compensate for null byte
mov eax, edi         ; return strlen result
mov esp, ebp
pop ebp
ret

strchr

Declaration:

char *strchr(const char *str, int c)

My assembly x86 implementation:

strchr:
push ebp
mov ebp, esp
mov edi, [ebp+8]     ; get first parameter
mov bl, [ebp+12]     ; set bl to second parameter
mov al, 0            ; set al to null byte
_loop:
mov cl, [edi]        ; store current character
cmp cl, bl           ; check if character is what we search         
jz _return           ; jump to return if match
scasb                ; check if null byte
jnz _loop            ; loop if no match
mov edi, 0           ; set edi to zero, so function will return null
_return:
mov eax, edi         ; return pointer to first occurence 
mov esp, ebp
pop ebp
ret

memcpy

Declaration:

void *memcpy(void *str1, const void *str2, size_t n)

My assembly x86 implementation:

memcpy:
push ebp
mov ebp, esp
mov esi, [ebp+8]     ; src location (first parameter) 
mov edi, [ebp+12]    ; dst location (second parameter) 
mov ecx, [ebp+16]    ; number of bytes (third parameter)
_loop:
mov al, [esi];       ; copy byte from src ...
mov [edi], al;       ; ... to dst
inc esi              ; go to next byte in src ...
inc edi              ; ... and dst
dec ecx              ; decrement counter
jnz _loop            ; loop n-times
mov esp, ebp
pop ebp
ret

memset

Declaration:

void *memset(void *str, int c, size_t n)

My assembly x86 implementation:

memset:
push ebp
mov ebp, esp
mov edi, [ebp+8]     ; string (first parameter) 
mov al, [ebp+12]     ; character (second parameter) 
mov ecx, [ebp+16]    ; number of bytes (third parameter)
rep stosb
mov esp, ebp
pop ebp
ret

strcmp

Declaration:

int strcmp(const char *str1, const char *str2)

My assembly x86 implementation (uses the strlen routine):

strcmp:
push ebp
mov ebp, esp
mov edi, [ebp+12]    ; get second string 
push edi             ; next for lines calc len of string b
call strlen          ; ^^ 
add esp, 4           ; ^^                   
mov ebx, eax         ; ^^ 
mov esi, [ebp+8]     ; get first string
push esi             ; next for lines calc len of string a
call strlen          ; ^^ 
add esp, 4           ; ^^                   
_check:
cmp eax, ebx         ; compare lengths
ja _greater          ; string a is longer than string b 
jb _less             ; string b is longer than string a 
jmp _equal_length    ; strings have same length
_greater:
mov eax, 1
jmp _return
_less:
mov eax, -1
jmp _return
_equal_length:
mov edi, [ebp+12]    ; get second string (restore)
mov esi, [ebp+8]     ; get first string (restore)
mov ecx, eax         ; length of strings
repe cmpsb           ; compare strings
jg _greater          ; string a is greater
jl _less             ; string b is greater
mov eax, 0           ; strings are equal
jmp _return
_return:
mov esp, ebp
pop ebp
ret

strlen:
push ebp
mov ebp, esp
mov edi, [ebp+8]     ; get first parameter
mov edx, edi         ; copy address to start of string
xor eax, eax         ; set eax to null byte
mov ecx, -1          ; make sure ecx does not become zero 
repne scasb          ; search null byte
sub edi, edx         ; substract start address from end address 
dec edi              ; decrement difference to compensate for null byte
mov eax, edi         ; return strlen result
mov esp, ebp
pop ebp
ret

strset

Declaration:

char *strset( const char *str,char ch );

My assembly x86 implementation (uses the strlen routine):

strset:
push ebp
mov ebp, esp
mov edi, [ebp+8]     ; get first string
mov edx, edi         ; make copy of esi
push edi             ; next for line put str length in ecx
call strlen          ; ^
add esp, 4           ; ^
mov ecx, eax         ; ^
mov al, [ebp+12]     ; get fill character 
mov edi, edx         ; restore esi
rep stosb            ; fill string
mov eax, edx         ; return reference to string 
mov esp, ebp
pop ebp
ret

strlen:
push ebp
mov ebp, esp
mov edi, [ebp+8]     ; get first parameter
mov edx, edi         ; copy address to start of string
xor eax, eax         ; set eax to null byte
mov ecx, -1          ; make sure ecx does not become zero 
repne scasb          ; search null byte
sub edi, edx         ; substract start address from end address 
dec edi              ; decrement difference to compensate for null byte
mov eax, edi         ; return strlen result
mov esp, ebp
pop ebp
ret
comments powered by Disqus