Repository: mandiant/capa
File: capa/features/extractors/ghidra/helpers.py (Python)

Code before:
def is_zxor(insn: ghidra.program.database.code.InstructionDB) -> bool:
    # assume XOR insn
    # XOR's against the same operand zero out
    ops = []
    operands = []
    for i in range(insn.getNumOperands()):
        ops.append(insn.getOpObjects(i))

    # Operands stored in a 2D array
    for j in range(len(ops)):
        for k in range(len(ops[j])):
            operands.append(ops[j][k])

    return all(n == operands[0] for n in operands)


def dereference_ptr(insn: ghidra.program.database.code.InstructionDB):
    addr_code = OperandType.ADDRESS | OperandType.CODE
    to_deref = insn.getAddress(0)
    dat = getDataContaining(to_deref)  # type: ignore [name-defined] # noqa: F821
    thfunc = getFunctionContaining(to_deref)  # type: ignore [name-defined] # noqa: F821

    if insn.getOperandType(0) == addr_code:
        if thfunc and thfunc.isThunk():
            # addr | code can point to a thunked function, we can
            # follow the thunk chain down once. Addresses point cyclically
            # ex. mimikatz.exe_:0x455a41
            thunk_jmp = getInstructionAt(to_deref)  # type: ignore [name-defined] # noqa: F821
            if thunk_jmp:
                for i in range(thunk_jmp.getNumOperands()):
                    if OperandType.isAddress(thunk_jmp.getOperandType(i)):
                        thunk_addr = thunk_jmp.getAddress(i)
                        thunk_dat = getDataContaining(thunk_addr)  # type: ignore [name-defined] # noqa: F821

                        # Sometimes in .elf files, we have to follow the chain down one more level
                        # ex. 7351f...:0x408790
                        if not thunk_dat:
                            return thunk_addr
                        elif thunk_dat.isDefined() & thunk_dat.isPointer():
                            return thunk_dat.getValue()
                        else:
                            return thunk_addr
        else:
            # if it doesn't poin to a thunk, it's usually a jmp to a label
            return to_deref
    if not dat:
        return to_deref
    if dat.isDefined() and dat.isPointer():
        addr = dat.getValue()
        # now we need to check the addr space to see if it is truly resolvable
        # ghidra sometimes likes to hand us direct RAM addrs, which typically point

Review comment (refactor):
Does this always hold true? Or is this specific to `mimikatz.exe_`? For IDA, we follow thunk chains to a specific depth: https://github.com/mandiant/capa/blob/934d0f969b00bf4b8b87e46ffc15cedea306dbe3/capa/features/extractors/ida/insn.py#L44

Code after:
    operands = []
    for i in range(insn.getNumOperands()):
        ops.append(insn.getOpObjects(i))

    # Operands stored in a 2D array
    for j in range(len(ops)):
        for k in range(len(ops[j])):
            operands.append(ops[j][k])

    return all(n == operands[0] for n in operands)


def handle_thunk(addr: ghidra.program.model.address.Address):
    """Follow thunk chains down to a reasonable depth"""
    ref = addr
    for _ in range(THUNK_CHAIN_DEPTH_DELTA):
        thunk_jmp = getInstructionAt(ref)  # type: ignore [name-defined] # noqa: F821
        if thunk_jmp and is_call_or_jmp(thunk_jmp):
            if OperandType.isAddress(thunk_jmp.getOperandType(0)):
                ref = thunk_jmp.getAddress(0)
        else:
            thunk_dat = getDataContaining(ref)  # type: ignore [name-defined] # noqa: F821
            if thunk_dat and thunk_dat.isDefined() and thunk_dat.isPointer():
                ref = thunk_dat.getValue()
                break  # end of thunk chain reached
    return ref


def dereference_ptr(insn: ghidra.program.database.code.InstructionDB):
    addr_code = OperandType.ADDRESS | OperandType.CODE
    to_deref = insn.getAddress(0)
    dat = getDataContaining(to_deref)  # type: ignore [name-defined] # noqa: F821

    if insn.getOperandType(0) == addr_code:
        thfunc = getFunctionContaining(to_deref)  # type: ignore [name-defined] # noqa: F821
        if thfunc and thfunc.isThunk():
            return handle_thunk(to_deref)
        else:
            # if it doesn't poin to a thunk, it's usually a jmp to a label
            return to_deref
    if not dat:
        return to_deref
    if dat.isDefined() and dat.isPointer():
        addr = dat.getValue()
        # now we need to check the addr space to see if it is truly resolvable
        # ghidra sometimes likes to hand us direct RAM addrs, which typically point
        # to api calls that we can't actually resolve as such
        if addr.getAddressSpace().getType() == AddressSpace.TYPE_RAM:
            return to_deref
        else:
            return addr