Send-PowerDbgCommand
Commands such as Analyze-PowerDbgThreads
performs all the steps necessary to display the results of the command.
In some cases, however, you may want to send an arbitrary command to
the debugger and do your own post processing on the output of the
command. In these cases, the Send-PowerDbgCommand can be used. The Send-PowerDbgCommand
takes a string as an argument that includes the command you want to
execute in the debugger. For example, if we want to run the kb 200 command, we would run the following:
PS C:\Windows\System32> Send-PowerDbgCommand "kb 200"
The net result of the command execution is a file called POWERDBGOUTPUT.LOG
that contains the results. This file can then be used to do post
processing of the command result and display the data in the most
appropriate format. Because parsing the file can be cumbersome, PowerDbg
also comes with a set of cmdlets called Parse-PowerDbg* where * is replaced by the equivalent debugger command. These commands take the file contents produced by the Send-PowerDbgCommandPOWERDBG-PARSED.LOG) where the content is nicely formatted and suitable to be used with the Convert-PowerDbgCSVToHashTable cmdlet, which converts it into a hash table for easier scripting. For example, the native debugger command ~* kpn 1000 (displays stack traces of all threads in the process) can be executed using the PowerDbg in the following fashion: cmdlet and create a new file (
PS C:\Windows\System32> Send-PowerDbgCommand "~* kpn 1000"
PS C:\Windows\System32> Parse-PowerDbgK
PS C:\Windows\System32> $ht = @{}
PS C:\Windows\System32> $ht = Convert-PowerDbgCSVToHashTable
The first command sends the ~*kpn 1000 command to the debugger, which runs the command and stores the results in the POWERDBG-OUTPUT.LOG. The Parse-PowerDbgK cmdlet then parses the results stored in that file and creates a new file called POWERDBG-PARSED.LOG, which is subsequently used by the Convert-PowerDbgCSVToHashTable
cmdlet to return a hash table with the content of the parsed file.
After we have the hash table, we can use it to display the different
stack traces by using the write-host command:
PS C:\Windows\System32> write-host $ht["0"].Replace($global:g_frameDelimiter, "'n")
ChildEBP RetAddr
00 0017f060 778d8d94 ntdll!KiFastSystemCallRet
01 0017f064 778e9522 ntdll!NtRequestWaitReplyPort+0xc
02 0017f084 77507e05 ntdll!CsrClientCallServer+0xc2
03 0017f170 77507f35 KERNEL32!GetConsoleInput+0xd2
04 0017f190 001ca61c KERNEL32!ReadConsoleInputA+0x1a
Frame IP not in any known module. Following frames may be wrong.
05 0017f218 793e8f28 0x1ca61c
06 0017f280 793e8e33 mscorlib_ni+0x328f28
07 0017f2d0 79e7c6cc mscorlib_ni+0x328e33
08 0017f350 79e7c8e1 mscorwks!CallDescrWorkerWithHandler+0xa3
09 0017f490 79e7c783 mscorwks!MethodDesc::CallDescr+0x19c
0a 0017f4ac 79e7c90d mscorwks!MethodDesc::CallTargetWorker+0x1f
0b 0017f4c0 79eefb9e mscorwks!MethodDescCallSite::Call+0x18
0c 0017f624 79eef830 mscorwks!ClassLoader::RunMain+0x263
0d 0017f88c 79ef01da mscorwks!Assembly::ExecuteMainMethod+0xa6
0e 0017fd5c 79fb9793 mscorwks!SystemDomain::ExecuteMainMethod+0x43f
0f 0017fdac 79fb96df mscorwks!ExecuteEXE+0x59
10 0017fdf4 7900b1b3 mscorwks!_CorExeMain+0x15c
11 0017fe04 774a4911 mscoree!_CorExeMain+0x2c
12 0017fe10 778be4b6 KERNEL32!BaseThreadInitThunk+0xe
13 0017fe50 778be489 ntdll!__RtlUserThreadStart+0x23
14 0017fe68 00000000 ntdll!_RtlUserThreadStart+0x1b
PS C:\Windows\System32> write-host $ht["1"].Replace($global:g_frameDelimiter, "'n")
ChildEBP RetAddr
00 016ff95c 778d9244 ntdll!KiFastSystemCallRet
01 016ff960 774ac3e4 ntdll!ZwWaitForMultipleObjects+0xc
02 016ff9fc 774ac64e KERNEL32!WaitForMultipleObjectsEx+0x11d
03 016ffa18 79f4e8d8 KERNEL32!WaitForMultipleObjects+0x18
04 016ffa78 79f4e831 mscorwks!DebuggerRCThread::MainLoop+0xe9
05 016ffaa8 79f4e765 mscorwks!DebuggerRCThread::ThreadProc+0xe5
06 016ffad8 774a4911 mscorwks!DebuggerRCThread::ThreadProcStatic+0x9c
07 016ffae4 778be4b6 KERNEL32!BaseThreadInitThunk+0xe
08 016ffb24 778be489 ntdll!__RtlUserThreadStart+0x23
09 016ffb3c 00000000 ntdll!_RtlUserThreadStart+0x1b
Table 2 details the different Parse-PowerDbg* Cmdlets available.
Table 2. Parse-PowerDbg* cmdlets
CmdLet | Description |
---|
Parse-PowerDbgDT | Parses the output from the dt command. |
Parse-PowerDbgNAME2EE | Parses the output from the SOS name2ee command. |
Parse-PowerDbgDUMPMD | Parses the output from the SOS dumpmd command. |
Parse-PowerDbgDUMPMODULE | Parses the output from the SOS dumpmodule command. |
Parse-PowerDbgLMI | Parses the output from the lm1 command. |
Parse-PowerDbgVERTARGET | Parses the output from the vertarget command. |
Parse-PowerDbgRUNAWAY | Parses the output from the runaway command. |
Parse-PowerDbgK | Parses the output from the k command. |
Parse-PowerDbgSymbolsFromK | Extracts symbolic information from the k command. |
Parse-PowerDbgLM1M | Parses the output from the lm1m command. |
Parse-PowerDbgPRINTEXCEPTION | Extracts exception information. |
Parse-PowerDbgDD-L1 | Parses the output from the dd command. |
Parse-PowerDbgGCHANDLELEAKS | Parses the output from the SOS gchandleleaks command. |
Parse-PowerDbgDUMPOBJ | Parses the output from the SOS dumpobj command. |
Extending PowerDbg
In addition to the
plethora of cmdlets that the PowerDbg tool contains, it is also
extremely easy to extend by utilizing the existing cmdlet source.
In this case, the teb command can be used, which displays all the data contained within the teb. Below is an example of the teb command:
0:004> !teb
TEB at 7ffda000
ExceptionList: 04a3f9a4
StackBase: 04a40000
StackLimit: 04a3c000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffda000
EnvironmentPointer: 00000000
ClientId: 0000196c . 000018ac
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffdb000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0
We can use the Send-PowerDbg command to send the teb command to the bugger and get the preceding results in the POWERDBG-OUTPUT.LOG. The next step is to implement the Parse-PowerDbgHandle command, which takes the results in POWERDBG-OUTPUT.LOG and massages them into a format suitable for the Convert-PowerDbgCSVToHashTable cmdlet and stores that result in POWERDBG-PARSED. LOG. We could then use the Convert-PowerDbgCSVToHashTable
cmdlet to get the data into a hash table and manipulate it using other
scripts. The majority of the work required is in implementing the Parse-PowerDbgHandle cmdlet, which is shown in Listing 1.
Listing 1. Parse-PowerDbgHandle cmdlet
function Parse-PowerDbgTEB() { set-psdebug -strict $ErrorActionPreference = "stop" trap {"Error message: $_"}
# Extract output removing commands. $builder = New-Object System.Text.StringBuilder
# Title for the CSV fields. $builder = $builder.AppendLine("key,value")
foreach($line in $(get-content $global:g_fileCommandOutput)) { if($line.Contains("TEB at")) { } else { $fields=$line.Split(":"); if([String]::IsNullOrEmpty($fields[0])) { } if([String]::IsNullOrEmpty($fields[1])) { } else { $f1=$fields[0].Trim(); $f2=$fields[1].Trim(); $builder = $builder.AppendLine($f1 + $global:g_CSVDelimiter + $f2) } } }
# Send output to our default file. out-file -filepath $global:g_fileParsedOutput -inputobject "$builder" }
|
The function walks each
line in the raw data file and extracts each field’s name and value and
appends each key value pair to a string. After all fields have been
extracted, the string is written to the parsed output file.
Subsequently, we can use the Convert-PowerDbgCSVtoHashTable to convert the parsed output file to a hash table as shown:
PS C:\> $h={}
PS C:\> $h=Convert-PowerDbgCSVtoHashTable
To see the values in
the hash table, simply specify the hash table and PowerShell iterates
through all the key pair values and displays them:
PS C:\> $h
Name Value
---- -----
LastErrorValue 0
ExceptionList 04a3f9a4
FiberData 00001e00
StackLimit 04a3c000
StackBase 04a40000
Count Owned Locks 0
RpcHandle 00000000
Self 7ffda000
LastStatusValue 0
HardErrorMode 0
SubSystemTib 00000000
ClientId 0000196c . 000018ac
PEB Address 7ffdb000
ArbitraryUserPointer 00000000
Tls Storage 00000000
EnvironmentPointer 00000000
The
PowerDbg library is quite extensible and cmdlets can easily be created
to reduce the time spent debugging certain categories of bugs by doing
automatic analysis of debugger command output.