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.
