Practical Reverse Engineering Solutions – Page 35 (Part I)
my go at the exercises 1 to 4 on page 35This 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 be0x80047400
(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 numbern
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
Archived Comments
Note: I removed the Disqus integration in an effort to cut down on bloat. The following comments were retrieved with the export functionality of Disqus. If you have comments, please reach out to me by Twitter or email.