Vector35 / binaryninja-api

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

`analysis.experimental.translateWindowsCfgCalls` misses trivial calls #4444

Open bb010g opened 1 year ago

bb010g commented 1 year ago

Version and Platform (required):

Bug Description:

Even with analysis.experimental.translateWindowsCfgCalls enabled, call qword [rel __guard_xfg_dispatch_icall_fptr] lines in the assembly still translate to _guard_xfg_dispatch_icall_nop() in HLIL after analysis. Manually editing an assembly line to call rax, as described in Vector35/binaryninja-api#1760, produces correct analysis.

Steps To Reproduce:

Analyze C:\Windows\System32\windows.storage.dll from Windows version 10.0.22621 Build 22621 with analysis.experimental.translateWindowsCfgCalls enabled and look at the ILs for CAutoDestListParser::_GetDestListStream. The HLIL should show multiple calls to _guard_xfg_dispatch_icall_nop().

Expected Behavior:

These Windows CFG calls should be translated to direct calls.

Screenshots:

HLIL: image

Disassembly: image

Additional Information:

bb010g commented 1 year ago

Until this is resolved, here's a snippet you can run from the Python console that shouldn't break anything else:

with bv.undoable_transaction():
    xfg_dispatch_icall_sym = bv.get_symbols_by_name('__guard_xfg_dispatch_icall_fptr')[0]
    xfg_dispatch_icall_addr = xfg_dispatch_icall_sym.address
    print('xdg_dispatch_icall_addr', hex(xfg_dispatch_icall_addr))
    xfg_dispatch_icall_patch_call = bv.arch.assemble("call qword rax")
    xfg_dispatch_icall_patch_jmp = bv.arch.assemble("jmp qword rax")
    print('xfg_dispatch_icall_patch', xfg_dispatch_icall_patch)
    for ref in bv.get_code_refs(xfg_dispatch_icall_addr):
        print('ref', ref)
        ref_bv = ref.function.view
        ref_llil = ref.llil
        if isinstance(ref.llil, LowLevelILCall) and isinstance(ref_dest := ref_llil.dest, LowLevelILLoad) and isinstance(ref_dest.src, LowLevelILConstPtr) and ref_dest.src.constant == xfg_dispatch_icall_addr and (ref_disasm := next(bv.disassembly_tokens(ref_address := ref_llil.address))) is not None and len(xfg_dispatch_icall_patch_call) <= ref_bv.get_instruction_length(ref_address):
            ref_disasm = ref_disasm[0]
            if len(ref_disasm) == 7 and ref_disasm[0].text.strip() == 'call' and ref_disasm[1].text.strip() == '' and ref_disasm[2].text.strip() == 'qword' and ref_disasm[3].text.strip() == '[' and ref_disasm[4].text.strip() == 'rel' and ref_disasm[5].type == InstructionTextTokenType.CodeRelativeAddressToken and ref_disasm[6].text.strip() == ']':
                if ref_bv.convert_to_nop(ref_address):
                    ref_bv.write(ref_address, xfg_dispatch_icall_patch_call)
                    print('patched call', hex(ref_address))
        elif isinstance(ref.llil, LowLevelILTailcall) and isinstance(ref_dest := ref_llil.dest, LowLevelILLoad) and isinstance(ref_dest.src, LowLevelILConstPtr) and ref_dest.src.constant == xfg_dispatch_icall_addr and (ref_disasm := next(bv.disassembly_tokens(ref_address := ref_llil.address))) is not None and len(xfg_dispatch_icall_patch_jmp) <= ref_bv.get_instruction_length(ref_address):
            ref_disasm = ref_disasm[0]
            if len(ref_disasm) == 7 and ref_disasm[0].text.strip() == 'jmp' and ref_disasm[1].text.strip() == '' and ref_disasm[2].text.strip() == 'qword' and ref_disasm[3].text.strip() == '[' and ref_disasm[4].text.strip() == 'rel' and ref_disasm[5].type == InstructionTextTokenType.CodeRelativeAddressToken and ref_disasm[6].text.strip() == ']':
                if ref_bv.convert_to_nop(ref_address):
                    ref_bv.write(ref_address, xfg_dispatch_icall_patch_jmp)
                    print('patched jmp', hex(ref_address))
        else:
            continue