Leveraging UnsafeNativeMethods
Let’s try to improve our shellcode runner.
It executed three primary steps related to the Win32 APIs.
It located the function,
specified argument data types, and invoked the function.
Let’s first focus on the techniques we used to locate the functions.
There are two primary ways to locate functions
in unmanaged dynamic link libraries.
Our original technique relied on the Add-Type and dll import keywords,
or the Declare keyword in VBA.
However, Add-Type calls the csc Compiler, which writes to disk.
We must avoid this if we want to operate completely in-memory.
Alternatively, we can use a technique known as dynamic lookup,
which is commonly used by low-level languages like C.
By taking this path, we hope to create the .NET assembly in memory
instead of writing code and compiling it.
This will take significantly more work,
but it is a valuable technique to understand.
To perform a dynamic lookup of function addresses, the operating system
provides two special Win32 APIs called GetModuleHandle and GetProcAddress.
GetModuleHandle obtains a handle to the specified DLL,
which is actually the memory address of the DLL.
To find the address of a specific function we’ll pass the DLL handle,
and the function name to GetProcAddress, which will return the function address.
We can use these functions to locate any API,
but we must invoke them without using Add-Type.
Since we cannot create any new assemblies,
we’ll try to locate existing assemblies that we can reuse.
We’ll use the following code to find assemblies that match our criteria.
To begin, we are relying on GetAssemblies
to search preloaded assemblies in the PowerShell process.
Since each assembly is an object,
we’ll use the ForEach-Object cmdlet to loop through them.
We'll then invoke Get-Types for each object through the underscore variable,
which contains the current object to contain its methods and structures.
It stands to reason that we could search the preloaded assemblies
for the presence of GetModuleHandle and GetProcAddress,
but we can also narrow the search more specifically.
For example, when C# code wants to directly invoke Win32 APIs
it must provide the Unsafe keyword.
Furthermore, if any functions are to be used,
they must be declared as static to avoid instantiation.
Knowing this, we’ll perform yet another ForEach-Object loop
on all the discovered objects,
and invoke the Get-Member cmdlet
with the -Static flag to only locate static properties or methods.
Finally, we pipe these static properties and methods
through the Where-Object cmdlet and filter any TypeName, which contains
meta information about the object, that contains the keyword Unsafe.
This should dump every function that satisfies our criteria.
Let’s run it,
and examine the output.
This code generates an enormous amount of output.
If we search the output for GetModuleHandle we locate 16 occurrences.
We also notice that the same class contains GetProcAddress,
our other required function.
Let’s try to identify which assembly contains these two functions.
To do this, we’ll modify the parsing code
to first print the current assembly location through the Location property
and then inside the nested ForEach-Object loop make the TypeName match
Microsoft.Win32.UnsafeNativeMethods
instead of listing all methods with the static keyword.
After we run the updated code,
the output shows that the assembly is System.dll.
This is reasonable since it’s a common system library that contains
fundamental content such as common data types and references.
However, these methods are only meant to be used internally by the .NET code.
This blocks us from calling them directly from Powershell or C#.
To solve this issue, we have to develop a way that allows us to call it indirectly.
This requires us to use multiple techniques that will lead us down a deep rabbit hole.
The first step is to obtain a reference to these functions.
To do that, we must first obtain a reference to the System.dll assembly
using the GetType method.
This reference to the System.dll assembly will allow us to subsequently locate
the GetModuleHandle and GetProcAddress methods inside it.
Like the previous filtering we've performed, this is not straightforward.
First, we’ll pipe all the assemblies into Where-Object and filter on two conditions.
The first is whether the GlobalAssemblyCache property is set.
The Global Assembly Cache is essentially a list of all native and registered
assemblies on Windows, which will allow us to filter out non-native assemblies.
The second filter is whether the last part of its file path is “System.dll”
as obtained through the Location property.
Recall the full path we found for system.dll.
We’ll use the Split method to split it into an array
based on the backslash directory delimiter.
Finally, we select the last element of the split string array with the “-1” index
and check if it's equal to “System.dll”.
Using GetType to obtain a reference to the System.dll assembly at runtime
is an example of the Reflection technique.
This is a very powerful feature that allows us to dynamically obtain
references to objects that are otherwise private or internal.
The combined code will return the method object inside the System.dll assembly,
in spite of it being an internal only method.
We can now use the internal Invoke method to call GetModuleHandle
and obtain the base address of an unmanaged DLL.
Invoke takes two arguments and both are objects.
The first argument is the object to invoke it on,
but since we use it on a static method we may set it to “$null”.
The second argument is an array consisting of the
arguments for the method we are invoking, GetModuleHandle.
Since the Win32 API only takes the name of the DLL as a string
we only need to supply that.
To repeat earlier examples, we are going to resolve user32.dll,
so that we can again call MessageBox.
To verify that the lookup worked,
we translate the value to its hexadecimal equivalent and open Process Explorer.
In Process Explorer, we’ll select the PowerShell ISE process.
Navigate to View
Lower Pane View
DLLs,
in the new sub window locate user32.dll,
and double click it.
In the properties window, we can compare the resolved value to the Load Address .
With the invocation of GetModuleHandle and the resulting correct DLL base address,
we gain more confidence that this avenue will lead to a usable result.
Next we need to locate GetProcAddress to resolve arbitrary APIs.
We’ll use reflection through GetMethod
to locate GetProcAddress like we did for GetModuleHandle.
We’ll again use GetMethod on the $unsafeObj variable that contains
the reference to Win32.UnsafeNativeMethods in System.dll.
Unfortunately, it ends up as an exception
with an error message of: “Ambiguous match found”.
The error message tells us exactly what the problem is.
There are multiple instances of GetProcAddress
within Microsoft.Win32.UnsafeNativeMethods.
So, instead of GetMethod, we can use GetMethods
to obtain all methods in Microsoft.Win32.UnsafeNativeMethods,
and then filter to only print those called GetProcAddress.
The filtering is done by a ForEach-Object loop
with a comparision condition on the Name property of the method.
If the output matches GetProcAddress, it's printed.
This reveals each occurence of GetProcAddress
inside Microsoft.Win32.UnsafeNativeMethods.
With two results, we can simply create an array to hold both instances
and then use the first to resolve the function’s address,
which in our case is MessageBoxA.
In this code, $user32
contains the previously-found base address of user32.dll.
We'll create an empty array to store both GetProcAddress instances,
after which we repeat the ForEach-Object loop
to search Microsoft.Win32.UnsafeNativeMethods
and locate them.
Once found, they are appended to the array.
We’ll assign the first element of the array to the $GetProcAddress variable,
and we can now use that to find the location of MessageBoxA
through the Invoke method.
Since the C version of GetProcAddress takes both the base address of the DLL
and the name of the function, we supply both of these as arguments in the array.
Let’s execute this to find out if it works.
It reveals a decimal value, which, when translated to hexadecimal
appears to be inside user32.dll.
It appears our efforts have paid off.
We have resolved the address of an arbitrary Win32 API.
Now, to make our code more portable and compact
it is worth rewriting the PowerShell script into a method.
This will allow us to reference it multiple times.
With the techniques developed in this section, we have managed
to implement a function that can resolve any Win32 API
without using the Add-Type keyword.
This completely avoids writing to the hard disk.
In the next section, we must match the address of the Win32 API
that we've located with its arguments and return values.