binaryninja-api icon indicating copy to clipboard operation
binaryninja-api copied to clipboard

Create TypeLibrary with ordinals for mfc* libraries

Open cesaryuan opened this issue 1 year ago • 5 comments

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

cesaryuan avatar Mar 27 '24 06:03 cesaryuan

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.

psifertex avatar Mar 27 '24 23:03 psifertex

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?

xusheng6 avatar Apr 02 '24 05:04 xusheng6

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 'Ordinal_mfc90u_'
    
    :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)

cesaryuan avatar Apr 02 '24 09:04 cesaryuan

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.

plafosse avatar Apr 02 '24 14:04 plafosse

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

codykrieger avatar Apr 12 '24 23:04 codykrieger