Practical Reverse Engineering Solutions – Page 35 (Part II)
my go at exercises 7 on page 35This blog post presents my solution to exercise 7 on page 35 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. For an overview of my solutions consult this progress page.
Problem Statement
Sample H. The function sub_10BB6 has a loop searching for something. First recover the function prototype and then infer the types based on the context. Hint: You should probably have a copy of the PE specification nearby.
Here you can see the complete function. In the following I’m doing a walk-through of the code, based on the PE offset reference. After that
Walk-Through
▶ First Function Parameter
The function does not have a function prologue. Instead it starts with copying the first function parameter to EAX
:
mov eax, [esp+4]
From the hint, and the way the offset work out later on, I infer that this parameter is a pointer to a PE file. So EAX
now points to the start of an executable, which is the IMAGE_DOS_HEADER.
▶ Offset to Start of PE Header
Lines 2 and 3 just save EBX
and ESI
on the stack, so the registers can be used locally. Line 4 adds 0x3C to EAX
and stores the result in ESI
:
push ebx push esi mov esi, [eax+3Ch]
EAX
is a pointer to an IMAGE_DOS_HEADER
, at offset 3C
we have the member e_lfanew
, which gives the offset to the real PE header. So ESI = IMAGE_DOS_HEADER.e_lfanew.
▶ Position of PE Header
add esi, eax
ESI
holds the offset to the PE header. By adding the start of the PE file still in EAX
we get the position of the PE header. The header is a struct called IMAGE_NT_HEADER
, so ESI = PIMAGE_NT_HEADER.
▶ Size of the Optional Header
movzx eax, word ptr [esi+14h]
The struct IMAGE_NT_HEADER
starts with a 4 byte signature. Next follows – at offset 4 – the struct IMAGE_FILE_HEADER
. At offset 0x10 in IMAGE_FILE_HEADER we have the word SizeOfOptionalHeader
. So EAX = [esi + 14h] = [esi + 4 + 10h] = IMAGE_FILE_HEADER.SizeOfOptionalHeader
.
▶ Return if there aren’t any Sections
xor ebx, ebx cmp [esi+6], bx ... ... jbe short loc_0_10BEB
Line 7 sets EBX=0
. In line 8 we get the member of IMAGE_NT_HEADER
at offset 6. Subtracting the 4 byte for IMAGE_NT_HEADER.Signature
we land 2 bytes into the IMAGE_FILE_HEADER
, which is the member IMAGE_FILE_HEADER.NumberOfSections
. Apart from headers, a PE file consists of sections such as .text or .data. IMAGE_FILE_HEADER.NumberOfSections
indicates how many sections there are. The above snippet will make a jump to loc_0_10BEB
if the number of sections is zero (or less). As will be shown later, loc_0_10BEB
returns NULL
.
▶ Get Start of Section Table
push edi lea edi, [eax+esi+18h]
The LEA
instruction is used as a fast way to add three values in one instruction. ESI
is still a pointer to the IMAGE_NT_HEADER
. At offset 0x18h in this struct we find the IMAGE_OPTIONAL_HEADER
. The size of this header is in EAX = IMAGE_FILE_HEADER.SizeOfOptionalHeader
. By adding the size of the optional header to the start of IMAGE_OPTIONAL_HEADER
we jump over this header and end up at the start of the section table. The section table consists of IMAGE_FILE_HEADER.NumberOfSections
sections of type IMAGE_SECTION_HEADER
.
In summary: Lines 1-10 calculate the position of the first IMAGE_SECTION_HEADER
(elements of the section table), and store the result in EDI
.
▶ Call Another Subroutine
loc_0_10BCE: push [esp+0Ch+8] push edi call ds:dword_0_169A4
The routine doesn’t use the EBP
. It therefore has to retrieve its function parameters in relation to the ESP
. The function already pushed 12 bytes on the stack, that’s why the second function parameter is now at [esp+0Ch+8]
. The value is pushed on the stack, along with EDI
which is the start of the section table. The call is therefore
int r = some_function(image_section_header, arg2);
Without reversing this function too, it’s hard to tell what it does. It might for instance compare the name of the section header to arg2
.
▶ Check Return Value
test eax, eax pop ecx pop ecx jz short loc_0_10BF3
The unknown subroutine returns a double word. Line 17 an 20 check if the return value is zero. If this is the case, the subroutine jumps to loc_0_10BF3
. I’ll talk about this section later. For now, assume that the return value was not zero, which might indicate that the “search” in dword_0_169A4
was not successful.
The two pop
instructions in lines 18 and 19 simply clean up the stack – dword_0_169A4
is obviously using the CDECL
calling convention.
▶ Iterate
movzx eax, word ptr [esi+6] add edi, 28h inc ebx cmp ebx, eax jb short loc_0_10BCE
Line 21, as in line 8, retrieves the number of sections IMAGE_FILE_HEADER.NumberOfSections
. Line 23 increments EBX
, which is still 0 from the instruction in line 7, by one. EBX
is the loop counter. Line 24 and 25 are the test of a loop. Line 22 points EDI
to the next image section header. So these lines form a loop:
for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++) { // (...) // line 22: add edi, 28h (28h = sizeof(IMAGE_SECTION_HEADER)) img_section_header += sizeof(IMAGE_SECTION_HEADER); }
▶ Return Value
If the jump in line 20 is never taken, i.e., the loops runs out without dword_0_169A4
ever reporting sucess, then the snippet continues with:
loc_0_10BEB: xor eax, eax loc_0_10BED: pop edi pop esi pop ebx retn 8
Line 28 sets the return value to NULL
. The pop
instructions in lines 31 to 33 restore the registers. Finally, line 34 restores the stack (STDCALL-convention) and returns.
If, on the other hand, at some point dword_0_169A4
returns a non zero value, then the routine jumps to:
loc_0_10BF3: mov eax, edi jmp short loc_0_10BED
This just sets the return value of our subroutine to the current section table entry. The jump to loc_0_10BED
will then cleanup the stack and return.
C++ Disassembly
This is the routine in C++:
#include <Windows.h> IMAGE_SECTION_HEADER* get_section(char* pe_file, char* criterion) { // line 1: mov eax, [esp+4] IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)pe_file; // lines 2-4: mov esi, [eax+3Ch] unsigned int pe_header_offset = dos_header->e_lfanew; // line 5: add esi, eax IMAGE_NT_HEADERS* nt_header = (IMAGE_NT_HEADERS*)(pe_file + pe_header_offset); // line 6: movzx eax, word ptr [esi+14h] unsigned short size_of_optional_header = nt_header->FileHeader.SizeOfOptionalHeader; // line 10: lea, [eax+esi+18h] IMAGE_OPTIONAL_HEADER* optional_header = &nt_header->OptionalHeader; // esi + 18h IMAGE_SECTION_HEADER* img_section_header = (IMAGE_SECTION_HEADER*)(optional_header + size_of_optional_header); // for loop in lines 7,8, 11 and 21, 23, 24, 25 for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++) { // lines 14-16: call ds:dword_0_169A4 int ret = check_section(img_section_header, criterion); // lines 17-20: if (!ret) { // lines 17-19, 30-34: mov eax, edi return img_section_header; } // line 22: add edi, 28h (28h = sizeof(IMAGE_SECTION_HEADER)) img_section_header += sizeof(IMAGE_SECTION_HEADER); } // lines 27-34: xor eax, eax return NULL; }
Summary
The function iterates over all image section headers of a PE file. It returns the first such section header for which an unknown function ds:dword_0_169A4
returns zero, if no such section exists, the function returns NULL
.
The second parameter of our function is passed to ds:dword_0_169A4
and might be the criterion applied to the search of the section header. The unknown function might for instance compare the name in the image section headers to the second argument of our function.
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.