Vector35 / binaryninja-api

Public API, examples, documentation and issues for Binary Ninja
https://binary.ninja/
MIT License
838 stars 194 forks source link

Create TypeLibrary with ordinals for mfc* libraries #5216

Open cesaryuan opened 3 months ago

cesaryuan commented 3 months ago

What is the feature you'd like to have? Rename imported ordinal function to its original name which can be get from pdb.

Is your feature request related to a problem? Use case: I am dealing with a crackme that uses MFC, and the import functions are all like Ordinal_mfc90u_286. I loaded the MFC DLL in binja and applied the PDB. Is there a simple way to rename all ordinal functions in crackme based on the information of the MFC dll that has loaded the PDB?

Are any alternative solutions acceptable? No

Additional Information: image image

psifertex commented 3 months ago

Can you share the binaries in question? This is probably best addressed with a quick script we can write up for you as opposed to a change in the product at least in the near-term.

xusheng6 commented 3 months ago

I have previously ran into a similar case and I also think that writing a script for it would be the best choice. @cesaryuan could you please share the binary with us so that it would be easier for us to work with it?

cesaryuan commented 3 months ago

I have previously ran into a similar case and I also think that writing a script for it would be the best choice

@psifertex @xusheng6 Sorry for late reply. Thanks for your advice, but I cannot share the binary due to some privacy issues.

However, after checking the API documents,. I resolved it by writing a script. Here is the script.

MFC_template_classname_map = {
    'CStringT<wchar_t,class StrTraitMFC_DLL<wchar_t,class ATL::ChTraitsCRT<wchar_t> > >': 'CStringW',
    'CStringT<char,class StrTraitMFC_DLL<char,class ATL::ChTraitsCRT<char> > >': 'CStringA',
    'CArray<class CRect,class CRect>': 'CArray<CRect>',
    'CSimpleStringT<char,0>': 'CSimpleStringA0',
    'CSimpleStringT<wchar_t,0>': 'CSimpleStringW0',
    'CSimpleStringT<char,1>': 'CSimpleStringA1',
    'CSimpleStringT<wchar_t,1>': 'CSimpleStringW1',
}
def replace_template_classname(name: str):
    '''
    Used to replace the template class name to the human-readable name
    '''
    for template_classname, new_classname in MFC_template_classname_map.items():
        name = name.replace(template_classname, new_classname)
    return name

import json
mfc90u_exports = json.load(open(r'C:\Users\cesar\AppData\Roaming\Binary Ninja\plugins\nodejs-napi-types-importer\mfc90u_exports.json'))
def get_function_by_ordinal(view: BinaryView, ordinal: int) -> Function | None:
    '''
    Due to issue [Combing the function name info from PDB and the ordinal info from PE when hanlding PE exports](https://github.com/Vector35/binaryninja-api/issues/5217).
    We have to use the following code to get the function by ordinal
    The mfc90u_exports.json is generated by pefile
import pefile
def extract_exports(exe_file_path):
    try:
        pe = pefile.PE(exe_file_path)
        export_entries = []
        if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
            for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
                export_entry = {
                    "Name": exp.name.decode() if exp.name else None,
                    "Ordinal": exp.ordinal,
                    "Address": hex(pe.OPTIONAL_HEADER.ImageBase + exp.address)
                }
                export_entries.append(export_entry)
        return export_entries
    except Exception as e:
        print(f"Error extracting export table: {e}")
        return None
```
'''
for export in mfc90u_exports:
    if export['Ordinal'] == ordinal:
        addr = int(export['Address'], 16)
        return view.get_function_at(addr)
return None

function_map: dict[int, Function] = None

def get_function_by_ordinal(view: BinaryView, ordinal: int) -> Function | None:

'''

This function is used to get the function by ordinal,

but due to the issue https://github.com/Vector35/binaryninja-api/issues/5217,

We have to use the above code to get the function by ordinal

'''

global function_map

if function_map is None:

function_map = {}

for func in view.functions:

if func.symbol is not None and func.symbol.type == SymbolType.FunctionSymbol:

function_map[func.symbol.ordinal] = func

if function_map.get(ordinal) is None:

print(f'Function with ordinal {ordinal} not found')

return None

return function_map[ordinal]

def rename_imported_function(bv: BinaryView, imported_dll_bndb: str = "D:\user\Desktop\mfc90u.dll.bndb"): ''' This function is used to rename all functions and data variables in bv whose name starts with 'Ordinalmfc90u'

:param bv: The binary view of the target binary
:param imported_dll_bndb: The path to the bndb file of mfc90u.dll which having the function names
'''
with load(imported_dll_bndb) as bv_mfc:
    pass
def get_qualified_func_name(bv, func: Function):
    type, names = demangle.demangle_ms(bv.platform, func.name)
    if type is None:
        return func.name
    for i, name in enumerate(names):
        names[i] = replace_template_classname(name)
    qualified_name = demangle.get_qualified_name(names)
    # if name_list.get(qualified_name) is not None:
    #     print(f'Name {qualified_name} already exists')
    #     print(name_list[qualified_name])
    #     print(func_mfc)
    #     continue
    # name_list[qualified_name] = func_mfc
    return qualified_name.replace("ATL::", "")
for _, data_var in bv.data_vars.items():
    if data_var.name is not None and data_var.name.startswith('Ordinal_mfc90u') and isinstance(data_var.type, PointerType):
        if not data_var.symbol:
            continue
        ordinal = data_var.symbol.ordinal
        func_mfc = get_function_by_ordinal(bv_mfc, ordinal)
        if func_mfc is None:
            print(f'Function with ordinal {ordinal} not found')
            continue
        qualified_name = get_qualified_func_name(bv, func_mfc)
        # print(qualified_name)
        data_var.type = Type.pointer(bv.arch, func_mfc.type)
        data_var.name = qualified_name
for func in bv.functions:
    if func.name is not None and func.name.startswith('Ordinal_mfc90u'):
        ordinal = func.symbol.ordinal
        func_mfc = get_function_by_ordinal(bv_mfc, ordinal)
        if func_mfc is None:
            continue
        qualified_name = get_qualified_func_name(bv, func_mfc)
        func.type = func_mfc.type
        func.name = qualified_name

rename_imported_function(bv)

plafosse commented 3 months ago

This is a kind of easy task but it might take a bit of time before we get to it. What needs to happen is we need to create an ordinal-only TypeLibrary for all of the MFC libraries. So we first open and apply the pdb then write a script that exports a type library from this.

codykrieger commented 2 months ago

FWIW, this is something Ghidra does as well. It ships XML files mapping the ordinal numbers to symbol names. e.g.: https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Base/data/symbols/win64/mfc140u.exports