Practical Reverse Engineering Solutions – Page 35 (Part II)my go at exercises 7 on page 35

This 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.

comments powered by Disqus