|
|
@@ -0,0 +1,858 @@ |
|
|
function Get-Doppelgangers |
|
|
{ |
|
|
<# |
|
|
.SYNOPSIS |
|
|
|
|
|
Detects use of NTFS transactions for stealth/evasion, aka 'Process Doppelganging' |
|
|
|
|
|
Author: Joe Desimone (@dez_) |
|
|
License: BSD 3-Clause |
|
|
|
|
|
.DESCRIPTION |
|
|
|
|
|
Scans all processes for memory sections that are MEM_IMAGE but return ERROR_TRANSACTION_NOT_ACTIVE |
|
|
when querying the associated section file name object. This is typical for sections that are |
|
|
associated with rolled back transactions. |
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
Get-Doppelgangers -verbose |
|
|
#> |
|
|
|
|
|
[CmdletBinding()] |
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 0)] |
|
|
[ValidateNotNullOrEmpty()] |
|
|
[String] |
|
|
$OutPath = $PWD.Path |
|
|
) |
|
|
|
|
|
$Mod = New-InMemoryModule -ModuleName Win32 |
|
|
|
|
|
$MemoryBasicInformation = struct $Mod Mem.MEMORY_BASIC_INFORMATION @{ |
|
|
BaseAddress = field 0 IntPtr |
|
|
AllocationBase = field 1 IntPtr |
|
|
AllocationProtect = field 2 UInt32 |
|
|
RegionSize = field 3 IntPtr |
|
|
State = field 4 UInt32 |
|
|
Protect = field 5 UInt32 |
|
|
Type = field 6 UInt32 |
|
|
} |
|
|
|
|
|
$FunctionDefinitions = @( |
|
|
(func kernel32 OpenProcess ([UInt32]) @([UInt32], [UInt32], [UInt32]) -SetLastError), |
|
|
(func kernel32 CloseHandle ([UInt32]) @([UInt32]) -SetLastError), |
|
|
(func kernel32 VirtualQueryEx ([IntPtr]) @([IntPtr], [IntPtr], $MemoryBasicInformation.MakeByRefType(), [IntPtr]) -SetLastError), |
|
|
(func kernel32 ReadProcessMemory ([UInt32]) @([UInt32], [IntPtr], [Byte[]], [IntPtr], [IntPtr].MakeByRefType()) -SetLastError), |
|
|
(func kernel32 K32GetMappedFileName ([UInt32]) @([UInt32], [IntPtr], [Byte[]], [UInt32]) -SetLastError) |
|
|
) |
|
|
|
|
|
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' |
|
|
$Kernel32 = $Types['kernel32'] |
|
|
|
|
|
$processList = Get-Process |
|
|
foreach ($process in $processList) |
|
|
{ |
|
|
$id = $process | select id |
|
|
$processId = $id.id |
|
|
$processHandle = $Kernel32::OpenProcess(0x0400 -bxor 0x0010, 0, $processId) |
|
|
if ($processHandle -ne 0) |
|
|
{ |
|
|
#"PID: $processId, Handle: $processHandle" |
|
|
$mbi = [Activator]::CreateInstance($MemoryBasicInformation) |
|
|
|
|
|
while(1) |
|
|
{ |
|
|
$retSz = 0 |
|
|
$val = [System.IntPtr]::Size |
|
|
|
|
|
$pAddr = [Int64]$mbi.BaseAddress + $mbi.RegionSize |
|
|
$pAddr = [IntPtr]$pAddr |
|
|
|
|
|
$retSz = $Kernel32::VirtualQueryEx($processHandle, $pAddr, [ref]$mbi, $MemoryBasicInformation::GetSize()) |
|
|
if ($retSz -eq 0) |
|
|
{ |
|
|
break |
|
|
} |
|
|
|
|
|
if($mbi.Type -eq 0x01000000 -and $mbi.State -eq 0x1000) |
|
|
{ |
|
|
$fileBuf = New-Object byte[](1024) |
|
|
$retSz = $Kernel32::K32GetMappedFileName($processHandle, $mbi.BaseAddress, $fileBuf, $fileBuf.Length);$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error() |
|
|
$fileName = [System.Text.Encoding]::UNICODE.GetString($fileBuf) |
|
|
|
|
|
if($retSz -eq 0 -and $LastError.NativeErrorCode -eq 6701) |
|
|
{ |
|
|
Write-Verbose ("Hit: {0} [pid {1:d}], 0x{2:x} - 0x{3:x} (size: 0x{4:x})" -f $process.ProcessName, |
|
|
$processId, [Int64]$mbi.BaseAddress, ([Int64]$mbi.BaseAddress+$mbi.RegionSize), [Int64]$mbi.RegionSize) |
|
|
|
|
|
[IntPtr]$szRead = 0 |
|
|
$buf = New-Object byte[]($mbi.RegionSize) |
|
|
$bRet = $Kernel32::ReadProcessMemory($processHandle, $mbi.BaseAddress, $buf, $buf.Length, [ref]$szRead) |
|
|
if($bRet -eq 1) |
|
|
{ |
|
|
Save-Data $process.ProcessName $buf $mbi.AllocationBase $OutPath |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
$bRet = $Kernel32::CloseHandle($processHandle) |
|
|
} |
|
|
} |
|
|
Write-Verbose ("Scanned {0:d} processes" -f $processList.Length) |
|
|
} |
|
|
|
|
|
function Save-Data |
|
|
{ |
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 0, Mandatory = $True)] |
|
|
[String] |
|
|
$ProcessName, |
|
|
|
|
|
[Parameter(Position = 1, Mandatory = $True)] |
|
|
[Byte[]] |
|
|
$buffer, |
|
|
|
|
|
[Parameter(Position = 2, Mandatory = $True)] |
|
|
[IntPtr] |
|
|
$base, |
|
|
|
|
|
[Parameter(Position = 3, Mandatory = $True)] |
|
|
[String] |
|
|
$Path |
|
|
|
|
|
) |
|
|
|
|
|
New-Item -ItemType directory -force -Path $Path |
|
|
$file_path = "{0}\{1}_{2:x}" -f $Path, $ProcessName, [Int64]$base |
|
|
if (Test-Path $file_path) |
|
|
{ |
|
|
# =( |
|
|
$existing = [io.file]::ReadAllBytes($file_path) |
|
|
$all = New-Object byte[]($buffer.length + $existing.length) |
|
|
$all = $existing + $buffer |
|
|
[io.file]::WriteAllBytes($file_path,$all) |
|
|
} |
|
|
else |
|
|
{ |
|
|
[io.file]::WriteAllBytes($file_path,$buffer) |
|
|
} |
|
|
|
|
|
Get-Item $file_path |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#region PSReflect |
|
|
function New-InMemoryModule |
|
|
{ |
|
|
<# |
|
|
.SYNOPSIS |
|
|
|
|
|
Creates an in-memory assembly and module |
|
|
|
|
|
Author: Matthew Graeber (@mattifestation) |
|
|
License: BSD 3-Clause |
|
|
Required Dependencies: None |
|
|
Optional Dependencies: None |
|
|
|
|
|
.DESCRIPTION |
|
|
|
|
|
When defining custom enums, structs, and unmanaged functions, it is |
|
|
necessary to associate to an assembly module. This helper function |
|
|
creates an in-memory module that can be passed to the 'enum', |
|
|
'struct', and Add-Win32Type functions. |
|
|
|
|
|
.PARAMETER ModuleName |
|
|
|
|
|
Specifies the desired name for the in-memory assembly and module. If |
|
|
ModuleName is not provided, it will default to a GUID. |
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
$Module = New-InMemoryModule -ModuleName Win32 |
|
|
#> |
|
|
|
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 0)] |
|
|
[ValidateNotNullOrEmpty()] |
|
|
[String] |
|
|
$ModuleName = [Guid]::NewGuid().ToString() |
|
|
) |
|
|
|
|
|
$AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue($null, @()) |
|
|
$LoadedAssemblies = $AppDomain.GetAssemblies() |
|
|
|
|
|
foreach ($Assembly in $LoadedAssemblies) { |
|
|
if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) { |
|
|
return $Assembly |
|
|
} |
|
|
} |
|
|
|
|
|
$DynAssembly = New-Object Reflection.AssemblyName($ModuleName) |
|
|
$Domain = $AppDomain |
|
|
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run') |
|
|
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False) |
|
|
|
|
|
return $ModuleBuilder |
|
|
} |
|
|
|
|
|
# A helper function used to reduce typing while defining function |
|
|
# prototypes for Add-Win32Type. |
|
|
function func |
|
|
{ |
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 0, Mandatory = $True)] |
|
|
[String] |
|
|
$DllName, |
|
|
|
|
|
[Parameter(Position = 1, Mandatory = $True)] |
|
|
[string] |
|
|
$FunctionName, |
|
|
|
|
|
[Parameter(Position = 2, Mandatory = $True)] |
|
|
[Type] |
|
|
$ReturnType, |
|
|
|
|
|
[Parameter(Position = 3)] |
|
|
[Type[]] |
|
|
$ParameterTypes, |
|
|
|
|
|
[Parameter(Position = 4)] |
|
|
[Runtime.InteropServices.CallingConvention] |
|
|
$NativeCallingConvention, |
|
|
|
|
|
[Parameter(Position = 5)] |
|
|
[Runtime.InteropServices.CharSet] |
|
|
$Charset, |
|
|
|
|
|
[String] |
|
|
$EntryPoint, |
|
|
|
|
|
[Switch] |
|
|
$SetLastError |
|
|
) |
|
|
|
|
|
$Properties = @{ |
|
|
DllName = $DllName |
|
|
FunctionName = $FunctionName |
|
|
ReturnType = $ReturnType |
|
|
} |
|
|
|
|
|
if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes } |
|
|
if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention } |
|
|
if ($Charset) { $Properties['Charset'] = $Charset } |
|
|
if ($SetLastError) { $Properties['SetLastError'] = $SetLastError } |
|
|
if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint } |
|
|
|
|
|
New-Object PSObject -Property $Properties |
|
|
} |
|
|
|
|
|
function Add-Win32Type |
|
|
{ |
|
|
<# |
|
|
.SYNOPSIS |
|
|
|
|
|
Creates a .NET type for an unmanaged Win32 function. |
|
|
|
|
|
Author: Matthew Graeber (@mattifestation) |
|
|
License: BSD 3-Clause |
|
|
Required Dependencies: None |
|
|
Optional Dependencies: func |
|
|
|
|
|
.DESCRIPTION |
|
|
|
|
|
Add-Win32Type enables you to easily interact with unmanaged (i.e. |
|
|
Win32 unmanaged) functions in PowerShell. After providing |
|
|
Add-Win32Type with a function signature, a .NET type is created |
|
|
using reflection (i.e. csc.exe is never called like with Add-Type). |
|
|
|
|
|
The 'func' helper function can be used to reduce typing when defining |
|
|
multiple function definitions. |
|
|
|
|
|
.PARAMETER DllName |
|
|
|
|
|
The name of the DLL. |
|
|
|
|
|
.PARAMETER FunctionName |
|
|
|
|
|
The name of the target function. |
|
|
|
|
|
.PARAMETER EntryPoint |
|
|
|
|
|
The DLL export function name. This argument should be specified if the |
|
|
specified function name is different than the name of the exported |
|
|
function. |
|
|
|
|
|
.PARAMETER ReturnType |
|
|
|
|
|
The return type of the function. |
|
|
|
|
|
.PARAMETER ParameterTypes |
|
|
|
|
|
The function parameters. |
|
|
|
|
|
.PARAMETER NativeCallingConvention |
|
|
|
|
|
Specifies the native calling convention of the function. Defaults to |
|
|
stdcall. |
|
|
|
|
|
.PARAMETER Charset |
|
|
|
|
|
If you need to explicitly call an 'A' or 'W' Win32 function, you can |
|
|
specify the character set. |
|
|
|
|
|
.PARAMETER SetLastError |
|
|
|
|
|
Indicates whether the callee calls the SetLastError Win32 API |
|
|
function before returning from the attributed method. |
|
|
|
|
|
.PARAMETER Module |
|
|
|
|
|
The in-memory module that will host the functions. Use |
|
|
New-InMemoryModule to define an in-memory module. |
|
|
|
|
|
.PARAMETER Namespace |
|
|
|
|
|
An optional namespace to prepend to the type. Add-Win32Type defaults |
|
|
to a namespace consisting only of the name of the DLL. |
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
$Mod = New-InMemoryModule -ModuleName Win32 |
|
|
|
|
|
$FunctionDefinitions = @( |
|
|
(func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError), |
|
|
(func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError), |
|
|
(func ntdll RtlGetCurrentPeb ([IntPtr]) @()) |
|
|
) |
|
|
|
|
|
$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' |
|
|
$Kernel32 = $Types['kernel32'] |
|
|
$Ntdll = $Types['ntdll'] |
|
|
$Ntdll::RtlGetCurrentPeb() |
|
|
$ntdllbase = $Kernel32::GetModuleHandle('ntdll') |
|
|
$Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb') |
|
|
|
|
|
.NOTES |
|
|
|
|
|
Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189 |
|
|
|
|
|
When defining multiple function prototypes, it is ideal to provide |
|
|
Add-Win32Type with an array of function signatures. That way, they |
|
|
are all incorporated into the same in-memory module. |
|
|
#> |
|
|
|
|
|
[OutputType([Hashtable])] |
|
|
Param( |
|
|
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] |
|
|
[String] |
|
|
$DllName, |
|
|
|
|
|
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] |
|
|
[String] |
|
|
$FunctionName, |
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = $True)] |
|
|
[String] |
|
|
$EntryPoint, |
|
|
|
|
|
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] |
|
|
[Type] |
|
|
$ReturnType, |
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = $True)] |
|
|
[Type[]] |
|
|
$ParameterTypes, |
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = $True)] |
|
|
[Runtime.InteropServices.CallingConvention] |
|
|
$NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall, |
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = $True)] |
|
|
[Runtime.InteropServices.CharSet] |
|
|
$Charset = [Runtime.InteropServices.CharSet]::Auto, |
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName = $True)] |
|
|
[Switch] |
|
|
$SetLastError, |
|
|
|
|
|
[Parameter(Mandatory = $True)] |
|
|
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] |
|
|
$Module, |
|
|
|
|
|
[ValidateNotNull()] |
|
|
[String] |
|
|
$Namespace = '' |
|
|
) |
|
|
|
|
|
BEGIN |
|
|
{ |
|
|
$TypeHash = @{} |
|
|
} |
|
|
|
|
|
PROCESS |
|
|
{ |
|
|
if ($Module -is [Reflection.Assembly]) |
|
|
{ |
|
|
if ($Namespace) |
|
|
{ |
|
|
$TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName") |
|
|
} |
|
|
else |
|
|
{ |
|
|
$TypeHash[$DllName] = $Module.GetType($DllName) |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
# Define one type for each DLL |
|
|
if (!$TypeHash.ContainsKey($DllName)) |
|
|
{ |
|
|
if ($Namespace) |
|
|
{ |
|
|
$TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit') |
|
|
} |
|
|
else |
|
|
{ |
|
|
$TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit') |
|
|
} |
|
|
} |
|
|
|
|
|
$Method = $TypeHash[$DllName].DefineMethod( |
|
|
$FunctionName, |
|
|
'Public,Static,PinvokeImpl', |
|
|
$ReturnType, |
|
|
$ParameterTypes) |
|
|
|
|
|
# Make each ByRef parameter an Out parameter |
|
|
$i = 1 |
|
|
foreach($Parameter in $ParameterTypes) |
|
|
{ |
|
|
if ($Parameter.IsByRef) |
|
|
{ |
|
|
[void] $Method.DefineParameter($i, 'Out', $null) |
|
|
} |
|
|
|
|
|
$i++ |
|
|
} |
|
|
|
|
|
$DllImport = [Runtime.InteropServices.DllImportAttribute] |
|
|
$SetLastErrorField = $DllImport.GetField('SetLastError') |
|
|
$CallingConventionField = $DllImport.GetField('CallingConvention') |
|
|
$CharsetField = $DllImport.GetField('CharSet') |
|
|
$EntryPointField = $DllImport.GetField('EntryPoint') |
|
|
if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False } |
|
|
|
|
|
if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedFuncName = $FunctionName } |
|
|
|
|
|
# Equivalent to C# version of [DllImport(DllName)] |
|
|
$Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String]) |
|
|
$DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor, |
|
|
$DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(), |
|
|
[Reflection.FieldInfo[]] @($SetLastErrorField, |
|
|
$CallingConventionField, |
|
|
$CharsetField, |
|
|
$EntryPointField), |
|
|
[Object[]] @($SLEValue, |
|
|
([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), |
|
|
([Runtime.InteropServices.CharSet] $Charset), |
|
|
$ExportedFuncName)) |
|
|
|
|
|
$Method.SetCustomAttribute($DllImportAttribute) |
|
|
} |
|
|
} |
|
|
|
|
|
END |
|
|
{ |
|
|
if ($Module -is [Reflection.Assembly]) |
|
|
{ |
|
|
return $TypeHash |
|
|
} |
|
|
|
|
|
$ReturnTypes = @{} |
|
|
|
|
|
foreach ($Key in $TypeHash.Keys) |
|
|
{ |
|
|
$Type = $TypeHash[$Key].CreateType() |
|
|
|
|
|
$ReturnTypes[$Key] = $Type |
|
|
} |
|
|
|
|
|
return $ReturnTypes |
|
|
} |
|
|
} |
|
|
|
|
|
function psenum |
|
|
{ |
|
|
<# |
|
|
.SYNOPSIS |
|
|
|
|
|
Creates an in-memory enumeration for use in your PowerShell session. |
|
|
|
|
|
Author: Matthew Graeber (@mattifestation) |
|
|
License: BSD 3-Clause |
|
|
Required Dependencies: None |
|
|
Optional Dependencies: None |
|
|
|
|
|
.DESCRIPTION |
|
|
|
|
|
The 'psenum' function facilitates the creation of enums entirely in |
|
|
memory using as close to a "C style" as PowerShell will allow. |
|
|
|
|
|
.PARAMETER Module |
|
|
|
|
|
The in-memory module that will host the enum. Use |
|
|
New-InMemoryModule to define an in-memory module. |
|
|
|
|
|
.PARAMETER FullName |
|
|
|
|
|
The fully-qualified name of the enum. |
|
|
|
|
|
.PARAMETER Type |
|
|
|
|
|
The type of each enum element. |
|
|
|
|
|
.PARAMETER EnumElements |
|
|
|
|
|
A hashtable of enum elements. |
|
|
|
|
|
.PARAMETER Bitfield |
|
|
|
|
|
Specifies that the enum should be treated as a bitfield. |
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
$Mod = New-InMemoryModule -ModuleName Win32 |
|
|
|
|
|
$ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{ |
|
|
UNKNOWN = 0 |
|
|
NATIVE = 1 # Image doesn't require a subsystem. |
|
|
WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem. |
|
|
WINDOWS_CUI = 3 # Image runs in the Windows character subsystem. |
|
|
OS2_CUI = 5 # Image runs in the OS/2 character subsystem. |
|
|
POSIX_CUI = 7 # Image runs in the Posix character subsystem. |
|
|
NATIVE_WINDOWS = 8 # Image is a native Win9x driver. |
|
|
WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem. |
|
|
EFI_APPLICATION = 10 |
|
|
EFI_BOOT_SERVICE_DRIVER = 11 |
|
|
EFI_RUNTIME_DRIVER = 12 |
|
|
EFI_ROM = 13 |
|
|
XBOX = 14 |
|
|
WINDOWS_BOOT_APPLICATION = 16 |
|
|
} |
|
|
|
|
|
.NOTES |
|
|
|
|
|
PowerShell purists may disagree with the naming of this function but |
|
|
again, this was developed in such a way so as to emulate a "C style" |
|
|
definition as closely as possible. Sorry, I'm not going to name it |
|
|
New-Enum. :P |
|
|
#> |
|
|
|
|
|
[OutputType([Type])] |
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 0, Mandatory = $True)] |
|
|
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] |
|
|
$Module, |
|
|
|
|
|
[Parameter(Position = 1, Mandatory = $True)] |
|
|
[ValidateNotNullOrEmpty()] |
|
|
[String] |
|
|
$FullName, |
|
|
|
|
|
[Parameter(Position = 2, Mandatory = $True)] |
|
|
[Type] |
|
|
$Type, |
|
|
|
|
|
[Parameter(Position = 3, Mandatory = $True)] |
|
|
[ValidateNotNullOrEmpty()] |
|
|
[Hashtable] |
|
|
$EnumElements, |
|
|
|
|
|
[Switch] |
|
|
$Bitfield |
|
|
) |
|
|
|
|
|
if ($Module -is [Reflection.Assembly]) |
|
|
{ |
|
|
return ($Module.GetType($FullName)) |
|
|
} |
|
|
|
|
|
$EnumType = $Type -as [Type] |
|
|
|
|
|
$EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType) |
|
|
|
|
|
if ($Bitfield) |
|
|
{ |
|
|
$FlagsConstructor = [FlagsAttribute].GetConstructor(@()) |
|
|
$FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @()) |
|
|
$EnumBuilder.SetCustomAttribute($FlagsCustomAttribute) |
|
|
} |
|
|
|
|
|
foreach ($Key in $EnumElements.Keys) |
|
|
{ |
|
|
# Apply the specified enum type to each element |
|
|
$null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) |
|
|
} |
|
|
|
|
|
$EnumBuilder.CreateType() |
|
|
} |
|
|
|
|
|
# A helper function used to reduce typing while defining struct |
|
|
# fields. |
|
|
function field |
|
|
{ |
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 0, Mandatory = $True)] |
|
|
[UInt16] |
|
|
$Position, |
|
|
|
|
|
[Parameter(Position = 1, Mandatory = $True)] |
|
|
[Type] |
|
|
$Type, |
|
|
|
|
|
[Parameter(Position = 2)] |
|
|
[UInt16] |
|
|
$Offset, |
|
|
|
|
|
[Object[]] |
|
|
$MarshalAs |
|
|
) |
|
|
|
|
|
@{ |
|
|
Position = $Position |
|
|
Type = $Type -as [Type] |
|
|
Offset = $Offset |
|
|
MarshalAs = $MarshalAs |
|
|
} |
|
|
} |
|
|
|
|
|
function struct |
|
|
{ |
|
|
<# |
|
|
.SYNOPSIS |
|
|
|
|
|
Creates an in-memory struct for use in your PowerShell session. |
|
|
|
|
|
Author: Matthew Graeber (@mattifestation) |
|
|
License: BSD 3-Clause |
|
|
Required Dependencies: None |
|
|
Optional Dependencies: field |
|
|
|
|
|
.DESCRIPTION |
|
|
|
|
|
The 'struct' function facilitates the creation of structs entirely in |
|
|
memory using as close to a "C style" as PowerShell will allow. Struct |
|
|
fields are specified using a hashtable where each field of the struct |
|
|
is comprosed of the order in which it should be defined, its .NET |
|
|
type, and optionally, its offset and special marshaling attributes. |
|
|
|
|
|
One of the features of 'struct' is that after your struct is defined, |
|
|
it will come with a built-in GetSize method as well as an explicit |
|
|
converter so that you can easily cast an IntPtr to the struct without |
|
|
relying upon calling SizeOf and/or PtrToStructure in the Marshal |
|
|
class. |
|
|
|
|
|
.PARAMETER Module |
|
|
|
|
|
The in-memory module that will host the struct. Use |
|
|
New-InMemoryModule to define an in-memory module. |
|
|
|
|
|
.PARAMETER FullName |
|
|
|
|
|
The fully-qualified name of the struct. |
|
|
|
|
|
.PARAMETER StructFields |
|
|
|
|
|
A hashtable of fields. Use the 'field' helper function to ease |
|
|
defining each field. |
|
|
|
|
|
.PARAMETER PackingSize |
|
|
|
|
|
Specifies the memory alignment of fields. |
|
|
|
|
|
.PARAMETER ExplicitLayout |
|
|
|
|
|
Indicates that an explicit offset for each field will be specified. |
|
|
|
|
|
.EXAMPLE |
|
|
|
|
|
$Mod = New-InMemoryModule -ModuleName Win32 |
|
|
|
|
|
$ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{ |
|
|
DOS_SIGNATURE = 0x5A4D |
|
|
OS2_SIGNATURE = 0x454E |
|
|
OS2_SIGNATURE_LE = 0x454C |
|
|
VXD_SIGNATURE = 0x454C |
|
|
} |
|
|
|
|
|
$ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{ |
|
|
e_magic = field 0 $ImageDosSignature |
|
|
e_cblp = field 1 UInt16 |
|
|
e_cp = field 2 UInt16 |
|
|
e_crlc = field 3 UInt16 |
|
|
e_cparhdr = field 4 UInt16 |
|
|
e_minalloc = field 5 UInt16 |
|
|
e_maxalloc = field 6 UInt16 |
|
|
e_ss = field 7 UInt16 |
|
|
e_sp = field 8 UInt16 |
|
|
e_csum = field 9 UInt16 |
|
|
e_ip = field 10 UInt16 |
|
|
e_cs = field 11 UInt16 |
|
|
e_lfarlc = field 12 UInt16 |
|
|
e_ovno = field 13 UInt16 |
|
|
e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4) |
|
|
e_oemid = field 15 UInt16 |
|
|
e_oeminfo = field 16 UInt16 |
|
|
e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10) |
|
|
e_lfanew = field 18 Int32 |
|
|
} |
|
|
|
|
|
# Example of using an explicit layout in order to create a union. |
|
|
$TestUnion = struct $Mod TestUnion @{ |
|
|
field1 = field 0 UInt32 0 |
|
|
field2 = field 1 IntPtr 0 |
|
|
} -ExplicitLayout |
|
|
|
|
|
.NOTES |
|
|
|
|
|
PowerShell purists may disagree with the naming of this function but |
|
|
again, this was developed in such a way so as to emulate a "C style" |
|
|
definition as closely as possible. Sorry, I'm not going to name it |
|
|
New-Struct. :P |
|
|
#> |
|
|
|
|
|
[OutputType([Type])] |
|
|
Param |
|
|
( |
|
|
[Parameter(Position = 1, Mandatory = $True)] |
|
|
[ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] |
|
|
$Module, |
|
|
|
|
|
[Parameter(Position = 2, Mandatory = $True)] |
|
|
[ValidateNotNullOrEmpty()] |
|
|
[String] |
|
|
$FullName, |
|
|
|
|
|
[Parameter(Position = 3, Mandatory = $True)] |
|
|
[ValidateNotNullOrEmpty()] |
|
|
[Hashtable] |
|
|
$StructFields, |
|
|
|
|
|
[Reflection.Emit.PackingSize] |
|
|
$PackingSize = [Reflection.Emit.PackingSize]::Unspecified, |
|
|
|
|
|
[Switch] |
|
|
$ExplicitLayout |
|
|
) |
|
|
|
|
|
if ($Module -is [Reflection.Assembly]) |
|
|
{ |
|
|
return ($Module.GetType($FullName)) |
|
|
} |
|
|
|
|
|
[Reflection.TypeAttributes] $StructAttributes = 'AnsiClass, |
|
|
Class, |
|
|
Public, |
|
|
Sealed, |
|
|
BeforeFieldInit' |
|
|
|
|
|
if ($ExplicitLayout) |
|
|
{ |
|
|
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout |
|
|
} |
|
|
else |
|
|
{ |
|
|
$StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout |
|
|
} |
|
|
|
|
|
$StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize) |
|
|
$ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0] |
|
|
$SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) |
|
|
|
|
|
$Fields = New-Object Hashtable[]($StructFields.Count) |
|
|
|
|
|
# Sort each field according to the orders specified |
|
|
# Unfortunately, PSv2 doesn't have the luxury of the |
|
|
# hashtable [Ordered] accelerator. |
|
|
foreach ($Field in $StructFields.Keys) |
|
|
{ |
|
|
$Index = $StructFields[$Field]['Position'] |
|
|
$Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]} |
|
|
} |
|
|
|
|
|
foreach ($Field in $Fields) |
|
|
{ |
|
|
$FieldName = $Field['FieldName'] |
|
|
$FieldProp = $Field['Properties'] |
|
|
|
|
|
$Offset = $FieldProp['Offset'] |
|
|
$Type = $FieldProp['Type'] |
|
|
$MarshalAs = $FieldProp['MarshalAs'] |
|
|
|
|
|
$NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public') |
|
|
|
|
|
if ($MarshalAs) |
|
|
{ |
|
|
$UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType]) |
|
|
if ($MarshalAs[1]) |
|
|
{ |
|
|
$Size = $MarshalAs[1] |
|
|
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, |
|
|
$UnmanagedType, $SizeConst, @($Size)) |
|
|
} |
|
|
else |
|
|
{ |
|
|
$AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType)) |
|
|
} |
|
|
|
|
|
$NewField.SetCustomAttribute($AttribBuilder) |
|
|
} |
|
|
|
|
|
if ($ExplicitLayout) { $NewField.SetOffset($Offset) } |
|
|
} |
|
|
|
|
|
# Make the struct aware of its own size. |
|
|
# No more having to call [Runtime.InteropServices.Marshal]::SizeOf! |
|
|
$SizeMethod = $StructBuilder.DefineMethod('GetSize', |
|
|
'Public, Static', |
|
|
[Int], |
|
|
[Type[]] @()) |
|
|
$ILGenerator = $SizeMethod.GetILGenerator() |
|
|
# Thanks for the help, Jason Shirk! |
|
|
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) |
|
|
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, |
|
|
[Type].GetMethod('GetTypeFromHandle')) |
|
|
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, |
|
|
[Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type]))) |
|
|
$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret) |
|
|
|
|
|
# Allow for explicit casting from an IntPtr |
|
|
# No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure! |
|
|
$ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit', |
|
|
'PrivateScope, Public, Static, HideBySig, SpecialName', |
|
|
$StructBuilder, |
|
|
[Type[]] @([IntPtr])) |
|
|
$ILGenerator2 = $ImplicitConverter.GetILGenerator() |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop) |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0) |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, |
|
|
[Type].GetMethod('GetTypeFromHandle')) |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, |
|
|
[Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type]))) |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder) |
|
|
$ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret) |
|
|
|
|
|
$StructBuilder.CreateType() |
|
|
} |
|
|
#endregion PSReflect |