Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

AtomicBombing brand new code injection form windows

$
0
0

TL;DRHere’s a new code injection technique, dubbed AtomBombing, which exploits windows atom tables and Async Procedure Calls (APC). Currently, this technique goes undetected by common security solutions that focus on preventing infiltration.

Code injection has been a strong weapon in the hacker’s arsenal for many years. For background on code injection and its various uses in APT type attack scenarios please take a look at:

http://blog.ensilo.com/atombombing-a-code-injection-that-bypasses-current-security-solutions

Overview

I started poking around to see how hard it would be for a threat actor to find a new method that security vendors are unaware of and bypasses most security products. It also needed to work on different processes rather than being tailored to fit a specific process.

I would like to introduce you to AtomBombing a brand new code injection technique for Windows.

AtomBombing works in three main stages:

Write-What-Where Writing arbitrary data to arbitrary locations in the target process’s address space. Execution Hijacking a thread ofthe target process to execute the code written in stage 1. Restoration Cleaning up and restoring the execution of the thread hijacked in stage 2. AtomBombing Stage 1: Write-What-Where

I stumbled onto a couple of rather interesting API calls:

GlobalAddAtom Adds a character string to the global atom table and returns a unique value (an atom) identifying the string. GlobalGetAtomName Retrieves a copy of the character string associated with the specified global atom.

By calling GlobalAddAtom one can store a null terminated buffer in the global atom table. This table is accessible from every other process on the system. The buffer can then be retrieved by calling GlobalGetAtomName. GlobalGetAtomName expects a pointer to an output buffer, so the caller chooses where the null terminated buffer will be stored.

In theory, if I could add a buffer containing shellcode to the global atom table by calling GlobalAddAtom, and then somehow get the target process to call GlobalGetAtomName I could copy code from my process to the target process, without calling WriteProcessMemory.

Calling GlobalAddAtom from my process is pretty straightforward, but how would I get the target process to call GlobalGetAtomName?

By using Async Procedure Calls (APC):

QueueUserApc adds a user-mode asynchronous procedure call (APC) object to the APC queue of the specified thread.

DWORD WINAPI QueueUserAPC(
_In_ PAPCFUNC pfnAPC,
_In_ HANDLE hThread,
_In_ ULONG_PTR dwData
);

QueueUserApc receives a pointer to an APCProc which is defined as follows:

VOID CALLBACK APCProc(
_In_ ULONG_PTR dwParam
);

GlobalGetAtomName’s prototype is:

UINT WINAPI GlobalGetAtomName(
_In_ ATOM nAtom,
_Out_ LPTSTR lpBuffer,
_In_ int nSize
);

Since GlobalGetAtomName expects 3 parameters (while APCProc is defined to expect only 1 parameter) we can’t use QueueUserApc to get the target process to call GlobalGetAtomName.

Let’s take a look at the internals of QueueUserApc:


AtomicBombing   brand new code injection form windows

Figure 1: QueueUserApc

As you can see QueueUserApc uses the undocumented NtQueueApcThread syscall in order to add the APC to the target thread’s APC queue.

Interestingly enough NtQueueApcThread receives a pointer to a function that is to be called asynchronously in the target thread, but the function being passed is not the original APCProc function the caller passed to QueueUserApc. Instead the function being passed is ntdll!RtlDispatchAPC, and the original APCProc function passed to QueueUserApc is passed as a parameter to ntdll!RtlDispatchAPC.

Let’s take a look at ntdll!RtlDispatchAPC:


AtomicBombing   brand new code injection form windows

Figure 2: ntdll!RtlDispatchAPC

It starts by checking if the 3 rd parameter is valid, which means an ActivationContext needs to be activated before dispatching the APC.

If an ActivationContext needs to be activated:


AtomicBombing   brand new code injection form windows

Figure 3: ntdll!RtlDispatchAPC RtlActivateActivationContextUnsafeFast

The function ntdll!RtlDispatchAPC executes the following:

The passed ActivationContext (currently in ESI) will be activated by calling RtlActivateActivationContextUnsafeFast. The parameter to the original APCProc function (i.e. the third parameter passed to QueueUserApc) is pushed onto the stack. This is because we are about to call the original APCProc function. Right before dispatching the APC, a call to CFG (__guard_check_icall_fptr) is made to make sure the APC target is a CFG valid function. A call to the original APCProc is made, and that’s it the APC has been dispatched.

Once APCProc returns, the activation context is deactivated:


AtomicBombing   brand new code injection form windows

Figure 4: ntdll!RtlDispatchAPC RtlDeactivateActivationContextUnsafeFast

If, on the other hand, no activation context needs to be activated:


AtomicBombing   brand new code injection form windows

Figure 5: ntdll!RtlDispatchAPC no activation context

The code skips all the activation context related stuff and simply dispatches the APC right away after calling CFG.

What does all this mean? When calling QueueUserApc we are forced to pass an APCProc which expects one parameter. However, under the hood QueueUserApc uses NtQueueApcThread to call ntdll!RtlDispatchAPC which expects 3 parameters.

What was our goal? To call GlobalGetAtomName. How many parameters does it expect? 3. Can we do this? Yes. How? NtQueueApcThread!

See main_ApcWriteProcessMemory in AtomBombing’s GitHub repository .

AtomBombing Stage 2: Execution

Obviously I could never hope to consistently find RWX code caves in my target processes. I needed a way to consistently allocate RWX memory in the target process without calling VirtualAllocEx within the context of the injecting process. Sadly, I could not find any such function that I could invoke via APC and would allow me to allocate executable memory or change the protection flags of already allocated memory.

What do we have so far? Write-what-where + a burning desire to get some executable memory. I thought long and hard how to get over this hurdle, and then it hit me. When DEP was invented, its creators thought, “that’s it, data is no longer executable, therefore no one will ever be able to exploit vulnerabilities again”. Unfortunately, that was not the case; a new exploitation technique was invented solely to bypass DEP: ROP Return Oriented Programming.

How can we use ROP to our advantage in order to execute our shellcode in the target process?

We can co

Viewing all articles
Browse latest Browse all 12749

Trending Articles