-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathprint_cil_from_dn_file.py
149 lines (114 loc) · 4.95 KB
/
print_cil_from_dn_file.py
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
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Union, Optional
if TYPE_CHECKING:
from dnfile import dnPE
from dnfile.mdtable import MethodDefRow
import argparse
import dnfile
from dnfile.enums import MetadataTables
from dncil.cil.body import CilMethodBody
from dncil.cil.error import MethodBodyFormatError
from dncil.clr.token import Token, StringToken, InvalidToken
from dncil.cil.body.reader import CilMethodBodyReaderBase
# key token indexes to dotnet meta tables
DOTNET_META_TABLES_BY_INDEX = {table.value: table.name for table in MetadataTables}
class DnfileMethodBodyReader(CilMethodBodyReaderBase):
def __init__(self, pe: dnPE, row: MethodDefRow):
""" """
self.pe: dnPE = pe
self.offset: int = self.pe.get_offset_from_rva(row.Rva)
def read(self, n: int) -> bytes:
""" """
data: bytes = self.pe.get_data(self.pe.get_rva_from_offset(self.offset), n)
self.offset += n
return data
def tell(self) -> int:
""" """
return self.offset
def seek(self, offset: int) -> int:
""" """
self.offset = offset
return self.offset
def read_dotnet_user_string(pe: dnfile.dnPE, token: StringToken) -> Union[str, InvalidToken]:
"""read user string from #US stream"""
try:
user_string: Optional[dnfile.stream.UserString] = pe.net.user_strings.get(token.rid)
except UnicodeDecodeError as e:
return InvalidToken(token.value)
if user_string is None or (isinstance(user_string, bytes) or user_string.value is None):
return InvalidToken(token.value)
return user_string.value
def resolve_token(pe: dnPE, token: Token) -> Any:
""" """
if isinstance(token, StringToken):
return read_dotnet_user_string(pe, token)
table_name: str = DOTNET_META_TABLES_BY_INDEX.get(token.table, "")
if not table_name:
# table_index is not valid
return InvalidToken(token.value)
table: Any = getattr(pe.net.mdtables, table_name, None)
if table is None:
# table index is valid but table is not present
return InvalidToken(token.value)
try:
return table.rows[token.rid - 1]
except IndexError:
# table index is valid but row index is not valid
return InvalidToken(token.value)
def read_method_body(pe: dnPE, row: MethodDefRow) -> CilMethodBody:
""" """
return CilMethodBody(DnfileMethodBodyReader(pe, row))
def format_operand(pe: dnPE, operand: Any) -> str:
""" """
if isinstance(operand, Token):
operand = resolve_token(pe, operand)
if isinstance(operand, str):
return f'"{operand}"'
elif isinstance(operand, int):
return hex(operand)
elif isinstance(operand, list):
return f"[{', '.join(['({:04X})'.format(x) for x in operand])}]"
elif isinstance(operand, dnfile.mdtable.MemberRefRow):
if isinstance(operand.Class.row, (dnfile.mdtable.TypeRefRow,)):
return f"{str(operand.Class.row.TypeNamespace)}.{operand.Class.row.TypeName}::{operand.Name}"
elif isinstance(operand, dnfile.mdtable.TypeRefRow):
return f"{str(operand.TypeNamespace)}.{operand.TypeName}"
elif isinstance(operand, (dnfile.mdtable.FieldRow, dnfile.mdtable.MethodDefRow)):
return f"{operand.Name}"
elif operand is None:
return ""
return str(operand)
def main(args):
""" """
pe: dnPE = dnfile.dnPE(args.path)
for row in pe.net.mdtables.MethodDef:
if not row.ImplFlags.miIL or any((row.Flags.mdAbstract, row.Flags.mdPinvokeImpl)):
# skip methods that do not have a method body
continue
try:
body: CilMethodBody = read_method_body(pe, row)
except MethodBodyFormatError as e:
print(e)
continue
if not body.instructions:
continue
print(f"\nMethod: {row.Name}")
for insn in body.instructions:
print(
"{:04X}".format(insn.offset)
+ " "
+ f"{' '.join('{:02x}'.format(b) for b in insn.get_bytes()) : <20}"
+ f"{str(insn.opcode) : <15}"
+ format_operand(pe, insn.operand)
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog="Print IL from the managed methods of a .NET binary")
parser.add_argument("path", type=str, help="Full path to .NET binary")
main(parser.parse_args())