Managed Com / win32Api Warper for PS1 it's not the usual com warper you would think of - some more explained info. Code: * first, it accept, CLSID only or IID too better pass IID, so, it could check, it 7/3 base function else, it might fail, you be warned * second, Create & release now become idiot proof * use delagate C# function dynamcly Create'd, instead build interface/struct's user just have to write C# matched paramters, and matched output * you don't have calculate position, +3+7, it will do the doding. basic com 3+, Idispatch is 3+4+, it will auto detect disptach and auto find location of function * its totaly idiot proof, you manage Objects, and move object's to initilzie, invoke, release, the logic done automaticly, and also get you forget mistake, i do suggest Try, Catch, Finish - release. * you have to pre make parameter's and validate, CLSID,IID, RETURN. PARAMS and call delegate with right parameter. what you really need, is basic skill, of how function should look you can also advise with AI, they good understand C/C+ code style delegate will be be add Type of C# code, Out, Ref, etc but powershell doe's not have Out, it always [REF], so, take it in account, when call delegate later Demo Code for example. { from netlistmgr.h } Code: MIDL_INTERFACE("DCB00000-570F-4A9B-8D69-199FDBA5723B") INetworkListManager : public IDispatch { public: virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetworks( /* [in] */ NLM_ENUM_NETWORK Flags, /* [retval][out] */ __RPC__deref_out_opt IEnumNetworks **ppEnumNetwork) = 0; virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetwork( /* [in] */ GUID gdNetworkId, /* [retval][out] */ __RPC__deref_out_opt INetwork **ppNetwork) = 0; virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetworkConnections( /* [retval][out] */ __RPC__deref_out_opt IEnumNetworkConnections **ppEnum) = 0; virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetNetworkConnection( /* [in] */ GUID gdNetworkConnectionId, /* [retval][out] */ __RPC__deref_out_opt INetworkConnection **ppNetworkConnection) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_IsConnectedToInternet( /* [retval][out] */ __RPC__out VARIANT_BOOL *pbIsConnected) = 0; virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_IsConnected( /* [retval][out] */ __RPC__out VARIANT_BOOL *pbIsConnected) = 0; virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetConnectivity( /* [retval][out] */ __RPC__out NLM_CONNECTIVITY *pConnectivity) = 0; }; class DECLSPEC_UUID("DCB00C01-570F-4A9B-8D69-199FDBA5723B") NetworkListManager; #endif #endif /* __NETWORKLIST_LIBRARY_DEFINED__ */ so first --> basic parse Code: interface = DCB00000-570F-4A9B-8D69-199FDBA5723B class = DCB00C01-570F-4A9B-8D69-199FDBA5723B Position = 5 paramaters = VARIANT_BOOL *pbIsConnected return = HRESULT Name = IsConnectedToInternet SO, what can do with this ? here the magic happen Code: # netlistmgr.h # https://raw.githubusercontent.com/nihon-tc/Rtest/refs/heads/master/header/Microsoft%20SDKs/Windows/v7.0A/Include/netlistmgr.h # get_IsConnectedToInternet # https://learn.microsoft.com/en-us/windows/win32/api/netlistmgr/nf-netlistmgr-inetworklistmanager-get_isconnectedtointernet $interfaceSpec = [PSCustomObject]@{ Index = (5) Return = "int" Name = "IsConnectedToInternet" Params = "out short pbIsConnected" IID = "DCB00000-570F-4A9B-8D69-199FDBA5723B" CLSID = "DCB00C01-570F-4A9B-8D69-199FDBA5723B" } $comObj = Initialize-ComObject -InterfaceSpec $interfaceSpec [int16]$rawIsConnected = 0 $hresult = $comObj | Invoke-ComObject -Params ([ref]$rawIsConnected) if ($hresult -eq 0) { $isConnectedBool = ($rawIsConnected -ne 0) Write-Host "Connected to Internet: $isConnectedBool" } else { Write-Warning "Failed to get internet connection status. HRESULT: $hresult" } $comObj | Release-ComObject ------------------------- Added support for Unmanaged Api call using delegate. Code: Clear-host write-host $Global:ntdll = Init-NTDLL $Global:kernel32 = Init-KERNEL32 $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb() $global:LoadedModules = Get-LoadedModules -SortType Memory | Select-Object BaseAddress, ModuleName, LoadAsData # Com Example Use-ComInterface ` -CLSID "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" ` -IID "f2dcb80d-0670-44bc-9002-cd18688730af" ` -Index 3 ` -Name "ShowProductKeyUI" ` -Return "void" Invoke-UnmanagedMethod ` -Dll "kernel32.dll" ` -Function "Beep" ` -Return "bool" ` -Params "uint dwFreq, uint dwDuration" ` -Values @(750, 300) # 750 Hz beep for 300 ms ------------------------- expend it to able Load memoey module's, Enum memory module's & Other good stuff, Like Create Memory, Or Ref, free Memoey from all type's Etc, etc etc etc ------------------------- Warper source Code https://forums.mydigitallife.net/threads/com-warper-for-ps1.89437/#post-1883895
Basic code, only ! use attached files from first post For updated latest revision Code: # work - job Here. $Global:ntdll = Init-NTDLL $Global:kernel32 = Init-KERNEL32 $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb() $global:LoadedModules = Get-LoadedModules -SortType Memory | Select-Object BaseAddress, ModuleName, LoadAsData # Com Example Clear-host Write-Host # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677 # SYSTEM_PROCESS_INFORMATION structure # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677 # SYSTEM_BASIC_INFORMATION structure # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_basic_information.htm # Step 1: Get required buffer size $ReturnLength = 0 $dllResult = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(0, [IntPtr]::Zero, 0, [ref]$ReturnLength) # Allocate buffer (add some extra room just in case) $infoBuffer = New-IntPtr -Size $ReturnLength # Step 2: Actual call with allocated buffer $result = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(0, $infoBuffer, $ReturnLength, [ref]$ReturnLength) if ($result -ne 0) { Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)" Parse-ErrorMessage -MessageId $result New-IntPtr -hHandle $infoBuffer -Release return } # Parse values from the structure $sysBasicInfo = [PSCustomObject]@{ PageSize = [Marshal]::ReadInt32($infoBuffer, 0x08) NumberOfPhysicalPages = [Marshal]::ReadInt32($infoBuffer, 0x0C) LowestPhysicalPageNumber = [Marshal]::ReadInt32($infoBuffer, 0x10) HighestPhysicalPageNumber = [Marshal]::ReadInt32($infoBuffer, 0x14) AllocationGranularity = [Marshal]::ReadInt32($infoBuffer, 0x18) MinimumUserModeAddress = [Marshal]::ReadIntPtr($infoBuffer, 0x20) MaximumUserModeAddress = [Marshal]::ReadIntPtr($infoBuffer, 0x28) ActiveProcessorsAffinityMask = [Marshal]::ReadIntPtr($infoBuffer, 0x30) NumberOfProcessors = [Marshal]::ReadByte($infoBuffer, 0x38) } # Step 1: Get required buffer size $ReturnLength = 0 $dllResult = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(5, [IntPtr]::Zero, 0, [ref]$ReturnLength) # Allocate buffer (add some extra room just in case) $ReturnLength += 200 $procBuffer = New-IntPtr -Size $ReturnLength # Step 2: Actual call with allocated buffer $result = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(5, $procBuffer, $ReturnLength, [ref]$ReturnLength) if ($result -ne 0) { Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)" Parse-ErrorMessage -MessageId $result New-IntPtr -hHandle $procBuffer -Release return } $offset = 0 $processList = @() while ($true) { try { $entryPtr = [IntPtr]::Add($procBuffer, $offset) $nextOffset = [Marshal]::ReadInt32($entryPtr, 0x00) $namePtr = [Marshal]::ReadIntPtr($entryPtr, 0x38 + [IntPtr]::Size) $processName = if ($namePtr -ne [IntPtr]::Zero) { [Marshal]::PtrToStringUni($namePtr) } else { "[System]" } $procObj = [PSCustomObject]@{ ProcessId = [Marshal]::ReadIntPtr($entryPtr, 0x50) ProcessName = $processName NumberOfThreads = [Marshal]::ReadInt32($entryPtr, 0x04) } $processList += $procObj if ($nextOffset -eq 0) { break } $offset += $nextOffset } catch { Write-Host "Parsing error at offset $offset. Stopping." break } } New-IntPtr -hHandle $infoBuffer -Release New-IntPtr -hHandle $procBuffer -Release $sysBasicInfo | Format-List $processList | Sort-Object ProcessName | Format-Table ProcessId, ProcessName, NumberOfThreads -AutoSize [intPtr]$ppEnumNetwork = [intPtr]::Zero Use-ComInterface ` -CLSID "DCB00C01-570F-4A9B-8D69-199FDBA5723B" ` -IID "DCB00000-570F-4A9B-8D69-199FDBA5723B" ` -Index 1 ` -Name "GetNetwork" ` -Return "uint" ` -Params 'system.UINT32 Flags, out INTPTR ppEnumNetwork' ` -Values @(1, [ref]$ppEnumNetwork) if ($ppEnumNetwork -ne [IntPtr]::Zero) { $networkList = $ppEnumNetwork | Receive-ComObject foreach ($network in $networkList) { "Name: $($network.GetName()), IsConnected: $($network.IsConnected())" } $networkList | Release-ComObject } PS1 Libary Code: using namespace System.Reflection using namespace System.Reflection.Emit using namespace System.Runtime.InteropServices # WIN32 API Parts function New-IntPtr { param( [Parameter(Mandatory=$false)] [int]$Size, [Parameter(Mandatory=$false)] [int]$InitialValue = 0, [Parameter(Mandatory=$false)] [IntPtr]$hHandle, [switch]$WriteSizeAtZero, [switch]$Release ) if ($hHandle -or $Release) { if ($Size) { throw "Size option can't go with *hHandle or *Release" } if (-not $hHandle -or -not $Release) { throw "hHandle & Release, Must use both parametes" } } if ($Release) { if ($hHandle -and $hHandle -ne [IntPtr]::Zero) { [Marshal]::FreeHGlobal($hHandle) } return } # Allocate unmanaged memory $ptr = [Marshal]::AllocHGlobal($Size) # Zero memory $Global:ntdll::RtlZeroMemory($ptr, [UIntPtr]::new($Size)) # Write size at offset 0 if requested if ($WriteSizeAtZero) { [Marshal]::WriteInt32($ptr, 0, $Size) } # Otherwise, if size == 4 and initial value != 0, write initial value elseif ($Size -eq 4 -and $InitialValue -ne 0) { [Marshal]::WriteInt32($ptr, 0, $InitialValue) } return $ptr } Function Init-NTDLL { $Method = [PSCustomObject]@{ attributes = [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl CallingConventions = [CallingConventions]::Standard nativeCallConv = [CallingConvention]::Winapi nativeCharSet = [CharSet]::Unicode ImplAttributes = [MethodImplAttributes]::PreserveSig TypeAttributes = [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed } # Define dynamic type and methods before main try-finally $asmName = New-Object AssemblyName "NativeOSVersionAssembly" $asm = [AppDomain]::CurrentDomain.DefineDynamicAssembly($asmName, [AssemblyBuilderAccess]::Run) $mod = $asm.DefineDynamicModule("NativeOSVersionModule") $tb = $mod.DefineType("NativeMethods", $Method.TypeAttributes) $NtdllFunctions = @( @{ Name = "RtlGetVersion"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [IntPtr] }, @{ Name = "RtlGetCurrentPeb"; Dll = "ntdll.dll"; ReturnType = [IntPtr]; Parameters = @() }, @{ Name = "RtlGetProductInfo"; Dll = "ntdll.dll"; ReturnType = [bool]; Parameters = [Type[]]@([UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) }, @{ Name = "RtlGetNtVersionNumbers"; Dll = "ntdll.dll"; ReturnType = [void]; Parameters = ([Uint32].MakeByRefType(), [Uint32].MakeByRefType(), [Uint32].MakeByRefType()) }, @{ Name = "RtlZeroMemory"; Dll = "ntdll.dll"; ReturnType = [void]; Parameters = [Type[]]@([IntPtr], [UIntPtr]) }, @{ Name = "RtlGetProcessHeaps"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [IntPtr]) }, @{ Name = "NtQueryInformationProcess"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([IntPtr], [UInt32], [IntPtr], [UInt32], [IntPtr]) }, @{ Name = "NtQuerySystemInformation"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [IntPtr], [UInt32], [IntPtr]) }, @{ Name = "ZwQueryLicenseValue"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([IntPtr], [UInt32].MakeByRefType(), [IntPtr], [UInt32], [UInt32].MakeByRefType()) }, @{ Name = "RtlCreateUnicodeString"; Dll = "ntdll.dll"; ReturnType = [uint64]; Parameters = [Type[]]@([IntPtr], [string]) }, @{ Name = "RtlFreeUnicodeString"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([IntPtr]) }, @{ Name = "RtlFindMessage"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@( [IntPtr], # DllHandle [Uint32], # MessageTableId [Uint32], # MessageLanguageId [Uint32], # MessageId // ULONG [IntPtr].MakeByRefType() # ref IntPtr for output MESSAGE_RESOURCE_ENTRY* ) }, @{ Name = "RtlNtStatusToDosError"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([Int32]) }, @{ Name = "LsaNtStatusToWinError"; Dll = "advapi32.dll"; ReturnType = [UInt32] ; Parameters = [Type[]]@([UInt32]) }, <# [In]String\Flags, [In][REF]Flags, [In][REF]UNICODE_STRING, [Out]Handle void LdrLoadDll(ulonglong param_1,uint *param_2,uint *param_3,undefined8 *param_4) https://rextester.com/KCUV42565 RtlInitUnicodeStringStruct (&unicodestring, L"USER32.dll"); LdrLoadDllStruct (NULL, 0, &unicodestring, &hModule); https://doxygen.reactos.org/d7/d55/ldrapi_8c_source.html NTSTATUS NTAPI DECLSPEC_HOTPATCH LdrLoadDll( _In_opt_ PWSTR SearchPath, _In_opt_ PULONG DllCharacteristics, _In_ PUNICODE_STRING DllName, _Out_ PVOID *BaseAddress) { #> @{ Name = "LdrLoadDll"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@( # [IntPtr]::Zero // [STRING] -> NULL -> C Behavior [IntPtr], # [IntPtr]::Zero // Uint.makeByRef[] # Legit, no flags, can be 0x0 -> if (param_2 == (uint *)0x0) {uVar4 = 0;} [IntPtr], [IntPtr], # ModuleFileName Pointer (from RtlCreateUnicodeString) [IntPtr].MakeByRefType() # out ModuleHandle ) }, @{ Name = "LdrUnLoadDll"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@( [IntPtr] # ModuleHandle (PVOID*) )}, @{ Name = "LdrGetProcedureAddressForCaller" Dll = "ntdll.dll" ReturnType = [Int64] Parameters = [Type[]]@( [IntPtr], # [HMODULE] Module handle pointer [IntPtr], # [PSTRING] Pointer to STRING struct (pass IntPtr directly, NOT [ref]) [int64], # [ULONG] Ordinal / Flags (usually 0) [IntPtr].MakeByRefType(), # [PVOID*] Out pointer to procedure address (pass [ref]) [byte], # [Flags] 0 or 1 (usually 0) [IntPtr] # [Caller] Nullable caller address, pass [IntPtr]::Zero if none ) } ) foreach ($func in $NtdllFunctions) { $tb.DefinePInvokeMethod($func.Name, $func.Dll, $Method.attributes, $Method.CallingConventions, $func.ReturnType, $func.Parameters, $Method.nativeCallConv, $Method.nativeCharSet ).SetImplementationFlags($Method.ImplAttributes) } return $tb.CreateType() } Function Init-KERNEL32 { $Method = [PSCustomObject]@{ attributes = [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl CallingConventions = [CallingConventions]::Standard nativeCallConv = [CallingConvention]::Winapi nativeCharSet = [CharSet]::Unicode ImplAttributes = [MethodImplAttributes]::PreserveSig TypeAttributes = [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed } $asm = [AppDomain]::CurrentDomain.DefineDynamicAssembly( (New-Object System.Reflection.AssemblyName "Kernel32API"), [System.Reflection.Emit.AssemblyBuilderAccess]::Run ) $mod = $asm.DefineDynamicModule("Kernel32APIModule") $tb = $mod.DefineType("NativeMethods", $Method.TypeAttributes) $functions = @( @{ Name = "FindFirstFileW"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([string], [IntPtr]) }, @{ Name = "FindNextFileW"; Dll = "KernelBase.dll"; ReturnType = [bool]; Parameters = [Type[]]@([IntPtr], [IntPtr]) }, @{ Name = "FindClose"; Dll = "KernelBase.dll"; ReturnType = [bool]; Parameters = [Type[]]@([IntPtr]) }, @{ Name = "LocalFree" ; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([IntPtr]) }, @{ Name = "EnumSystemFirmwareTables"; Dll = "KernelBase.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [IntPtr], [UInt32]) }, @{ Name = "GetSystemFirmwareTable"; Dll = "KernelBase.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [UInt32], [IntPtr], [UInt32]) }, @{ Name = "LoadLibraryExW"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([string], [IntPtr], [UInt32]) }, @{ Name = "FreeLibrary"; Dll = "KernelBase.dll"; ReturnType = [BOOL]; Parameters = [Type[]]@([IntPtr]) }, @{ Name = "GetProcessHeap"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@()}, @{ Name = "HeapFree"; Dll = "KernelBase.dll"; ReturnType = [bool] ; Parameters = [Type[]]@([IntPtr], [uint32], [IntPtr]) }, @{ Name = "GetProcAddress"; Dll = "KernelBase.dll"; ReturnType = [IntPtr]; Parameters = [Type[]]@([IntPtr], [string]) } ) foreach ($f in $functions) { # Default to Unicode unless overridden $charSet = $Method.nativeCharSet # Use ANSI marshaling only for GetProcAddress if ($f.Name -eq 'GetProcAddress') { $charSet = [CharSet]::Ansi } $tb.DefinePInvokeMethod( $f.Name, $f.Dll, $Method.attributes, $Method.CallingConventions, $f.ReturnType, $f.Parameters, $Method.nativeCallConv, $charSet ).SetImplementationFlags($Method.ImplAttributes) } return $tb.CreateType() } <# ********************* !Managed Api & Com Warper.! - Helper's - ********************* #> function Process-Parameters { param ( [Parameter(Mandatory=$true)] [PSCustomObject]$InterfaceSpec, [switch]$Ignore ) # Initialize the parameters list with the base parameter (thisPtr) $allParams = New-Object System.Collections.Generic.List[string] if (-not $Ignore) { $BaseParams = "IntPtr thisPtr" $allParams.Add($BaseParams) # Add the base parameter (thisPtr) first } # Process user-provided parameters if they exist if (-not [STRING]::IsNullOrEmpty($InterfaceSpec.Params)) { # Split the user-provided parameters by comma and trim whitespace $userParams = $InterfaceSpec.Params.Split(',') | ForEach-Object { $_.Trim() } foreach ($param in $userParams) { $modifier = "" $typeAndName = $param # Check for 'ref' or 'out' keywords, optionally wrapped in brackets, and separate them if ($param -match "^\s*\[?(ref|out)\]?\s+(.+)$") { $modifier = $Matches[1] # This will capture "ref" or "out" (e.g., if input was "[REF]", $Matches[1] will be "REF") $modifier = $modifier.ToLowerInvariant() # Convert modifier to lowercase ("REF" -> "ref") $typeAndName = $Matches[2] # Extract the actual type and name } # Split the type and name (e.g., "uint Flags" -> "uint", "Flags") $parts = $typeAndName.Split(' ', 2) if ($parts.Length -eq 2) { $type = $parts[0] $name = $parts[1] $fixedType = $type # Default to original type if no match # Standardize common C# types to their correct casing using a switch statement switch ($type.ToLowerInvariant()) { "system.boolean" { $fixedType = "bool" } "system.byte" { $fixedType = "byte" } "system.sbyte" { $fixedType = "sbyte" } "system.char" { $fixedType = "char" } "system.decimal" { $fixedType = "decimal" } "system.double" { $fixedType = "double" } "system.single" { $fixedType = "float" } "system.int32" { $fixedType = "int" } "system.uint32" { $fixedType = "uint" } "system.intptr" { $fixedType = "IntPtr" } "system.uintptr" { $fixedType = "UIntPtr" } "system.int64" { $fixedType = "long" } "system.uint64" { $fixedType = "ulong" } "system.int16" { $fixedType = "short" } "system.uint16" { $fixedType = "ushort" } "system.object" { $fixedType = "object" } "system.string" { $fixedType = "string" } "bool" { $fixedType = "bool" } "byte" { $fixedType = "byte" } "sbyte" { $fixedType = "sbyte" } "char" { $fixedType = "char" } "decimal" { $fixedType = "decimal" } "double" { $fixedType = "double" } "float" { $fixedType = "float" } "int" { $fixedType = "int" } "uint" { $fixedType = "uint" } "long" { $fixedType = "long" } "ulong" { $fixedType = "ulong" } "short" { $fixedType = "short" } "ushort" { $fixedType = "ushort" } "object" { $fixedType = "object" } "string" { $fixedType = "string" } "intptr" { $fixedType = "IntPtr" } "uintptr" { $fixedType = "UIntPtr" } "nint" { $fixedType = "nint" } "nuint" { $fixedType = "nuint" } "boolean" { $fixedType = "bool" } "byte" { $fixedType = "byte" } "sbyte" { $fixedType = "sbyte" } "char" { $fixedType = "char" } "decimal" { $fixedType = "decimal" } "double" { $fixedType = "double" } "single" { $fixedType = "float" } "int32" { $fixedType = "int" } "uint32" { $fixedType = "uint" } "int64" { $fixedType = "long" } "uint64" { $fixedType = "ulong" } "int16" { $fixedType = "short" } "uint16" { $fixedType = "ushort" } "object" { $fixedType = "object" } "string" { $fixedType = "string" } "Guid" { $fixedType = "Guid" } } # Reconstruct the parameter string with the fixed type and optional modifier $formattedParam = "$fixedType $name" if (-not [STRING]::IsNullOrEmpty($modifier)) { $formattedParam = "$modifier $formattedParam" } $allParams.Add($formattedParam) } else { # If the parameter couldn't be parsed, add it as is $allParams.Add($param) } } } # Join all processed parameters with a comma and add indentation for readability $Params = $allParams -join ("," + "`n" + " " * 10) return $Params } function Process-ReturnType { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ReturnType ) $fixedReturnType = $ReturnType switch ($ReturnType.ToLowerInvariant()) { "system.boolean" { $fixedReturnType = "bool" } "system.byte" { $fixedReturnType = "byte" } "system.sbyte" { $fixedReturnType = "sbyte" } "system.char" { $fixedReturnType = "char" } "system.decimal" { $fixedReturnType = "decimal" } "system.double" { $fixedReturnType = "double" } "system.single" { $fixedReturnType = "float" } "system.int32" { $fixedReturnType = "int" } "system.uint32" { $fixedReturnType = "uint" } "system.intptr" { $fixedReturnType = "IntPtr" } "system.uintptr" { $fixedReturnType = "UIntPtr" } "system.int64" { $fixedReturnType = "long" } "system.uint64" { $fixedReturnType = "ulong" } "system.int16" { $fixedReturnType = "short" } "system.uint16" { $fixedReturnType = "ushort" } "system.object" { $fixedReturnType = "object" } "system.string" { $fixedReturnType = "string" } "bool" { $fixedReturnType = "bool" } "byte" { $fixedReturnType = "byte" } "sbyte" { $fixedReturnType = "sbyte" } "char" { $fixedReturnType = "char" } "decimal" { $fixedReturnType = "decimal" } "double" { $fixedReturnType = "double" } "float" { $fixedReturnType = "float" } "int" { $fixedReturnType = "int" } "uint" { $fixedReturnType = "uint" } "long" { $fixedReturnType = "long" } "ulong" { $fixedReturnType = "ulong" } "short" { $fixedReturnType = "short" } "ushort" { $fixedReturnType = "ushort" } "object" { $fixedReturnType = "object" } "string" { $fixedReturnType = "string" } "intptr" { $fixedReturnType = "IntPtr" } "uintptr" { $fixedReturnType = "UIntPtr" } "nint" { $fixedReturnType = "nint" } "nuint" { $fixedReturnType = "nuint" } "void" { $fixedReturnType = "void" } "boolean" { $fixedReturnType = "bool" } "Guid" { $fixedReturnType = "Guid" } "boolean" { $fixedReturnType = "bool" } "int32" { $fixedReturnType = "int" } "uint32" { $fixedReturnType = "uint" } "int64" { $fixedReturnType = "long" } "uint64" { $fixedReturnType = "ulong" } "int16" { $fixedReturnType = "short" } "uint16" { $fixedReturnType = "ushort" } } return $fixedReturnType } <# ********************* !Managed Com Warper.! - Example code. - ********************* # netlistmgr.h # https://raw.githubusercontent.com/nihon-tc/Rtest/refs/heads/master/header/Microsoft%20SDKs/Windows/v7.0A/Include/netlistmgr.h # get_IsConnectedToInternet # https://learn.microsoft.com/en-us/windows/win32/api/netlistmgr/nf-netlistmgr-inetworklistmanager-get_isconnectedtointernet ------------------------------- Clear-host write-host "`n`nCLSID & Propertie's [Test]`nDCB00C01-570F-4A9B-8D69-199FDBA5723B->Default->IsConnected,IsConnectedToInternet`n" $NetObj = "DCB00C01-570F-4A9B-8D69-199FDBA5723B" | Initialize-ComObject write-host "IsConnected: $($NetObj.IsConnected)" write-host "IsConnectedToInternet: $($NetObj.IsConnectedToInternet)" $NetObj | Release-ComObject ------------------------------- Clear-host write-host "`n`nIEnumerator & Params\values [Test]`nDCB00C01-570F-4A9B-8D69-199FDBA5723B->DCB00000-570F-4A9B-8D69-199FDBA5723B->GetNetwork`n" [intPtr]$ppEnumNetwork = [intPtr]::Zero Use-ComInterface ` -CLSID "DCB00C01-570F-4A9B-8D69-199FDBA5723B" ` -IID "DCB00000-570F-4A9B-8D69-199FDBA5723B" ` -Index 1 ` -Name "GetNetwork" ` -Return "uint" ` -Params 'system.UINT32 Flags, out INTPTR ppEnumNetwork' ` -Values @(1, [ref]$ppEnumNetwork) if ($ppEnumNetwork -ne [IntPtr]::Zero) { $networkList = $ppEnumNetwork | Receive-ComObject foreach ($network in $networkList) { "Name: $($network.GetName()), IsConnected: $($network.IsConnected())" } $networkList | Release-ComObject } ------------------------------- Clear-host write-host "`n`nVoid & No Return [Test]`n17CCA47D-DAE5-4E4A-AC42-CC54E28F334A->f2dcb80d-0670-44bc-9002-cd18688730af->ShowProductKeyUI`n" Use-ComInterface ` -CLSID "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" ` -IID "f2dcb80d-0670-44bc-9002-cd18688730af" ` -Index 3 ` -Name "ShowProductKeyUI" ` -Return "void" #> function Build-ComInterfaceSpec { param ( [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')] [string]$CLSID, [Parameter(Position = 2)] [string]$IID, [Parameter(Mandatory = $true, Position = 3)] [ValidateRange(1, [int]::MaxValue)] [int]$Index, [Parameter(Mandatory = $true, Position = 4)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory = $true, Position = 5)] [ValidateSet( "bool", "byte", "sbyte", "char", "decimal", "double", "float", "int", "uint", "long", "ulong", "short", "ushort", "object", "string", "IntPtr", "UIntPtr", "nint", "nuint", "boolean", "single", "int32", "uint32", "int64", "uint64", "int16", "uint16", "void", "Guid" )] [string]$Return, [Parameter(Position = 6)] [string]$Params ) if (-not [string]::IsNullOrEmpty($IID)) { if (-not [regex]::Match($IID,'^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')){ throw "ERROR: $IID not match ^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$" } } # Create and return the interface specification object $interfaceSpec = [PSCustomObject]@{ Index = $Index Return = $Return Name = $Name Params = if ($Params) { $Params } else { "" } CLSID = $CLSID IID = if ($IID) { $IID } else { "" } } return $interfaceSpec } function Build-ComDelegate { param ( [Parameter(Mandatory=$true, ValueFromPipeline)] [PSCustomObject]$InterfaceSpec, [Parameter(Mandatory=$true)] [string]$UNIQUE_ID ) # External function calls for Params and ReturnType $Params = Process-Parameters -InterfaceSpec $InterfaceSpec $fixedReturnType = Process-ReturnType -ReturnType $InterfaceSpec.Return # Construct the delegate code template $Return = @" [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate $($fixedReturnType) $($UNIQUE_ID)( $($Params) ); "@ # Define the C# namespace and using statements $namespace = "namespace DynamicDelegates" $using = "`nusing System;`nusing System.Runtime.InteropServices;`n" # Combine all parts to form the final C# code return "$using`n$namespace`n{`n$Return`n}`n" } function Initialize-ComObject { param ( [Parameter(ValueFromPipeline, Position = 0)] [PSCustomObject]$InterfaceSpec, [Parameter(ValueFromPipeline, Position = 1)] [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')] [GUID]$CLSID, [switch] $CreateInstance ) if ($CLSID -and $InterfaceSpec -and $CLSID.ToString() -eq $InterfaceSpec) { $InterfaceSpec = $null } # Oppsite XOR Case, Validate it Not both if (-not ([bool]$InterfaceSpec -xor [bool]$CLSID)) { throw "Select CLSID OR $InterfaceSpec" } # ------ BASIC SETUP ------- if ($InterfaceSpec) { $CLSID = [guid]$InterfaceSpec.CLSID } # Create COM instance $comObj = [Activator]::CreateInstance([type]::GetTypeFromCLSID($clsid)) if (-not $comObj) { throw "Failed to create COM object for CLSID $clsid" } if (-not $InterfaceSpec -or $CreateInstance) { return $comObj } # Default IID to IUnknown if not provided in InterfaceSpec $iid = if ($InterfaceSpec.IID) { [guid]$InterfaceSpec.IID } else { [guid]"00000000-0000-0000-C000-000000000046" } # Get IUnknown pointer from COM object $iUnknownPtr = [Marshal]::GetIUnknownForObject($comObj) # ------ QueryInterface Delegate ------- # Get IUnknown pointer from COM object $iUnknownPtr = [Marshal]::GetIUnknownForObject($comObj) # Get vtable pointer from IUnknown $vtablePtr = [Marshal]::ReadIntPtr($iUnknownPtr) # Get QueryInterface function pointer (index 0) $queryInterfacePtr = [Marshal]::ReadIntPtr($vtablePtr, 0 * [IntPtr]::Size) # Define QueryInterface delegate type if not already defined if (-not ([Type]::GetType("QueryInterfaceDelegate"))) { Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate int QueryInterfaceDelegate(IntPtr thisPtr, ref Guid riid, out IntPtr ppvObject); "@ -Language CSharp -ErrorAction Stop } # Create QueryInterface delegate $queryInterface = [Marshal]::GetDelegateForFunctionPointer( $queryInterfacePtr, [QueryInterfaceDelegate]) # ------ Continue with IID Setup ------- # Call QueryInterface with the IID from above [IntPtr]$interfacePtr = [IntPtr]::Zero $hresult = $queryInterface.Invoke($iUnknownPtr, [ref]$iid, [ref]$interfacePtr) if ($hresult -ne 0 -or $interfacePtr -eq [IntPtr]::Zero) { throw "QueryInterface failed with HRESULT 0x{0:X8}" -f $hresult } # Read the vtable pointer of the requested interface $requestedVTablePtr = [Marshal]::ReadIntPtr($interfacePtr) # ------ Check if inherit *** ------- $interfaces = ( @("00000112-0000-0000-C000-000000000046", 24), # IOleObject @("0000010E-0000-0000-C000-000000000046", 12), # IDataObject @("0000000C-0000-0000-C000-000000000046", 13), # IStream @("0000010A-0000-0000-C000-000000000046", 11), # IPersistStorage @("0000000B-0000-0000-C000-000000000046", 15), # IStorage @("00000003-0000-0000-C000-000000000046", 9), # IMarshal @("0000010B-0000-0000-C000-000000000046", 8), # IPersistFile @("00000118-0000-0000-C000-000000000046", 8), # IOleClientSite @("00020400-0000-0000-C000-000000000046", 7), # IDispatch @("00000139-0000-0000-C000-000000000046", 7), # IEnumSTATPROPSTG @("0000013A-0000-0000-C000-000000000046", 7), # IEnumSTATPROPSETSTG @("0000000D-0000-0000-C000-000000000046", 7), # IEnumSTATSTG @("00000109-0000-0000-C000-000000000046", 7), # IPersistStream @("00020404-0000-0000-C000-000000000046", 7), # IEnumVARIANT @("00000102-0000-0000-C000-000000000046", 7), # IEnumMoniker @("B196B286-BAB4-101A-B69C-00AA00341D07", 7), # IConnectionPoint @("00000101-0000-0000-C000-000000000046", 7), # IEnumString @("00000114-0000-0000-C000-000000000046", 5), # IOleWindow @("55272A00-42CB-11CE-8135-00AA004BB851", 5), # IPropertyBag @("0000010C-0000-0000-C000-000000000046", 4), # IPersist @("B196B283-BAB4-101A-B69C-00AA00341D07", 4), # IProvideClassInfo @("A6BC3AC0-DBAA-11CE-9DE3-00AA004BB851", 4), # IProvideClassInfo2 @("B196B28B-BAB4-101A-B69C-00AA00341D07", 4), # ISpecifyPropertyPages @("EB5E0020-8F75-11D1-ACDD-00C04FC2B085", 4), # IPersistPropertyBag @("B196B284-BAB4-101A-B69C-00AA00341D07", 4) # IConnectionPointContainer ) # Default for IUnknown $baseMethodOffset = 3 foreach ($iface in $interfaces) { $iid = $iface[0] $totalMethods = $iface[1] $ptr = [IntPtr]::Zero $hr = $queryInterface.Invoke($interfacePtr, [ref]$iid, [ref]$ptr) if ($hr -eq 0 -and $ptr -ne [IntPtr]::Zero) { $baseMethodOffset = $totalMethods [Marshal]::Release($ptr) | Out-Null break } } # ------ Continue with IID Setup ------- # Get function pointer for target method using methodIndex $methodIndex = $baseMethodOffset + ([int]$InterfaceSpec.Index - 1) $funcPtr = [Marshal]::ReadIntPtr($requestedVTablePtr, $methodIndex * [IntPtr]::Size) # ------ Create Function delegate type ------- $timestampSuffix = (Get-Date -Format "yyyyMMddHHmmssfff") $simpleUniqueDelegateName = "$($InterfaceSpec.Name)$timestampSuffix" $delegateCode = Build-ComDelegate -InterfaceSpec $InterfaceSpec -UNIQUE_ID $simpleUniqueDelegateName Add-Type -TypeDefinition $delegateCode -Language CSharp -ErrorAction Stop # Resolve delegate type from current domain using the explicit fully qualified name # This line now directly constructs the fully qualified name using $simpleUniqueDelegateName $fullDelegateTypeName = "DynamicDelegates.$simpleUniqueDelegateName" $delegateType = $null # Iterate through loaded assemblies to find the type more robustly foreach ($asm in [AppDomain]::CurrentDomain.GetAssemblies()) { $foundType = $asm.GetType($fullDelegateTypeName, $false, $true) if ($foundType) { $delegateType = $foundType break } } if (-not $delegateType) { throw "Delegate type '$simpleUniqueDelegateName' not found." } # Create delegate instance $delegateInstance = [Marshal]::GetDelegateForFunctionPointer($funcPtr, $delegateType) return [PSCustomObject]@{ ComObject = $comObj IUnknownPtr = $iUnknownPtr InterfacePtr = $interfacePtr VTablePtr = $requestedVTablePtr FunctionPtr = $funcPtr DelegateType = $delegateType DelegateInstance = $delegateInstance InterfaceSpec = $InterfaceSpec MethodIndex = $methodIndex DelegateCode = $delegateCode } } function Receive-ComObject { param ( [Parameter(Mandatory=$true, ValueFromPipeline)] [object]$punk ) try { return [Marshal]::GetObjectForIUnknown([intPtr]$punk) } catch { return $punk } } function Invoke-ComObject { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] $comInterface, [Parameter(ValueFromRemainingArguments = $true)] $Params ) $output = $null [int]$count = if ($Params.Count) {$Params.Count} else {0} switch ($count) { 0 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr) break } 1 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0]) break } 2 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1]) break } 3 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2]) break } 4 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3]) break } 5 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4]) break } 6 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5]) break } 7 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6]) break } 8 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7]) break } 9 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8]) break } 10 { $output = $comInterface.DelegateInstance.Invoke( $comInterface.IUnknownPtr,$Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8],$Params[9]) break } } return $output } function Release-ComObject { param ( [Parameter(Mandatory=$true, ValueFromPipeline)] $comInterface ) $ISComObject = $comInterface.GetType().Name -match '__ComObject' $IsPSCustomObject = $comInterface.GetType().Name -match 'PSCustomObject' if ($ISComObject) { [Marshal]::ReleaseComObject($comInterface) | Out-Null } if ($IsPSCustomObject) { try { if ($comInterface.ComObject) { [Marshal]::ReleaseComObject($comInterface.ComObject) | Out-Null } } catch {} try { if ($comInterface.IUnknownPtr -and $comInterface.IUnknownPtr -ne [IntPtr]::Zero) { [Marshal]::Release($comInterface.IUnknownPtr) | Out-Null } } catch {} try { if ($comInterface.InterfacePtr -and $comInterface.InterfacePtr -ne [IntPtr]::Zero) { [Marshal]::Release($comInterface.InterfacePtr) | Out-Null } } catch {} # Cleanup $comInterface.ComObject = $null $comInterface.DelegateInstance = $null $comInterface.VTablePtr = $null $comInterface.FunctionPtr = $null $comInterface.DelegateType = $null $comInterface.InterfaceSpec = $null $comInterface.IUnknownPtr = [IntPtr]::Zero [GC]::Collect() [GC]::WaitForPendingFinalizers() } } function Use-ComInterface { param ( [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')] [string]$CLSID, [Parameter(Position = 2)] [ValidatePattern('^[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}$')] [string]$IID, [Parameter(Mandatory = $true, Position = 3)] [ValidateRange(1, [int]::MaxValue)] [int]$Index, [Parameter(Mandatory = $true, Position = 4)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory = $true, Position = 5)] [ValidateSet( "bool", "byte", "sbyte", "char", "decimal", "double", "float", "int", "uint", "long", "ulong", "short", "ushort", "object", "string", "IntPtr", "UIntPtr", "nint", "nuint", "boolean", "single", "int32", "uint32", "int64", "uint64", "int16", "uint16", "void" )] [string]$Return, [Parameter(Position = 6)] [string]$Params, [Parameter(Position = 7)] [object[]]$Values ) # 1. Build the interface specification $interfaceSpec = Build-ComInterfaceSpec ` -CLSID $CLSID ` -IID $IID ` -Index $Index ` -Name $Name ` -Return $Return ` -Params $Params # 2. Initialize COM object $comObj = $interfaceSpec | Initialize-ComObject try { # 3. Invoke the method $result = $comObj | Invoke-ComObject -Params $Values # Return whatever the method returned (including HRESULT or actual return value) return $result } finally { # 4. Always release COM object $comObj | Release-ComObject } } <# ********************* !Managed Api Warper.! - Example code. - ********************* Clear-Host Write-Host Invoke-UnmanagedMethod ` -Dll "kernel32.dll" ` -Function "Beep" ` -Return "bool" ` -Params "uint dwFreq, uint dwDuration" ` -Values @(750, 300) # 750 Hz beep for 300 ms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Clear-Host Write-Host $buffer = New-IntPtr -Size 256 $result = Invoke-UnmanagedMethod ` -Dll "kernel32.dll" ` -Function "GetComputerNameA" ` -Return "bool" ` -Params "IntPtr lpBuffer, ref uint lpnSize" ` -Values @($buffer, [ref]256) if ($result) { $computerName = [Marshal]::PtrToStringAnsi($buffer) Write-Host "Computer Name: $computerName" } else { Write-Host "Failed to get computer name" } New-IntPtr -hHandle $buffer -Release ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Clear-Host Write-Host # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677 # SYSTEM_PROCESS_INFORMATION structure # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677 # SYSTEM_BASIC_INFORMATION structure # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_basic_information.htm # Step 1: Get required buffer size $ReturnLength = 0 $dllResult = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(0, [IntPtr]::Zero, 0, [ref]$ReturnLength) # Allocate buffer (add some extra room just in case) $infoBuffer = New-IntPtr -Size $ReturnLength # Step 2: Actual call with allocated buffer $result = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(0, $infoBuffer, $ReturnLength, [ref]$ReturnLength) if ($result -ne 0) { Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)" Parse-ErrorMessage -MessageId $result New-IntPtr -hHandle $infoBuffer -Release return } # Parse values from the structure $sysBasicInfo = [PSCustomObject]@{ PageSize = [Marshal]::ReadInt32($infoBuffer, 0x08) NumberOfPhysicalPages = [Marshal]::ReadInt32($infoBuffer, 0x0C) LowestPhysicalPageNumber = [Marshal]::ReadInt32($infoBuffer, 0x10) HighestPhysicalPageNumber = [Marshal]::ReadInt32($infoBuffer, 0x14) AllocationGranularity = [Marshal]::ReadInt32($infoBuffer, 0x18) MinimumUserModeAddress = [Marshal]::ReadIntPtr($infoBuffer, 0x20) MaximumUserModeAddress = [Marshal]::ReadIntPtr($infoBuffer, 0x28) ActiveProcessorsAffinityMask = [Marshal]::ReadIntPtr($infoBuffer, 0x30) NumberOfProcessors = [Marshal]::ReadByte($infoBuffer, 0x38) } # Step 1: Get required buffer size $ReturnLength = 0 $dllResult = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(5, [IntPtr]::Zero, 0, [ref]$ReturnLength) # Allocate buffer (add some extra room just in case) $ReturnLength += 200 $procBuffer = New-IntPtr -Size $ReturnLength # Step 2: Actual call with allocated buffer $result = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(5, $procBuffer, $ReturnLength, [ref]$ReturnLength) if ($result -ne 0) { Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)" Parse-ErrorMessage -MessageId $result New-IntPtr -hHandle $procBuffer -Release return } $offset = 0 $processList = @() while ($true) { try { $entryPtr = [IntPtr]::Add($procBuffer, $offset) $nextOffset = [Marshal]::ReadInt32($entryPtr, 0x00) $namePtr = [Marshal]::ReadIntPtr($entryPtr, 0x38 + [IntPtr]::Size) $processName = if ($namePtr -ne [IntPtr]::Zero) { [Marshal]::PtrToStringUni($namePtr) } else { "[System]" } $procObj = [PSCustomObject]@{ ProcessId = [Marshal]::ReadIntPtr($entryPtr, 0x50) ProcessName = $processName NumberOfThreads = [Marshal]::ReadInt32($entryPtr, 0x04) } $processList += $procObj if ($nextOffset -eq 0) { break } $offset += $nextOffset } catch { Write-Host "Parsing error at offset $offset. Stopping." break } } New-IntPtr -hHandle $infoBuffer -Release New-IntPtr -hHandle $procBuffer -Release $sysBasicInfo | Format-List $processList | Sort-Object ProcessName | Format-Table ProcessId, ProcessName, NumberOfThreads -AutoSize #> function Build-ApiDelegate { param ( [Parameter(Mandatory=$true, ValueFromPipeline)] [PSCustomObject]$InterfaceSpec, [Parameter(Mandatory=$true)] [string]$UNIQUE_ID ) $namespace = "namespace DynamicDelegates" $using = "`nusing System;`nusing System.Runtime.InteropServices;`n" $Params = Process-Parameters -InterfaceSpec $InterfaceSpec -Ignore $fixedReturnType = Process-ReturnType -ReturnType $InterfaceSpec.Return $Return = @" [UnmanagedFunctionPointer(CallingConvention.$($InterfaceSpec.CallingType))] public delegate $($fixedReturnType) $($UNIQUE_ID)( $($Params) ); "@ return "$using`n$namespace`n{`n$Return`n}`n" } function Build-ApiInterfaceSpec { param ( [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [string]$Dll, [Parameter(Mandatory = $true, Position = 2)] [ValidateNotNullOrEmpty()] [string]$Function, [Parameter(Mandatory = $true, Position = 3)] [ValidateSet("StdCall", "Cdecl")] [string]$CallingConvention = "StdCall", [Parameter(Mandatory = $true, Position = 4)] [ValidateNotNullOrEmpty()] [ValidateSet( "bool", "byte", "sbyte", "char", "decimal", "double", "float", "int", "uint", "long", "ulong", "short", "ushort", "object", "string", "IntPtr", "UIntPtr", "nint", "nuint", "boolean", "single", "int32", "uint32", "int64", "uint64", "int16", "uint16", "void", "Guid" )] [string]$Return, [Parameter(Mandatory = $false, Position = 5)] [string]$Params ) return [PSCustomObject]@{ Dll = $Dll Function= $Function Return = $Return Params = $Params CallingType = $CallingConvention } } function Initialize-ApiObject { param ( [Parameter(Mandatory=$true, ValueFromPipeline)] [PSCustomObject]$ApiSpec ) $hModule = [IntPtr]::Zero $BaseAddress = Ldr-LoadDll -dwFlags SEARCH_SYS32 -dll $ApiSpec.Dll if ($BaseAddress -ne $null -and $BaseAddress -ne [IntPtr]::Zero) { $hModule = [IntPtr]$BaseAddress } if ($hModule -eq [IntPtr]::Zero) { throw "Failed to load DLL: $($ApiSpec.Dll)" } $funcAddress = [IntPtr]::Zero $AnsiPtr = Init-NativeString -Value $ApiSpec.Function -Encoding Ansi $hresult = $Global:ntdll::LdrGetProcedureAddressForCaller( $hModule, $AnsiPtr, 0, [ref]$funcAddress, 0, [IntPtr]::Zero) Free-NativeString -StringPtr $AnsiPtr if ($funcAddress -eq [IntPtr]::Zero -or $hresult -ne 0) { throw "Failed to find function: $($ApiSpec.Function)" } # Build delegate $uniqueName = "$($ApiSpec.Function)Api$(Get-Random)" $delegateCode = Build-ApiDelegate -InterfaceSpec $ApiSpec -UNIQUE_ID $uniqueName Add-Type -TypeDefinition $delegateCode -Language CSharp -ErrorAction Stop $delegateType = [AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object { $_.GetType("DynamicDelegates.$uniqueName", $false, $true) } | Where-Object { $_ } | Select-Object -First 1 if (-not $delegateType) { throw "Failed to get delegate type for $uniqueName" } $delegate = [Marshal]::GetDelegateForFunctionPointer( $funcAddress, $delegateType) return [PSCustomObject]@{ Dll = $ApiSpec.Dll Function = $ApiSpec.Function FunctionPtr = $funcAddress DelegateInstance = $delegate DelegateType = $delegateType DelegateCode = $delegateCode } } function Invoke-ApiObject { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] $ApiInterface, [Parameter(ValueFromRemainingArguments = $true)] $Params ) $output = $null [int]$count = if ($Params.Count) {$Params.Count} else {0} switch ($count) { 0 { $output = $ApiInterface.DelegateInstance.Invoke() break } 1 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0]) break } 2 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1]) break } 3 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2]) break } 4 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3]) break } 5 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3],$Params[4]) break } 6 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5]) break } 7 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6]) break } 8 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7]) break } 9 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8]) break } 10 { $output = $ApiInterface.DelegateInstance.Invoke( $Params[0],$Params[1],$Params[2],$Params[3],$Params[4],$Params[5],$Params[6],$Params[7],$Params[8],$Params[9]) break } } return $output } function Release-ApiObject { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline)] [PSCustomObject]$ApiObject ) process { try { $ApiObject.DelegateInstance = $null $ApiObject.DelegateType = $null $ApiObject.FunctionPtr = [IntPtr]::Zero $ApiObject.DelegateCode = $null $ApiObject.Dll = $null $ApiObject.Function = $null [GC]::Collect() [GC]::WaitForPendingFinalizers() } catch { Write-Warning "Failed to release ApiObject: $_" } } } function Invoke-UnmanagedMethod { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Dll, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Function, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Return, [Parameter(Mandatory = $false)] [string]$Params, [Parameter(Mandatory = $false)] [ValidateSet("StdCall", "Cdecl")] [string]$CallingConvention, [Parameter(Mandatory = $false)] [object[]]$Values ) # Detect platform if (-not $CallingConvention) { if ([IntPtr]::Size -eq 8) { $CallingConvention = "StdCall" } else { $CallingConvention = "StdCall" } } $apiSpec = Build-ApiInterfaceSpec -Dll $Dll ` -Function $Function ` -Return $Return ` -CallingConvention $CallingConvention ` -Params $Params $apiObj = $apiSpec | Initialize-ApiObject try { return $apiObj | Invoke-ApiObject -Params $Values } finally { $apiObj | Release-ApiObject } } <# .HELPERS UnicodeString function helper, just for testing purpose +++ Struct Info +++ typedef struct _UNICODE_STRING { USHORT Length; [ushort = 2] USHORT MaximumLength; [ushort = 2] ** in x64 enviroment Add 4 byte's padding ** PWSTR Buffer; [IntPtr].Size } UNICODE_STRING, *PUNICODE_STRING; Buffer Offset == [IntPtr].Size { x86=4, x64=8 } +++ Test Code +++ Clear-Host Write-Host $unicodeStringPtr = Init-NativeString -Value 99 -Encoding Unicode Parse-NativeString -StringPtr $unicodeStringPtr -Encoding Unicode Free-NativeString -StringPtr $unicodeStringPtr $ansiStringPtr = Init-NativeString -Value 99 -Encoding Ansi Parse-NativeString -StringPtr $ansiStringPtr -Encoding Ansi Free-NativeString -StringPtr $ansiStringPtr $unicodeStringPtr = [IntPtr]::Zero $unicodeStringPtr = Manage-UnicodeString -Value 'data123' Parse-UnicodeString -unicodeStringPtr $unicodeStringPtr Manage-UnicodeString -UnicodeStringPtr $unicodeStringPtr -Release #> function Init-NativeString { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Value, [Parameter(Mandatory = $false)] [ValidateSet('Ansi', 'Unicode')] [string]$Encoding = 'Ansi' ) $stringPtr = New-IntPtr -Size 16 if ($Encoding -eq 'Ansi') { $Length = [System.Text.Encoding]::ASCII.GetByteCount($Value) if ($Length -ge 0xFFFE) { $Length = 0xFFFC } $bufferPtr = [Marshal]::StringToHGlobalAnsi($Value) $maxLength = $Length + 1 } else { $Length = $Value.Length * 2 if ($Length -ge 0xFFFE) { $Length = 0xFFFC } $bufferPtr = [Marshal]::StringToHGlobalUni($Value) $maxLength = $Length + 2 } [Marshal]::WriteInt16($stringPtr, 0, $Length) [Marshal]::WriteInt16($stringPtr, 2, $maxLength) [Marshal]::WriteIntPtr($stringPtr, [IntPtr]::Size, $bufferPtr) return $stringPtr } function Parse-NativeString { param ( [Parameter(Mandatory = $true)] [IntPtr]$StringPtr, [Parameter(Mandatory = $false)] [ValidateSet('Ansi', 'Unicode')] [string]$Encoding = 'Ansi' ) if ($StringPtr -eq [IntPtr]::Zero) { return } $Length = [Marshal]::ReadInt16($StringPtr, 0) $Size = [Marshal]::ReadInt16($StringPtr, 2) $BufferPtr = [Marshal]::ReadIntPtr($StringPtr, [IntPtr]::Size) if ($Encoding -eq 'Ansi') { # Length is number of bytes $Data = [Marshal]::PtrToStringAnsi($BufferPtr, $Length) } else { # Unicode, length is bytes, divide by 2 for chars $Data = [Marshal]::PtrToStringUni($BufferPtr, $Length / 2) } return [PSCustomObject]@{ Length = $Length MaximumLength = $Size StringData = $Data } } function Free-NativeString { param ( [Parameter(Mandatory = $true)] [IntPtr]$StringPtr ) if ($StringPtr -eq [IntPtr]::Zero) { Write-Warning 'Failed to free pointer: The pointer is null' return } $ptr = [IntPtr]::Zero try { $bufferPtr = [Marshal]::ReadIntPtr($StringPtr, [IntPtr]::Size) if ($bufferPtr -ne [IntPtr]::Zero) { [Marshal]::FreeHGlobal($bufferPtr) } else { Write-Warning 'Failed to free buffer: The buffer pointer is null.' } [Marshal]::FreeHGlobal($StringPtr) } catch { Write-Warning 'An error occurred while attempting to free memory' return } } <# PEB structure (winternl.h) https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb PEB_LDR_DATA structure (winternl.h) https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data PEB https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm PEB_LDR_DATA https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm?tx=185 LDR_DATA_TABLE_ENTRY https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry/index.htm?tx=179,185 ......................... typedef struct PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; ---> Pointer to PEB_LDR_DATA struct } typedef struct PEB_LDR_DATA { 0x0C, 0x10, LIST_ENTRY InLoadOrderModuleList; 0x14, 0x20, LIST_ENTRY InMemoryOrderModuleList; 0x1C, 0x30, LIST_ENTRY InInitializationOrderModuleList; ---> Pointer to LIST_ENTRY struct } typedef struct LIST_ENTRY { struct LDR_DATA_TABLE_ENTRY *Flink; ---> Pointer to next _LDR_DATA_TABLE_ENTRY struct } typedef struct LDR_DATA_TABLE_ENTRY { 0x00 0x00 LIST_ENTRY InLoadOrderLinks; 0x08 0x10 LIST_ENTRY InMemoryOrderLinks; 0x10 0x20 LIST_ENTRY InInitializationOrderLinks; ---> Actual LIST_ENTRY struct, Not Pointer ... PVOID DllBase; PVOID EntryPoint; ... UNICODE_STRING FullDllName; } ......................... ** x64 system example ** You don't get Pointer to [LDR_DATA_TABLE_ENTRY] Offset 0x0, it depend So, you need to consider, [LinkPtr] & [+Data Offset -0x00\0x10\0x20] -> Actual Offset of Data to read [PEB_LDR_DATA] & 0x10 -> Read Pointer -> \ List Head [LIST_ENTRY]->Flink \ -> [LDR_DATA_TABLE_ENTRY]->[LIST_ENTRY]->0x00 [AKA] InLoadOrderLinks [& Repeat] [PEB_LDR_DATA] & 0x20 -> Read Pointer -> \ List Head [LIST_ENTRY]->Flink \ -> [LDR_DATA_TABLE_ENTRY]->[LIST_ENTRY]->0x10 [AKA] InMemoryOrderLinks [& Repeat] [PEB_LDR_DATA] & 0x30 -> Read Pointer -> \ List Head [LIST_ENTRY]->Flink \ -> [LDR_DATA_TABLE_ENTRY]->[LIST_ENTRY]->0x20 [AKA] InInitializationOrderLinks [& Repeat] ......................... - (*PPEB_LDR_DATA)->InMemoryOrderModuleList -> [LIST_ENTRY] head - each [LIST_ENTRY] contain [*flink], which point to next [LIST_ENTRY] - [LDR_DATA_TABLE] is also [LIST_ENTRY], first offset 0x0 is [LIST_ENTRY], Like this -> (LDR_DATA_TABLE_ENTRY *) = (LIST_ENTRY *) the result of this is! [LIST_ENTRY] head, is actually [LIST_ENTRY] And not [LDR_DATA_TABLE] only used to start the Loop chain, to Read the next [LDR_DATA_TABLE] and than, read next [LDR_DATA_TABLE] item from [0x0 LIST_ENTRY] InLoadOrderLinks which is actually [0x0] flink* -> pointer to another [LDR_DATA_TABLE] C Code -> LIST_ENTRY* head = &Peb->Ldr->InMemoryOrderModuleList; LIST_ENTRY* current = head->Flink; while (current != head) { LDR_DATA_TABLE_ENTRY* module = (LDR_DATA_TABLE_ENTRY*)current; wprintf(L"Loaded Module: %wZ\n", &module->FullDllName); current = current->Flink; } Diagram -> [PEB_LDR_DATA] --- InMemoryOrderModuleList (LIST_ENTRY head) - Flink [LDR_DATA_TABLE_ENTRY] --- LIST_ENTRY InLoadOrderLinks (offset 0x0) --- DllBase, EntryPoint, SizeOfImage, etc. - Flink Another [LDR_DATA_TABLE_ENTRY] ......................... Managed code ? sure. [Process]::GetCurrentProcess().Modules #> function Read-MemoryValue { param ( [Parameter(Mandatory)] [IntPtr]$LinkPtr, [Parameter(Mandatory)] [int]$Offset, [Parameter(Mandatory)] [ValidateSet("IntPtr","Int16", "UInt16", "Int32", "UInt32", "UnicodeString")] [string]$Type ) # Calculate the actual address to read from: $Address = [IntPtr]::Add($LinkPtr, $Offset) try { switch ($Type) { "IntPtr" { return [Marshal]::ReadIntPtr($Address) } "Int16" { return [Marshal]::ReadInt16($Address) } "UInt16" { $rawValue = [Marshal]::ReadInt16($Address) return [UInt16]($rawValue -band 0xFFFF) } "Int32" { return [Marshal]::ReadInt32($Address) } "UInt32" { return [UInt32]([Marshal]::ReadInt32($Address)) } "UnicodeString" { $length = [Marshal]::ReadInt16($Address) if ($length -le 0 -or $length % 2 -ne 0 -or $length -gt 4096) { return "" } $pointerSize = [IntPtr]::Size $bufferOffset = if ($pointerSize -eq 8) { $Offset + 8 } else { $Offset + 4 } $bufferPtr = if ($pointerSize -eq 8) { [Marshal]::ReadIntPtr([IntPtr]::Add($LinkPtr, $bufferOffset)) } else { $ptrVal = [Marshal]::ReadInt32([IntPtr]::Add($LinkPtr, $bufferOffset)) if ($ptrVal -eq 0) { return "" } [IntPtr]::new($ptrVal) } if ($bufferPtr -eq [IntPtr]::Zero) { return "" } $bytes = New-Object byte[] $length [Marshal]::Copy($bufferPtr, $bytes, 0, $length) return [Text.Encoding]::Unicode.GetString($bytes) } } } catch { Write-Warning "Failed to read memory value at offset 0x$([Convert]::ToString($Offset,16)) (Type: $Type). Error: $_" return $null } } function Get-LoadedModules { param ( [Parameter(Mandatory=$true)] [ValidateSet("Load", "Memory", "Init")] [string]$SortType = "Memory", [Parameter(Mandatory=$false)] [IntPtr]$PebPtr = [IntPtr]::Zero ) Enum PebOffset_x86 { ldrOffset = 0x0C InLoadOrderModuleList = 0x0C InMemoryOrderModuleList = 0x14 InInitializationOrderModuleList = 0x1C InLoadOrderLinks = 0x00 InMemoryOrderLinks = 0x08 InInitializationOrderLinks = 0x10 DllBase = 0x18 EntryPoint = 0x1C SizeOfImage = 0x20 FullDllName = 0x24 BaseDllName = 0x2C Flags = 0x34 # ObsoleteLoadCount LoadCount = 0x38 LoadReason = 0x94 ReferenceCount = 0x9C } Enum PebOffset_x64 { ldrOffset = 0x18 InLoadOrderModuleList = 0x10 InMemoryOrderModuleList = 0x20 InInitializationOrderModuleList = 0x30 InLoadOrderLinks = 0x00 InMemoryOrderLinks = 0x10 InInitializationOrderLinks = 0x20 DllBase = 0x30 EntryPoint = 0x38 SizeOfImage = 0x40 FullDllName = 0x48 BaseDllName = 0x58 Flags = 0x68 # ObsoleteLoadCount LoadCount = 0x6C LoadReason = 0x010C ReferenceCount = 0x0114 } if ([IntPtr]::Size -eq 8) { $PebOffset = [PebOffset_x64] } else { $PebOffset = [PebOffset_x86] } # Get Peb address pointr if ($PebPtr -eq [IntPtr]::Zero) { $PebPtr = $Global:ntdll::RtlGetCurrentPeb() if ($PebPtr -eq [IntPtr]::Zero) { throw "Failed to get PEB pointer." } } # Get PEB->Ldr address pointr $ldrPtr = [Marshal]::ReadIntPtr( [IntPtr]::Add($PebPtr, $PebOffset::ldrOffset.value__)) if ($ldrPtr -eq [IntPtr]::Zero) { throw "PEB->Ldr is null. Cannot continue." } try { # Storage to hold module list $modules = @() # Determine offsets based on sorting type switch ($SortType) { "Load" { $ModuleListOffset = $PebOffset::InLoadOrderModuleList.value__ $LinkOffsetInEntry = $PebOffset::InLoadOrderLinks.value__ } "Memory" { $ModuleListOffset = $PebOffset::InMemoryOrderModuleList.value__ $LinkOffsetInEntry = $PebOffset::InMemoryOrderLinks.value__ } "Init" { $ModuleListOffset = $PebOffset::InInitializationOrderModuleList.value__ $LinkOffsetInEntry = $PebOffset::InInitializationOrderLinks.value__ } } <# PEB_LDR_DATA->?*ModuleList --> [LIST_ENTRY] Head Results depend on List Type, by user choice. #> $ListHeadPtr = [IntPtr]::Add($ldrPtr, $ModuleListOffset) <# *Flink --> First [LDR_DATA_TABLE_ENTRY] -> Offset of: InLoadOrderLinks -or InMemoryOrderLinks -or InInitializationOrderLinks So, you dont get base address of [LDR_DATA_TABLE_ENTRY], it shifted, depend, result from: * InLoadOrderLinks = if ([IntPtr]::Size -eq 8) { 0x00 } else { 0x00 } * InMemoryOrderLinks = if ([IntPtr]::Size -eq 8) { 0x10 } else { 0x08 } * InInitializationOrderLinks = if ([IntPtr]::Size -eq 8) { 0x20 } else { 0x10 } #> $NextLinkPtr = [Marshal]::ReadIntPtr($ListHeadPtr) <# Shift offset, to Fix BaseAddress MAP, so, calculate -> NextLinkPtr & (+Offset -StructBaseOffset) == Data Object *Fixed* Offset will be used later when call Read-MemoryValue function. #> $PebOffsetMap = @{} foreach ($Name in ("DllBase", "EntryPoint", "SizeOfImage", "FullDllName", "BaseDllName", "Flags", "LoadCount" , "LoadReason", "ReferenceCount")) { $PebOffsetMap[$Name] = $PebOffset.GetField($Name).GetRawConstantValue() - $LinkOffsetInEntry } # Start parse Data enum LdrFlagsMap { PackagedBinary = 0x00000001 MarkedForRemoval = 0x00000002 ImageDll = 0x00000004 LoadNotificationsSent = 0x00000008 TelemetryEntryProcessed= 0x00000010 ProcessStaticImport = 0x00000020 InLegacyLists = 0x00000040 InIndexes = 0x00000080 ShimDll = 0x00000100 InExceptionTable = 0x00000200 LoadInProgress = 0x00001000 LoadConfigProcessed = 0x00002000 EntryProcessed = 0x00004000 ProtectDelayLoad = 0x00008000 DontCallForThreads = 0x00040000 ProcessAttachCalled = 0x00080000 ProcessAttachFailed = 0x00100000 CorDeferredValidate = 0x00200000 CorImage = 0x00400000 DontRelocate = 0x00800000 CorILOnly = 0x01000000 ChpeImage = 0x02000000 Redirected = 0x10000000 CompatDatabaseProcessed= 0x80000000 } enum LdrLoadReasonMap { StaticDependency = 0 StaticForwarderDependency = 1 DynamicForwarderDependency = 2 DelayloadDependency = 3 DynamicLoad = 4 AsImageLoad = 5 AsDataLoad = 6 EnclavePrimary = 7 EnclaveDependency = 8 Unknown = -1 } $flagsValue = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['Flags'] -Type UInt32 $allFlagValues = [Enum]::GetValues([LdrFlagsMap]) $FlagNames = $allFlagValues | ? { ($flagsValue -band [int]$_) -ne 0 } | ForEach-Object { $_.ToString() } $ReadableFlags = if ($FlagNames.Count -gt 0) { $FlagNames -join ", " } else { "None" } $LoadReasonValue = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['LoadReason'] -Type UInt32 try { $LoadReasonName = [LdrLoadReasonMap]$LoadReasonValue } catch { $LoadReasonName = "Unknown ($LoadReasonValue)" } do { $modules += [PSCustomObject]@{ BaseAddress = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['DllBase'] -Type IntPtr EntryPoint = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['EntryPoint'] -Type IntPtr SizeOfImage = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['SizeOfImage'] -Type UInt32 FullDllName = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['FullDllName'] -Type UnicodeString ModuleName = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['BaseDllName'] -Type UnicodeString Flags = $ReadableFlags LoadReason = $LoadReasonName ReferenceCount = Read-MemoryValue -LinkPtr $NextLinkPtr -Offset $PebOffsetMap['ReferenceCount'] -Type UInt16 LoadAsData = $false } <# [LIST_ENTRY], 0x? -> [LIST_ENTRY] ???OrderLinks *Flink --> Next [LIST_ENTRY] -> [LDR_DATA_TABLE_ENTRY] So, we Read Item Pointer for next [LIST_ENTRY], AKA [LDR_DATA_TABLE_ENTRY] but, again, not BaseAddress of [LDR_DATA_TABLE_ENTRY], it depend on user Req. [LDR_DATA_TABLE_ENTRY] --> 0x0 -> [LIST_ENTRY], [LIST_ENTRY], [LIST_ENTRY], [Actuall Data] #> $NextLinkPtr = [Marshal]::ReadIntPtr($NextLinkPtr) } while ($NextLinkPtr -ne $ListHeadPtr) } catch { Write-Warning "Failed to enumerate modules. Error: $_" } return $modules } <# LdrLoadDll Data Convert Helper ------------------------------ >>>>>>>>>>>>>>>>>>>>>>>>>>> API-SPY --> SLUI 0x2a ERROR >>>>>>>>>>>>>>>>>>>>>>>>>>> 0x00000010 - LOAD_IGNORE_CODE_AUTHZ_LEVEL LdrLoadDll(1,[Ref]0, 0x000000cae83fda50, 0x000000cae83fda98) 0x00000008 - LOAD_WITH_ALTERED_SEARCH_PATH LdrLoadDll(9,[Ref]0, 0x000000cae7fee930, 0x000000cae7fee978) 0x00000800 - LOAD_LIBRARY_SEARCH_SYSTEM32 LdrLoadDll(2049, [Ref]0, 0x000000cae83fed00, 0x000000cae83fed48 ) 0x00002000 -bor 0x00000008 - LOAD_LIBRARY_SAFE_CURRENT_DIRS & LOAD_WITH_ALTERED_SEARCH_PATH LdrLoadDll(8201, [Ref]0, 0x000000cae85fcbb0, 0x000000cae85fcbf8 ) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> HMODULE __stdcall LoadLibraryExW(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> if ((dwFlags & 0x62) == 0) { local_res8[0] = 0; if ((dwFlags & 1) != 0) { local_res8[0] = 2; uVar3 = 2; } if ((char)dwFlags < '\0') { uVar3 = uVar3 | 0x800000; local_res8[0] = uVar3; } if ((dwFlags & 4) != 0) { uVar3 = uVar3 | 4; local_res8[0] = uVar3; } if ((dwFlags >> 0xf & 1) != 0) { local_res8[0] = uVar3 | 0x80000000; } iVar1 = LdrLoadDll(dwFlags & 0x7f08 | 1,local_res8,local_28,&local_res20); } >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> void LdrLoadDll(ulonglong param_1,uint *param_2,uint *param_3,undefined8 *param_4) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> if (param_2 == (uint *)0x0) { uVar4 = 0; } else { uVar4 = (*param_2 & 4) * 2; uVar3 = uVar4 | 0x40; if ((*param_2 & 2) == 0) { uVar3 = uVar4; } uVar4 = uVar3 | 0x80; if ((*param_2 & 0x800000) == 0) { uVar4 = uVar3; } uVar3 = uVar4 | 0x100; if ((*param_2 & 0x1000) == 0) { uVar3 = uVar4; } uVar4 = uVar3 | 0x400000; if (-1 < (int)*param_2) { uVar4 = uVar3; } } SearchPath ---------- (0x00000001 -band 0x7f08) -bor 1 // DONT_RESOLVE_DLL_REFERENCES (0x00000010 -band 0x7f08) -bor 1 // LOAD_IGNORE_CODE_AUTHZ_LEVEL (0x00000200 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_APPLICATION_DIR (0x00001000 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_DEFAULT_DIRS (0x00000100 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR (0x00000800 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_SYSTEM32 (0x00000400 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SEARCH_USER_DIRS (0x00000008 -band 0x7f08) -bor 1 // LOAD_WITH_ALTERED_SEARCH_PATH (0x00000080 -band 0x7f08) -bor 1 // LOAD_LIBRARY_REQUIRE_SIGNED_TARGET (0x00002000 -band 0x7f08) -bor 1 // LOAD_LIBRARY_SAFE_CURRENT_DIRS This --> will auto bypass to LoadLibraryEx? 0x00000002, LOAD_LIBRARY_AS_DATAFILE 0x00000040, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 0x00000020, LOAD_LIBRARY_AS_IMAGE_RESOURCE DllCharacteristics ------------------ Auto deteced by function. According to dwFlag value, who provide by user. #> enum LOAD_LIBRARY { NO_DLL_REF = 0x00000001 IGNORE_AUTHZ = 0x00000010 AS_DATAFILE = 0x00000002 AS_DATAFILE_EXCL = 0x00000040 AS_IMAGE_RES = 0x00000020 SEARCH_APP = 0x00000200 SEARCH_DEFAULT = 0x00001000 SEARCH_DLL_LOAD = 0x00000100 SEARCH_SYS32 = 0x00000800 SEARCH_USER = 0x00000400 ALTERED_SEARCH = 0x00000008 REQ_SIGNED = 0x00000080 SAFE_CURRENT = 0x00002000 } function Ldr-LoadDll { param ( [Parameter(Mandatory = $true)] [LOAD_LIBRARY]$dwFlags, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$dll, [Parameter(Mandatory = $false)] [switch]$Log, [Parameter(Mandatory = $false)] [switch]$ForceNew ) $HResults = [IntPtr]::Zero $FlagsPtr = [IntPtr]::Zero $stringPtr = [IntPtr]::Zero if (!$dwFlags -or $dwFlags.value__ -eq $null) { throw "can't access dwFlags value" } if ($dwFlags.value__ -lt 0) { throw "dwFlags Can't be less than 0" } if (-not $global:LoadedModules) { $global:LoadedModules = Get-LoadedModules -SortType Memory | Select-Object BaseAddress, ModuleName, LoadAsData } # $ForceNew == $Log ==> $false $ReUseHandle = !$Log -and !$ForceNew # Equivalent to: if ((dwFlags & 0x62) == 0) # AS_DATAFILE, AS_DATAFILE_EXCLUSIVE, AS_IMAGE_RESOURCE $IsDataLoad = ($dwFlags.value__ -band 0x62) -ne 0 if ($ReUseHandle) { $dllObjList = $global:LoadedModules | Where-Object { $_.ModuleName -ieq $dll } if ($dllObjList) { if ($IsDataLoad) { $dllObj = $dllObjList | Where-Object { $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress } else { $dllObj = $dllObjList | Where-Object { -not $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress } if ($dllObj) { Write-Warning "Returning reusable module object for $dll" return $dllObj } } } try { $FlagsPtr = New-IntPtr -Size 4 if ($IsDataLoad) { # Data Load -> Begin if ($Log) { Write-host "Flags = $($dwFlags.value__)" Write-host "SearchPath = NULL" Write-host "Function = LoadLibraryExW" return } Write-Warning 'Logging only --> LoadLibraryExW' $HResults = $Global:kernel32::LoadLibraryExW( $dll, [IntPtr]::Zero, $dwFlags.value__) if ($HResults -ne [IntPtr]::Zero) { $dllInfo = [PSCustomObject]@{ BaseAddress = $HResults ModuleName = $dll LoadAsData = $true } $global:LoadedModules += $dllInfo } return $HResults # Data Load -> End } else { # Normal Load -> Begin $uVar3 = 0 if (($dwFlags.value__ -band 1) -ne 0) { $uVar3 = 2 } if (($dwFlags.value__ -band 0x80) -ne 0) { $uVar3 = $uVar3 -bor 0x800000 } if (($dwFlags.value__ -band 4) -ne 0) { $uVar3 = $uVar3 -bor 4 } if ((($dwFlags.value__ -shr 15) -band 1) -ne 0) { $uVar3 = $uVar3 -bor 0x80000000 } $Flags = ($dwFlags.value__ -band 0x7f08) -bor 1 $DllCharacteristics = $uVar3 if ($Log) { Write-host "Flags = $Flags" Write-host "SearchPath = $DllCharacteristics" Write-host "Function = LdrLoadDll" return } # parameter [1] Filepath $FilePath = [IntPtr]::new($Flags) # parameter [2] DllCharacteristics [Marshal]::WriteInt32( $FlagsPtr, $DllCharacteristics) # parameter [3] UnicodeString $stringPtr = Init-NativeString -Value $dll -Encoding Unicode # Out Results Write-Warning 'Logging only --> LdrLoadDll' $null = $Global:ntdll::LdrLoadDll( $FilePath, # Flags $FlagsPtr, # NULL / [REF]Long $stringPtr, # [REF]UnicodeString [ref]$HResults # [Out]Handle ) if ($HResults -ne [IntPtr]::Zero) { $dllInfo = [PSCustomObject]@{ BaseAddress = $HResults ModuleName = $dll LoadAsData = $false } $global:LoadedModules += $dllInfo } return $HResults # Normal Load -> End } } catch { } finally { if ($FlagsPtr -ne $null -and $FlagsPtr -ne [IntPtr]::Zero) { New-IntPtr -hHandle $FlagsPtr -Release } if ($unicodeStringPtr -ne $null -and $unicodeStringPtr -ne [IntPtr]::Zero) { Free-NativeString -StringPtr $stringPtr } } return $HResults } <# .SYNOPSIS Manages native UNICODE_STRING memory and content for P/Invoke. .DESCRIPTION This function allows for the creation of new UNICODE_STRING structures, in-place updating of existing ones, and safe release of all associated unmanaged memory (both the structure and its internal string buffer) using low-level NTDLL APIs. .USE Manage-UnicodeString -Value '?' Manage-UnicodeString -Value '?' -UnicodeStringPtr ? Manage-UnicodeString -UnicodeStringPtr ? -Release #> function Manage-UnicodeString { param ( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $Value, [Parameter(Mandatory = $false)] [IntPtr] $UnicodeStringPtr = [IntPtr]::Zero, [switch] $Release ) # Check if the pointer is valid (non-zero) $isValidPtr = $UnicodeStringPtr -ne [IntPtr]::Zero # Case 1: Value only ג†’ allocate and create a new string (if pointer is zero) if ($Value -and -not $isValidPtr -and -not $Release) { $unicodeStringPtr = New-IntPtr -Size 16 $returnValue = $Global:ntdll::RtlCreateUnicodeString($unicodeStringPtr, $Value) # Check if the lowest byte is 1 (indicating success as per the C code's CONCAT71) if (($returnValue -band 0xFF) -ne 1) { throw "Failed to create Unicode string for '$Value'. NTSTATUS return value: 0x$hexReturnValue" } return $unicodeStringPtr } # Case 2: Value + existing pointer ג†’ reuse the pointer (if pointer is valid) elseif ($Value -and $isValidPtr -and -not $Release) { $null = $Global:ntdll::RtlFreeUnicodeString($unicodeStringPtr) $Global:ntdll::RtlZeroMemory($unicodeStringPtr, [UIntPtr]::new(16)) $returnValue = $Global:ntdll::RtlCreateUnicodeString($unicodeStringPtr, $Value) # Check if the lowest byte is 1 (indicating success as per the C code's CONCAT71) if (($returnValue -band 0xFF) -ne 1) { throw "Failed to create Unicode string for '$Value'. NTSTATUS return value: 0x$hexReturnValue" } return } # Case 3: Pointer + Release ג†’ cleanup the string (if pointer is valid) elseif (-not $Value -and $isValidPtr -and $Release) { $null = $Global:ntdll::RtlFreeUnicodeString($unicodeStringPtr) New-IntPtr -hHandle $unicodeStringPtr -Release return } # Invalid combinations (no valid operation matched) else { throw "Invalid parameter combination. You must provide one of the following: 1) -Value to create a new string, 2) -Value and -unicodeStringPtr to reuse the pointer, 3) -unicodeStringPtr and -Release to free the string." } } # work - job Here. $Global:ntdll = Init-NTDLL $Global:kernel32 = Init-KERNEL32 $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb() $global:LoadedModules = Get-LoadedModules -SortType Memory | Select-Object BaseAddress, ModuleName, LoadAsData
c style warper, take whole interface / structs, and cast them or like i see abbodi1406 did, build dummy function etc etc this one not, it use delegate, and fucntion index, and pointer's, you don't write any code and you don't have consider count from 0 + 3 Function + 4 because .. nope.! and i also saved you from write --> no params, its ok. still work. No need to think - dispatch or not ?
Another Example. show windows Install key UI * From CLIC.H Corp' https://github.com/asdcorp/clic/blob/principalis/clic.c Code: GUID guidEditionUpgradeManager = { 0x17CCA47D, 0xDAE5, 0x4E4A, {0xAC, 0x42, 0xCC, 0x54, 0xE2, 0x8F, 0x33, 0x4A} }; GUID guidIEditionUpgradeManager = { 0xF2DCB80D, 0x0670, 0x44BC, {0x90, 0x02, 0xCD, 0x18, 0x68, 0x87, 0x30, 0xAF} }; typedef struct _tagIEditionUpgradeManagerVtbl { HRESULT (WINAPI *QueryInterface)( VOID *pThis, const IID *riid, void **ppvObject ); ULONG (WINAPI *AddRef)( VOID *pThis ); ULONG (WINAPI *Release)( VOID *pThis ); HRESULT (WINAPI *InitializeWindow)( VOID *pThis, OLE_HANDLE parentWindow ); HRESULT (WINAPI *UpdateOperatingSystem)( VOID *pThis, LPWSTR lpwszContentId, DWORD dwAsync ); HRESULT (WINAPI *ShowProductKeyUI)( VOID *pThis, DWORD dwAsync ); HRESULT (WINAPI *UpdateOperatingSystemWithParams)( VOID *pThis, LPWSTR lpwszPKey, BOOL bReboot, BOOL bShowUI, BOOL bShowPrompt, BOOL bCommandLine, DWORD dwAsync ); HRESULT (WINAPI *AcquireModernLicenseForWindows)( VOID *pThis, DWORD dwAsync, DWORD *pdwReturnCode ); HRESULT (WINAPI *AcquireModernLicenseWithPreviousId)( VOID *pThis, LPWSTR lpwszPreviousId, DWORD *pdwReturnCode ); } IEditionUpgradeManagerVtbl; End result's Code: $interfaceSpec = [PSCustomObject]@{ Index = 3 Return = "int" Params = "uint dwAsync" Name = "ShowProductKeyUI" CLSID = "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" IID = "F2DCB80D-0670-44BC-9002-CD18688730AF" } try { $comObject = Initialize-ComObject -InterfaceSpec $interfaceSpec $returnCode = 0 $hr = $comObject | Invoke-ComObject -Params ( @(0)) } catch { Write-Warning "An error occurred: $($_.Exception.Message)" return $false } finally { $comObject | Release-ComObject }
i got a petent Code: using namespace System using namespace System.Collections.Generic using namespace System.Drawing using namespace System.IO using namespace System.IO.Compression using namespace System.Management.Automation using namespace System.Net using namespace System.Diagnostics using namespace System.Numerics using namespace System.Reflection using namespace System.Reflection.Emit using namespace System.Runtime.InteropServices using namespace System.Security.AccessControl using namespace System.Security.Principal using namespace System.ServiceProcess using namespace System.Text using namespace System.Text.RegularExpressions using namespace System.Threading using namespace System.Windows.Forms
i got a rule, use Ps1 - only ps1 code, until i arrived to com, it almost impossible, dont have delegate in PS1. so, i have to break my rule this time so, basiclyl most of code does manages .net, only 2 call's for delegate, are not. 90% it good than nothing. and, ps1 and com, making me hard life, hard to cast object, fail, or build dummy class's so, no other option's left. for some classes' its does let you access for few internalse, like .. DCB00C01-570F-4A9B-8D69-199FDBA5723B it expose them or free. include some function. $comObj.ComObject.IsConnected $comObj.ComObject.IsConnectedToInternet
Update code, add support for Un manged call & Com object's i put enough Com & Api invoke sample's for you to use delegate option is great flexible option, instead Import Dll, using Add Type Code: Clear-host write-host $Global:ntdll = Init-NTDLL $Global:kernel32 = Init-KERNEL32 $Global:PebPtr = $Global:ntdll::RtlGetCurrentPeb() Use-ComInterface ` -CLSID "17CCA47D-DAE5-4E4A-AC42-CC54E28F334A" ` -IID "f2dcb80d-0670-44bc-9002-cd18688730af" ` -Index 3 ` -Name "ShowProductKeyUI" ` -Return "void" Invoke-UnmanagedMethod ` -Dll "kernel32.dll" ` -Function "Beep" ` -Return "bool" ` -Params "uint dwFreq, uint dwDuration" ` -Values @(750, 300) # 750 Hz beep for 300 ms [intPtr]$ppEnumNetwork = [intPtr]::Zero Use-ComInterface ` -CLSID "DCB00C01-570F-4A9B-8D69-199FDBA5723B" ` -IID "DCB00000-570F-4A9B-8D69-199FDBA5723B" ` -Index 1 ` -Name "GetNetwork" ` -Return "uint" ` -Params 'system.UINT32 Flags, out INTPTR ppEnumNetwork' ` -Values @(1, [ref]$ppEnumNetwork) if ($ppEnumNetwork -ne [IntPtr]::Zero) { $networkList = $ppEnumNetwork | Receive-ComObject foreach ($network in $networkList) { "Name: $($network.GetName()), IsConnected: $($network.IsConnected())" } $networkList | Release-ComObject } $buffer = New-IntPtr -Size 256 $result = Invoke-UnmanagedMethod ` -Dll "kernel32.dll" ` -Function "GetComputerNameA" ` -Return "bool" ` -Params "IntPtr lpBuffer, ref uint lpnSize" ` -Values @($buffer, [ref]256) if ($result) { $computerName = [Marshal]::PtrToStringAnsi($buffer) Write-Host "Computer Name: $computerName" } else { Write-Host "Failed to get computer name" } New-IntPtr -hHandle $buffer -Release # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677 # SYSTEM_PROCESS_INFORMATION structure # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/process.htm # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm?tx=61&ts=0,1677 # SYSTEM_BASIC_INFORMATION structure # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_basic_information.htm # Step 1: Get required buffer size $ReturnLength = 0 $dllResult = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(0, [IntPtr]::Zero, 0, [ref]$ReturnLength) # Allocate buffer (add some extra room just in case) $infoBuffer = New-IntPtr -Size $ReturnLength # Step 2: Actual call with allocated buffer $result = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(0, $infoBuffer, $ReturnLength, [ref]$ReturnLength) if ($result -ne 0) { Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)" Parse-ErrorMessage -MessageId $result New-IntPtr -hHandle $infoBuffer -Release return } # Parse values from the structure $sysBasicInfo = [PSCustomObject]@{ PageSize = [Marshal]::ReadInt32($infoBuffer, 0x08) NumberOfPhysicalPages = [Marshal]::ReadInt32($infoBuffer, 0x0C) LowestPhysicalPageNumber = [Marshal]::ReadInt32($infoBuffer, 0x10) HighestPhysicalPageNumber = [Marshal]::ReadInt32($infoBuffer, 0x14) AllocationGranularity = [Marshal]::ReadInt32($infoBuffer, 0x18) MinimumUserModeAddress = [Marshal]::ReadIntPtr($infoBuffer, 0x20) MaximumUserModeAddress = [Marshal]::ReadIntPtr($infoBuffer, 0x28) ActiveProcessorsAffinityMask = [Marshal]::ReadIntPtr($infoBuffer, 0x30) NumberOfProcessors = [Marshal]::ReadByte($infoBuffer, 0x38) } # Step 1: Get required buffer size $ReturnLength = 0 $dllResult = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(5, [IntPtr]::Zero, 0, [ref]$ReturnLength) # Allocate buffer (add some extra room just in case) $ReturnLength += 200 $procBuffer = New-IntPtr -Size $ReturnLength # Step 2: Actual call with allocated buffer $result = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "ZwQuerySystemInformation" ` -Return "uint32" ` -Params "int SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, ref uint ReturnLength" ` -Values @(5, $procBuffer, $ReturnLength, [ref]$ReturnLength) if ($result -ne 0) { Write-Host "NtQuerySystemInformation failed: 0x$("{0:X}" -f $result)" Parse-ErrorMessage -MessageId $result New-IntPtr -hHandle $procBuffer -Release return } $offset = 0 $processList = @() while ($true) { try { $entryPtr = [IntPtr]::Add($procBuffer, $offset) $nextOffset = [Marshal]::ReadInt32($entryPtr, 0x00) $namePtr = [Marshal]::ReadIntPtr($entryPtr, 0x38 + [IntPtr]::Size) $processName = if ($namePtr -ne [IntPtr]::Zero) { [Marshal]::PtrToStringUni($namePtr) } else { "[System]" } $procObj = [PSCustomObject]@{ ProcessId = [Marshal]::ReadIntPtr($entryPtr, 0x50) ProcessName = $processName NumberOfThreads = [Marshal]::ReadInt32($entryPtr, 0x04) } $processList += $procObj if ($nextOffset -eq 0) { break } $offset += $nextOffset } catch { Write-Host "Parsing error at offset $offset. Stopping." break } } New-IntPtr -hHandle $infoBuffer -Release New-IntPtr -hHandle $procBuffer -Release $sysBasicInfo | Format-List $processList | Sort-Object ProcessName | Format-Table ProcessId, ProcessName, NumberOfThreads -AutoSize
Darki upload new version, GetProcAddress replaced with LdrGetProcedureAddressForCaller Code: LdrGetProcedureAddressForCaller @{ Name = "LdrGetProcedureAddressForCaller" Dll = "ntdll.dll" ReturnType = [Int64] Parameters = [Type[]]@( [IntPtr], # [HMODULE] Module handle pointer [IntPtr], # [PSTRING] Pointer to STRING struct (pass IntPtr directly, NOT [ref]) [int64], # [ULONG] Ordinal / Flags (usually 0) [IntPtr].MakeByRefType(), # [PVOID*] Out pointer to procedure address (pass [ref]) [byte], # [Flags] 0 or 1 (usually 0) [IntPtr] # [Caller] Nullable caller address, pass [IntPtr]::Zero if none ) } And, also, new helper's like Init-NativeString, Parse-NativeString, Free-NativeString, which help to build _STRING & _UNICODE_STRING using [Marshal] Code: https://learn.microsoft.com/en-us/windows/win32/api/Winternl/ns-winternl-string typedef struct _STRING { USHORT Length; USHORT MaximumLength; PCHAR Buffer; } STRING; https://learn.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING;
Update Code [again] yehhh, Ldr-Load will return the Current loaded module handle *** if possible Code: if (-not $global:LoadedModules) { $global:LoadedModules = Get-LoadedModules -SortType Memory | Select-Object BaseAddress, ModuleName, LoadAsData } # Equivalent to: if ((dwFlags & 0x62) == 0) $IsNormalLoad = ($dwFlags.value__ -band 0x62) -eq 0 if (!$ForceNew -and !$Log) { $dllObjList = $global:LoadedModules | Where-Object { $_.ModuleName -ieq $dll } if ($dllObjList) { if ($IsNormalLoad) { $dllObj = $dllObjList | Where-Object { -not $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress } else { $dllObj = $dllObjList | Where-Object { $_.LoadAsData } | Select-Object -Last 1 -ExpandProperty BaseAddress } if ($dllObj) { Write-Warning "Returning reusable module object for $dll" return $dllObj } } } and, here, just call ldr-Load, who will handle everything Code: function Initialize-ApiObject { param ( [Parameter(Mandatory=$true, ValueFromPipeline)] [PSCustomObject]$ApiSpec ) $hModule = [IntPtr]::Zero $BaseAddress = Ldr-LoadDll -dwFlags SEARCH_SYS32 -dll $ApiSpec.Dll
So, i've upload new version. Base Version + Extended version Extended version, will allow you to do this Code: Write-Host $length = [Uint32]1 $Ptr = [IntPtr]::Zero $lastErr = Invoke-UnmanagedMethod ` -Dll "ntdll.dll" ` -Function "NtEnumerateBootEntries" ` -Return "Int64" ` -Params "IntPtr Ptr, ref uint length" ` -Values @($Ptr, [ref]$length) $ntStatus = Parse-ErrorMessage ` -MessageId $lastErr Write-Host "NtEnumerateBootEntries return Status -> $ntStatus" Extended version, Support new feature to expend TokenPrivileges for current process, so, you can load NTDLL Api, that need extra Privileges Added this section in this version Code: # Get Minimal Privileges To Load Some NtDll function $ProcId = [Process]::GetCurrentProcess() |select -ExpandProperty ID Adjust-TokenPrivileges ` -ProcessId $ProcId ` -Privilege @("SeDebugPrivilege","SeImpersonatePrivilege","SeIncreaseQuotaPrivilege","SeAssignPrimaryTokenPrivilege", "SeSystemEnvironmentPrivilege") ` -Log -SysCall i did able to read some info of boot entiries, but didnt get same result *as* bcdedit /enum but it was a good experince what it return ? https://github.com/winsiderss/phnt/blob/master/ntexapi.h https://processhacker.sourceforge.io/doc/ntexapi_8h_source.html Code: /** * The NtEnumerateBootEntries routine retrieves information about all boot entries in the system boot configuration. * * @param Buffer A pointer to a buffer that receives the boot entries information. * @param BufferLength A pointer to a variable that specifies the size of the buffer. On return, it contains the size of the data returned. * @return NTSTATUS Successful or errant status. */ NTSYSCALLAPI NTSTATUS NTAPI NtEnumerateBootEntries( _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer, _Inout_ PULONG BufferLength ); // private typedef struct _BOOT_ENTRY_LIST { ULONG NextEntryOffset; BOOT_ENTRY BootEntry; } BOOT_ENTRY_LIST, *PBOOT_ENTRY_LIST; typedef struct _BOOT_ENTRY { ULONG Version; ULONG Length; ULONG Id; ULONG Attributes; ULONG FriendlyNameOffset; ULONG BootFilePathOffset; ULONG OsOptionsLength; _Field_size_bytes_(OsOptionsLength) UCHAR OsOptions[1]; } BOOT_ENTRY, *PBOOT_ENTRY; // private typedef struct _FILE_PATH { ULONG Version; ULONG Length; ULONG Type; _Field_size_bytes_(Length) UCHAR FilePath[1]; } FILE_PATH, *PFILE_PATH;
Did upload v4 version of base & extended Some load improvements It’s make api calls very easy Better than reflections or add type I use to do dynamic loading in c$ But didn’t see such in ps1. maybe ms should make one also update new-intptr To allow set first 4 bytes with value Of size or different Value Which is very useful Some structures req to write int size in 0x0 So very useful and extended version Add real use sample for privileges must api calls Read boot entires, which is good option To demonstrate run api that req more than usually privileges Did happen to read something not all data It’s for demonstration only
Update script, New IntPtr support extra Option Code: function New-IntPtr { param( [Parameter(Mandatory=$false)] [int]$Size, [Parameter(Mandatory=$false)] [int]$InitialValue = 0, [Parameter(Mandatory=$false)] [IntPtr]$hHandle, [switch]$MakeRefType, [switch]$WriteSizeAtZero, [switch]$Release ) Also add IsValid-IntPtr & Free-IntPtr, Support: Code: "HGlobal", "Handle", "NtHandle", "ServiceHandle", "Heap", "Auto" Also, add Function To run Process As System, thought it can be useful. ~~~~~~~~~~~~~ MakeRefType, it seems Managed code can't handle well ~IntPtr->MakeRef, so, i create this option. real life example, that it actually matter. otherwise, createProcess api fail ! Spend lot of time, Manual & Advice with MR GPT, why it cause trouble, and found out, that was the rare case, IntPtr->Ref, not work here! so, you can't trust, that it will work every time.! Code: # Allocate unmanaged memory for the handle $handlePtr = New-IntPtr -hHandle $tHandle -MakeRefType $ret = $Global:kernel32::UpdateProcThreadAttribute( $lpAttributeList, 0, 0x00020000, $handlePtr, [IntPtr]::Size, 0, 0)