From 83d7c034bac525a462bdfab400e76d50f4a798fe Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 15 Aug 2026 13:01:04 +0000 Subject: [PATCH] Preserve indirect store addresses across type casts --- .../decompiler/structured_codegen/c.py | 11 ++-- .../decompiler/test_structured_codegen.py | 61 +++++++++++++++++-- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/angr/analyses/decompiler/structured_codegen/c.py b/angr/analyses/decompiler/structured_codegen/c.py index 2d107654a..bf5ad09ef 100644 --- a/angr/analyses/decompiler/structured_codegen/c.py +++ b/angr/analyses/decompiler/structured_codegen/c.py @@ -3328,15 +3328,18 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis, Serializab lvalue: bool, renegotiate_type: Callable[[SimType, SimType], SimType] = lambda old, proposed: old, ) -> CExpression: - def _force_type_cast(src_type_: SimType, dst_type_: SimType, expr_: CExpression) -> CUnaryOp: + def _force_type_cast( + src_type_: SimType, dst_type_: SimType, expr_: CExpression, take_reference: bool + ) -> CUnaryOp: src_type_ptr = SimTypePointer(src_type_).with_arch(self.project.arch) dst_type_ptr = SimTypePointer(dst_type_).with_arch(self.project.arch) + cast_expr = CUnaryOp("Reference", expr_, codegen=self) if take_reference else expr_ return CUnaryOp( "Dereference", CTypeCast( src_type_ptr, dst_type_ptr, - CUnaryOp("Reference", expr_, codegen=self), + cast_expr, codegen=self, ), codegen=self, @@ -3369,11 +3372,11 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis, Serializab # case 2: we're done because we can never find it and we might as well stop early if base_expr: if not type_equals(base_type, data_type): - return _force_type_cast(base_type, data_type, base_expr) + return _force_type_cast(base_type, data_type, base_expr, True) return base_expr if not type_equals(base_type, data_type): - return _force_type_cast(base_type, data_type, expr) + return _force_type_cast(base_type, data_type, expr, False) return CUnaryOp("Dereference", expr, codegen=self) stride = 1 if base_type.size is None else base_type.size // self.project.arch.byte_width or 1 diff --git a/tests/analyses/decompiler/test_structured_codegen.py b/tests/analyses/decompiler/test_structured_codegen.py index 85f1353bc..ea4036e68 100644 --- a/tests/analyses/decompiler/test_structured_codegen.py +++ b/tests/analyses/decompiler/test_structured_codegen.py @@ -7,7 +7,20 @@ __package__ = __package__ or "tests.analyses.decompiler" # pylint:disable=redef import unittest import angr -from angr.ailment import Expr +from angr.ailment import Expr, Manager, Stmt +from angr.ailment.expression import VirtualVariableCategory +from angr.analyses.decompiler import CStructuredCodeGenerator +from angr.analyses.decompiler.variable_map import VariableMap +from angr.sim_type import SimTypeChar, SimTypePointer +from angr.sim_variable import SimRegisterVariable, SimStackVariable + + +def _make_codegen() -> CStructuredCodeGenerator: + proj = angr.load_shellcode(b"\x31\xc0\xc3", arch="AMD64") + cfg = proj.analyses.CFGFast(normalize=True) + codegen = proj.analyses.Decompiler(cfg.functions[0], cfg=cfg).codegen + assert isinstance(codegen, CStructuredCodeGenerator) + return codegen class TestConvertRendering(unittest.TestCase): @@ -16,9 +29,7 @@ class TestConvertRendering(unittest.TestCase): @classmethod def setUpClass(cls): # any decompilation will do; all we need is a codegen instance to render expressions with - proj = angr.load_shellcode(b"\x31\xc0\xc3", arch="AMD64") # xor eax, eax; ret - cfg = proj.analyses.CFGFast(normalize=True) - cls.codegen = proj.analyses.Decompiler(cfg.functions[0], cfg=cfg).codegen + cls.codegen = _make_codegen() def _render(self, from_bits: int, to_bits: int, value: int = 0x1234) -> str: conv = Expr.Convert(0, from_bits, to_bits, False, Expr.Const(0, value, from_bits)) @@ -43,5 +54,47 @@ class TestConvertRendering(unittest.TestCase): assert self._render(32, 64, value=3) == "(unsigned long long)3" +class TestStoreRendering(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.codegen = _make_codegen() + + def test_mismatched_store_cast_distinguishes_pointer_from_storage(self): + manager = Manager(arch=self.codegen.project.arch) + addr = Expr.VirtualVariable( + manager.next_atom(), 1, self.codegen.project.arch.bits, VirtualVariableCategory.REGISTER + ) + data = Expr.Const(manager.next_atom(), 0x11223344, 32) + pointer_store = Stmt.Store( + manager.next_atom(), addr, data, 4, self.codegen.project.arch.memory_endness, ins_addr=0x401000 + ) + direct_store = Stmt.Store( + manager.next_atom(), addr, data, 4, self.codegen.project.arch.memory_endness, ins_addr=0x401004 + ) + addr_variable = SimRegisterVariable(0x28, self.codegen.project.arch.bytes, ident="ir_test", name="iter") + storage_variable = SimStackVariable(-0x10, 4, ident="is_test", name="storage") + variable_map = VariableMap() + variable_map.set_variable(addr, addr_variable) + variable_map.set_variable(direct_store, storage_variable) + + variable_manager = self.codegen.kb.dec_variables[self.codegen._func.addr] + variable_manager.set_unified_variable(addr_variable, addr_variable) + variable_manager.set_unified_variable(storage_variable, storage_variable) + variable_manager.set_variable_type( + addr_variable, SimTypePointer(SimTypeChar()).with_arch(self.codegen.project.arch) + ) + variable_manager.set_variable_type(storage_variable, SimTypeChar().with_arch(self.codegen.project.arch)) + old_variable_map = self.codegen._variable_map + self.codegen._variable_map = variable_map + try: + pointer_rendered = self.codegen._handle(pointer_store, is_expr=False).c_repr() + direct_rendered = self.codegen._handle(direct_store, is_expr=False).c_repr() + finally: + self.codegen._variable_map = old_variable_map + + assert direct_rendered == "*((unsigned int *)&storage) = 287454020;\n" + assert pointer_rendered == "*((unsigned int *)iter) = 287454020;\n" + + if __name__ == "__main__": unittest.main()