· 8 years ago · Mar 25, 2018, 12:06 AM
1 Userland API Monitoring and Code Injection Detection
2Malware
3freestylefebruary
4malware
5windows
6dtm 2018-02-21 10:57:07 UTC #1
7Userland API Monitoring and Code Injection Detection
8About This Paper
9
10The following document is a result of self-research of malicious software (malware) and its interaction with the Windows Application Programming Interface (WinAPI). It details the fundamental concepts behind how malware is able to implant malicious payloads into other processes and how it is possible to detect such functionality by monitoring communication with the Windows operating system. The notion of observing calls to the API will also be illustrated by the procedure of hooking certain functions which will be used to achieve the code injection techniques.
11
12Disclaimer: Since this was a relatively accelerated project due to some time constraints, I would like to kindly apologise in advance for any potential misinformation that may be presented and would like to ask that I be notified as soon as possible so that it may revised. On top of this, the accompanying code may be under-developed for practical purposes and have unforseen design flaws.
13Contents
14
15 Introduction
16 Section I: Fundemental Concepts
17 Inline Hooking
18 API Monitoring
19 Code Injection Primer
20 DLL Injection
21 CreateRemoteThread
22 SetWindowsHookEx
23 QueueUserAPC
24 Process Hollowing
25 Atom Bombing
26 Section II: UnRunPE
27 Code Injection Detection
28 Code Injection Dumping
29 UnRunPE Demonstration
30 Section III: Dreadnought
31 Detecting Code Injection Method
32 Heuristics
33 Dreadnought Demonstration
34 Process Injection - Process Hollowing
35 DLL Injection - SetWindowsHookEx
36 DLL Injection - QueueUserAPC
37 Code Injection - Atom Bombing
38 Conclusion
39 Limitations
40 References
41
42Introduction
43
44In the present day, malware are developed by cyber-criminals with the intent of compromising machines that may be leveraged to perform activities from which they can profit. For many of these activities, the malware must be able survive out in the wild, in the sense that they must operate covertly with all attempts to avert any attention from the victims of the infected and thwart detection by anti-virus software. Thus, the inception of stealth via code injection was the solution to this problem.
45Section I: Fundamental Concepts
46Inline Hooking
47
48Inline hooking is the act of detouring the flow of code via hotpatching. Hotpatching is defined as the modification of code during the runtime of an executable image[1]. The purpose of inline hooking is to be able to capture the instance of when the program calls a function and then from there, observation and/or manipulation of the call can be accomplished. Here is a visual representation of how normal execution works:
49
50Normal Execution of a Function Call
51
52+---------+ +----------+
53| Program | ----------------------- calls function -----------------------------> | Function | | execution
54+---------+ | . | | of
55 | . | | function
56 | . | |
57 | | v
58 +----------+
59
60versus execution of a hooked function:
61
62Execution of a Hooked Function Call
63
64+---------+ +--------------+ + -------> +----------+
65| Program | -- calls function --> | Intermediate | | execution | | Function | | execution
66+---------+ | Function | | of calls | . | | of
67 | . | | intermediate normal | . | | function
68 | . | | function function | . | |
69 | . | v | | | v
70 +--------------+ ------------------+ +----------+
71
72This can be separated into three steps. To demonstrate this process, the WinAPI function MessageBox will be used.
73
74 Hooking the function
75
76To hook the function, we first require the intermediate function which must replicate parameters of the targetted function. Microsoft Developer Network (MSDN) defines MessageBox as the following:
77
78int WINAPI MessageBox(
79 _In_opt_ HWND hWnd,
80 _In_opt_ LPCTSTR lpText,
81 _In_opt_ LPCTSTR lpCaption,
82 _In_ UINT uType
83);
84
85So the intermediate function may be defined like so:
86
87int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
88 // our code in here
89}
90
91Once this exists, execution flow has somewhere for the code to be redirected. To actually hook the MessageBox function, the first few bytes of the code can be patched (keep in mind that the original bytes must be saved so that the function may be restored for when the intermediate function is finished). Here are the original assembly instructions of the function as represented in its corresponding module user32.dll:
92
93; MessageBox
948B FF mov edi, edi
9555 push ebp
968B EC mov ebp, esp
97
98versus the hooked function:
99
100; MessageBox
10168 xx xx xx xx push <HookedMessageBox> ; our intermediate function
102C3 ret
103
104Here I have opted to use the push-ret combination instead of an absolute jmp due to my past experiences of it not being reliable for reasons to be discovered. xx xx xx xx represents the little-endian byte-order address of HookedMessageBox.
105
106 Capturing the function call
107
108When the program calls MessageBox, it will execute the push-ret and effectively jump into the HookedMessageBox function and once there, it has complete control over the paramaters and the call itself. To replace the text that will be shown on the message box dialog, the following can be defined in HookedMessageBox:
109
110int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
111 TCHAR szMyText[] = TEXT("This function has been hooked!");
112}
113
114szMyText can be used to replace the LPCTSTR lpText parameter of MessageBox.
115
116 Resuming normal execution
117
118To forward this parameter, execution needs to continue to the original MessageBox so that the operating system can display the dialog. Since calling MessageBox again will just result in an infinite recursion, the original bytes must be restored (as previously mentioned).
119
120int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
121 TCHAR szMyText[] = TEXT("This function has been hooked!");
122
123 // restore the original bytes of MessageBox
124 // ...
125
126 // continue to MessageBox with the replaced parameter and return the return value to the program
127 return MessageBox(hWnd, szMyText, lpCaption, uType);
128}
129
130If rejecting the call to MessageBox was desired, it is as easy as returning a value, preferrably one that is defined in the documentation. For example, to return the “No†option from a “Yes/No†dialog, the intermediate function can be:
131
132int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
133 return IDNO; // IDNO defined as 7
134}
135
136API Monitoring
137
138The concept of API monitoring follows on from function hooking. Because gaining control of function calls is possible, observation of all of the parameters is also possible, as previously mentioned hence the name API monitoring. However, there is a small issue which is caused by the availability of different high-level API calls that are unique but operate using the same set of API at a lower level. This is called function wrapping, defined as subroutines whose purpose is to call a secondary subroutine. Returning to the MessageBox example, there are two defined functions: MessageBoxA for parameters that contain ASCII characters and a MessageBoxW for parameters that contain wide characters. In reality, to hook MessageBox, it is required that both MessageBoxA and MessageBoxW be patched. The solution to this problem is to hook at the lowest possible common point of the function call hierarchy.
139
140 +---------+
141 | Program |
142 +---------+
143 / \
144 | |
145 +------------+ +------------+
146 | Function A | | Function B |
147 +------------+ +------------+
148 | |
149 +-------------------------------+
150 | user32.dll, kernel32.dll, ... |
151 +-------------------------------+
152 +---------+ +-------- hook -----------------> |
153 | API | <---- + +-------------------------------------+
154 | Monitor | <-----+ | ntdll.dll |
155 +---------+ | +-------------------------------------+
156 +-------- hook -----------------> | User mode
157 -----------------------------------------------------
158 Kernel mode
159
160Here is what the MessageBox call hierarchy looks like:
161
162Here is MessageBoxA:
163
164user32!MessageBoxA -> user32!MessageBoxExA -> user32!MessageBoxTimeoutA -> user32!MessageBoxTimeoutW
165
166and MessageBoxW:
167
168user32!MessageBoxW -> user32!MessageBoxExW -> user32!MessageBoxTimeoutW
169
170The call hierarchy both funnel into MessageBoxTimeoutW which is an appropriate location to hook. For functions that have a deeper hierarchy, hooking any lower could prove to be unecessarily troublesome due to the possibility of an increasing complexity of the function’s parameters. MessageBoxTimeoutW is an undocumented WinAPI function and is defined[2] like so:
171
172int WINAPI MessageBoxTimeoutW(
173 HWND hWnd,
174 LPCWSTR lpText,
175 LPCWSTR lpCaption,
176 UINT uType,
177 WORD wLanguageId,
178 DWORD dwMilliseconds
179);
180
181To log the usage:
182
183int WINAPI MessageBoxTimeoutW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType, WORD wLanguageId, DWORD dwMilliseconds) {
184 std::wofstream logfile; // declare wide stream because of wide parameters
185 logfile.open(L"log.txt", std::ios::out | std::ios::app);
186
187 logfile << L"Caption: " << lpCaption << L"\n";
188 logfile << L"Text: " << lpText << L"\n";
189 logfile << L"Type: " << uType << :"\n";
190
191 logfile.close();
192
193 // restore the original bytes
194 // ...
195
196 // pass execution to the normal function and save the return value
197 int ret = MessageBoxTimeoutW(hWnd, lpText, lpCaption, uType, wLanguageId, dwMilliseconds);
198
199 // rehook the function for next calls
200 // ...
201
202 return ret; // return the value of the original function
203}
204
205Once the hook has been placed into MessageBoxTimeoutW, MessageBoxA and MessageBoxW should both be captured.
206Code Injection Primer
207
208For the purposes of this paper, code injection will be defined as the insertion of executable code into an external process. The possibility of injecting code is a natural result of the functionality allowed by the WinAPI. If certain functions are stringed together, it is possible to access an existing process, write data to it and then execute it remotely under its context. In this section, the relevant techniques of code injection that was covered in the research will be introduced.
209DLL Injection
210
211Code can come from a variety of forms, one of which is a Dynamic Link Library (DLL). DLLs are libraries that are designed to offer extended functionality to an executable program which is made available by exporting subroutines. Here is an example DLL that will be used for the remainder of the paper:
212
213extern "C" void __declspec(dllexport) Demo() {
214 ::MessageBox(nullptr, TEXT("This is a demo!"), TEXT("Demo"), MB_OK);
215}
216
217bool APIENTRY DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved) {
218 if (fdwReason == DLL_PROCESS_ATTACH)
219 ::CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)Demo, nullptr, 0, nullptr);
220 return true;
221}
222
223When a DLL is loaded into a process and initialised, the loader will call DllMain with fdwReason set to DLL_PROCESS_ATTACH. For this example, when it is loaded into a process, it will thread the Demo subroutine to display a message box with the title Demo and the text This is a demo!. To correctly finish the initialisation of a DLL, it must return true or it will be unloaded.
224CreateRemoteThread
225
226DLL injection via the CreateRemoteThread function utilises this function to execute a remote thread in the virtual space of another process. As mentioned above, all that is required to execute a DLL is to have it load into the process by forcing it to execute the LoadLibrary function. The following code can be used to accomplish this:
227
228void injectDll(const HANDLE hProcess, const std::string dllPath) {
229 LPVOID lpBaseAddress = ::VirtualAllocEx(hProcess, nullptr, dllPath.length(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
230
231 ::WriteProcessMemory(hProcess, lpBaseAddress, dllPath.c_str(), dllPath.length(), &dwWritten);
232
233 HMODULE hModule = ::GetModuleHandle(TEXT("kernel32.dll"));
234
235 LPVOID lpStartAddress = ::GetProcAddress(hModule, "LoadLibraryA"); // LoadLibraryA for ASCII string
236
237 ::CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpStartAddress, lpBaseAddress, 0, nullptr);
238}
239
240MSDN defines LoadLibrary as:
241
242HMODULE WINAPI LoadLibrary(
243 _In_ LPCTSTR lpFileName
244);
245
246It takes a single parameter which is the path name to the desired library to load. The CreateRemoteThread function allows one parameter to be passed into the thread routine which matches exactly that of LoadLibrary's function definition. The goal is to allocate the string parameter in the virtual address space of the target process and then pass that allocated space’s address into the parameter argument of CreateRemoteThread so that LoadLibrary can be invoked to load the DLL.
247
248 Allocating virtual memory in the target process
249
250Using VirtualAllocEx allows space to be allocated within a selected process and on success, it will return the starting address of the allocated memory.
251
252Virtual Address Space of Target Process
253 +--------------------+
254 | |
255 VirtualAllocEx +--------------------+
256 Allocated memory ---> | Empty space |
257 +--------------------+
258 | |
259 +--------------------+
260 | Executable |
261 | Image |
262 +--------------------+
263 | |
264 | |
265 +--------------------+
266 | kernel32.dll |
267 +--------------------+
268 | |
269 +--------------------+
270
271 Writing the DLL path to allocated memory
272
273Once memory has been initialised, the path to the DLL can be injected into the allocated memory returned by VirtualAllocEx using WriteProcessMemory.
274
275Virtual Address Space of Target Process
276 +--------------------+
277 | |
278 WriteProcessMemory +--------------------+
279 Inject DLL path ----> | "..\..\myDll.dll" |
280 +--------------------+
281 | |
282 +--------------------+
283 | Executable |
284 | Image |
285 +--------------------+
286 | |
287 | |
288 +--------------------+
289 | kernel32.dll |
290 +--------------------+
291 | |
292 +--------------------+
293
294 Get address of LoadLibrary
295
296Since all system DLLs are mapped to the same address space across all processes, the address of LoadLibrary does not have to be directly retrieved from the target process. Simply calling GetModuleHandle(TEXT("kernel32.dll")) and GetProcAddress(hModule, "LoadLibraryA") will do the job.
297
298 Loading the DLL
299
300The address of LoadLibrary and the path to the DLL are the two main elements required to load the DLL. Using the CreateRemoteThread function, LoadLibrary is executed under the context of the target process with the DLL path as a parameter.
301
302Virtual Address Space of Target Process
303 +--------------------+
304 | |
305 +--------------------+
306 +--------- | "..\..\myDll.dll" |
307 | +--------------------+
308 | | |
309 | +--------------------+ <---+
310 | | myDll.dll | |
311 | +--------------------+ |
312 | | | | LoadLibrary
313 | +--------------------+ | loads
314 | | Executable | | and
315 | | Image | | initialises
316 | +--------------------+ | myDll.dll
317 | | | |
318 | | | |
319 CreateRemoteThread v +--------------------+ |
320 LoadLibraryA("..\..\myDll.dll") --> | kernel32.dll | ----+
321 +--------------------+
322 | |
323 +--------------------+
324
325SetWindowsHookEx
326
327Windows offers developers the ability to monitor certain events with the installation of hooks by using the SetWindowsHookEx function. While this function is very common in the monitoring of keystrokes for keylogger functionality, it can also be used to inject DLLs. The following code demonstrates DLL injection into itself:
328
329int main() {
330 HMODULE hMod = ::LoadLibrary(DLL_PATH);
331 HOOKPROC lpfn = (HOOKPROC)::GetProcAddress(hMod, "Demo");
332 HHOOK hHook = ::SetWindowsHookEx(WH_GETMESSAGE, lpfn, hMod, ::GetCurrentThreadId());
333 ::PostThreadMessageW(::GetCurrentThreadId(), WM_RBUTTONDOWN, (WPARAM)0, (LPARAM)0);
334
335 // message queue to capture events
336 MSG msg;
337 while (::GetMessage(&msg, nullptr, 0, 0) > 0) {
338 ::TranslateMessage(&msg);
339 ::DispatchMessage(&msg);
340 }
341
342 return 0;
343}
344
345SetWindowsHookEx defined by MSDN as:
346
347HHOOK WINAPI SetWindowsHookEx(
348 _In_ int idHook,
349 _In_ HOOKPROC lpfn,
350 _In_ HINSTANCE hMod,
351 _In_ DWORD dwThreadId
352);
353
354takes a HOOKPROC parameter which is a user-defined callback subroutine that is executed when the specific hook event is trigged. In this case, the event is WH_GETMESSAGE which deals with messages in the message queue. The code initially loads the DLL into its own virtual process space and the exported Demo function’s address is obtained and defined as the callback function in the call to SetWindowsHookEx. To force the callback function to execute, PostThreadMessage is called with the message WM_RBUTTONDOWN which will trigger the WH_GETMESSAGE hook and thus the message box will be displayed.
355QueueUserAPC
356
357DLL injection with QueueUserAPC works similar to that of CreateRemoteThread. Both allocate and inject the DLL path into the virtual address space of a target process and then force a call to LoadLibrary under its context.
358
359int injectDll(const std::string dllPath, const DWORD dwProcessId, const DWORD dwThreadId) {
360 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessId);
361
362 HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, false, dwThreadId);
363
364 LPVOID lpLoadLibraryParam = ::VirtualAllocEx(hProcess, nullptr, dllPath.length(), MEM_COMMIT, PAGE_READWRITE);
365
366 ::WriteProcessMemory(hProcess, lpLoadLibraryParam, dllPath.data(), dllPath.length(), &dwWritten);
367
368 ::QueueUserAPC((PAPCFUNC)::GetProcAddress(::GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryA"), hThread, (ULONG_PTR)lpLoadLibraryParam);
369
370 return 0;
371}
372
373One major difference between this and CreateRemoteThread is that QueueUserAPC operates on alertable states. Asynchronous procedures queued by QueueUserAPC are only handled when a thread enters this state.
374Process Hollowing
375
376Process hollowing, AKA RunPE, is a popular method used to evade anti-virus detection. It allows the injection of entire executable files to be loaded into a target process and executed under its context. Often seen in crypted applications, a file on disk that is compatible with the payload is selected as the host and is created as a process, has its main executable module hollowed out and replaced. This procedure can be broken up into four stages.
377
378 Creating a host process
379
380In order for the payload to be injected, the bootstrap must first locate a suitable host. If the payload is a .NET application, the host must also be a .NET application. If the payload is a native executable defined to use the console subsystem, the host must also reflect the same attributes. The same is applied to x86 and x64 programs. Once the host has been chosen, it is created as a suspended process using CreateProcess(PATH_TO_HOST_EXE, ..., CREATE_SUSPENDED, ...).
381
382Executable Image of Host Process
383 +--- +--------------------+
384 | | PE |
385 | | Headers |
386 | +--------------------+
387 | | .text |
388 | +--------------------+
389 CreateProcess + | .data |
390 | +--------------------+
391 | | ... |
392 | +--------------------+
393 | | ... |
394 | +--------------------+
395 | | ... |
396 +--- +--------------------+
397
398 Hollowing the host process
399
400For the payload to work correctly after injection, it must be mapped to a virtual address space that matches its ImageBase value found in the optional header of the payload’s PE headers.
401
402typedef struct _IMAGE_OPTIONAL_HEADER {
403 WORD Magic;
404 BYTE MajorLinkerVersion;
405 BYTE MinorLinkerVersion;
406 DWORD SizeOfCode;
407 DWORD SizeOfInitializedData;
408 DWORD SizeOfUninitializedData;
409 DWORD AddressOfEntryPoint; // <---- this is required later
410 DWORD BaseOfCode;
411 DWORD BaseOfData;
412 DWORD ImageBase; // <----
413 DWORD SectionAlignment;
414 DWORD FileAlignment;
415 WORD MajorOperatingSystemVersion;
416 WORD MinorOperatingSystemVersion;
417 WORD MajorImageVersion;
418 WORD MinorImageVersion;
419 WORD MajorSubsystemVersion;
420 WORD MinorSubsystemVersion;
421 DWORD Win32VersionValue;
422 DWORD SizeOfImage; // <---- size of the PE file as an image
423 DWORD SizeOfHeaders;
424 DWORD CheckSum;
425 WORD Subsystem;
426 WORD DllCharacteristics;
427 DWORD SizeOfStackReserve;
428 DWORD SizeOfStackCommit;
429 DWORD SizeOfHeapReserve;
430 DWORD SizeOfHeapCommit;
431 DWORD LoaderFlags;
432 DWORD NumberOfRvaAndSizes;
433 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
434} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
435
436This is important because it is more than likely that absolute addresses are involved within the code which is entirely dependent on its location in memory. To safely map the executable image, the virtual memory space starting at the described ImageBase value must be unmapped. Since many executables share common base addresses (usually 0x400000), it is not uncommon to see the host process’s own executable image unmapped as a result. This is done with NtUnmapViewOfSection(IMAGE_BASE, SIZE_OF_IMAGE).
437
438Executable Image of Host Process
439 +--- +--------------------+
440 | | |
441 | | |
442 | | |
443 | | |
444 | | |
445 NtUnmapViewOfSection + | |
446 | | |
447 | | |
448 | | |
449 | | |
450 | | |
451 | | |
452 +--- +--------------------+
453
454 Injecting the payload
455
456To inject the payload, the PE file must be parsed manually to transform it from its disk form to its image form. After allocating virtual memory with VirtualAllocEx, the PE headers are directly copied to that base address.
457
458Executable Image of Host Process
459 +--- +--------------------+
460 | | PE |
461 | | Headers |
462 +--- +--------------------+
463 | | |
464 | | |
465 WriteProcessMemory + | |
466 | |
467 | |
468 | |
469 | |
470 | |
471 | |
472 +--------------------+
473
474To convert the PE file to an image, all of the sections must be individually read from their file offsets and then placed correctly into their correct virtual offsets using WriteProcessMemory. This is described in each of the sections’ own section header.
475
476typedef struct _IMAGE_SECTION_HEADER {
477 BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
478 union {
479 DWORD PhysicalAddress;
480 DWORD VirtualSize;
481 } Misc;
482 DWORD VirtualAddress; // <---- virtual offset
483 DWORD SizeOfRawData;
484 DWORD PointerToRawData; // <---- file offset
485 DWORD PointerToRelocations;
486 DWORD PointerToLinenumbers;
487 WORD NumberOfRelocations;
488 WORD NumberOfLinenumbers;
489 DWORD Characteristics;
490} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
491
492Executable Image of Host Process
493 +--------------------+
494 | PE |
495 | Headers |
496 +--- +--------------------+
497 | | .text |
498 +--- +--------------------+
499 WriteProcessMemory + | .data |
500 +--- +--------------------+
501 | | ... |
502 +---- +--------------------+
503 | | ... |
504 +---- +--------------------+
505 | | ... |
506 +---- +--------------------+
507
508 Execution of payload
509
510The final step is to point the starting address of execution to the payload’s aforementioned AddressOfEntryPoint. Since the process’s main thread is suspended, using GetThreadContext to retrieve the relevant information. The context structure is defined as:
511
512typedef struct _CONTEXT
513{
514 ULONG ContextFlags;
515 ULONG Dr0;
516 ULONG Dr1;
517 ULONG Dr2;
518 ULONG Dr3;
519 ULONG Dr6;
520 ULONG Dr7;
521 FLOATING_SAVE_AREA FloatSave;
522 ULONG SegGs;
523 ULONG SegFs;
524 ULONG SegEs;
525 ULONG SegDs;
526 ULONG Edi;
527 ULONG Esi;
528 ULONG Ebx;
529 ULONG Edx;
530 ULONG Ecx;
531 ULONG Eax; // <----
532 ULONG Ebp;
533 ULONG Eip;
534 ULONG SegCs;
535 ULONG EFlags;
536 ULONG Esp;
537 ULONG SegSs;
538 UCHAR ExtendedRegisters[512];
539} CONTEXT, *PCONTEXT;
540
541To modify the starting address, the Eax member must be changed to the virtual address of the payload’s AddressOfEntryPoint. Simply, context.Eax = ImageBase + AddressOfEntryPoint. To apply the changes to the process’s thread, calling SetThreadContext and passing in the modified CONTEXT struct is sufficient. All that is required now is to call ResumeThread and payload should start execution.
542Atom Bombing
543
544The Atom Bombing is a code injection technique that takes advantage of global data storage via Windows’s global atom table. The global atom table’s data is accessible across all processes which is what makes it a viable approach. The data stored in the table is a null-terminated C-string type and is represented with a 16-bit integer key called the atom, similar to that of a map data structure. To add data, MSDN provides a GlobalAddAtom function and is defined as:
545
546ATOM WINAPI GlobalAddAtom(
547 _In_ LPCTSTR lpString
548);
549
550where lpString is the data to be stored. The 16-bit integer atom is returned on a successful call. To retrieve the data stored in the global atom table, MSDN provides a GlobalGetAtomName defined as:
551
552UINT WINAPI GlobalGetAtomName(
553 _In_ ATOM nAtom,
554 _Out_ LPTSTR lpBuffer,
555 _In_ int nSize
556);
557
558Passing in the identifying atom returned from GlobalAddAtom will place the data into lpBuffer and return the length of the string excluding the null-terminator.
559
560Atom bombing works by forcing the target process to load and execute code placed within the global atom table and this relies on one other crucial function, NtQueueApcThread, which is lowest level userland call for QueueUserAPC. The reason why NtQueueApcThread is used over QueueUserAPC is because, as seen before, QueueUserAPC's APCProc only receives one parameter which is a parameter mismatch compared to GlobalGetAtomName[3].
561
562VOID CALLBACK APCProc( UINT WINAPI GlobalGetAtomName(
563 _In_ ATOM nAtom,
564 _In_ ULONG_PTR dwParam -> _Out_ LPTSTR lpBuffer,
565 _In_ int nSize
566); );
567
568However, the underlying implementation of NtQueueApcThread allows for three potential parameters:
569
570NTSTATUS NTAPI NtQueueApcThread( UINT WINAPI GlobalGetAtomName(
571 _In_ HANDLE ThreadHandle, // target process's thread
572 _In_ PIO_APC_ROUTINE ApcRoutine, // APCProc (GlobalGetAtomName)
573 _In_opt_ PVOID ApcRoutineContext, -> _In_ ATOM nAtom,
574 _In_opt_ PIO_STATUS_BLOCK ApcStatusBlock, _Out_ LPTSTR lpBuffer,
575 _In_opt_ ULONG ApcReserved _In_ int nSize
576); );
577
578Here is a visual representation of the code injection procedure:
579
580Atom bombing code injection
581 +--------------------+
582 | |
583 +--------------------+
584 | lpBuffer | <-+
585 | | |
586 +--------------------+ |
587 +---------+ | | | Calls
588 | Atom | +--------------------+ | GlobalGetAtomName
589 | Bombing | | Executable | | specifying
590 | Process | | Image | | arbitrary
591 +---------+ +--------------------+ | address space
592 | | | | and loads shellcode
593 | | | |
594 | NtQueueApcThread +--------------------+ |
595 +---------- GlobalGetAtomName ----> | ntdll.dll | --+
596 +--------------------+
597 | |
598 +--------------------+
599
600This is a very simplified overview of atom bombing but should be adequate for the remainder of the paper. For more information on atom bombing, please refer to enSilo’s AtomBombing: Brand New Code Injection for Windows.
601Section II: UnRunPE
602
603UnRunPE is a proof-of-concept (PoC) tool that was created for the purposes of applying API monitoring theory to practice. It aims to create a chosen executable file as a suspended process into which a DLL will be injected to hook specific functions utilised by the process hollowing technique.
604Code Injection Detection
605
606From the code injection primer, the process hollowing method was described with the following WinAPI call chain:
607
608 CreateProcess
609 NtUnmapViewOfSection
610 VirtualAllocEx
611 WriteProcessMemory
612 GetThreadContext
613 SetThreadContext
614 ResumeThread
615
616A few of these calls do not have to be in this specific order, for example, GetThreadContext can be called before VirtualAllocEx. However, the general arrangement cannot deviate much because of the reliance on former API calls, for example, SetThreadContext must be called before GetThreadContext or CreateProcess must be called first otherwise there will be no target process to inject the payload. The tool assumes this as a basis on which it will operate in an attempt to detect a potentially active process hollowing.
617
618Following the theory of API monitoring, it is best to hook the lowest, common point but when it comes it malware, it should ideally be the lowest possible that is accessible. Assuming a worst case scenario, the author may attempt to skip the higher-level WinAPI functions and directly call the lowest function in the call hierarchy, usually found in the ntdll.dll module. The following WinAPI functions are the lowest in the call hierarchy for process hollowing:
619
620 NtCreateUserProcess
621 NtUnmapViewOfSection
622 NtAllocateVirtualMemory
623 NtWriteVirtualMemory
624 NtGetContextThread
625 NtSetContextThread
626 NtResumeThread
627
628Code Injection Dumping
629
630Once the necessary functions are hooked, the target process is executed and each of the hooked functions’ parameters are logged to keep track of the current progress of the process hollowing and the host process. The most significant hooks are NtWriteVirtualMemory and NtResumeThread because the former applies the injection of the code and the latter executes it. Along with logging the parameters, UnRunPE will also attempt to dump the bytes written using NtWriteVirtualMemory and then when NtResumeThread is reached, it will attempt to dump the entire payload that has been injected into the host process. To achieve this, it uses the process and thread handle parameters logged in NtCreateUserProcess and the base address and size logged from NtUnmapViewOfSection. Using the parameters provided by NtAllocateVirtualMemory may be more appropriate however, due to some unknown reasons, hooking that function results in some runtime errors. When the payload has been dumped from NtResumeThread, it will terminate the target process and its host process to prevent execution of the injected code.
631UnRunPE Demonstration
632
633For the demonstration, I have chosen to use a trojanised binary that I had previously created as an experiment. It consists of the main executable PEview.exe and PuTTY.exe as the hidden executable.
634Section III: Dreadnought
635
636Dreadnought is a PoC tool that was built upon UnRunPE to support a wider variety of code injection detection, namely, those listed in Code Injection Primer. To engineer such an application, a few augmentations are required.
637Detecting Code Injection Method
638
639Because there are so many methods of code injection, differentiating each technique was a necessity. The first approach to this was to recognise a “trigger†API call, that is, the API call which would peform the remote execution of the payload. Using this would do two things: identify the completion of and, to an extent, the type of the code injection. The type can be categorised into four groups:
640
641 Section: Code injected as/into a section
642 Process: Code injected into a process
643 Code: Generic code injection or shellcode
644 DLL: Code injected as DLLs
645
646process-injection
647Process%2BInjection%25281%2529.png1024x768
648
649Process Injection Info Graphic[4] by Karsten Hahn
650
651Each trigger API is listed underneath Execute. When either of these APIs have been reached, Dreadought will perform a code dumping method that matches the assumed injection type in a similar fashion to what occurs with process hollowing in UnRunPE. Reliance on this is not enough because there is still potential for API calls to be mixed around to achieve the same functionality as displayed from the stemming of arrows.
652Heuristics
653
654For Dreadnought to be able to determine code injection methods more accurately, a heuristic should be involved as an assist. In the development, a very simplistic heuristic was applied. Following the process injection infographic, every time an API was hooked, it would increase the weight of one or more of the associated code injection types stored within a map data structure. As it traces each API call, it will start to favour a certain type. Once the trigger API has been entered, it will identify and compare the weights of the relevant types and proceed with an appropriate action.
655Dreadnought Demonstration
656Process Injection - Process Hollowing
657DLL Injection - SetWindowsHookEx
658DLL Injection - QueueUserAPC
659Code Injection - Atom Bombing
660Conclusion
661
662This paper aimed to bring a technical understanding of code injection and its interaction with the WinAPI. Furthermore, the concept of API monitoring in userland was entertained with the malicious use of injection methods utilised by malware to bypass anti-virus detection. The following presents the current status of Dreadnought as of this writing.
663Limitations
664
665Dreadnought’s current heuristic and detection design is incredibly poor but was sufficient enough for theoretical demonstration purposes. Practical use may not be ideal since there is a high possibility that there will be collateral with respect to the hooked API calls during regular operations with the operating system. Because of the impossibility to discern benign from malicious behaviour, false positives and negatives may arise as a result.
666
667With regards to Dreadnought and its operations within userland, it may not be ideal use when dealing with sophisticated malware, especially those which have access to direct interactions with the kernel and those which have the capabilities to evade hooks in general.
668PoC Repositories
669
670 GitHub - UnRunPE
671 GitHub - Dreadnought
672
673References
674
675 [1] https://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Sotirov.pdf
676
677 [2] https://www.codeproject.com/Articles/7914/MessageBoxTimeout-API
678
679 [3] https://blog.ensilo.com/atombombing-brand-new-code-injection-for-windows
680
681 [4] http://struppigel.blogspot.com.au/2017/07/process-injection-info-graphic.html
682
683 ReactOs
684
685 NTAPI Undocumented Functions
686
687 ntcoder
688
689 GitHub - Process Hacker
690
691 YouTube - MalwareAnalysisForHedgehogs
692
693 YouTube - OALabs
694
695BlackYenii (Yenii) 2018-02-21 22:02:06 UTC #2
696
697Greate article @dtm :grinning: !!
698
699So rootkits can be detected if we hooked functions used to inject code ?
700dtm 2018-02-22 00:50:33 UTC #3
701
702Thank you for reading!
703
704Theoretically, you can hook anything if your monitoring application is at a low enough level but is most ideal when it is within the kernel so that it can oversee all processes. This is generally the case for anti-virus software which lies in the kernel and injects hooks into newly created processes. The main issue may lie with separating benign and malicious behaviour however, if your rules are strict enough and (maybe) works on an assumption that the object you are hooking is suspicious (which is what enables Dreadnought), it could potentially detect rootkits.
705
706On paper, it could work, but applying it in a practical scenario is an entirely different world.
707zxy86228436 2018-02-23 09:55:12 UTC #4
708
709This post was flagged by the community and is temporarily hidden.
710dtm 2018-03-23 10:57:07 UTC #5
711
712This topic was automatically closed after 30 days. New replies are no longer allowed.
713Home Categories FAQ/Guidelines Terms of Service Privacy Policy
714
715Powered by Discourse, best viewed with JavaScript enabled