2015年2月27日金曜日

Use PowerShell to Interact with the Windows API

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:

.NET Reflector 8
ILSpy .NET Decompiler
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:

0 件のコメント:

コメントを投稿