[PowerShell Script] Extracting All Key/Value Pairs from a Dictionary Object

Brad Linscott, a teammate of mine since the old times of SIE, came up with a very helpful idea: find an automated way to get the key/value pairs from a Dictionary object.

Moreover, Brad has a recipe that teaches how to get the key/value pair from a Dictionary using a manual approach, either using the SOS extension or the commands for native debugging. You can learn from his approach, it’s neat!

Thus, the script below implements Brad’s approach using native commands since it requires fewer steps than using SOS.

Note: Soon another PowerDbg version that doesn’t use messages sent to the WinDbg window is going to be released! It means more performance and no more problems when changing the window focus when running a script. Anyway, the current approach is still faster than you getting the information from the dump.

Not using PowerShell yet? Don’t think you should learn it? So, think about it: John Robbins, a very well known debugging guru has been using PowerShell.

Manual Approach Using SOS

1. Dump out the Dictionary object:

          !do 027a2d84 ß this is going to be the argument for our script.

          Name: System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]]

          MethodTable: 64d48208

          EEClass: 64b0acfc

          Size: 52(0x34) bytes

          GC Generation: 0

          (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

          Fields:

          MT Field Offset Type VT Attr Value Name

          64d52a88 40009af 4 System.Int32[] 0 instance 027a2f80 buckets

          00000000 40009b0 8 SZARRAY 0 instance 027a2fa8 entries

2. Dump the array that is the 'entries' field

          !da 027a2fa8

          Name: System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.String, mscorlib]][]

          MethodTable: 64d42008

          EEClass: 64b071a4

          Size: 124(0x7c) bytes

          Array: Rank 1, Number of elements 7, Type VALUETYPE

          Element Methodtable: 64d41fb4

          [0] 027a2fb0

          [1] 027a2fc0

          [2] 027a2fd0

          [3] 027a2fe0

          [4] 027a2ff0

          [5] 027a3000

          [6] 027a3010

3. Dump the ValueClass that is each element.

          !dumpvc 64d41fb4 027a2fb0

          Name: System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.String, mscorlib]]

          MethodTable 64d41fb4

          EEClass: 64b0ae40

          Size: 24(0x18) bytes

 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

          Fields:

          MT Field Offset Type VT Attr Value Name

          64d52b38 40009ba 8 System.Int32 1 instance 160595251 hashCode

          64d52b38 40009bb c System.Int32 1 instance -1 next

          64d5055c 40009bc 0 System.__Canon 0 instance 027a2a60 key

          64d5055c 40009bd 4 System.__Canon 0 instance 027a2aa8 value

4. Finally, dump out each key/value pair

          !do 027a2a60

          Name: System.String

          MethodTable: 64d508ec

          EEClass: 64b0d64c

          Size: 72(0x48) bytes

          GC Generation: 0

 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

          String: 1 really really long string

          Fields:

          MT Field Offset Type VT Attr Value Name

          64d52b38 4000096 4 System.Int32 1 instance 28 m_arrayLength

          64d52b38 4000097 8 System.Int32 1 instance 27 m_stringLength

          64d515cc 4000098 c System.Char 1 instance 31 m_firstChar

          64d508ec 4000099 10 System.String 0 shared static Empty

          >> Domain:Value 00535708:027a1198 <<

          64d5151c 400009a 14 System.Char[] 0 shared static WhitespaceChars

          >> Domain:Value 00535708:027a1944 <<

          0:003> !do 027a2aa8

          Name: System.String

          MethodTable: 64d508ec

          EEClass: 64b0d64c

          Size: 42(0x2a) bytes

          GC Generation: 0

 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

          String: Hello World1

          Fields:

          MT Field Offset Type VT Attr Value Name

          64d52b38 4000096 4 System.Int32 1 instance 13 m_arrayLength

          64d52b38 4000097 8 System.Int32 1 instance 12 m_stringLength

          64d515cc 4000098 c System.Char 1 instance 48 m_firstChar

          64d508ec 4000099 10 System.String 0 shared static Empty

          >> Domain:Value 00535708:027a1198 <<

          64d5151c 400009a 14 System.Char[] 0 shared static WhitespaceChars

          >> Domain:Value 00535708:027a1944 <<

5. Repeat steps 3 and 4 for each element in the ‘entries’ array.

Manual Approach Using Native Commands

If we take the offsets from the output of the SOS commands provided above, we can use Windbg’s dump command (‘d’) to accomplish the same feat in fewer steps.

du poi( poi(0x027a2d84+8) +8) +c

027a2a6c "1 really really long string"

du poi( poi(0x027a2d84+8) +c) +c

027a2ab4 "Hello World1"

Note that in order to get all the key/value pairs, you would have to do this for each element of the ‘entries’ array. And unless you investigated the data structure first (either with SOS or by using the ‘d’ command), there’s no way to know how many elements are in that array. For this Dictionary object, the number of elements is the 2nd DWORD after dumping out the ‘entries’ object. For this sample, the array contains 7 elements (although the last 3 are NULL and can be discarded).

dd poi(0x027a2d84+8) +4 L1

027a2fac 00000007

Screenshots:

 

Source code for PowerShellScriptDumpDict.ps1:

########################################################################################################

# Script: PowerDbgScriptDumpDict

#

# Parameters: [string] <$dictInstance>

# Address of the instance of System.Collections.Generic.Dictionary.

#

# Purpose: Dumps all the key/values from System.Collections.Generic.Dictionary.

#

# Changes History: 04/03/09 - Changed to run with PowerDbg v5.2 and newest.

#

# Brad Linscott

# Roberto Alexis Farah

# All my functions are provided "AS IS" with no warranties, and confer no rights.

########################################################################################################

param(

[string] $dictInstance = $(throw "Error! You must provide the address of the instance of System.Collections.Generic.Dictionary that you want to dump.")

)

set-psdebug -strict

$ErrorActionPreference = "stop"

trap {"Error message: $_"}

# Gets the output of the "dd" command return an array with the 5 DWORDs.

# Note: It stores just 5 addresses. This function is very specific for this script.

#

# Example:

# If the issued command was:

# dd 027a2d84 L4

#

# And the consequent output:

# 027a2d84 64d48208 027a2f80 027a2fa8 027a2df4

#

# We should get an array with five elements that correspond to the addresses above. Then we use this call:

#

# $array = Get-DD-L4()

function Get-DD-L4()

{

# Initialize array.

$outArray = @(0, 0, 0, 0, 0)

$i = 0 # Initialize index counter.

$stringReader = [System.IO.StringReader] $global:g_commandOutput

# Scan the symbols line by line.

while(($line = $stringReader.ReadLine()) -ne $null)

{

if($line -match "((?<address1>(^\w+))\s+(?<address2>(\w+))\s+(?<address3>(\w+))\s+(?<address4>(\w+))\s+(?<address5>(\w+)))")

{

$outArray[$i++] = $matches["address1"]

$outArray[$i++] = $matches["address2"]

$outArray[$i++] = $matches["address3"]

$outArray[$i++] = $matches["address4"]

$outArray[$i] = $matches["address5"]

}

}

return $outArray

}

write-Host "Getting all keys/value pairs for object instance " -foreground Green -background Black -nonewline

write-Host $dictInstance -foreground Red -background Black -nonewline

write-Host " ..." -foreground Green -background Black

Send-PowerDbgCommand("dd $dictInstance L4")

# Get the five DWORDs from the "dd" command output.

$array = Get-DD-L4

# Gets the 4th DWORD and dump it to get the number of elements.

$itemsAddress = $array[3]

Send-PowerDbgCommand("dd $itemsAddress L4")

# Get the five DWORDs from the "dd" command output.

$array = Get-DD-L4

# First element is the key, second element is the value and so on.

# The array below is NOT a multidimensional array.

$output = @($array[3], $array[4])

$startAddress = $array[0]

# Whenever we use "dd" we get one key/value pair. So, if we have 8 elements that means 4 keys and 4 values.

# If we use dd four times, shifting 15 bytes each time we'll get 4 lines that have all 8 elements.

for([System.Int32]$i = 0;; $i++)

{

# The math below is to get the next address. So our array will have five DWORDs for each loop interation, like:

# 027a2fa8 64d42008 00000007 027a2a60 027a2aa8

# 027a2fb8 09927d33 ffffffff 027a2ad4 027a2b1c

# 027a2fc8 496ed5f7 ffffffff 027a2b48 027a2b90

$factor = 16 * ($i + 1)

Send-PowerDbgCommand("dd $startAddress + 0n$factor L4")

# Get the five DWORDs from the "dd" command output.

$array = Get-DD-L4

# Exit loop when element is NULL.

if($array[3] -eq "00000000")

{

break # Exit loop. No more elements.

}

$output = $output + $array[3] # Save key.

$output = $output + $array[4] # Save value.

}

write-Host "Done!" -foreground Green -background Black

# At this point we have our array with keys and values, however, they're just addresses.

# Now let's display the output in string format. So, we have to loop through all key/value pairs.

for($i = 0; $i -lt ($output.Length / 2); $i++ )

{

write-Host "`n==========================================================" -foreground Green -background Black

write-Host "Key:" -foreground Green -background Black

# Remember, the array is unidimensional. The first element is the key, the second is the value and so on...

$index = $i * 2

$value = $output[$index] # This is to avoid problems when sending the command to the debugger.

# Gets the string that starts at offset 0xC.

Send-PowerDbgCommand "du $value + 0xc"

# Displays the string. We get the output from the command directly from the file.

# The file has always the command output :)

write-host $global:g_commandOutput -foreground Red -background Black

write-Host "`nValue:" -foreground Green -background Black

$value = $output[$index + 1] # This is to avoid problems when sending the command to the debugger.

# Gets the string that starts at offset 0xC.

Send-PowerDbgCommand "du $value + 0xc"

# Displays the string. We get the output from the command directly from the file.

# The file has always the command output :)

write-host $global:g_commandOutput -foreground Red -background Black

}

# Notifies user. The user is probably looking at the WinDbg window.

Send-PowerDbgComment "PowerDbgScriptDumpDict was executed! See the PowerShell window to get the results."