C DLL Injection Tutorial Written by Ferbal 5-17-05 Introduction Hello all! This is a tutorial on DLL Injection in C. The purpose of this paper is to show an example of a working injection program that I made and to explain all the parts of the code to hopefully give you a good understanding of what exactly is going and to further your coding skills. Hope you enjoy! ....The Code.... Here is all of the code for the injection AND DLL (later on) programs, I will explain the code in sections later in this paper. Code: /* ***********DLL INJECTION******************** *********** By Ferbal ********************** *********** Update 5-17-05 ***************** This is a working dll injector. Target is found from finding its Process ID (can use task manager to do this). Usage: Enjoy! NOTE: VirtualAllocEx() ONLY works on NT family!!!! */ #include #include #define WIN32_LEAN_MEAN int Inject(DWORD, LPCTSTR); int main(int argc, char **argv) { if(argc != 3) { printf("Bad Arguments..."); printf("Usage: "); return -1; } Inject(atol(argv[1]), argv[2]); return 0; } int Inject(DWORD ProcID, LPCTSTR szDllPath) { LPVOID lpRemoteMemory; HANDLE hRemoteThread; HWND hOpenProcess; SIZE_T nSize = strlen(szDllPath); unsigned long IDProcess = ProcID; HMODULE hKernel; // ************ OpenProcess() API CALL ************** hOpenProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, IDProcess); if(hOpenProcess == NULL) { printf("\nOpenProcess is NULL"); printf("\nOpenProcess Error: %d", GetLastError()); } printf("\nOpenProcess: %d", hOpenProcess); //********* VirtualAllocEx() API CALL ***************** lpRemoteMemory = VirtualAllocEx(hOpenProcess, 0, strlen(szDllPath), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(!lpRemoteMemory) { printf("\nVirtualAlloc Error: %d", GetLastError()); return -1; } printf("\nVirtualAlloc: 0x%x", lpRemoteMemory); //********* WriteProcessMemory() API CALL *************** if(!WriteProcessMemory(hOpenProcess, lpRemoteMemory, (LPVOID)szDllPath, strlen(szDllPath), NULL)) { printf("\nWriteProcessMemory Failed: %d", GetLastError()); } printf("\nWriteProcessMemory Complete!"); printf("\nSize of DLL = %d", nSize); // ********** GetModuleHandle() of kernel32.dll API CALL ***************** hKernel = GetModuleHandle("KERNEL32.DLL"); if(!hKernel) { printf("\nKernelModule Failed: %d", GetLastError()); } printf("\nKernelModule Complete!"); // *********** CreateRemoteThread() API CALL ************* //hRemoteThread = CreateRemoteThread(hOpenProcess, NULL, 0, (LPTHREAD_START_ROUTINE) LoadLibrary(szDllPath), lpRemoteMemory, 0, &IDProcess); hRemoteThread = CreateRemoteThread(hOpenProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel, "LoadLibraryA"), lpRemoteMemory, 0, NULL); if(!hRemoteThread) { printf("\nCreateRemoteThread Error: %d", GetLastError()); return -1; } printf("\nCreateRemoteThread: %d", hRemoteThread); //*********************************************************** // CLEAN UP PROCEDURES if(!CloseHandle(hRemoteThread) && !CloseHandle(hOpenProcess)) { printf("\nHandle NOT closed!!"); printf("\nError Code: %d", GetLastError()); } printf("\nCloseHandle Complete!"); // Wait for the thread to complete (ie. LoadLibrary function returns)... if(!WaitForSingleObject(hRemoteThread, INFINITE)) { printf("\nWaitThreadObject Failed!"); printf("\nError Code: %d", GetLastError()); } printf("\nWaitThreadObject Complete!"); // Free memory in remote process and close thread handle... if(!VirtualFreeEx(hOpenProcess, lpRemoteMemory, 0, MEM_RELEASE)) { printf("\nVirtualFreeMemory Failed!"); printf("\nError Code: %d", GetLastError()); } printf("\nVirtualFreeMemory Complete!"); return 0; } Note: This paper is assuming that you have an understanding of C code. Code: int main(int argc, char **argv) { if(argc != 3) { printf("Bad Arguments..."); printf("Usage: "); return -1; } Inject(atol(argv[1]), argv[2]); return 0; } Okay. This is the main() function, obviously. You all should know this. Basically, if the arguments supplied when the program is ran is not three, then the program will terminate. Otherwise, it will call Inject with the arguments, which is where all the good/fun code is . Code: int Inject(DWORD ProcID, LPCTSTR szDllPath) { LPVOID lpRemoteMemory; HANDLE hRemoteThread; HWND hOpenProcess; SIZE_T nSize = strlen(szDllPath); unsigned long IDProcess = ProcID; HMODULE hKernel; First we will start off with the parameters that the Inject() function takes. ProcID, which is of DWORD type and szDllPath, which is of LPCTSTR type. ProcID is the process ID of the target program to inject into. szDllPath is the path of the DLL file to inject into the target program. Moving onto the initialized variables: Variables: LPVOID lpRemoteMemory is used to hold the VirtualAllocEx() function return. HANDLE hRemoteThread is used to hold the handle for the CreateRemoteThread() function return. HWND hOpenProcess is used to hold the OpenProcess() function return. SIZE_T nSize is used to hold the length of the DLL. unsigned long IDProcess is used to hold the target process ID. HMODULE hKernel is used to hold the Module Handle of kernel32.dll. These variables will come up again later on. Code: // ************ OpenProcess() API CALL ************** hOpenProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, IDProcess); if(hOpenProcess == NULL) { printf("\nOpenProcess is NULL"); printf("\nOpenProcess Error: %d", GetLastError()); } printf("\nOpenProcess: %d", hOpenProcess); This next part is the OpenProcess() API call. The point of this call is to get a handle of the target program. Parameters: PROCESS_ALL_ACCESS means that we will have all access rights to the target program. FALSE...this parameter is not important to us, but here is the msdn descriptiion: If this parameter is TRUE, the handle is inheritable. If the parameter is FALSE, the handle cannot be inherited. IDProcessis the identifier (ID) of the process we want to open. The if statement checks to see if hOpenProcess is null. A null return means that the function failed and the print statements would give us the error code to use to debug/help fix the problem. Otherwise it prints the handle. Code: //********* VirtualAllocEx() API CALL ***************** lpRemoteMemory = VirtualAllocEx(hOpenProcess, 0, strlen(szDllPath), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(!lpRemoteMemory) { printf("\nVirtualAlloc Error: %d", GetLastError()); return -1; } printf("\nVirtualAlloc: 0x%x", lpRemoteMemory); This section of code is the VirtualAllocEx() API call. An important note to add is that this API call is only usable on the NT family and is not supported (officially) by Win95/98 etc. The point of this call, is to make some room for us in the target program to insert code. Now onto the parameters: Parameters: hOpenProcess is our target process. The memory allocated is in this process. 0 is our starting address to insert the code. 0, or null, means that the function will automatically determine where to allocate the memory, so we don't need to pick a starting address. strlen(szDllPath) is the size of the memory to allocate. In this case it is the DLL that needs to be allocated. MEM_RESERVE|MEM_COMMIT is the type of memory allocation. RESERVE means to reserve the range of memory and COMMIT means (taken word for word from msdn.com) Allocates physical storage in memory. PAGE_EXECUTE_READWRITE is a protection parameter. We need read write and execute access for our code that will be inserted. As before, the print statements just print out whether it fails or not. if (!lpRemoteMemory) means if (lpRemoteMemory == null). In other words, if the function fails. Code: //********* WriteProcessMemory() API CALL *************** if(!WriteProcessMemory(hOpenProcess, lpRemoteMemory, (LPVOID)szDllPath, strlen(szDllPath), NULL)) { printf("\nWriteProcessMemory Failed: %d", GetLastError()); } printf("\nWriteProcessMemory Complete!"); printf("\nSize of DLL = %d", nSize); This is the WriteProcessMemory() API call. This is exactly what it says, write to process memory. Here is where we write our DLL to the target process memory. Parameters: hOpenProcess is as before the target process handle. lpRemoteMemory is the virtual memory that we allocated in the target process. (LPVOID)szDllPath is what will be written (our DLL). *Note: (LPVOID) is what is called a cast. We are casting the type of szDllPath to LPVOID from whatever type it was before. strlen(szDllPath) is to find the size of our DLL. This was used in VirtualAllocEx as well. NULL ...this last parameter is an optional one. If we wanted to know the number of bytes being moved to the target process, we could put a pointer here that would collect it and if you wanted to, you could print it out to the user, for whatever reason you want. NULL means that this parameter will be ignored and not used. As with the other functions, print statements just let us know if it fails or succeeds. Code: // ********** GetModuleHandle() of kernel32.dll API CALL ***************** hKernel = GetModuleHandle("KERNEL32.DLL"); if(!hKernel) { printf("\nKernelModule Failed: %d", GetLastError()); } printf("\nKernelModule Complete!"); This code shows the use of the GetModuleHandl() API call. This call finds the module handle of kernel32.dll. This dll is needed for our injection, because kernel32.dll has a permanent starting address in all running process in Windows. This will be used in the CreateRemoteThread() API call, which will be discussed next. Code: // *********** CreateRemoteThread() API CALL ************* hRemoteThread = CreateRemoteThread(hOpenProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel, "LoadLibraryA"), lpRemoteMemory, 0, NULL); if(!hRemoteThread) { printf("\nCreateRemoteThread Error: %d", GetLastError()); return -1; } printf("\nCreateRemoteThread: %d", hRemoteThread); This code shows us the CreateRemoteThread() API call. This call creates a thread in the virtual address space in our target process. 7 parameters WOW!!!! Parameters: hOpenProcess As before, our handle to our target process. NULL This parameter is not important to us, but here is the msdn description: Pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from the primary token of the creator. 0 Again, not important to us, here is the msdn description: Initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the new thread uses the default size for the executable. (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel, "LoadLibraryA") Woah!!! A big one . This is where our hKernel module handle comes into play. It is a necessity to find the address of kernel32.dll in our process. That is what GetProcAddress() does. The second parameter, LoadLibraryA: (taken from msdn) The LoadLibrary function maps the specified executable module into the address space of the calling process. (end msdn) The A means ANSI as there is a Unicode brother, W, which could be exchanged for A. SO, by using GetProcAddress, we are finding the Loadlibrary function in kernel32.dll. For more info, check out msdn! lpRemoteMemoryOur virtual memory, to be passed. 0 This parameter is used for flags, not important to us. Check out msdn, for more info on it. NULL This is another optional parameter. If a variable is here, it would receive the threadID, and since we don't need, we use NULL to ignore it. Wow, this was a biggy . Now it is time to clean up this mess we have made!!!! (Contrary to belief, cleaning up is easier then making the injection!) Code: // CLEAN UP PROCEDURES if(!CloseHandle(hRemoteThread) && !CloseHandle(hOpenProcess)) { printf("\nHandle NOT closed!!"); printf("\nError Code: %d", GetLastError()); } printf("\nCloseHandle Complete!"); // Wait for the thread to complete (ie. LoadLibrary function returns)... if(!WaitForSingleObject(hRemoteThread, INFINITE)) { printf("\nWaitThreadObject Failed!"); printf("\nError Code: %d", GetLastError()); } printf("\nWaitThreadObject Complete!"); // Free memory in remote process and close thread handle... if(!VirtualFreeEx(hOpenProcess, lpRemoteMemory, 0, MEM_RELEASE)) { printf("\nVirtualFreeMemory Failed!"); printf("\nError Code: %d", GetLastError()); } printf("\nVirtualFreeMemory Complete!"); This is all the cleaning up thats needed. CloseHandle(), to close the handles we opened; hRemoteThread and hOpenProcess. WaitForSingleObject() takes the thread that we made, and waits for it to do what its supposed to do and then close. The INFINITE parameter basically means wait forever to return. We could put a number here (measured in milliseconds) and after that time elapsed the function would return even if the thread isn't complete. VirtualFreeEx() is the sister to VirtalAllocEx(), basically free the memory that we allocated! THATS IT!?!?!?! Well, we are done with the injector program. This is the meat of this tutorial, however it needs the DLL counterpart to work!!!! Here is the example DLL: Code: /* Example DLL Code By Ferbal 5-10-05 */ #include #include DWORD WINAPI Func1(LPVOID pData) { MessageBox(NULL, "Valuable code would execute here!", "Success", MB_OK | MB_ICONASTERISK); return 1; } BOOL APIENTRY DllMain(HANDLE hModule, DWORD lpReason, LPVOID lpReserved) { HANDLE hThread; // Thread handle DWORD nThread; // Thread ID if(lpReason == DLL_PROCESS_ATTACH) { //Try to create a new thread (which will run Func1()) if((hThread = CreateThread(NULL, 0, Func1, NULL, 0, &nThread)) != NULL) { // Close handle CloseHandle(hThread); } } return TRUE; } Woah, not much at all! Well, this is only an example so there isn't much, however this could be many lines long of code filled with useful stuff! Okay the most important part is this... Code: BOOL APIENTRY DllMain(HANDLE hModule, DWORD lpReason, LPVOID lpReserved) { HANDLE hThread; // Thread handle DWORD nThread; // Thread ID if(lpReason == DLL_PROCESS_ATTACH) { //Try to create a new thread (which will run Func1()) if((hThread = CreateThread(NULL, 0, Func1, NULL, 0, &nThread)) != NULL) { // Close handle CloseHandle(hThread); } } return TRUE; All DLLs will have a DllMain() method. This is its entry. I will only talk about one parameter, lpReason. lpReason is used to see what state the DLL is being used in. Whether it is being attached, detached or whatever. In our case it will be attached to a target process. So, if lpReason is to attach to a process, it will try to create a new thread to run whatever is in the dll that we want to run. There is really only one important parameter for us and this function, that is the third one. Func1 is the function in the dll that would be called. This is where you put whatever it is you want to call. The last parameter, &nThread is an optional one, where it puts the thread ID into a pointer to a variable for whatever use we want. My Func1 just prints out a message box. Thats all, but any code you want can go there. WOOOHOOO that was fun, right? Anyway, I hope you enjoyed my article and I hope you learned something, which I think is really awesome. www.msdn.com was used for reference, if anyone cares. Questions/Comments, email me at *UPDATE 7-14-07, email: webmaster@computerxl.com*