2015年2月27日金曜日

Use PowerShell to Interact with the Windows API

Reflection gives a programmer the ability to perform type introspection on code. The most common form of type introspection you would perform in Windows PowerShell would be using the Get-Member cmdlet, which enables you to discover the methods and properties of an object. In the last post, we also used reflection to find a non-public method that implemented the kernel32.dll CopyFile function. In this post, we will be using reflection to generate code dynamically. This concept is known as metaprogramming.

Steps to define a dynamic method

To define a dynamic method that will call the CopyFile method in kernel32.dll, the following steps are needed:

Define a dynamic assembly. Recall that the assembly is the container for modules, types, and members.
Define the dynamic assembly in your current AppDomain. Think of the AppDomain as your Windows PowerShell session. It's the environment in which our method is going to execute.
Define a dynamic module. A module is a container for types and their members.
Define a dynamic type. A type (that is, class) is a container for members (methods, properties, nested types, fields, events, and constructors).
Define our dynamic method. Here we specify attributes (Public and Static) and the method's parameters and return type.
Manually build a DllImport attribute. The result will be the equivalent of the following C# attribute:
[DllImport("kernel32.dll", SetLastError = true, PreserveSig = true, CallingConvention = CallingConvention.WinApi, CharSet = CharSet.Unicode)]
Apply the custom DllImport attribute to the dynamic method.
Call the CreateType method to bake everything together and make our method available to our Windows PowerShell session.
As you can see, this is not a trivial process. This process involves performing tasks that Add-Type and the C# compiler would typically take care of on your behalf.

So you may be asking now, "Why the heck would I want to go through all this trouble just to call a function?" The reason I typically do this is twofold:

I need to maintain a minimal forensic footprint while executing my script. I don't want to invoke the C# compiler and write temp files to disk.
The Windows API function I want is not present in the portion of the .NET Framework loaded by my current Windows PowerShell session.
To see all of this in action, I wrote another implementation of the Copy-RawItem function that uses reflection. The complete text of this function is available in the Script Center Repository: Copy-RawItem (Reflection Version).

Copy-RawItem ? Reflection 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

)

# Create a new dynamic assembly. An assembly (typically a dll file) is the container for modules

$DynAssembly = New-Object System.Reflection.AssemblyName('Win32Lib')

# Define the assembly and tell is to remain in memory only (via [Reflection.Emit.AssemblyBuilderAccess]::Run)

$AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)

# Define a new dynamic module. A module is the container for types (a.k.a. classes)

$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32Lib', $False)

# Define a new type (class). This class will contain our method - CopyFile

# I'm naming it 'Kernel32' so that you will be able to call CopyFile like this:

# [Kernel32]::CopyFile(src, dst, FailIfExists)

$TypeBuilder = $ModuleBuilder.DefineType('Kernel32', 'Public, Class')

# Define the CopyFile method. This method is a special type of method called a P/Invoke method.

# A P/Invoke method is an unmanaged exported function from a module - like kernel32.dll

$PInvokeMethod = $TypeBuilder.DefineMethod(

'CopyFile',

[Reflection.MethodAttributes] 'Public, Static',

[Bool],

[Type[]] @([String], [String], [Bool]))

#region DllImportAttribute

# Set the equivalent of: [DllImport(

# "kernel32.dll",

# SetLastError = true,

# PreserveSig = true,

# CallingConvention = CallingConvention.WinApi,

# CharSet = CharSet.Unicode)]

# Note: DefinePInvokeMethod cannot be used if SetLastError needs to be set

$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))

$FieldArray = [Reflection.FieldInfo[]] @(

[Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),

[Runtime.InteropServices.DllImportAttribute].GetField('PreserveSig'),

[Runtime.InteropServices.DllImportAttribute].GetField('SetLastError'),

[Runtime.InteropServices.DllImportAttribute].GetField('CallingConvention'),

[Runtime.InteropServices.DllImportAttribute].GetField('CharSet')

)

$FieldValueArray = [Object[]] @(

'CopyFile',

$True,

$True,

[Runtime.InteropServices.CallingConvention]::Winapi,

[Runtime.InteropServices.CharSet]::Unicode

)

$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(

$DllImportConstructor,

@('kernel32.dll'),

$FieldArray,

$FieldValueArray)

$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

0 件のコメント:

コメントを投稿