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

Code before:
    """
    insn: ghidra.program.database.code.InstructionDB = ih.inner

    if insn.getMnemonicString().startswith("RET"):
        # skip things like:
        #   .text:0042250E retn 8
        return

    if capa.features.extractors.ghidra.helpers.is_sp_modified(insn):
        # skip things like:
        #   .text:00401145 add esp, 0Ch
        return

    for i in range(insn.getNumOperands()):
        # Exceptions for LEA insn:
        # invalid operand encoding, considered numbers instead of offsets
        # see: mimikatz.exe_:0x4018C0
        if insn.getOperandType(i) == OperandType.DYNAMIC and insn.getMnemonicString().startswith("LEA"):
            op_objs = insn.getOpObjects(i)
            if isinstance(op_objs[-1], ghidra.program.model.scalar.Scalar):
                # Additional check, avoid yielding "wide" values (ex. mimikatz.exe:0x471EE6 LEA EBX, [ECX + EAX*0x4])
                if isinstance(op_objs[-2], ghidra.program.model.lang.Register):
                    # EAX*0x4, note: Ghidra does not currently support this level of offset & number differentiation
                    # all byte addressing is referred to as a DYNAMIC OperandType
                    if op_objs[-2].getName() + "*" + hex(op_objs[-1].getValue()) in insn.toString():
                        continue

                const = op_objs[-1].getUnsignedValue()
                addr = ih.address

                yield Number(const), addr
                yield OperandNumber(i, const), addr
        elif not OperandType.isScalar(insn.getOperandType(i)):
            # skip things like:
            #   references, void types
            continue
        else:
            const = insn.getScalar(i).getUnsignedValue()
            addr = ih.address

            yield Number(const), addr
            yield OperandNumber(i, const), addr

            if insn.getMnemonicString().startswith("ADD") and 0 < const < MAX_STRUCTURE_SIZE:
                # for pattern like:
                #
                #     add eax, 0x10
                #
                # assume 0x10 is also an offset (imagine eax is a pointer).
                yield Offset(const), addr
                yield OperandOffset(i, const), addr

Review comment (refactor):
If I'm following, these checks avoid extracting `4` from instructions like `LEA EBX, [ECX + EAX*0x4]`? Can we simply check if the  instruction is `LEA` and `len(insn.getOpObjects[1])` != 4? If not, is it guarenteed that `insn.toString()` uses hex representation for numbers? e.g. if a user sets the disassembly view to base 10 representation.

Code after:
    """
    insn: ghidra.program.database.code.InstructionDB = ih.inner

    if insn.getMnemonicString().startswith("RET"):
        # skip things like:
        #   .text:0042250E retn 8
        return

    if capa.features.extractors.ghidra.helpers.is_sp_modified(insn):
        # skip things like:
        #   .text:00401145 add esp, 0Ch
        return

    for i in range(insn.getNumOperands()):
        # Exceptions for LEA insn:
        # invalid operand encoding, considered numbers instead of offsets
        # see: mimikatz.exe_:0x4018C0
        if insn.getOperandType(i) == OperandType.DYNAMIC and insn.getMnemonicString().startswith("LEA"):
            # Additional check, avoid yielding "wide" values (ex. mimikatz.exe:0x471EE6 LEA EBX, [ECX + EAX*0x4])
            op_objs = insn.getOpObjects(i)
            if len(op_objs) == 3:  # ECX, EAX, 0x4
                continue

            if isinstance(op_objs[-1], ghidra.program.model.scalar.Scalar):
                const = op_objs[-1].getUnsignedValue()
                addr = ih.address

                yield Number(const), addr
                yield OperandNumber(i, const), addr
        elif not OperandType.isScalar(insn.getOperandType(i)):
            # skip things like:
            #   references, void types
            continue
        else:
            const = insn.getScalar(i).getUnsignedValue()
            addr = ih.address

            yield Number(const), addr
            yield OperandNumber(i, const), addr

            if insn.getMnemonicString().startswith("ADD") and 0 < const < MAX_STRUCTURE_SIZE:
                # for pattern like:
                #
                #     add eax, 0x10
                #
                # assume 0x10 is also an offset (imagine eax is a pointer).
                yield Offset(const), addr
                yield OperandOffset(i, const), addr


def extract_insn_offset_features(fh: FunctionHandle, bb: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]: