mirror of
https://github.com/mandiant/dncil
synced 2026-06-08 15:41:41 +00:00
150 lines
5.0 KiB
Python
150 lines
5.0 KiB
Python
# 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())
|