angr/tests/analyses/test_variablerecovery.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

489 lines
18 KiB
Python
Executable file

#!/usr/bin/env python3
from __future__ import annotations
__package__ = __package__ or "tests.analyses" # pylint:disable=redefined-builtin
import logging
import os
import unittest
import angr
from angr.knowledge_plugins.variables import VariableType
from angr.sim_variable import SimRegisterVariable, SimStackVariable
from tests.common import bin_location, print_decompilation_result
test_location = os.path.join(bin_location, "tests")
l = logging.getLogger("test_variablerecovery")
#
# Utility methods
#
class TestVariableRecovery(unittest.TestCase):
def _compare_memory_variable(self, variable, variable_info):
if variable_info["location"] == "stack":
if not isinstance(variable, SimStackVariable):
return False
# base
if "base" in variable_info:
base = variable_info["base"]
if variable.base != base:
return False
# offset
if "offset" in variable_info:
offset = variable_info["offset"]
if variable.offset != offset:
return False
# size
if "size" in variable_info:
size = variable_info["size"]
if variable.size != size:
return False
return True
if isinstance(variable, SimStackVariable):
# it is not a variable on the stack
return False
raise NotImplementedError
def _compare_register_variable(self, variable, variable_info): # pylint:disable=unused-argument
if not isinstance(variable, SimRegisterVariable):
return False
if "reg" in variable_info:
reg = variable_info["reg"]
if variable.reg != reg:
return False
if "size" in variable_info:
size = variable_info["size"]
if variable.size != size:
return False
return True
def _run_variable_recovery_analysis(self, func_name, groundtruth, is_fast):
binary_path = os.path.join(test_location, "x86_64", "fauxware")
project = angr.Project(binary_path, load_options={"auto_load_libs": False})
cfg = project.analyses.CFG(normalize=True)
func = cfg.kb.functions[func_name]
# Create a temporary KnowledgeBase instance
tmp_kb = angr.KnowledgeBase(project)
if is_fast:
l.debug("Running VariableRecoveryFast on function %r.", func)
vr = project.analyses.VariableRecoveryFast(func, kb=tmp_kb)
else:
l.debug("Running VariableRecovery on function %r.", func)
vr = project.analyses.VariableRecovery(func, kb=tmp_kb)
variable_manager = vr.variable_manager[func.addr]
for insn_addr, variables in groundtruth["variables_by_instruction"].items():
for var_info in variables:
var_sort = var_info["sort"]
vars_and_offset = variable_manager.find_variables_by_insn(insn_addr, var_sort)
# enumerate vars and find the variable that we want
if var_sort == VariableType.MEMORY:
the_var = next(
(var for var, _ in vars_and_offset if self._compare_memory_variable(var, var_info)),
None,
)
elif var_sort == VariableType.REGISTER:
the_var = next(
(var for var, _ in vars_and_offset if self._compare_register_variable(var, var_info)),
None,
)
else:
l.error("Unsupported variable sort %s.", var_sort)
assert False
assert the_var is not None, (
f"The variable {var_info} in groundtruth at instruction {insn_addr:#x} cannot be found in variable manager."
)
l.debug("Found variable %s at %#x.", the_var, insn_addr)
for block_addr, variables in groundtruth["phi_variables_by_block"].items():
phi_variables = variable_manager.get_phi_variables(block_addr)
for var_info in variables:
var_sort = var_info["sort"]
# enumerate vars and find the variable that we want
if var_sort == VariableType.MEMORY:
the_var = next(
(var for var in phi_variables if self._compare_memory_variable(var, var_info)),
None,
)
elif var_sort == VariableType.REGISTER:
the_var = next(
(var for var in phi_variables if self._compare_register_variable(var, var_info)),
None,
)
else:
l.error("Unsupported variable sort %s.", var_sort)
assert False
assert the_var is not None, (
f"The phi variable {var_info} in groundtruth at block {block_addr:#x} cannot be found in variable manager."
)
l.debug("Found phi variable %s at %#x.", the_var, block_addr)
def test_variable_recovery_fauxware_authenticate_true(self):
self._run_variable_recovery_analysis(
"authenticate",
{
"variables_by_instruction": {
0x40066C: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x18,
"size": 8,
},
],
0x400670: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x20,
"size": 8,
},
],
0x400674: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x8,
"size": 1,
},
],
0x40067F: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x20,
"size": 8,
},
],
0x400699: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x18,
"size": 8,
},
],
0x4006AF: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x4,
"size": 4,
},
],
0x4006B2: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x10,
"size": 1,
},
],
},
"phi_variables_by_block": {
0x4006EB: [
{"sort": VariableType.REGISTER, "reg": 16, "size": 8},
{"sort": VariableType.REGISTER, "reg": 32, "size": 8},
{"sort": VariableType.REGISTER, "reg": 64, "size": 8},
{"sort": VariableType.REGISTER, "reg": 72, "size": 8},
]
},
},
True,
)
def test_variable_recovery_fauxware_authenticate_false(self):
self._run_variable_recovery_analysis(
"authenticate",
{
"variables_by_instruction": {
0x40066C: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x18,
"size": 8,
},
],
0x400670: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x20,
"size": 8,
},
],
0x400674: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x8,
"size": 1,
},
],
0x40067F: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x20,
"size": 8,
},
],
0x400699: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x18,
"size": 8,
},
],
0x4006AF: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x4,
"size": 4,
},
],
0x4006B2: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x10,
"size": 1,
},
],
},
"phi_variables_by_block": {
0x4006EB: [
{"sort": VariableType.REGISTER, "reg": 16, "size": 8},
{"sort": VariableType.REGISTER, "reg": 32, "size": 8},
{"sort": VariableType.REGISTER, "reg": 64, "size": 8},
{"sort": VariableType.REGISTER, "reg": 72, "size": 8},
]
},
},
False,
)
def test_variable_recovery_fauxware_main_true(self):
self._run_variable_recovery_analysis(
"main",
{
"variables_by_instruction": {
0x400725: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x34,
"size": 4,
},
],
0x400728: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x40,
"size": 8,
},
],
0x40072C: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x8,
"size": 1,
},
],
0x400730: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x18,
"size": 1,
},
],
0x40073E: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x10,
"size": 1,
},
],
0x400754: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x24,
"size": 1,
},
],
0x400774: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x20,
"size": 1,
},
],
},
"phi_variables_by_block": {},
},
True,
)
def test_variable_recovery_fauxware_main_false(self):
self._run_variable_recovery_analysis(
"main",
{
"variables_by_instruction": {
0x400725: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x34,
"size": 4,
},
],
0x400728: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x40,
"size": 8,
},
],
0x40072C: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x8,
"size": 1,
},
],
0x400730: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x18,
"size": 1,
},
],
0x40073E: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x10,
"size": 1,
},
],
0x400754: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x24,
"size": 1,
},
],
0x400774: [
{
"sort": VariableType.MEMORY,
"location": "stack",
"base": "bp",
"offset": -0x20,
"size": 1,
},
],
},
"phi_variables_by_block": {},
},
False,
)
def test_format_string_type_hints_sscanf(self):
"""
Test that VariableRecoveryFast extracts type hints from format strings.
The read_six_numbers function in the CMU bomb binary calls:
sscanf(buf, "%d %d %d %d %d %d", &a, &b, &c, &d, &e, &f)
The format string tells us each variadic argument is an int*, so type constraints
of the form Subtype(arg_typevar, Pointer64(Int32)) should be generated.
"""
binary_path = os.path.join(test_location, "x86_64", "bomb")
if not os.path.exists(binary_path):
self.skipTest("bomb binary not found")
project = angr.Project(binary_path, auto_load_libs=False)
cfg = project.analyses.CFGFast(normalize=True, data_references=True)
func = cfg.kb.functions["read_six_numbers"]
# Run the decompiler which invokes VariableRecoveryFast with AIL blocks internally
dec = project.analyses.Decompiler(func, cfg=cfg.model)
assert dec.codegen is not None and dec.codegen.text is not None
print_decompilation_result(dec)
code = dec.codegen.text
# The second parameter (a1) should be typed as a pointer (int* or unsigned int*)
# rather than a plain integer or char*
assert "int *" in code or "int*" in code, f"Expected a1 to be typed as an int pointer, but got:\n{code}"
# It should NOT be char* - the format string should override that
assert "char *a1" not in code and "char* a1" not in code, f"a1 should not be typed as char*, but got:\n{code}"
if __name__ == "__main__":
l.setLevel(logging.DEBUG)
logging.getLogger("angr.analyses.variable_recovery_fast").setLevel(logging.DEBUG)
logging.getLogger("angr.analyses.variable_recovery").setLevel(logging.DEBUG)
unittest.main()