aboutsummaryrefslogtreecommitdiff
path: root/RE_Tools/Get-ILDisassembly.ps1
blob: 6cb9db8e092454fe27bbbec2a744695ed3614a36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
function Get-ILDisassembly
{
<#
.Synopsis

 PowerSploit Module - Get-ILDisassembly
 Author: Matthew Graeber (@mattifestation)
 License: BSD 3-Clause
 
.Description

 Get-ILDisassembly disassembles a raw MSIL byte array passed in from a MethodInfo object in a manner
 similar to that of Ildasm.
 
 The majority of this code was simply translated from C# (with permission) from a code example taken from:
 "C# 4.0 in a Nutshell", Copyright 2010, Joseph Albahari and Ben Albahari, pg. 728-733
 
.Parameter MethodInfo

 A MethodInfo object that describes the implementation of the method and contains the IL for the method.
 
.Example

 PS> [Int].GetMethod('Parse', [String]) | Get-ILDisassembly | Format-Table Position, Instruction, Operand -AutoSize
 
 Position Instruction Operand
 -------- ----------- -------
 IL_0000  ldarg.0
 IL_0001  ldc.i4.7
 IL_0002  call        System.Globalization.NumberFormatInfo.get_CurrentInfo
 IL_0007  call        System.Number.ParseInt32
 IL_000C  ret
 
 Description
 -----------
 Disassembles the System.Int32.Parse(String) method
 
.Example

 PS> $MethodInfo = [Array].GetMethod('BinarySearch', [Type[]]([Array], [Object]))
 PS> Get-ILDisassembly $MethodInfo | Format-Table Position, Instruction, Operand -AutoSize
 
 Position Instruction Operand
 -------- ----------- -------
 IL_0000  ldarg.0
 IL_0001  brtrue.s    IL_000E
 IL_0003  ldstr       'array'
 IL_0008  newobj      System.ArgumentNullException..ctor
 IL_000D  throw
 IL_000E  ldarg.0
 IL_000F  ldc.i4.0
 IL_0010  callvirt    System.Array.GetLowerBound
 IL_0015  stloc.0
 IL_0016  ldarg.0
 IL_0017  ldloc.0
 IL_0018  ldarg.0
 IL_0019  callvirt    System.Array.get_Length
 IL_001E  ldarg.1
 IL_001F  ldnull
 IL_0020  call        System.Array.BinarySearch
 IL_0025  ret
 
 Description
 -----------
 Disassembles the System.Array.BinarySearch(Array, Object) method
 
.Inputs
 System.Reflection.MethodInfo. The method description containing the raw IL bytecodes.
 
.Outputs
 System.Object. Returns a custom object consisting of a position, instruction, and opcode parameter.
 
.Link
 My blog: http://www.exploit-monday.com
 Original C# code: http://www.albahari.com/nutshell/cs4ch18.aspx
 OpCodes Class: http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.aspx
 ECMA-335 IL Standard: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf
#>

Param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Reflection.MethodInfo] $MethodInfo )

    if (!($MethodInfo.GetMethodBody())) {
        return
    }
    
    $IL = $MethodInfo.GetMethodBody().GetILAsByteArray()
    $MethodModule = $MethodInfo.DeclaringType.Module

    $OpCodeTable = @{}

    # Fill OpCodeTable with every OpCode so that it can be referenced by numeric byte value
    [System.Reflection.Emit.OpCodes].GetMembers() |
        ForEach-Object {
            try {
                $OpCode = $_.GetValue($null)
                $OpCodeTable[[Int16] $OpCode.Value] = $OpCode
            } catch {}
        }

    $Position = 0

    # Disassemble every instruction until the end of the IL bytecode array is reached
    while ($Position -lt $IL.Length) {

        # Get current instruction position
        $InstructionPostion = "IL_{0}" -f ($Position.ToString('X4'))

        if ($IL[$Position] -eq 0xFE) {
            # You are dealing with a two-byte opcode in this case
            $Op = $OpCodeTable[[Int16] ([BitConverter]::ToInt16($IL[($Position+1)..$Position], 0))]
            $Position++
        } else {
            # Otherwise, it's a one-byte opcode
            $Op = $OpCodeTable[[Int16] $IL[$Position]]
        }
        
        $Position++
        
        $Type = $Op.OperandType
        $Operand = $null
        
        if ($Type -eq 'InlineNone') {
            $OperandLength = 0
        } elseif (($Type -eq 'ShortInlineBrTarget') -or ($Type -eq 'ShortInlineI') -or ($Type -eq 'ShortInlineVar')) {
            $OperandLength = 1
            
            if ($Type -eq 'ShortInlineBrTarget') { # Short relative jump instruction
                # [SByte]::Parse was used because PowerShell doesn't handle signed bytes well
                $Target = $Position + ([SByte]::Parse($IL[$Position].ToString('X2'), 'AllowHexSpecifier')) + 1
                $Operand = "IL_{0}" -f ($Target.ToString('X4'))
            }
        } elseif ($Type -eq 'InlineVar') {
            $OperandLength = 2
        } elseif (($Type -eq 'InlineI8') -or (($Type -eq 'InlineR'))) {
            $OperandLength = 8
        } elseif ($Type -eq 'InlineSwitch') {
            # This is the only operand type with a variable number of operands
            $TargetCount = [BitConverter]::ToInt32($IL, $Position)
            $OperandLength = 4 * ($TargetCount + 1)
            $Targets = New-Object String[]($TargetCount)
            
            foreach ($i in 0..($TargetCount - 1)) {
                # Get all switch jump targets
                $Target = [BitConverter]::ToInt32($IL, ($Position + ($i + 1) * 4))
                $Targets[$i] = "IL_{0}" -f (($Position + $Target + $OperandLength).ToString('X4'))
            }
            
            $Operand = "({0})" -f ($Targets -join ',')
        } else {
            $OperandLength = 4
            $Operand = $null
            
            $OpInt = [BitConverter]::ToInt32($IL, $Position)
            
            if (($Type -eq 'InlineTok') -or ($Type -eq 'InlineMethod') -or ($Type -eq 'InlineField') -or ($Type -eq 'InlineType')) {
                # Resolve all operands with metadata tokens
                Write-Verbose "OpCode Metadata for member: $OpInt"
                try { $MemberInfo = $MethodModule.ResolveMember($OpInt) } catch { $Operand = $null }
                if (!$MemberInfo) { $Operand = $null }
                
                # Retrieve the actual name of the class and method
                if ($MemberInfo.ReflectedType) {
                    $Operand = "{0}.{1}" -f ($MemberInfo.ReflectedType.Fullname), ($MemberInfo.Name)
                } elseif ($MemberInfo -is [Type]) {
                    $Operand = $MemberInfo.GetType().FullName
                } else {
                    $Operand = $MemberInfo.Name
                }
            } elseif ($Type -eq 'InlineString') {
                # Retrieve the referenced string
                $Operand = "`'{0}`'" -f ($MethodModule.ResolveString($OpInt))
            } elseif ($Type -eq 'InlineBrTarget') {
                $Operand = "IL_{0}" -f (($Position + $OpInt + 4).ToString('X4'))
            } else {
                $Operand = $null
            }
        }
        
        if (($OperandLength -gt 0) -and ($OperandLength -ne 4) -and ($Type -ne 'InlineSwitch') -and ($Type -ne 'ShortInlineBrTarget')) {
            # Simply print the hex for all operands with immediate values
            $Operand = "0x{0}" -f (($IL[$Position..($Position+$OperandLength-1)] | ForEach-Object { $_.ToString('X2') }) -join '')
        }
        
        $Instruction = @{
            Position = $InstructionPostion
            Instruction = $Op.Name
            Operand = $Operand
        }
        
        # Return a custom object containing a position, instruction, and fully-qualified operand
        New-Object PSObject -Property $Instruction
        
        # Adjust the position in the opcode array accordingly
        $Position += $OperandLength
    }
}