Search This Blog

Friday, December 16, 2011

Executable Code Injection the Interesting Way

So. Executable code injection. In general, this term is associated with malicious intent. It is true in many cases, but in, at least, as many, it is not. Being malware researcher for the most of my career, I can assure you, that this technique appears to be very useful when researching malicious software, as it allows (in most cases) to defeat its protection and gather much of the needed information. Although, it is highly recommended not to use such approach, sometimes it is simply unavoidable.

There are several ways to perform code injection. Let's take a look at them.

DLL Injection
The most simple way to inject a DLL into another process is to create a remote thread in the context of that process by passing the address of the LoadLibrary API as a ThreadProc. However, it appears to be unreliable in modern versions of Windows due to the address randomization (which is currently not true, but who knows, may be once it becomes real randomization).

Another way, a bit more complicated, implies a shell code to be injected into the address space of another process and launched as a remote thread. This method offers more flexibility and is described here.

Manual DLL Mapping
Unfortunately, it has become fashionable to give new fancy names to the old good techniques. Manual DLL Mapping is nothing more than a complicated code injection. Why complicated, you may ask - because it involves implementation of custom PE loader, which should be able to resolve relocations. Adhering the Occam's Razor principle, I take the responsibility to claim, that it is much easier and makes more sense to simply allocate memory in another process using VirtualAllocEx API and inject the position independent shell code. 

Simple Code Injection
As the title of this section states, this is the simplest way. Allocate a couple of memory blocks in the address space of the remote process using VirtualAllocEx (one for code and one for data), copy your shell code and its data into those blocks and launch it as a remote thread.

All the methods listed above are covered well on the Internet. You may just google for "code injection" and you will get thousands of well written tutorials and articles. My intention is to describe a more complex, but also a more interesting way of code injection (in a hope that you have nothing else to do but try to implement this).

Before we start:
Another note for nerds. 
  • The code in this article does not contain any security checks unless it is needed as an example.
  • This is not malware writing tutorial, so I do not care whether the AV alerts when you try to use this method.
  • No, manual DLL mapping is not better ;-).
  • Neither do I care about how stable this solution is. If you decide to implement this, you will be doing it at your own risk.
Now, let's have some fun.




Disk vs Memory Layout
Before we proceed with the explanation, let's take a look at the PE file layout, whether on disk or in memory, as our solution relies on that.

This layout is logically identical for both PE files on disk and PE files in memory. The only differences are that some parts may not be present in memory and, the most important for us, on disk items are aligned by "File Alignment" while in memory they are aligned by "Page Alignment" values, which, in turn may be found in the Optional Header. For full PE COFF format reference check here.

Right now, we are particularly interested in sections that contain executable code ((SectionHeader.characteristics & 0x20000020) != 0). Usually, the actual code does not fill the whole section, leaving some parts simply padded by zeros. For example, if our code section only contains 'ExitProcess(0)', which may be compiled into 8 bytes, it will still occupy FileAlignment bytes on disk (usually 0x200 bytes). It will take even more space in memory, as the next section may not be mapped closer than this_section_virtual_address + PageAlignement  (in this particular case), which means that if we have 0x1F8 free bytes when the file is on disk, we'll have 0xFF8 free bytes when the file is loaded in memory.
The "formula" to calculate available space in code section is next_section_virtual_address - (this_section_virtual_address + this_section_virtual_size) as virtual size is (usually) the amount of actual data in section. Remember this, as that is the space that we are going to use as our injection target.
It may happen, that the target executable does not have enough spare space for our shell code, but let this not bother you too much. A process contains more than one module (the main executable and all the DLLs). This means that you can look for spare space in the code sections of all modules. Why only code sections? Just in order not to mess too much with memory protection.

Shellcode
The first and the most important rule for shellcode - it MUST be position independent. In our case, this rule is especially unavoidable (if you may say so) as it is going to be spread all over the memory space (depends on the size of your shell code, of course). 

The second, but not less important rule - carefully plan your code according to your needs. The less space it takes, the easier the injection process would be.

Let's keep our shell code simple. All it would do is interception of a single API (does not matter which one, select whichever you want from the target executable's import section), and show a message box each time that API is called (you should probably select ExitProcess for interception if you do not want the message box popping up all the time).

Divide your shellcode into independent functional blocks. By independent, I mean that it should not have any direct or relative calls or jumps. Each block should have one data field, which would contain the address of the table containing addresses of all our functions (and data if needed). Such mechanism would allow us to spread the code all over the available space in different modules without the need to mess with relocations at all.

The picture on the left and the diagram below will help you to better understand the concept. 
Init - our initialization function. Once the code is injected, you would want to call this function as a remote thread.
Patch - this block is responsible for actually patching the import table with the address of our Fake.

The code in each of the above blocks will have to access Data in order to retrieve addresses of functions from other blocks.

Your initialization procedure would have to locate the KERNEL32.DLL in memory in order to obtain the addresses of LoadLibrary (yes, it would be better to use LoadLibrary rather then GetModuleHandle), GetProcAddress and VirtualProtect API functions which are crucial even for such a simple task as patching one API call. Those addresses would be stored in Data.

The Injector
While the shellcode is pretty trivial (at least in this particular case), the injector is not. It will not allocate memory in the address space of another process (if possible, of course). Instead, it will parse the the PEB (Process Environment Block) of the victim in order to get the list of loaded modules. Once that is done, it will parse section headers of every module in order to create list of available memory locations (remember, we prefer code sections only) and fill the Data block with appropriate addresses. Let's take a look at each step.

First of all, it may be a good idea to suspend the process by calling SuspendThread function on each of its threads. You may want to read this post about threads enumeration. One more thing to remember is to open the victim process with the following flags: PROCESS_VM_READ | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME in order to be able to perform all the following operations. The function itself is quite simple:

DWORD WINAPI SuspendThread(__in HANDLE hThread);

Don't forget to resume all threads with ResumeThread once the injection is done.

The next step would be calling the NtQueryInformationProcess function from the ntdll.dll. The only problem with it is that it has no associated import library and you will have to locate it with GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess"), unless you have a way to explicitly specify it in the import table of your injector. Also, try LoadLibrary if the GetModuleHandle does not work for you.

NTSTATUS WINAPI NtQueryInformationProcess(
   __in      HANDLE ProcessHandle,
   __in      PROCESSINFOCLASS ProcessInformationClass, /* Use 0 in order to 
                                               get the PEB address */
   __out     PVOID ProcessInformation,  /* Pointer to the PROCESS_BASIC_INFORMATION
                                                       structure */
   __in      ULONG ProcessInformationLength, /* Size of the PROCESS_BASIC_INFORMATION
                                                     structure in bytes */
   __out_opt PULONG ReturnLength
);

typedef struct _PROCESS_BASIC_INFORMATION
{
   PVOID     ExitStatus;
   PPEB      PebBaseAddress;
   PVOID     AffinityMask;
   PVOID     BasePriority;
   ULONG_PTR UniqueProcessId;
   PVOID     InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;

The NtQueryInformationProces will provide you with the address of the PEB of the victim process. This post will explain you how to deal with PEB content. Of course, you will not be able to access that content directly (as it is in the address space of another process), so you will have to use WriteProcessMemory and ReadProcessMemory functions for that.

BOOL WINAPI WriteProcessMemory(
   __in   HANDLE   hProcess,
   __in   LPVOID   lpBaseAddress,  /* Address in another process */
   __in   LPCVOID  lpBuffer,  /* Local buffer */
   __in   SIZE_T   nSize,  /* Size of the buffer in bytes */
   __out  SIZE_T*  lpNumberOfBytesWritten
};

BOOL WINAPI ReadProcessMemory(
   __in   HANDLE   hProcess,
   __in   LPCVOID  lpBaseAddress, /* Address in another process */
   __out  LPVOID   lpBuffer,  /* Local buffer */
   __in   SIZE_T   nSize,  /* Size of the buffer in bytes */
   __out  SIZE_T*  lpNumberOfBytesRead
};

Due to the fact that you are going to deal with read only memory locations, you should call VirtualProtectEx in order to make those locations writable (PAGE_EXECUTE_READWRITE).  Don't forget to restore memory access permissions to PAGE_EXECUTE_READ when you are done. 

BOOL WINAPI VirtualProtectEx(
   __in  HANDLE hProcess,
   __in  LPVOID lpAddress, /* Address in another process */
   __in  SIZE_T dwSize,  /* Size of the range in bytes */
   __in  DWORD  flNewProtect, /* New protection */
   __out PDWORD lpflOldProtect
};

You may also want to change the VirtualSize of those sections of the victim process you used for injection in order to cover the injected code. Just adjust it in the headers in memory.

That's all folks. Let me leave the hardest part (writing the code) up to you this time. 

Hope this post was interesting and see you at the next.

2 comments:

Note: Only a member of this blog may post a comment.