Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### New Features

- features: emit `number(0)` for xor-zeroing idioms like `xor eax, eax` across all backends #2622

### Breaking Changes

### New Rules (0)
Expand Down
9 changes: 7 additions & 2 deletions capa/features/extractors/binexport2/arch/arm/insn.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,13 @@ def extract_insn_nzxor_characteristic_features(
# so we don't have to realize the tree/list.
operands: list[BinExport2.Operand] = [be2.operand[operand_index] for operand_index in instruction.operand_index]

if operands[1] != operands[2]:
yield Characteristic("nzxor"), ih.address
if operands[1] == operands[2]:
# eor rd, rn, rn zeros the destination register.
# emit Number(0) to let rules match on the produced value.
yield Number(0), ih.address
return

yield Characteristic("nzxor"), ih.address


INDIRECT_CALL_PATTERNS = BinExport2InstructionPatternMatcher.from_str("""
Expand Down
3 changes: 3 additions & 0 deletions capa/features/extractors/binexport2/arch/intel/insn.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ def extract_insn_nzxor_characteristic_features(
operands: list[BinExport2.Operand] = [be2.operand[operand_index] for operand_index in instruction.operand_index]

if operands[0] == operands[1]:
# xor eax, eax and similar instructions zero a register.
# emit Number(0) to let rules match on the produced value.
yield Number(0), ih.address
return

instruction_address: int = idx.insn_address_by_index[ii.instruction_index]
Expand Down
16 changes: 14 additions & 2 deletions capa/features/extractors/binja/insn.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,6 @@ def extract_insn_nzxor_characteristic_features(
results = []

def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
# If the two operands of the xor instruction are the same, the LLIL will be translated to other instructions,
# e.g., <llil: eax = 0>, (LLIL_SET_REG). So we do not need to check whether the two operands are the same.
if il.operation == LowLevelILOperation.LLIL_XOR:
# Exclude cases related to the stack cookie
if is_nzxor_stack_cookie(fh.inner, bbh.inner, il):
Expand All @@ -373,6 +371,20 @@ def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index
else:
return True

# Binary Ninja canonicalizes `xor reg, reg` to LLIL_SET_REG(reg, 0) rather than LLIL_XOR,
# so the llil_checker above never fires for zeroing XOR idioms.
# Detect them here by checking the mnemonic and the lifted result.
insn: DisassemblyInstruction = ih.inner
if insn.text and insn.text[0].text.lower() in ("xor", "xorpd", "xorps", "pxor"):
for llil in func.get_llils_at(ih.address):
if (
llil.operation == LowLevelILOperation.LLIL_SET_REG
and llil.src.operation == LowLevelILOperation.LLIL_CONST
and llil.src.constant == 0
):
yield Number(0), ih.address
return

for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)

Expand Down
2 changes: 2 additions & 0 deletions capa/features/extractors/ghidra/insn.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,8 @@ def extract_insn_nzxor_characteristic_features(
if capa.features.extractors.ghidra.helpers.is_stack_referenced(insn):
return
if capa.features.extractors.ghidra.helpers.is_zxor(insn):
# xor eax, eax and similar zero a register; emit Number(0) instead of nzxor.
yield Number(0), ih.address
return
if check_nzxor_security_cookie_delta(f, insn):
return
Expand Down
2 changes: 2 additions & 0 deletions capa/features/extractors/ida/insn.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ def extract_insn_nzxor_characteristic_features(
if insn.itype not in (idaapi.NN_xor, idaapi.NN_xorpd, idaapi.NN_xorps, idaapi.NN_pxor):
return
if capa.features.extractors.ida.helpers.is_operand_equal(insn.Op1, insn.Op2):
# xor eax, eax and similar zero a register; emit Number(0) instead of nzxor.
yield Number(0), ih.address
return
if is_nzxor_stack_cookie(fh.inner, bbh.inner, insn):
return
Expand Down
3 changes: 3 additions & 0 deletions capa/features/extractors/viv/insn.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ def extract_insn_nzxor_characteristic_features(
return

if insn.opers[0] == insn.opers[1]:
# xor eax, eax and similar instructions zero a register.
# emit Number(0) to let rules match on the produced value.
yield Number(0), ih.address
return

if is_security_cookie(f, bb, insn):
Expand Down
16 changes: 16 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,8 @@ def get_data_path_by_name(name) -> Path:
return CD / "data" / "773290480d5445f11d3dc1b800728966.exe_"
elif name.startswith("3b13b"):
return CD / "data" / "3b13b6f1d7cd14dc4a097a12e2e505c0a4cff495262261e2bfc991df238b9b04.dll_"
elif name == "microsocks":
return CD / "data" / "microsocks.elf_"
elif name == "7351f.elf":
return CD / "data" / "7351f8a40c5450557b24622417fc478d.elf_"
elif name.startswith("79abd"):
Expand Down Expand Up @@ -919,6 +921,11 @@ def parametrize(params, values, **kwargs):
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xFF), True),
("mimikatz", "function=0x40105D", capa.features.insn.Number(0x3136B0), True),
("mimikatz", "function=0x401000", capa.features.insn.Number(0x0), True),
# insn/number: xor-zeroing idiom, small ELF (microsocks.elf_, xor ebp,ebp at 0x2002564)
("microsocks", "function=0x2002560,bb=0x2002560,insn=0x2002564", capa.features.insn.Number(0x0), True),
# insn/number: xor-zeroing idiom (xor eax, eax -> Number(0))
# function 0x40105D contains `xor ebx, ebx` at 0x401066
("mimikatz", "function=0x40105D,bb=0x40105D,insn=0x401066", capa.features.insn.Number(0x0), True),
Comment thread
williballenthin marked this conversation as resolved.
# insn/number: stack adjustments
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xC), False),
("mimikatz", "function=0x40105D", capa.features.insn.Number(0x10), False),
Expand Down Expand Up @@ -1033,6 +1040,15 @@ def parametrize(params, values, **kwargs):
# insn/characteristic(nzxor)
("mimikatz", "function=0x410DFC", capa.features.common.Characteristic("nzxor"), True),
("mimikatz", "function=0x40105D", capa.features.common.Characteristic("nzxor"), False),
# insn/characteristic(nzxor): xor-zeroing idiom must not be tagged as nzxor
(
"mimikatz",
"function=0x40105D,bb=0x40105D,insn=0x401066",
capa.features.common.Characteristic("nzxor"),
False,
),
# insn/characteristic(nzxor): xor-zeroing idiom, small ELF (microsocks.elf_, xor ebp,ebp at 0x2002564)
("microsocks", "function=0x2002560,bb=0x2002560,insn=0x2002564", capa.features.common.Characteristic("nzxor"), False),
# insn/characteristic(nzxor): no security cookies
("mimikatz", "function=0x46D534", capa.features.common.Characteristic("nzxor"), False),
# insn/characteristic(nzxor): xorps
Expand Down
Loading