‘Self Modifying Code: Changing Memory Protection‘ described how self modifying code first needs to add write permissions to the memory page(s) before it can modify the code. There is another way of creating self modifying code which doesn’t require a call to VirtualProtect() — modify the characteristics of the .text (code) section in the EXE file.
The Win32 PE file format has different sections for the different types of data — executable code, read only data, uninitialised data, imported function information, exported function information, resources, etc..
The section that we are interested in, being authors of self modifying code, is the .text section, that is, the section that contains executable code. To find the section headers, we first need to wade through a number of other headers, the first being IMAGE_DOS_HEADER which can be found at the start of a Windows EXE file and, according to the structure defined in winnt.h, looks like this:
typedef struct _IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; LONG e_lfanew; } IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
Now, the piece of data that we are interested in here is the e_lfanew field which contains the offset of the IMAGE_NT_HEADER structure. With a WORD being two bytes long, we can expect to find the e_lfanew field at offset 0x3c (30 WORDs — there are two arrays in there — multiplied by 2 bytes per WORD) in to the file, and it is a LONG data type, so it will be four bytes long.
Load memtst.exe (assemble memtst.s) in to your favourite hex editor. In my case I’m using bvi on Linux, but I believe WinHex is a popular choice for Windows users (does anyone remember Norton’s Disk Editor — diskedit.exe?!).
Scroll down to offset 0x3c and you will find the value of e_lfanew. In my memtst.exe it is 0x80000000. Now that is stored in little endian (least significant byte first) format which means that the actual value is 0x00000080. So, head off to offset 0x80 to find the IMAGE_NT_HEADER structure. Take a brolly with you as I believe it’s forecast rain down that way, but then what do you expect when you host things in a cloud.
It is easy to tell when you’ve found the IMAGE_NT_HEADER as it starts with the ‘PE’ signature/magic number. Here is my version of the IMAGE_NT_HEADER structure defined in winnt.h. It is the same, except that I’ve annotated each field with its offset from the start of the structure, and also expanded the IMAGE_FILE_HEADER structure. I did this because you often see malware referencing fields in these structures using a register and offset. This makes it quicker to find (as a grep using the offset as the regular expression, will find the possible fields) which field the instruction could be referencing. I’ve also added some comments so I don’t have to keep looking the same information up:
IMAGE_NT_HEADER { 0x0000 DWORD Signature /* 0x5045 == 'PE' */ IMAGE_FILE_HEADER { 0x0004 WORD Machine; 0x0006 WORD NumberOfSections; 0x0008 DWORD TimeDateStamp; 0x000c DWORD PointerToSymbolTable; 0x0010 DWORD NumberOfSymbols; 0x0014 WORD SizeOfOptionalHeader; /* includes DataDirectory entries, so add this to find start of IMAGE_SECTION_HEADER */ 0x0016 WORD Characteristics; } FileHeader IMAGE_OPTIONAL_HEADER { 0x0018 WORD Magic; 0x001a BYTE MajorLinkerVersion; 0x001b BYTE MinorLinkerVersion; 0x001c DWORD SizeOfCode; 0x0020 DWORD SizeOfInitializedData; 0x0024 DWORD SizeOfUninitializedData; 0x0028 DWORD AddressOfEntryPoint; 0x002c DWORD BaseOfCode; 0x0030 DWORD BaseOfData; 0x0034 DWORD ImageBase; 0x0038 DWORD SectionAlignment; 0x003c DWORD FileAlignment; 0x0040 WORD MajorOperatingSystemVersion; 0x0042 WORD MinorOperatingSystemVersion; 0x0044 WORD MajorImageVersion; 0x0046 WORD MinorImageVersion; 0x0048 WORD MajorSubsystemVersion; 0x004a WORD MinorSubsystemVersion; 0x004c DWORD Win32VersionValue; 0x0050 DWORD SizeOfImage; 0x0054 DWORD SizeOfHeaders; 0x0058 DWORD CheckSum; 0x005c WORD Subsystem; 0x005e WORD DllCharacteristics; 0x0060 DWORD SizeOfStackReserve; 0x0064 DWORD SizeOfStackCommit; 0x0068 DWORD SizeOfHeapReserve; 0x006c DWORD SizeOfHeapCommit; 0x0070 DWORD LoaderFlags; 0x0074 DWORD NumberOfRvaAndSizes; /* number of DataDirectory entries -- always set to 16 by current tools */ 0x0078 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
Pay attention to the comment that I added next to the SizeOfOptionalHeader field in the IMAGE_FILE_HEADER structure. This field contains the size of the IMAGE_OPTIONAL_HEADER structure, and I have noted that it includes the DataDirectory[] array elements.
The section headers follow on after the DataDirectory[] array in the IMAGE_OPTIONAL_HEADER and hence adding the SizeOfOptionalHeader field to the starting offset of the IMAGE_OPTIONAL_HEADER structure will yield the start of the first IMAGE_SECTION_HEADER. I don’t know about you, but I can’t wait to find it — I’ve been worried sick.
So, back to my copy of memtst.exe which has the IMAGE_NT_HEADER starting at 0x80. My annotated version of IMAGE_NT_HEADER structure above shows that the IMAGE_FILE_HEADER.SizeOfOptionalHeader field is at offset 0x14 from the start of the IMAGE_NT_HEADER structure, that is, at 0x94. SizeOfOptionalHeader is a WORD, so two bytes, which in my copy read 0xe000. Again, translating from little endian this yields 0x00e0. Why Grandma, what a big IMAGE_OPTIONAL_HEADER you have.
Adding the 0xe0 bytes (IMAGE_FILE_HEADER.SizeOfOptionalHeader) on to the starting offset of the IMAGE_OPTIONAL_HEADER (offset 0x18 bytes from the start of IMAGE_NT_HEADER — again, using my annotated IMAGE_NT_HEADER structure definition above), gives us an offset of 0x80 (start of IMAGE_NT_HEADER) + 0x18 (offset of IMAGE_OPTIONAL_HEADER in IMAGE_NT_HEADER) + 0xe0 (SizeOfOptionalHeader) which is 0x178.
Jumping down to offset 0x178 we find an IMAGE_SECTION_HEADER resting peacefully in its natural habitat. Let’s see if we can examine it and find its characteristics — don’t worry, it doesn’t bite. To find its characteristics, we’ll have a look at the IMAGE_SECTION_HEADER structure in winnt.h so that we can figure out where to look. Here’s another one of my annotated versions that I prepared earlier:
IMAGE_SECTION_HEADER { 0x0000 BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; /* IMAGE_SIZEOF_SHORT_NAME == 8 */ 0x0008 union { 0x0008 DWORD PhysicalAddress; 0x0008 DWORD VirtualSize; } Misc; 0x000c DWORD VirtualAddress; 0x0010 DWORD SizeOfRawData; 0x0014 DWORD PointerToRawData; 0x0018 DWORD PointerToRelocations; 0x001c DWORD PointerToLinenumbers; 0x0020 WORD NumberOfRelocations; 0x0022 WORD NumberOfLinenumbers; 0x0024 DWORD Characteristics; }
As you can see, each section header starts with the name of the section, and each section header is 0x28 bytes long — the last field is at offset 0x24 and it is a DWORD which is four bytes long. The Characteristics field which defines various properties about the section such as whether it contains executable code or data, whether it should be loaded, whether space should be allocated for it, whether it is writeable, etc., is at offset 0x24.
The name of the section which the section header is describing, can be found as an ASCII string in the first eight bytes of the section header. In this case the first section header is for the .text section, which just happens to be the one that we are interested in since it is the section that contains our self modifying code.
Have a look at offset 0x19c (0x178, being the start of the section header, + 0x24, being the offset of the IMAGE_SECTION_HEADER.Characteristics field) and in my case there is 0x20005060 there, which when converted from little endian to big endian (most significant byte first) gives us 0x60500020. Now we need to find the constants that tell us what that number is made up of.
That 0x60500020 value is more than likely made up by ORing several single bit values (namely 0x40000000, 0x20000000, 0x400000, 0x100000, and 0x20), where each single bit value represents a particular characteristic. Let’s try a search for the 0x40000000 value in the Windows include files and see if anything shows up. This may or may not work, as sometimes constants are defined as a value shifted left or right which, in this case, could be 4 << 28, or 2 << 29, for instance. We’ll search for the longest constant, as a search for 0x20 will likely yield a large number of results.
/usr/i586-mingw32msvc/include$ grep “0x40000000” /usr/i586-mingw32msvc/include/*
Even that resulted in 58 results. Scrolling through those results looking for the most likely name, we find IMAGE_SCN_MEM_READ in winnt.h. Let’s have a look at it in context and see if there are similar characteristic sounding constants near it. Yes there are, it is preceded by IMAGE_SCN_MEM_EXECUTE, and proceeded by IMAGE_SCN_MEM_WRITE, and if you scroll up to the start of the IMAGE_SCN_* constants in winnt.h you’ll see the following, which is referencing section headers. You could probably also suggest that the ‘SCN’ is short for ‘SECTION’. This all suggests that we’re on the right track:
#define IMAGE_FIRST_SECTION(h) ((PIMAGE_SECTION_HEADER) ((DWORD)h+FIELD_OFFSET(IMAGE_NT_HEADERS,OptionalHeader)+((PIMAGE_NT_HEADERS)(h))->FileHeader.SizeOfOptionalHeader))
Right then, time to make our little .text section writeable. What we are hoping for is that setting the IMAGE_SCN_MEM_WRITE characteristic on the .text section will cause the Windows loader to create the memory pages for the .text section, with write permission. This will mean that we won’t have to call the VirtualProtect() function and that our memtst.exe program will be able to modify itself with code blocks R and S (the blocks that call VirtualProtect() and print the results) commented out.
Firstly, assemble a copy of memtst.s with code blocks R and S commented out, and run it. You should see that it ‘encountered a problem and needs to close’. This is as we saw in ‘Self Modifying Code: Changing Memory Protection‘.
Now to set the IMAGE_SCN_MEM_WRITE characteristic on the .text section and run it again. To do this, we need to find the value of the IMAGE_SCN_MEM_WRITE constant, so search for it in the winnt.h include file:
/usr/i586-mingw32msvc/include$ grep "IMAGE_SCN_MEM_WRITE" winnt.h #define IMAGE_SCN_MEM_WRITE 0x80000000
To modify the characteristics, go back to the hex editor with memtst.exe loaded. Remember that we found the IMAGE_SECTION_HEADER.Characteristics field at offset 0x19c. We need to OR 0x80000000 to the current characteristics of 0x60500020. Doing so, we end up with 0xe0500020. Converting this back to little endian before storing it back in to the file, we get 0x200050e0.
Before we modify the file, let’s have a look at the .text section’s current characteristics so that we can see what a difference our change makes. Windows users can use ‘PEBrowse Professional‘ to do so by loading the file, right clicking on the .text section and selecting ‘Detail for .text’. I prefer this method as it avoids having to grope a mouse:
$ objdump -h memtst.exe memtst.exe: file format pei-i386 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000728 00401000 00401000 00000400 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE
Notice the ‘READONLY’ characteristic. Now, as they used to say on the Sooty show, ‘Izzy whizzy let’s get busy’.
It’s time to modify the file, so head down to offset 0x19c in memtst.exe and change the fourth byte from 0x60 to 0xe0, and save it. Now let’s check the characteristics again:
$ objdump -h memtst.exe memtst.exe: file format pei-i386 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000728 00401000 00401000 00000400 2**4 CONTENTS, ALLOC, LOAD, CODE
Notice how the ‘READONLY’ characteristic has disappeared — c’est bon. Now try running it again et voilà. This time you’ll notice that it no longer causes an ACCESS_VIOLATION exception, despite the VirtualProtect() call being commented out.
That then is another way in which we can affect the permissions on our code memory pages in order to allow our code to modify itself. Self modifying code can be used to make code harder to reverse engineer, but if you think about it, unpacking code also needs to modify or write code to memory.
The concepts of memory protection presented in both this article and the previous ‘Self Modifying Code: Changing Memory Protection‘ article also need to be taken in to consideration when writing unpacking code, as it too is writing code to memory in order to execute it. Hence it also needs both writeable and executable memory.
Now for the suspense building ending: This knowledge is about to come in handy.
Pingback: Automated Unpacking: A Behaviour Based Approach | Malware Musings