en-US/about_PSDetour.help.txt

TOPIC
    about_psdetour
 
SHORT DESCRIPTION
    The PSDetour module is a PowerShell module that can be used to hook C
    functions in either the current process or other processes on the host. It
    is designed to work with PowerShell 7.2 and newer on Windows using the
    Detours library from Microsoft to do the actual API hooks.
 
LONG DESCRIPTION
    A hook is used to replace a C function in a dll that the process may call so
    that the hook can modify input/output data or just log certain details about
    the call. As well as hooking functions in the current process, the module
    also offers a way to bootstrap the current PowerShell version into another
    process so it can setup hooks in that process. For more information around
    using PSDetour sessions in other processes, see
    about_PSDetourSessions
    .
    As hooks traverse the boundary between managed (dotnet) and unmanaged (C)
    code, the way that arguments are transferred across these boundaries is very
    important. Using bad type arguments can cause the whole process to crash.
    For more information around type managemenet in hooks, see
    about_PSDetourMarshalling
    .
 
HOOK DETAILS
    A hook in PSDetour is define by a PowerShell scriptblock. When the function
    is hooked, the PowerShell scriptblock will be invoked instead. A hook can
    either be run in the same Runspace as where PowerShell is running, or a
    brand new Runspace is created. The existing Runspace is used only if:
    * The hooked function is running on the same thread as PowerShell itself, and
    * No `$using:...` variables were referenced in the hook
    If the detoured function was called from another thread, or `$using:...` was
    used in the action, then the hook is run in a brand new Runspace. This
    Runspace won't have access to the parents scope, so any outside information
    must be passed in with the `$using:...` syntax of through `$this.State` and
    a state object when starting the detour.
    The `$using:` prefix can be used to inject variables from the outside, just
    like it can be done with `ForEach-Object -Parallel`. It is up to the autor
    to define what the scriptblock can do but keep in mind doing nothing could
    lead to fatal errors as the callers might expect things to happen during
    that call. The key components of a scriptblock that is used a hook are:
    * `[OutputType]` - to denote the return type of the function to hook
    * `param(...)` - all the arguments/parameters and their types of the
    function to hook
    * `$this` - a special variable that contains metadata about the hook,
    currently only the `Invoke` method, `State` property, and `DetouredModules`
    property are implemented.
    * `$using:...` - injects the variable named by `...` into the running action hook
    An example of a hook that just logs the input parameters and return values
    would look like:
 
    $Hooks = [System.Collections.Generic.List[string]]::new()
     
    # The DllName represents the DLL where the function is defined. The MethodName
    # is the name of the function/symbol in that DLL to hook.
    $hook = New-PSDetourHook -DllName Kernel32 -MethodName OpenProcess -Action {
        # [OutputType] defines the return type of the hook/
        [OutputType([IntPtr])]
        param (
            # Each parameter represents the function to hooks arguments and their
            # types. Ensure each parameter is typed to the proper type and [object]
            # is not used.
            [int]$Access,
            [bool]$InheritHandle,
            [int]$ProcessId
        )
     
        # $this.Invoke(...) invokes the actual method being hooked. It takes in
        # the same arguments and return value as the scriptblock definition.
        $res = $this.Invoke($Access, $InheritHandle, $ProcessId)
     
        # Wrapping $using:Hooks will inject the variable into the action and can
        # be used like any other object.
        ($using:Hooks).Add("OpenProcess($Access, $InheritHandle, $ProcessId) -> $res")
     
        # The last output value in the scriptblock is treated as the return value.
        # It should match the same `[OutputType]` defined or else `T = default;` is
        # used. Any preceding output is ignored and discarded.
        $res
    }
 
    The `New-PSDetourHook` only defines the hook metadata, a hook is not enabled
    until it is passed into `Start-PSDetour`.
    The state object is another method that can be used to access data inside
    the running hook. Unlike the `$using:...` syntax, or refering to a variable
    outside, the state object works when the action is run in the current
    runspace or when a new one is spawned. The state object can be any object,
    including a hashtable with custom key/values. It is specified under the
    `-State` parameter of `Start-PSDetour` and accessed in the running action
    through `$this.State` like so:
 
    $state = @{
        foo = 'bar'
    }
     
    $hook = New-PSDetourHook -DllName Kernel32 -MethodName OpenProcess -Action {
        [OutputType([IntPtr])]
        param (
            [int]$Access,
            [bool]$InheritHandle,
            [int]$ProcessId
        )
     
        Write-Host "OpenProcess ran with foo state $($this.State.foo)"
        $this.Invoke($Access, $InheritHandle, $ProcessId)
     
        # The state object can also be modified in the hook action.
        $this.State.foo = 'other'
    }
     
    Start-PSDetour -Hook $hook -State $state
     
    ...
     
    Stop-PSDetour
     
    # The foo key will be modified if the hook was run
    $state
 
    The `DetouredModules` property is a dictionary that contains the invoke
    context for all hooks currently loaded. This can be used so the hook can
    call another method that is currently hooked in the session. This call won't
    go through the hook but call the underlying method method that has been
    detoured. The `DetouredModules` dictionary contains the keys of all the
    `-DllName` dlls that have been detoured (without the extension). Each value
    of the dll keys is another dictionary where the string is the `-MethodName`
    names that have been detoured. The value of each method is the
    `InvokeContext` object with the `Invoke(...)` method that can be called.
    Here is an example that hooks both `OpenProcess` and `OpenProcessToken`
    where the `OpenProcess` hook calls the `OpenProcessToken` function
 
    $hooks = @(
        New-PSDetourHook -DllName Kernel32.dll -MethodName OpenProcess -Action {
            [OutputType([IntPtr])]
            param (
                [int]$Access,
                [bool]$InheritHandle,
                [int]$ProcessId
            )
     
            $processHandle = $this.Invoke($Access, $InheritHandle, $ProcessId)
     
            # A way to check if the method has been detoured
            if ($this.DetouredModules.Advapi32.ContainsKey('OpenProcessToken')) {
                $accessToken = [IntPtr]::Zero
                $openRes = $this.DetouredModules.Advapi32.OpenProcessToken.Invoke(
                    $processHandle,
                    [System.Security.Principal.TokenAccessLevels]::Query,
                    [ref]$accessToken)
                if ($openRes) {
                    Write-Host "Opened process $processId access token $accessToken"
                }
            }
     
            $processHandle
        }
     
        New-PSDetourHook -DllName Advapi32.dll -MethodName OpenProcessToken -Action {
            [OutputType([bool])]
            param (
                [IntPtr]$ProcessHandle,
                [int]$DesiredAccess,
                [PSDetour.Ref[IntPtr]]$TokenHandle
            )
     
            $this.Invoke($ProcessHandle, $DesiredAccess, $TokenHandle)
        }
    )
     
    $hooks | Start-PSDetour
     
    ...
     
    Stop-PSDetour