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

Code before:
    op_bit_len = op.bitLength()
    op_val = op.getValue()

    if op_bit_len == 8:
        chars = struct.pack("<B", op_val & 0xFF)
    elif op_bit_len == 16:
        chars = struct.pack("<H", op_val & 0xFFFF)
    elif op_bit_len == 32:
        chars = struct.pack("<I", op_val & 0xFFFFFFFF)
    elif op_bit_len == 64:
        chars = struct.pack("<Q", op_val & 0xFFFFFFFFFFFFFFFF)
    else:
        raise ValueError(f"Unhandled operand data type 0x{op_bit_len:x}.")

    def is_printable_ascii(chars_: bytes):
        return all(c < 127 and chr(c) in string.printable for c in chars_)

    def is_printable_utf16le(chars_: bytes):
        if all(c == 0x00 for c in chars_[1::2]):
            return is_printable_ascii(chars_[::2])

    if is_printable_ascii(chars):
        return int(op_bit_len / 8)

    if is_printable_utf16le(chars):
        return int(op_bit_len / 8)

    return 0


def is_mov_imm_to_stack(insn: ghidra.program.database.code.InstructionDB) -> bool:
    """verify instruction moves immediate onto stack"""

    # Ghidra will Bitwise OR the OperandTypes to assign multiple
    # i.e., the first operand is a stackvar (dynamically allocated),
    # and the second is a scalar value (single int/char/float/etc.)
    mov_its_ops = [(OperandType.ADDRESS | OperandType.DYNAMIC), OperandType.SCALAR]
    found = False

    # MOV dword ptr [EBP + local_*], 0x65
    if insn.getMnemonicString() == "MOV":
        found = all(insn.getOperandType(i) == mov_its_ops[i] for i in range(2))

    return found


def bb_contains_stackstring(bb: ghidra.program.model.block.CodeBlock) -> bool:
    """check basic block for stackstring indicators

    true if basic block contains enough moves of constant bytes to the stack
    """

Review comment (suggestion):
Should these functions both divide by `8`? Also, should we be using floor division (`//`) for consistent typing? Floor division should allow us to remove the extra `int` cast.

Code after:
    op_bit_len = op.bitLength()
    op_byte_len = op_bit_len // 8
    op_val = op.getValue()

    if op_bit_len == 8:
        chars = struct.pack("<B", op_val & 0xFF)
    elif op_bit_len == 16:
        chars = struct.pack("<H", op_val & 0xFFFF)
    elif op_bit_len == 32:
        chars = struct.pack("<I", op_val & 0xFFFFFFFF)
    elif op_bit_len == 64:
        chars = struct.pack("<Q", op_val & 0xFFFFFFFFFFFFFFFF)
    else:
        raise ValueError(f"Unhandled operand data type 0x{op_bit_len:x}.")

    def is_printable_ascii(chars_: bytes):
        return all(c < 127 and chr(c) in string.printable for c in chars_)

    def is_printable_utf16le(chars_: bytes):
        if all(c == 0x00 for c in chars_[1::2]):
            return is_printable_ascii(chars_[::2])

    if is_printable_ascii(chars):
        return op_byte_len

    if is_printable_utf16le(chars):
        return op_byte_len

    return 0


def is_mov_imm_to_stack(insn: ghidra.program.database.code.InstructionDB) -> bool:
    """verify instruction moves immediate onto stack"""

    # Ghidra will Bitwise OR the OperandTypes to assign multiple
    # i.e., the first operand is a stackvar (dynamically allocated),
    # and the second is a scalar value (single int/char/float/etc.)
    mov_its_ops = [(OperandType.ADDRESS | OperandType.DYNAMIC), OperandType.SCALAR]
    found = False

    # MOV dword ptr [EBP + local_*], 0x65
    if insn.getMnemonicString().startswith("MOV"):
        found = all(insn.getOperandType(i) == mov_its_ops[i] for i in range(2))

    return found


def bb_contains_stackstring(bb: ghidra.program.model.block.CodeBlock) -> bool:
    """check basic block for stackstring indicators

    true if basic block contains enough moves of constant bytes to the stack