# 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())