mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
705 lines
30 KiB
Python
Executable file
705 lines
30 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 time
|
|
import unittest
|
|
from functools import wraps
|
|
|
|
import archinfo
|
|
|
|
import angr
|
|
from angr.analyses.complete_calling_conventions import (
|
|
DEAD_WORKER_GRACE_PERIOD,
|
|
CallingConventionAnalysisMode,
|
|
CompleteCallingConventionsAnalysis,
|
|
)
|
|
from angr.calling_conventions import (
|
|
SimCCCdecl,
|
|
SimCCSystemVAMD64,
|
|
SimRegArg,
|
|
SimStackArg,
|
|
)
|
|
from angr.errors import AngrRuntimeError
|
|
from angr.sim_type import SimTypeBottom, SimTypeFloat, SimTypeFunction, SimTypeInt, SimTypeLongLong
|
|
from tests.common import bin_location, requires_binaries_private
|
|
|
|
test_location = os.path.join(bin_location, "tests")
|
|
|
|
|
|
def cca_mode(modes: str):
|
|
def wrapper(func):
|
|
@wraps(func)
|
|
def inner(*args, **kwargs):
|
|
for mode_str in modes.split(","):
|
|
mode = CallingConventionAnalysisMode[mode_str.upper()]
|
|
func(*args, mode=mode, **kwargs)
|
|
|
|
return inner
|
|
|
|
return wrapper
|
|
|
|
|
|
# pylint: disable=missing-class-docstring
|
|
# pylint: disable=no-self-use
|
|
class TestCallingConventionAnalysis(unittest.TestCase):
|
|
def test_itanium_qualified_free_function_does_not_gain_this(self):
|
|
"""Machine facts must disambiguate namespace functions from members."""
|
|
binary = os.path.join(test_location, "x86_64", "cpp_qualified_symbols.so")
|
|
project = angr.Project(binary, auto_load_libs=False)
|
|
cfg = project.analyses.CFGFast(normalize=True)
|
|
project.analyses.CompleteCallingConventions(recover_variables=True, cfg=cfg.model, analyze_callsites=True)
|
|
free = project.kb.functions["_ZN4demo10free_valueEv"]
|
|
assert free.prototype is not None
|
|
assert len(free.prototype.args) == 0
|
|
assert isinstance(free.prototype.returnty, SimTypeInt)
|
|
member = project.kb.functions["_ZN4demo3Box3addEi"]
|
|
assert member.prototype is not None
|
|
assert len(member.prototype.args) == 2
|
|
|
|
def _run_fauxware(self, arch, function_and_cc_list):
|
|
binary_path = os.path.join(test_location, arch, "fauxware")
|
|
fauxware = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = fauxware.analyses.CFG()
|
|
|
|
for func_name, expected_cc in function_and_cc_list:
|
|
authenticate = cfg.functions[func_name]
|
|
_ = fauxware.analyses.VariableRecoveryFast(authenticate)
|
|
|
|
cc_analysis = fauxware.analyses.CallingConvention(authenticate, cfg=cfg.model, analyze_callsites=True)
|
|
cc = cc_analysis.cc
|
|
|
|
assert cc == expected_cc
|
|
|
|
def _run_cgc(self, binary_name):
|
|
pass
|
|
# binary_path = os.path.join(bin_location, '..', 'binaries-private', 'cgc_qualifier_event', 'cgc', binary_name)
|
|
# project = angr.Project(binary_path, auto_load_libs=False)
|
|
#
|
|
# categorization = project.analyses.FunctionCategorizationAnalysis()
|
|
|
|
# tag_manager = categorization.function_tag_manager
|
|
# print "INPUT:", map(hex, tag_manager.input_functions())
|
|
# print "OUTPUT:", map(hex, tag_manager.output_functions())
|
|
|
|
def test_fauxware_i386(self):
|
|
self._run_fauxware("i386", [("authenticate", SimCCCdecl(archinfo.arch_from_id("i386")))])
|
|
|
|
def test_fauxware_x86_64(self):
|
|
amd64 = archinfo.arch_from_id("amd64")
|
|
self._run_fauxware(
|
|
"x86_64",
|
|
[
|
|
(
|
|
"authenticate",
|
|
SimCCSystemVAMD64(
|
|
amd64,
|
|
),
|
|
),
|
|
],
|
|
)
|
|
|
|
@requires_binaries_private
|
|
def test_cgc_binary1(self):
|
|
self._run_cgc("002ba801_01")
|
|
|
|
@requires_binaries_private
|
|
def test_cgc_binary2(self):
|
|
self._run_cgc("01cf6c01_01")
|
|
|
|
#
|
|
# Full-binary calling convention analysis
|
|
#
|
|
|
|
def check_arg(self, arg, expected_str):
|
|
if isinstance(arg, SimRegArg):
|
|
arg_str = f"r_{arg.reg_name}"
|
|
else:
|
|
raise TypeError(f"Unsupported argument type {type(arg)}.")
|
|
return arg_str == expected_str
|
|
|
|
def check_args(self, func_name, args, expected_arg_strs):
|
|
assert len(args) == len(expected_arg_strs), (
|
|
f"Wrong number of arguments for function {func_name}. Got {len(args)}, expect {len(expected_arg_strs)}."
|
|
)
|
|
|
|
for idx, (arg, expected_arg_str) in enumerate(zip(args, expected_arg_strs)):
|
|
r = self.check_arg(arg, expected_arg_str)
|
|
assert r, f"Incorrect argument {idx} for function {func_name}. Got {arg}, expect {expected_arg_str}."
|
|
|
|
def _a(self, funcs, func_name):
|
|
func = funcs[func_name]
|
|
return func.calling_convention.arg_locs(func.prototype)
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_x8664_dir_gcc_O0(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "dir_gcc_-O0")
|
|
proj = angr.Project(binary_path, auto_load_libs=False, load_debug_info=False)
|
|
|
|
cfg = proj.analyses.CFG() # fill in the default kb
|
|
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
funcs = cfg.kb.functions
|
|
|
|
# check args
|
|
expected_args = {
|
|
"c_ispunct": ["r_rdi"],
|
|
"file_failure": ["r_rdi", "r_rsi", "r_rdx"],
|
|
"to_uchar": ["r_rdi"],
|
|
"dot_or_dotdot": ["r_rdi"],
|
|
"emit_mandatory_arg_note": [],
|
|
"emit_size_note": [],
|
|
"emit_ancillary_info": ["r_rdi"],
|
|
"emit_try_help": [],
|
|
"dev_ino_push": ["r_rdi", "r_rsi"],
|
|
"main": ["r_rdi", "r_rsi"],
|
|
"queue_directory": ["r_rdi", "r_rsi", "r_rdx"],
|
|
}
|
|
|
|
for func_name, args in expected_args.items():
|
|
self.check_args(func_name, self._a(funcs, func_name), args)
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_armel_fauxware(self, *, mode):
|
|
binary_path = os.path.join(test_location, "armel", "fauxware")
|
|
proj = angr.Project(binary_path, auto_load_libs=False, load_debug_info=False)
|
|
|
|
cfg = proj.analyses.CFG() # fill in the default kb
|
|
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
funcs = cfg.kb.functions
|
|
|
|
# check args
|
|
expected_args = {
|
|
# main args are not used
|
|
# "main": ["r_r0", "r_r1"],
|
|
"accepted": [],
|
|
"rejected": [],
|
|
"authenticate": ["r_r0", "r_r1"],
|
|
# or "authenticate": ["r_r0", "r_r1", "r_r2"]
|
|
# TECHNICALLY WRONG but what are you gonna do about it
|
|
# details: open(3) can take either 2 or 3 args. we use the 2 arg version but we have the 3 arg version
|
|
# hardcoded in angr. the third arg is still "live" from the function start.
|
|
}
|
|
|
|
for func_name, args in expected_args.items():
|
|
self.check_args(func_name, self._a(funcs, func_name), args)
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_x8664_void(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "types", "void")
|
|
proj = angr.Project(binary_path, auto_load_libs=False, load_debug_info=False)
|
|
|
|
cfg = proj.analyses.CFG()
|
|
|
|
proj.analyses.CompleteCallingConventions(
|
|
mode=mode, recover_variables=True, cfg=cfg.model, analyze_callsites=True
|
|
)
|
|
|
|
funcs = cfg.kb.functions
|
|
|
|
groundtruth = {
|
|
"func_1": None,
|
|
"func_2": None,
|
|
"func_3": "rax",
|
|
"func_4": None,
|
|
"func_5": None,
|
|
"func_6": "rax",
|
|
}
|
|
|
|
for func in funcs.values():
|
|
if func.is_simprocedure or func.is_alignment:
|
|
continue
|
|
if func.calling_convention is None:
|
|
continue
|
|
if func.name in groundtruth:
|
|
r = groundtruth[func.name]
|
|
if r is None:
|
|
assert isinstance(func.prototype.returnty, SimTypeBottom)
|
|
else:
|
|
ret_val = func.calling_convention.return_val(func.prototype.returnty)
|
|
assert isinstance(ret_val, SimRegArg)
|
|
assert ret_val.reg_name == r
|
|
|
|
def test_x86_saved_regs(self):
|
|
# Calling convention analysis should be able to determine calling convention of functions with registers
|
|
# saved on the stack.
|
|
binary_path = os.path.join(test_location, "cgc", "NRFIN_00036")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG()
|
|
func = cfg.functions[0x80494F0] # int2str
|
|
|
|
proj.analyses.VariableRecoveryFast(func)
|
|
cca = proj.analyses.CallingConvention(func)
|
|
cc = cca.cc
|
|
prototype = cca.prototype
|
|
|
|
assert cc is not None, (
|
|
"Calling convention analysis failed to determine the calling convention of function 0x80494f0."
|
|
)
|
|
assert isinstance(cc, SimCCCdecl)
|
|
assert prototype is not None
|
|
assert len(prototype.args) == 3
|
|
arg_locs = cc.arg_locs(prototype)
|
|
assert arg_locs[0] == SimStackArg(4, 4)
|
|
assert arg_locs[1] == SimStackArg(8, 4)
|
|
assert arg_locs[2] == SimStackArg(12, 4)
|
|
|
|
func_exit = cfg.functions[0x804A1A9] # exit
|
|
|
|
proj.analyses.VariableRecoveryFast(func_exit)
|
|
cca = proj.analyses.CallingConvention(func_exit)
|
|
cc = cca.cc
|
|
prototype = cca.prototype
|
|
|
|
assert func_exit.returning is False
|
|
assert cc is not None, (
|
|
"Calling convention analysis failed to determine the calling convention of function 0x804a1a9."
|
|
)
|
|
assert isinstance(cc, SimCCCdecl)
|
|
assert prototype is not None
|
|
assert len(prototype.args) == 1
|
|
assert cc.arg_locs(prototype)[0] == SimStackArg(4, 4)
|
|
|
|
def test_callsite_inference_amd64(self):
|
|
# Calling convention analysis should be able to determine calling convention of a library function by
|
|
# analyzing its callsites.
|
|
binary_path = os.path.join(test_location, "x86_64", "decompiler", "morton")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
cfg = proj.analyses.CFG(data_references=True, normalize=True)
|
|
|
|
func = cfg.functions.function(name="mosquitto_publish", plt=True)
|
|
assert func is not None
|
|
proj.analyses.VariableRecoveryFast(func)
|
|
cca = proj.analyses.CallingConvention(func, analyze_callsites=True)
|
|
assert cca.prototype is not None
|
|
assert len(cca.prototype.args) == 7
|
|
|
|
def test_x64_return_value_used(self):
|
|
binary_path = os.path.join(test_location, "x86_64", "cwebp-0.3.1-feh-original")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
cfg = proj.analyses.CFGFast(normalize=True, data_references=True, force_complete_scan=False)
|
|
func = proj.kb.functions.get_by_addr(0x4046E0)
|
|
proj.analyses.VariableRecoveryFast(func)
|
|
cca = proj.analyses.CallingConvention(func=func, cfg=cfg.model, analyze_callsites=True)
|
|
|
|
assert cca.prototype is not None
|
|
assert cca.prototype.returnty is not None
|
|
|
|
def test_armhf_thumb_movcc(self):
|
|
binary_path = os.path.join(test_location, "armhf", "amp_challenge_07.gcc")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
_ = proj.analyses.CFGFast(normalize=True, data_references=True, regions=[(0xFEC94, 0xFEF60)])
|
|
f = proj.kb.functions[0xFEC95]
|
|
proj.analyses.VariableRecoveryFast(f)
|
|
cca = proj.analyses.CallingConvention(f)
|
|
|
|
assert cca.prototype is not None
|
|
assert cca.cc is not None
|
|
assert isinstance(cca.prototype, SimTypeFunction)
|
|
assert len(cca.prototype.args) == 2
|
|
|
|
def test_armhf_thumb_floats(self):
|
|
binary_path = os.path.join(test_location, "armhf", "float_int_conversion.elf")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
_ = proj.analyses.CFGFast(normalize=True, data_references=True)
|
|
proj.analyses.CompleteCallingConventions()
|
|
|
|
assert proj.kb.functions["float_to_int"].prototype is not None
|
|
assert proj.kb.functions["float_to_int"].prototype.args == (SimTypeFloat(),)
|
|
assert proj.kb.functions["float_to_int"].prototype.returnty == SimTypeInt()
|
|
assert proj.kb.functions["int_to_float"].prototype is not None
|
|
assert proj.kb.functions["int_to_float"].prototype.args == (SimTypeInt(),)
|
|
assert proj.kb.functions["int_to_float"].prototype.returnty == SimTypeFloat()
|
|
assert proj.kb.functions["increment_float"].prototype is not None
|
|
assert proj.kb.functions["increment_float"].prototype.args == (SimTypeFloat(), SimTypeFloat())
|
|
assert proj.kb.functions["increment_float"].prototype.returnty == SimTypeFloat()
|
|
assert proj.kb.functions["float_to_rounded_float"].prototype is not None
|
|
assert proj.kb.functions["float_to_rounded_float"].prototype.args == (SimTypeFloat(),)
|
|
assert proj.kb.functions["float_to_rounded_float"].prototype.returnty == SimTypeFloat()
|
|
assert proj.kb.functions["compare_floats"].prototype is not None
|
|
assert set(proj.kb.functions["compare_floats"].prototype.args) == {SimTypeFloat(), SimTypeFloat(), SimTypeInt()}
|
|
assert proj.kb.functions["compare_floats"].prototype.returnty == SimTypeInt()
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_workers(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "1after909")
|
|
proj = angr.Project(binary_path, auto_load_libs=False, load_debug_info=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True) # fill in the default kb
|
|
|
|
_ = proj.analyses.CompleteCallingConventions(
|
|
mode=mode, cfg=cfg.model, recover_variables=True, workers=4, show_progressbar=True
|
|
)
|
|
|
|
assert all(
|
|
func.prototype is not None
|
|
for func in proj.kb.functions.values()
|
|
if not (func.is_alignment or func.is_simprocedure or func.is_plt)
|
|
)
|
|
|
|
def test_dead_workers_raise_instead_of_hanging(self):
|
|
"""
|
|
Regression test for angr issue #6529.
|
|
|
|
If every worker process dies, the result-collection loop used to spin forever waiting for results that could
|
|
never arrive - the analysis "hung forever" with no error. It must now fail loudly instead. Workers can die
|
|
for reasons entirely outside this analysis (an unpicklable analysis under the "spawn" start method, an OOM
|
|
kill, a segfault in a native lifter), so we simply make the worker routine die on purpose.
|
|
"""
|
|
binary_path = os.path.join(test_location, "x86_64", "fauxware")
|
|
proj = angr.Project(binary_path, auto_load_libs=False, load_debug_info=False)
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
|
|
def _dying_worker_routine(self, worker_id, initializer): # pylint:disable=unused-argument
|
|
raise RuntimeError(f"simulated worker {worker_id} crash")
|
|
|
|
original = CompleteCallingConventionsAnalysis._worker_routine
|
|
CompleteCallingConventionsAnalysis._worker_routine = _dying_worker_routine
|
|
try:
|
|
start = time.time()
|
|
with self.assertRaises(AngrRuntimeError) as ctx:
|
|
proj.analyses.CompleteCallingConventions(cfg=cfg.model, workers=2)
|
|
elapsed = time.time() - start
|
|
finally:
|
|
CompleteCallingConventionsAnalysis._worker_routine = original
|
|
|
|
assert "worker processes exited" in str(ctx.exception)
|
|
# it must give up promptly rather than hang; the grace period is the only intentional delay
|
|
assert elapsed < DEAD_WORKER_GRACE_PERIOD + 60, f"took {elapsed:.1f}s to notice that all workers had died"
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_tail_calls(self, *, mode):
|
|
for opt_level in (1, 2):
|
|
binary_path = os.path.join(test_location, "x86_64", f"tailcall-O{opt_level}")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
for func in ["target", "direct", "plt"]:
|
|
proto = proj.kb.functions[func].prototype
|
|
assert proto is not None
|
|
assert len(proto.args) == 1
|
|
assert isinstance(proto.args[0], SimTypeInt)
|
|
assert isinstance(proto.returnty, SimTypeInt)
|
|
|
|
def test_ls_gcc_O0_timespec_cmp(self):
|
|
binary_path = os.path.join(test_location, "x86_64", "decompiler", "ls_gcc_O0")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
proj.analyses.CFG(normalize=True)
|
|
proj.analyses.VariableRecoveryFast(proj.kb.functions["timespec_cmp"])
|
|
cca = proj.analyses.CallingConvention(proj.kb.functions["timespec_cmp"])
|
|
|
|
assert cca.prototype is not None
|
|
assert len(cca.prototype.args) == 4
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_run_multiple_times(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "fauxware")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
expected_prototype = proj.kb.functions["main"].prototype
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
assert proj.kb.functions["main"].prototype == expected_prototype
|
|
|
|
proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
assert proj.kb.functions["main"].prototype == expected_prototype
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_test_three_arguments(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "test.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
# the node 0x401226 must be in its own function
|
|
node = cfg.model.get_any_node(0x401226)
|
|
assert node is not None
|
|
assert node.function_address == 0x401226
|
|
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
assert proj.kb.functions["test_syntax_error"].prototype is not None
|
|
assert proj.kb.functions["test_syntax_error"].prototype.variadic is True
|
|
assert proj.kb.functions["expr"].prototype is not None
|
|
assert len(proj.kb.functions["expr"].prototype.args) == 0
|
|
|
|
def test_windows_partial_input_variable_overwrite(self):
|
|
binary_path = os.path.join(test_location, "x86_64", "netfilter_b64.sys")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.VariableRecoveryFast(proj.kb.functions[0x140001A90])
|
|
cc = proj.analyses.CallingConvention(cfg.kb.functions[0x140001A90])
|
|
assert cc.cc is not None
|
|
assert cc.prototype is not None
|
|
assert len(cc.prototype.args) == 3
|
|
|
|
def test_windows_call_return_register_overwrite(self):
|
|
binary_path = os.path.join(test_location, "x86_64", "windows", "CorsairLLAccess64.sys")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
func = cfg.kb.functions[0x1400014D0]
|
|
proj.analyses.VariableRecoveryFast(func)
|
|
cc = proj.analyses.CallingConvention(func)
|
|
assert cc.cc is not None
|
|
assert cc.prototype is not None
|
|
assert len(cc.prototype.args) == 6
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_cdecl_nonconsecutive_stack_args(self, *, mode):
|
|
binary_path = os.path.join(test_location, "i386", "calling_convention_0.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
func0 = cfg.kb.functions["sub_12000"]
|
|
assert isinstance(func0.calling_convention, SimCCCdecl)
|
|
assert func0.prototype is not None
|
|
assert len(func0.prototype.args) == 4
|
|
|
|
func1 = cfg.kb.functions["sub_119320"]
|
|
assert isinstance(func1.calling_convention, SimCCCdecl)
|
|
assert func1.prototype is not None
|
|
assert len(func1.prototype.args) == 2
|
|
|
|
func2 = cfg.kb.functions["sub_8D9F0"]
|
|
assert isinstance(func2.calling_convention, SimCCCdecl)
|
|
assert func2.prototype is not None
|
|
assert len(func2.prototype.args) == 4
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_call_sites_arguments_csplit(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "csplit.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
func0 = cfg.kb.functions["check_for_offset"]
|
|
assert isinstance(func0.calling_convention, SimCCSystemVAMD64)
|
|
assert func0.prototype is not None
|
|
assert len(func0.prototype.args) == 3
|
|
|
|
func1 = cfg.kb.functions["xstrtoimax"]
|
|
assert isinstance(func1.calling_convention, SimCCSystemVAMD64)
|
|
assert func1.prototype is not None
|
|
assert len(func1.prototype.args) == 5
|
|
|
|
func2 = cfg.kb.functions["remove_line"]
|
|
assert isinstance(func2.calling_convention, SimCCSystemVAMD64)
|
|
assert func2.prototype is not None
|
|
assert len(func2.prototype.args) == 0
|
|
|
|
func3 = cfg.kb.functions["dump_rest_of_file"]
|
|
assert isinstance(func3.calling_convention, SimCCSystemVAMD64)
|
|
assert func3.prototype is not None
|
|
assert len(func3.prototype.args) == 0
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_call_sites_arguments_test(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "test.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
func0 = cfg.kb.functions["term"]
|
|
assert isinstance(func0.calling_convention, SimCCSystemVAMD64)
|
|
assert func0.prototype is not None
|
|
assert len(func0.prototype.args) == 0
|
|
|
|
func1 = cfg.kb.functions["unary_operator"]
|
|
assert isinstance(func1.calling_convention, SimCCSystemVAMD64)
|
|
assert func1.prototype is not None
|
|
assert len(func1.prototype.args) == 0
|
|
|
|
func2 = cfg.kb.functions["advance"]
|
|
assert isinstance(func2.calling_convention, SimCCSystemVAMD64)
|
|
assert func2.prototype is not None
|
|
assert len(func2.prototype.args) == 1
|
|
|
|
# external function
|
|
func3 = cfg.kb.functions["quote"]
|
|
assert isinstance(func3.calling_convention, SimCCSystemVAMD64)
|
|
assert func3.prototype is not None
|
|
assert len(func3.prototype.args) == 1
|
|
|
|
func4 = cfg.kb.functions["posixtest"]
|
|
assert isinstance(func4.calling_convention, SimCCSystemVAMD64)
|
|
assert func4.prototype is not None
|
|
assert len(func4.prototype.args) == 1
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_call_sites_arguments_copy(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "copy.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
func0 = cfg.kb.functions["punch_hole"]
|
|
assert isinstance(func0.calling_convention, SimCCSystemVAMD64)
|
|
assert func0.prototype is not None
|
|
assert len(func0.prototype.args) == 3
|
|
|
|
func1 = cfg.kb.functions["can_write_any_file"]
|
|
assert isinstance(func1.calling_convention, SimCCSystemVAMD64)
|
|
assert func1.prototype is not None
|
|
assert len(func1.prototype.args) == 0
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_call_sites_arguments_mv(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "mv.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
# external function
|
|
func0 = cfg.kb.functions["last_component"]
|
|
assert isinstance(func0.calling_convention, SimCCSystemVAMD64)
|
|
assert func0.prototype is not None
|
|
assert len(func0.prototype.args) == 1
|
|
|
|
# external function
|
|
func1 = cfg.kb.functions["strip_trailing_slashes"]
|
|
assert isinstance(func1.calling_convention, SimCCSystemVAMD64)
|
|
assert func1.prototype is not None
|
|
assert len(func1.prototype.args) == 1
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_call_sites_arguments_df(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "df.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
func0 = cfg.kb.functions["alloc_table_row"]
|
|
assert isinstance(func0.calling_convention, SimCCSystemVAMD64)
|
|
assert func0.prototype is not None
|
|
assert len(func0.prototype.args) == 0
|
|
|
|
func1 = cfg.kb.functions["get_header"]
|
|
assert isinstance(func1.calling_convention, SimCCSystemVAMD64)
|
|
assert func1.prototype is not None
|
|
assert len(func1.prototype.args) == 0
|
|
|
|
func2 = cfg.kb.functions["get_field_list"]
|
|
assert isinstance(func2.calling_convention, SimCCSystemVAMD64)
|
|
assert func2.prototype is not None
|
|
assert len(func2.prototype.args) == 0
|
|
|
|
func3 = cfg.kb.functions["alloc_field"]
|
|
assert isinstance(func3.calling_convention, SimCCSystemVAMD64)
|
|
assert func3.prototype is not None
|
|
assert len(func3.prototype.args) == 2
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_cdecl_nonconsecutive_stack_args_2(self, *, mode):
|
|
binary_path = os.path.join(
|
|
test_location, "i386", "windows", "48460c9633d06cad3e3b41c87de04177d129906610c5bbdebc7507a211100e98"
|
|
)
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
func_main = cfg.kb.functions[0x4106F0]
|
|
assert isinstance(func_main.calling_convention, SimCCCdecl)
|
|
assert func_main.prototype is not None
|
|
assert len(func_main.prototype.args) == 4
|
|
|
|
@cca_mode("fast,variables")
|
|
def test_cdecl_nonconsecutive_stack_args_3(self, *, mode):
|
|
binary_path = os.path.join(
|
|
test_location, "i386", "windows", "48460c9633d06cad3e3b41c87de04177d129906610c5bbdebc7507a211100e98"
|
|
)
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(mode=mode, recover_variables=True)
|
|
|
|
func_main = cfg.kb.functions[0x401A90]
|
|
assert func_main.info["bp_as_gpr"] is False
|
|
assert isinstance(func_main.calling_convention, SimCCCdecl)
|
|
assert func_main.prototype is not None
|
|
assert len(func_main.prototype.args) == 1
|
|
|
|
def _check_return_type_comprehensive(self, funcs, func_name, expected_type_cls):
|
|
func = funcs[func_name]
|
|
ret_type = func.prototype.returnty
|
|
is_match = ret_type == expected_type_cls()
|
|
self.assertTrue(is_match)
|
|
|
|
def test_return_typecheck(self):
|
|
for arch in ["x86_64", "riscv64", "aarch64"]:
|
|
binary_path = os.path.join(test_location, arch, f"test_return_type_{arch}.elf")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(recover_variables=True, cfg=cfg)
|
|
funcs = proj.kb.functions
|
|
|
|
self._check_return_type_comprehensive(funcs, "ret_add_int", SimTypeInt)
|
|
self._check_return_type_comprehensive(funcs, "ret_sub_int", SimTypeInt)
|
|
self._check_return_type_comprehensive(funcs, "ret_mul_int", SimTypeInt)
|
|
|
|
if arch == "riscv64":
|
|
self._check_return_type_comprehensive(funcs, "ret_or_int", SimTypeLongLong)
|
|
self._check_return_type_comprehensive(funcs, "ret_xor_int", SimTypeLongLong)
|
|
else:
|
|
self._check_return_type_comprehensive(funcs, "ret_or_int", SimTypeInt)
|
|
self._check_return_type_comprehensive(funcs, "ret_xor_int", SimTypeInt)
|
|
|
|
if arch == "aarch64":
|
|
self._check_return_type_comprehensive(funcs, "ret_cast_int", SimTypeLongLong)
|
|
else:
|
|
self._check_return_type_comprehensive(funcs, "ret_cast_int", SimTypeInt)
|
|
self._check_return_type_comprehensive(funcs, "ret_add_long", SimTypeLongLong)
|
|
self._check_return_type_comprehensive(funcs, "ret_or_long", SimTypeLongLong)
|
|
|
|
if "ret_ptr" in funcs and funcs["ret_ptr"].prototype:
|
|
ret_size = funcs["ret_ptr"].prototype.returnty.size
|
|
self.assertEqual(ret_size, 64)
|
|
|
|
@cca_mode("fast")
|
|
def test_void_tail_call(self, *, mode):
|
|
binary_path = os.path.join(test_location, "x86_64", "g_game.o")
|
|
proj = angr.Project(binary_path, auto_load_libs=False)
|
|
|
|
cfg = proj.analyses.CFG(normalize=True)
|
|
proj.analyses.CompleteCallingConventions(
|
|
mode=mode, recover_variables=True, cfg=cfg.model, analyze_callsites=True
|
|
)
|
|
|
|
func_reborn = cfg.kb.functions["G_PlayerReborn"]
|
|
assert func_reborn.prototype is not None
|
|
assert isinstance(func_reborn.prototype.returnty, SimTypeBottom), (
|
|
f"G_PlayerReborn should be void, got {func_reborn.prototype.returnty}"
|
|
)
|
|
|
|
func_init = cfg.kb.functions["G_InitPlayer"]
|
|
assert func_init.prototype is not None
|
|
assert isinstance(func_init.prototype.returnty, SimTypeBottom), (
|
|
f"G_InitPlayer should be void (tail-calls void function), got {func_init.prototype.returnty}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# logging.getLogger("angr.analyses.variable_recovery.variable_recovery_fast").setLevel(logging.DEBUG)
|
|
logging.getLogger("angr.analyses.calling_convention").setLevel(logging.INFO)
|
|
unittest.main()
|