angr/tests/analyses/test_disassembly.py
Kevin Phoenix f939c5b88c
Enable ruff isort rule (#6452)
* Enable ruff isort rule

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-02 14:48:07 -07:00

235 lines
9.4 KiB
Python
Executable file

#!/usr/bin/env python3
# pylint:disable=missing-class-docstring,no-self-use
from __future__ import annotations
from unittest import TestCase, main
from archinfo import ArchAArch64
import angr
from angr.analyses import Disassembly
from angr.analyses.disassembly import Instruction, MemoryOperand, Register, Value
from angr.errors import AngrTypeError
class TestDisassembly(TestCase):
def test_capstone_unsupported(self):
# TestError because Exception is too broad
# for the linter.
class TestError(Exception):
pass
class ArchAArch64NoCapstone(ArchAArch64):
name = "AARCH64_NOCAPSTONE"
@property
def capstone_support(self):
return False
arch = ArchAArch64NoCapstone()
proj = angr.load_shellcode(
b"\x00\xe4\x00\x6f\x43\x3c\x0b\x0e\x54\x9a\xb7\x72\xfc\x6f\xba\xa9\x88\x03\x98\x1a\x00\x60\x01\x4e",
arch,
0,
)
block = proj.factory.block(0)
expected_message = (
f"Cannot disassemble block with architecture {arch} for block type <class 'angr.codenode.BlockNode'>"
)
try:
_ = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
raise TestError("We expected disassembly to fail because it didn't have capstone support")
except AngrTypeError as error:
# Assert failures aren't very helpful showing the difference.
if error.args[0] != expected_message:
raise TestError(f"\nExpected: {expected_message}\nActual: {error.args[0]}") from error
def test_arm64_dissect_instructions(self):
proj = angr.load_shellcode(
b"\x00\xe4\x00\x6f\x43\x3c\x0b\x0e\x54\x9a\xb7\x72\xfc\x6f\xba\xa9\x88\x03\x98\x1a\x00\x60\x01\x4e",
"AARCH64",
0,
)
# movi v0.2d, #0000000000000000' ; SIMD register
# umov w3, v2.b[5] ; SIMD register index
# movk w20, #0xbcd2, lsl #16 ; ARM64 shifter
# stp x28, x27, [sp, #-0x60]! ; ARM64 pre-indexed operand
# csel w8, w28, w24, eq ; Condition code at the end
# tbl v0.16b, {v0.16b, v1.16b, v2.16b, v3.16b}, v1.16b ; Multiple SIMD regs in table
block = proj.factory.block(0)
disasm = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
insns = [r for r in disasm.raw_result if isinstance(r, Instruction)]
rendered_insns = [i.render()[0].lower() for i in insns]
assert "v0.2d" in rendered_insns[0]
assert "v2.b[5]" in rendered_insns[1]
assert "lsl#16" in rendered_insns[2].replace(" ", "")
assert rendered_insns[3].endswith("]!")
assert rendered_insns[4].endswith("eq")
insn = rendered_insns[5]
regs_table = insn[insn.index("{") + 1 : insn.index("}")].replace(" ", "").split(",")
assert regs_table == ["v0.16b", "v1.16b", "v2.16b", "v3.16b"]
def test_arm32_dissect_instructions(self):
proj = angr.load_shellcode(
b"\x00\xc0\x2d\xe9\x10\xf9\xf9\xe9",
"ARM",
0,
)
# push {lr, pc}
# ldmib sb!, {r4, r8, fp, ip, sp, lr, pc}^
block = proj.factory.block(0)
disasm = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
insns = [r for r in disasm.raw_result if isinstance(r, Instruction)]
rendered_insns = [i.render()[0].lower() for i in insns]
assert all(i in rendered_insns[0] for i in ("{", "}", "lr", "pc"))
assert "sb!" in rendered_insns[1]
assert rendered_insns[1].endswith("^")
def test_arm32_thumb_dissect_instructions(self):
proj = angr.load_shellcode(b"\x00\xf9\x01\x1a", "ARM", 0, thumb=True)
# vst1.8 {d1, d2}, [r0], r1
block = proj.factory.block(0, thumb=True)
disasm = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)], thumb=True)
insns = [r for r in disasm.raw_result if isinstance(r, Instruction)]
disassembly_operands = insns[0].operands
capstone_operands = insns[0].insn.operands
assert len(disassembly_operands) == len(capstone_operands)
def test_mips32_missing_offset_in_instructions(self):
proj = angr.load_shellcode(
b"\x8f\xbc\x00\x10"
b"\x02\x20\x30\x21"
b"\x8f\x85\x80\x28"
b"\x8f\x99\x81\x20"
b"\x02\x40\x38\x21"
b"\x24\xa5\x5e\x38"
b"\x03\x20\xf8\x09"
b"\x24\x04\x00\x02",
"MIPS32",
0,
)
# 0x0: lw $gp, 0x10($sp)
# 0x4: move $a2, $s1
# 0x8: lw $a1, -0x7fd8($gp)
# 0xc: lw $t9, -0x7ee0($gp)
# 0x10: move $a3, $s2
# 0x14: addiu $a1, $a1, 0x5e38
# 0x18: jalr $t9
# 0x1c: addiu $a0, $zero, 2
block = proj.factory.block(0)
disass = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
result = disass.raw_result
assert len(result) == 10, f"Incorrect number of instructions ({len(result)})"
ins = result[4]
operand_1 = ins.operands[1]
assert isinstance(operand_1, MemoryOperand)
assert len(operand_1.children) == 4
assert len(operand_1.values) == 1
assert len(operand_1.offset) == 1
assert operand_1.offset_location == "prefix"
rendered = disass.render(color=False)
assert (
rendered
== """ _start:
0 lw $gp, 0x10($sp)
4 move $a2, $s1
8 lw $a1, -0x7fd8($gp)
c lw $t9, -0x7ee0($gp)
10 move $a3, $s2
14 addiu $a1, $a1, 0x5e38
18 jalr $t9
1c addiu $a0, $zero, 0x2"""
)
def test_arm_data_address_display(self):
proj = angr.load_shellcode(b"\x26\x49\x17\x48\x0b\xf0", "ARMCortexM", load_address=0x80410E6)
# 0x80410e7: ldr r1, [pc, #0x98]
# 0x80410e9: ldr r0, [pc, #0x5c]
# 0x80410eb: bl xxx
block = proj.factory.block(0x80410E7)
disass = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
result = disass.raw_result
assert len(result) == 3, f"Incorrect number of instructions ({len(result)})"
ins0 = result[1]
assert len(ins0.operands) == 2
ins0op1 = ins0.operands[1]
assert isinstance(ins0op1, MemoryOperand)
assert len(ins0op1.values) == 1
value = ins0op1.values[0]
assert isinstance(value, Value)
assert value.val == 0x8041182
assert ins0.render()[0] == "ldr r1, [0x8041182]"
def test_arm_three_piece_memory_operand(self):
proj = angr.load_shellcode(b"\xdf\xe8\x13\xf0", "ARMCortexM", load_address=0x80407F4)
# 0x80407f4: tbh [pc, r3, lsl #2]
block = proj.factory.block(0x80407F5)
disass = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
result = disass.raw_result
assert len(result) == 2, f"Incorrect number of instructions ({len(result)})"
ins0 = result[1]
assert len(ins0.operands) == 1
ins0op0 = ins0.operands[0]
assert isinstance(ins0op0, MemoryOperand)
assert len(ins0op0.values) == 3
assert isinstance(ins0op0.values[0], Register)
assert isinstance(ins0op0.values[1], Register)
assert isinstance(ins0op0.values[2], str)
assert ins0.render()[0] == "tbh [pc,r3,lsl#1]"
def test_pcode(self):
proj = angr.load_shellcode(b"\xaa\x6c\x2d\x61\x82\x80\xef\xd0\x5f\xae", "RISCV", load_address=0x373E)
block = proj.factory.block(0x373E)
pcode = block.pcode
assert pcode.addr == 0x373E
assert len(pcode.insns) == 3
assert len(pcode.insns) == 3
assert pcode.insns[-1].mnemonic == "ret"
# TODO: add RVC jump tests
def test_riscv64_directness_instructions(self):
# 0x0: ef 02 40 06 jal t0, 0x640 ; Direct Call
# 0x4: 63 00 10 00 beq zero, ra, 0x4 ; Direct Branch
# 0x8: 67 00 25 00 jalr zero, 0(a0) ; Indirect Branch (jr a0)
# 0xc: 67 80 00 00 jalr zero, 0(ra) ; Indirect Branch (ret)
# 0x10: e7 00 05 00 jalr ra, 0(a0) ; Indirect Call
proj = angr.load_shellcode(
b"\xef\x02\x40\x06\x63\x00\x10\x00\x67\x00\x25\x00\x67\x80\x00\x00\xe7\x00\x05\x00",
"RISCV64",
0,
)
block = proj.factory.block(0, size=20)
disasm = proj.analyses[Disassembly].prep()(ranges=[(block.addr, block.addr + block.size)])
insns = [r for r in disasm.raw_result if isinstance(r, Instruction)]
assert len(insns) == 5
for i, insn in enumerate(insns):
assert insn.addr == i * 4
assert insn.size == 4
# jal t0, 0x640
assert insns[0].branch_type == "direct"
# beq zero, ra
assert insns[1].branch_type == "direct"
# jalr x0, 0(a0) / jr a0
assert insns[2].branch_type == "indirect"
# jalr x0, 0(ra) / ret
assert insns[3].branch_type == "indirect"
# jalr ra, 0(a0)
assert insns[4].branch_type == "indirect"
if __name__ == "__main__":
main()