Use PowerShell to Interact with the Windows API: Part 2

Doctor Scripto

Summary: Guest blogger, Matt Graeber, talks more about interacting with the Windows API.

Microsoft Scripting Guy, Ed Wilson, is here. Matt Graeber is back today with Part 2 of a three-part series that started yesterday: Use PowerShell to Interact with the Windows API: Part 1.

Now, here’s Matt…

In my last post, I described how to use the Add-Type cmdlet to interact with the Windows API; specifically, the CopyFile function in kernel32.dll. Today, I’m going to describe another method in which you can interact with Windows API functions: extracting private methods from the .NET Framework.

For most people, the Add-Type method will always be sufficient. However, for people like me who write security and forensics tools, where a minimal forensic footprint is necessary, Add-Type doesn’t cut it because it writes temporary files to disk and calls csc.exe to compile C# code. So if having a minimal forensic footprint is a necessity for your script (or if you like to do things the hard way), there are other methods of calling Windows API functions.

A large portion of the .NET Framework is actually built on the Windows API. It’s just not exposed to you publicly. Pulling out the Win API functions that are used by the .NET Framework requires a basic understanding of the internal layout of .NET.

There are several tools available for exploring the .NET Framework, including these:

What you need to know for our purposes is that Windows API function calls in .NET are usually non-public (that is, private), static methods that have a DllImport attribute associated with them. To help us dig into the .NET Framework, I wrote a helper function called Find-WinAPIFunction. (The script is also available in the Script Center Repository.) It searches the loaded modules in a Windows PowerShell session for a reference to a private Windows API function.

Find-WinAPIFunction function: 

function Find-WinAPIFunction

{

<#

.SYNOPSIS

    Searches all loaded assemblies in your PowerShell session for a

    Windows API function.

.PARAMETER Module

    Specifies the name of the module that implements the function. This

    is typically a system dll (e.g. kernel32.dll).

.PARAMETER FunctionName

    Specifies the name of the function you’re searching for.

.OUTPUTS

    [System.Reflection.MethodInfo]

.EXAMPLE

    Find-WinAPIFunction kernel32.dll CopyFile

#>

    [CmdletBinding()]

    [OutputType([System.Reflection.MethodInfo])]

    Param

    (

        [Parameter(Mandatory = $True, Position = 0)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Module,

        [Parameter(Mandatory = $True, Position = 1)]

        [ValidateNotNullOrEmpty()]

        [String]

        $FunctionName

    )

    [System.AppDomain]::CurrentDomain.GetAssemblies() |

        ForEach-Object { $_.GetTypes() } |

            ForEach-Object { $_.GetMethods(‘NonPublic, Public, Static’) } |

                ForEach-Object { $MethodInfo = $_; $_.GetCustomAttributes($false) } |

                    Where-Object {

                        $MethodInfo.Name.ToLower() -eq $FunctionName.ToLower() -and

                        $_.Value -eq $Module

                    } | ForEach-Object { $MethodInfo }

}

The Find-WinAPIFunction function works by drilling down through all of the loaded assemblies in a Windows PowerShell session. To understand what’s going on, think of your Windows PowerShell session as a series of containers. It starts with an AppDomain. Each Windows PowerShell session has a default AppDomain, which you can think of as an execution sandbox.

Within an AppDomain, there are multiple loaded assemblies. An assembly is a container for a module, and it is typically a DLL, such as mscorlib.dll or System.dll. Within assemblies, are modules that are containers for types (that is, classes).

Finally, a type is a container for members. Members consist of methods, properties, fields, events, nested types, and constructors. The concept of members should be familiar to those who are familiar with the Get-Member cmdlet. The following diagram may help illustrate this concept of compartmentalization more clearly.

Image of compartments

Find-WinAPIFunction starts by iterating through all assemblies in the current AppDomain. It then iterates through all types within those assemblies, ultimately looking for the method you’re searching for.

Image of command output

Now that we know that a CopyFile method exists, we know that we can use it in Windows PowerShell. However, rather than searching for it each time with Find-WinAPIFunction, let’s pull out some information that will allow us to quickly get a reference to our target CopyFile method:

Image of command output

As can be seen in this screenshot, we need the name of the assembly and type that contains the CopyFile method. These are respectively mscorlib.dll and Microsoft.Win32.Win32Native.

Now let’s pull everything together with another implementation of the Copy-RawItem function, which made its debut in Part 1 of this series. This time, we have the Private .NET Method version, which is also available in the Script Center Repository.

Copy-RawItem Private .NET Method version:

function Copy-RawItem

{

<#

.SYNOPSIS

    Copies a file from one location to another including files contained within DeviceObject paths.

.PARAMETER Path

    Specifies the path to the file to copy.

.PARAMETER Destination

    Specifies the path to the location where the item is to be copied.

.PARAMETER FailIfExists

    Do not copy the file if it already exists in the specified destination.

.OUTPUTS

    None or an object representing the copied item.

.EXAMPLE

    Copy-RawItem ‘\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2\Windows\System32\config\SAM’ ‘C:\temp\SAM’

#>

    [CmdletBinding()]

    [OutputType([System.IO.FileSystemInfo])]

    Param (

        [Parameter(Mandatory = $True, Position = 0)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Path,

        [Parameter(Mandatory = $True, Position = 1)]

        [ValidateNotNullOrEmpty()]

        [String]

        $Destination,

        [Switch]

        $FailIfExists

    )

    # Get a reference to the internal method – Microsoft.Win32.Win32Native.CopyFile()

    $mscorlib = [AppDomain]::CurrentDomain.GetAssemblies() | ? {$_.Location -and ($_.Location.Split(‘\’)[-1] -eq ‘mscorlib.dll’)}

    $Win32Native = $mscorlib.GetType(‘Microsoft.Win32.Win32Native’)

    $CopyFileMethod = $Win32Native.GetMethod(‘CopyFile’, ([Reflection.BindingFlags] ‘NonPublic, Static’))

    # Perform the copy

    $CopyResult = $CopyFileMethod.Invoke($null, @($Path, $Destination, ([Bool] $PSBoundParameters[‘FailIfExists’])))

    $HResult = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($CopyResult -eq $False -and $HResult -ne 0)

    {

        # An error occured. Display the Win32 error set by CopyFile

        throw ( New-Object ComponentModel.Win32Exception )

    }

    else

    {

        Write-Output (Get-ChildItem $Destination)

    }

}

This version of Copy-RawItem provides identical functionality as the Add-Type version, but it implements the private method extraction technique described in this post. The function starts by getting a reference to mscorlib.dll, which is the assembly that contains the CopyFile method. It then gets a reference to the type that contains the Microsoft.Win32.Win32Native method by calling the GetType method. Finally, it gets a reference to the CopyFile method by calling the GetMethod method, specifying that a NonPublic, Static method is requested.

The technique described requires a bit more knowledge of the layout of the .NET Framework. However, armed with this knowledge, you will be able to pull internal functionality into Windows PowerShell, which wouldn’t otherwise be available to you.

In the last and final post of the series, we’ll dig into .NET internals even more and see how to use reflection to dynamically build a method that calls Windows API functions.

The two scripts used today are available in the Script Center Repository:

~Matt

Thanks, Matt. Join us tomorrow as Matt brings you the final part of this series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

0 comments

Discussion is closed.

Feedback usabilla icon