mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
* [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.22 → v0.16.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.22...v0.16.0) * Apply fixes * Add values() --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kevin Phoenix <kevin@kphoenix.us>
699 lines
27 KiB
Python
Executable file
699 lines
27 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
__package__ = __package__ or "tests.engines" # pylint:disable=redefined-builtin
|
|
|
|
import os
|
|
import unittest
|
|
|
|
import claripy
|
|
from archinfo.arch_amd64 import ArchAMD64
|
|
from archinfo.arch_soot import (
|
|
ArchSoot,
|
|
SootAddressDescriptor,
|
|
SootAddressTerminator,
|
|
SootArgument,
|
|
SootMethodDescriptor,
|
|
)
|
|
|
|
import angr
|
|
from angr.engines.soot.method_dispatcher import resolve_method
|
|
from angr.engines.soot.values import SimSootValue_ArrayRef, SimSootValue_ThisRef
|
|
from angr.storage.memory_mixins import DefaultMemory, JavaVmMemory, KeyValueMemory
|
|
|
|
try:
|
|
import pysoot
|
|
except ModuleNotFoundError:
|
|
pysoot = None
|
|
|
|
from tests.common import bin_location
|
|
|
|
|
|
class TestJava(unittest.TestCase):
|
|
test_location = os.path.join(bin_location, "tests", "java")
|
|
sdk_path = os.path.join(os.path.expanduser("~"), "Android", "Sdk", "platforms")
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_fauxware(self):
|
|
# create project
|
|
binary_path = os.path.join(self.test_location, "fauxware_java_jni", "fauxware.jar")
|
|
jni_options = {"jni_libs": ["libfauxware.so"]}
|
|
project = angr.Project(binary_path, main_opts=jni_options, auto_load_libs=True)
|
|
entry = project.factory.entry_state()
|
|
simgr = project.factory.simgr(entry)
|
|
|
|
# find path to `accepted()` method
|
|
accepted_method = SootMethodDescriptor.from_string("Fauxware.accepted()").address()
|
|
simgr.explore(find=lambda s: s.addr == accepted_method)
|
|
|
|
assert len(simgr.found) == 1
|
|
state = simgr.found[0]
|
|
|
|
# eval password
|
|
cmd_line_args = project.simos.get_cmd_line_args(state)
|
|
password = state.solver.eval(cmd_line_args[0])
|
|
assert password == "SOSNEAKY"
|
|
|
|
@unittest.skipUnless(os.path.exists(sdk_path), "Android SDK found")
|
|
def test_apk_loading(self):
|
|
loading_opts = {
|
|
"android_sdk": self.sdk_path,
|
|
"entry_point": "com.example.antoniob.android1.MainActivity.onCreate",
|
|
"entry_point_params": ("android.os.Bundle",),
|
|
}
|
|
project = angr.Project(
|
|
os.path.join(self.test_location, "android1.apk"), main_opts=loading_opts, auto_load_libs=False
|
|
)
|
|
|
|
blank_state = project.factory.blank_state()
|
|
a1 = SimSootValue_ThisRef.new_object(blank_state, "com.example.antoniob.android1.MainActivity")
|
|
a2 = SimSootValue_ThisRef.new_object(blank_state, "android.os.Bundle", symbolic=True)
|
|
args = [SootArgument(arg, arg.type) for arg in [a1, a2]]
|
|
entry = project.factory.entry_state(args=args)
|
|
|
|
simgr = project.factory.simgr(entry)
|
|
simgr.step()
|
|
simgr.step()
|
|
assert simgr.active[0].addr.block_idx == 0
|
|
assert simgr.active[0].addr.stmt_idx == 3
|
|
simgr.run()
|
|
assert len(simgr.deadended) == 1
|
|
assert type(simgr.deadended[0].addr) is SootAddressTerminator
|
|
|
|
#
|
|
# Command line arguments
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_cmd_line_args(self):
|
|
project = self.create_project("cmd_line_args", load_native_libs=False)
|
|
entry = project.factory.entry_state()
|
|
simgr = project.factory.simgr(entry)
|
|
simgr.run()
|
|
assert len(simgr.deadended) == 2
|
|
state1, state2 = tuple(simgr.deadended)
|
|
|
|
# get symbol of args[0] from memory
|
|
args = state1.globals["cmd_line_args"]
|
|
args0_arrref = SimSootValue_ArrayRef(args, 0)
|
|
args0_strref = state1.memory.load(args0_arrref)
|
|
args0_strval = state1.memory.load(args0_strref)
|
|
|
|
# eval args[0] on both states
|
|
str1 = state1.solver.eval(args0_strval)
|
|
str2 = state2.solver.eval(args0_strval)
|
|
assert "secret_value" in [str1, str2]
|
|
|
|
#
|
|
# JNI Version Information
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_jni_version_information(self):
|
|
project = self.create_project("jni_version_information")
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_get_version", assert_locals={"i0": 0x10008})
|
|
|
|
#
|
|
# JNI Global and Local References
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_jni_global_and_local_refs(self):
|
|
project = self.create_project("jni_global_and_local_refs")
|
|
|
|
assertions = {"global refs dict": lambda state: state.jni_references.global_refs == {}}
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_jni_global_refs", assert_locals={"i0": 0xA}, assertions=assertions
|
|
)
|
|
|
|
#
|
|
# JNI Object Operations
|
|
#
|
|
|
|
def test_jni_object_operations(self):
|
|
project = self.create_project("jni_object_operations")
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_alloc_object", assert_locals={"i0": 0})
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_new_object", assert_locals={"i0": 1})
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_new_subclass_object", assert_locals={"i0": 2})
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_isinstanceof",
|
|
assert_locals={"i0": 1, "i1": 1, "i2": 0, "i3": 1},
|
|
)
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_issameobject", assert_locals={"i0": 0, "i1": 1})
|
|
|
|
#
|
|
# JNI String Operations
|
|
#
|
|
|
|
def test_jni_string_operations(self):
|
|
project = self.create_project("jni_string_operations")
|
|
|
|
assertions = {
|
|
"1st string": lambda state: state.solver.eval_one(self.load_string(state, "r0")) == "mum",
|
|
"2nd string": lambda state: state.solver.eval_one(self.load_string(state, "r1")) == "himum!",
|
|
}
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_string_operations",
|
|
assert_locals={"i0": 0x3, "i1": 0x6},
|
|
assertions=assertions,
|
|
)
|
|
|
|
#
|
|
# JNI Field Access
|
|
#
|
|
|
|
def test_jni_field_access(self):
|
|
project = self.create_project("jni_field_access")
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_static_field_access_basic",
|
|
assert_locals={"i0": 0x0, "i1": 0x1, "i2": 0xA, "i3": 0xB, "i4": 0x7, "i5": 0xB, "i6": 0x0, "i7": 0x9},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_jni_static_field_access", assert_locals={"i0": 0, "i1": 5}
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_static_field_access_subclass",
|
|
assert_locals={"i0": 1, "i1": 10, "i2": 30, "i3": 1},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_instance_field_access_0",
|
|
assert_locals={"i0": 0, "i1": 10, "i2": 5, "i3": 5},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_instance_field_access_1",
|
|
assert_locals={"i0": 0, "i1": 1, "i2": 10, "i3": 4, "i4": 4, "i5": 1},
|
|
)
|
|
|
|
#
|
|
# JNI Method Calls
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_jni_method_calls(self):
|
|
project = self.create_project("jni_method_calls")
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_jni_non_virtual_instance_method_call", assert_locals={"i0": 5}
|
|
)
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_jni_instance_method_calls_basic", assert_locals={"i0": 7, "i1": 7}
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_instance_method_calls_subclass",
|
|
assert_locals={"i0": 2, "i1": 2},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_instance_method_calls_shared_method_id",
|
|
assert_locals={"i0": 8, "i1": 2},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_jni_instance_method_calls_args", assert_locals={"i0": 11}
|
|
)
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_static_method_call", assert_locals={"i0": 10})
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_jni_static_method_call_return_obj", assert_locals={"i0": 7}
|
|
)
|
|
|
|
#
|
|
# JNI Primitive Datatypes
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_jni_primitive_datatypes(self):
|
|
project = self.create_project("jni_primitive_datatypes")
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_boolean",
|
|
assert_locals={"z0": 1, "z1": 0, "z2": 1, "z3": 0, "z4": 1},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_byte", assert_locals={"b5": 30, "b8": 0xFFFFFF80, "b11": 0}
|
|
)
|
|
|
|
self.run_method(project=project, method="MixedJava.test_char", assert_locals={"c4": 21, "c6": 0, "c9": 1})
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_short",
|
|
assert_locals={"s3": 0x1000, "s5": 0xFFFFF000, "s0": 11, "s9": 0},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_int",
|
|
assert_locals={"i1": 0xFFFFFFF6, "i3": 0, "i5": 0x80000001, "i7": 0x7FFFFFFF},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project, method="MixedJava.test_long", assert_locals={"l1": 0xFFFFFFFFFFFFFFFF, "l3": 1}
|
|
)
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_jni_object_arrays(self):
|
|
project = self.create_project("jni_object_array_operations")
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_access_object_array", assert_locals={"i0": 7})
|
|
|
|
self.run_method(project=project, method="MixedJava.test_jni_new_object_array", assert_locals={"i0": 10})
|
|
|
|
#
|
|
# JNI Array Operations
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_jni_array_operations(self):
|
|
project = self.create_project("jni_array_operations")
|
|
|
|
# test_jni_newarray
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_newarray",
|
|
assert_locals={"i0": 0, "i1": 1, "i2": 2, "i3": 3, "i4": 4},
|
|
)
|
|
|
|
# test_jni_getarrayregion
|
|
state = self.run_method(project=project, method="MixedJava.test_jni_getarrayregion")
|
|
a = self.load_value_from_stack(state, "i1")
|
|
state.solver.add(a == 15)
|
|
idx = state.posix.stdin.content[0][0]
|
|
assert state.solver.eval_one(idx) == 7
|
|
|
|
# test_jni_setarrayregion1
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_setarrayregion1",
|
|
assert_locals={"i0": 0, "i1": 3, "i2": 2, "i3": 1, "i4": 4},
|
|
)
|
|
|
|
# test_jni_setarrayregion2
|
|
state = self.run_method(project=project, method="MixedJava.test_jni_setarrayregion2")
|
|
a = self.load_value_from_stack(state, "i1")
|
|
state.solver.add(a == 2)
|
|
idx = state.posix.stdin.content[0][0]
|
|
idx_value = state.solver.eval_one(idx)
|
|
assert idx_value == 0
|
|
|
|
# test_jni_setarrayregion2
|
|
state = self.run_method(project=project, method="MixedJava.test_jni_setarrayregion2")
|
|
a = self.load_value_from_stack(state, "i1")
|
|
state.solver.add(a == 0)
|
|
idx = state.posix.stdin.content[0][0]
|
|
idx_value = state.solver.eval_exact(idx, 2)
|
|
assert 1 in idx_value
|
|
assert 2 in idx_value
|
|
assert 3 not in idx_value
|
|
|
|
# test_jni_getarrayelements_symbolic
|
|
winning_path = self.get_winning_path(
|
|
project=project, method_fullname="MixedJava.test_jni_getarrayelements_symbolic"
|
|
)
|
|
stdin_packets = winning_path.posix.stdin.content
|
|
idx = winning_path.solver.eval_one(stdin_packets[0][0])
|
|
min_length = winning_path.solver.min(stdin_packets[1][0])
|
|
assert idx == 223
|
|
assert min_length == 224
|
|
|
|
# test_jni_releasearrayelements
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_releasearrayelments",
|
|
assert_locals={"i0": 4, "i1": 3, "i2": 2, "i3": 1, "i4": 0},
|
|
)
|
|
|
|
# test_jni_getarrayelements_and_releasearrayelements
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_jni_getarrayelements_and_releasearrayelements",
|
|
assert_locals={
|
|
"c9": 0xFFFF,
|
|
"c14": 0x0000,
|
|
"b5": 0x0000007F,
|
|
"b10": 0xFFFFFF80,
|
|
"s6": 0x00007FFF,
|
|
"s11": 0xFFFF8000,
|
|
"i7": 0x7FFFFFFF,
|
|
"i12": 0x80000000,
|
|
"l8": 0x7FFFFFFFFFFFFFFF,
|
|
"l13": 0x8000000000000000,
|
|
},
|
|
)
|
|
|
|
# test_jni_getarraylength
|
|
state = self.run_method(project=project, method="MixedJava.test_jni_getarraylength")
|
|
a = state.memory.stack.load("i3")
|
|
assert state.solver.eval(a) == 10
|
|
b = state.memory.stack.load("i4")
|
|
assert state.solver.min(b) == 0
|
|
assert state.solver.max(b) == 255
|
|
|
|
#
|
|
# Method Calls
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_method_calls(self):
|
|
project = self.create_project("method_calls", load_native_libs=False)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_instance_method_calls",
|
|
assert_locals={"i0": 0, "i1": 1, "i2": 1, "i3": 2, "i4": 2, "i5": 2},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_static_method_calls_0",
|
|
assert_locals={"i0": 0, "i1": 1, "i2": 2, "i3": 2},
|
|
)
|
|
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_static_method_calls_1",
|
|
assert_locals={"i0": 0, "i1": 0, "i2": 1, "i3": 2, "i4": 2, "i5": 2},
|
|
)
|
|
|
|
self.run_method(project=project, method="MixedJava.test_special_invoke_0", assert_locals={"i0": 3})
|
|
|
|
self.run_method(project=project, method="MixedJava.test_special_invoke_1", assert_locals={"i0": 4})
|
|
|
|
#
|
|
# Array Operations
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_array_operations(self):
|
|
project = self.create_project("array_operations", load_native_libs=False)
|
|
|
|
# test_basic_array_operations
|
|
self.run_method(
|
|
project=project,
|
|
method="MixedJava.test_basic_array_operations",
|
|
assert_locals={"i1": 0, "i2": 1, "i3": 2, "i4": 3, "i5": 4, "i6": 5, "i7": 2, "i8": 0},
|
|
)
|
|
|
|
# test_symbolic_array_read
|
|
winning_path = self.get_winning_path(project=project, method_fullname="MixedJava.test_symbolic_array_read")
|
|
stdin_packets = winning_path.posix.stdin.content
|
|
input_char, _ = stdin_packets[0]
|
|
solutions = winning_path.solver.eval_upto(input_char, 2)
|
|
assert ord("A") in solutions
|
|
assert ord("C") in solutions
|
|
|
|
# test_symbolic_array_write
|
|
winning_path = self.get_winning_path(project=project, method_fullname="MixedJava.test_symbolic_array_write")
|
|
stdin_packets = winning_path.posix.stdin.content
|
|
idx_symbol, _ = stdin_packets[0]
|
|
val_symbol, _ = stdin_packets[1]
|
|
winning_path.solver.add(val_symbol != 0) # exclude trivial solution
|
|
idx = winning_path.solver.eval(idx_symbol)
|
|
val = winning_path.solver.eval(val_symbol)
|
|
assert idx == 73
|
|
assert val == 53
|
|
|
|
# test_symbolic_array_length
|
|
winning_path = self.get_winning_path(project=project, method_fullname="MixedJava.test_symbolic_array_length")
|
|
stdin_packets = winning_path.posix.stdin.content
|
|
input_char, _ = stdin_packets[0]
|
|
solution = winning_path.solver.eval(input_char)
|
|
assert solution == ord("F")
|
|
|
|
# test_index_of_of_bound0
|
|
state = self.run_method(project=project, method="MixedJava.test_index_of_of_bound0")
|
|
array_len = self.load_value_from_stack(state, "i1")
|
|
assert state.solver.min(array_len) == 0
|
|
assert state.solver.max(array_len) == 255
|
|
|
|
# test_index_of_of_bound1
|
|
state = self.run_method(project=project, method="MixedJava.test_index_of_of_bound1")
|
|
array_len = self.load_value_from_stack(state, "i1")
|
|
assert state.solver.min(array_len) == 101
|
|
assert state.solver.max(array_len) == 255
|
|
|
|
# test_index_of_of_bound2
|
|
state = self.run_method(project=project, method="MixedJava.test_index_of_of_bound2")
|
|
assert self.load_value_from_stack(state, "i1") is not None
|
|
assert self.load_value_from_stack(state, "i2") is None
|
|
assert self.load_value_from_stack(state, "i3") is None
|
|
assert self.load_value_from_stack(state, "i4") is not None
|
|
assert self.load_value_from_stack(state, "i5") is None
|
|
|
|
# test_index_of_of_bound3
|
|
state = self.run_method(project=project, method="MixedJava.test_index_of_of_bound3")
|
|
assert self.load_value_from_stack(state, "i1") is not None
|
|
assert self.load_value_from_stack(state, "i2") is not None
|
|
assert self.load_value_from_stack(state, "i3") is None
|
|
assert self.load_value_from_stack(state, "i4") is not None
|
|
assert self.load_value_from_stack(state, "i5") is None
|
|
|
|
# test_index_of_of_bound4
|
|
state = self.run_method(project=project, method="MixedJava.test_index_of_of_bound4")
|
|
assert self.load_value_from_stack(state, "i1") is not None
|
|
assert self.load_value_from_stack(state, "i2") is not None
|
|
assert self.load_value_from_stack(state, "i3") is None
|
|
|
|
# test_index_of_of_bound5
|
|
state = self.run_method(project=project, method="MixedJava.test_index_of_of_bound5")
|
|
assert self.load_value_from_stack(state, "i1") is not None
|
|
assert self.load_value_from_stack(state, "i2") is not None
|
|
assert self.load_value_from_stack(state, "i3") is None
|
|
|
|
#
|
|
# MultiArray Operations
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_multiarray_operations(self):
|
|
project = self.create_project("multiarray_operations", load_native_libs=False)
|
|
|
|
self.run_method(project=project, method="MixedJava.basic_multiarray_ops", assert_locals={"d1": 4})
|
|
|
|
#
|
|
# Loading
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_loading(self):
|
|
# Test1: test loading with load path
|
|
native_libs_ld_path = os.path.join(self.test_location, "misc", "loading1", "libs")
|
|
jar_path = os.path.join(self.test_location, "misc", "loading1", "mixedjava.jar")
|
|
# define which libraries to load (+ the load path)
|
|
jni_options = {"jni_libs": ["libmixedjava.so"], "jni_libs_ld_path": native_libs_ld_path}
|
|
project = angr.Project(jar_path, main_opts=jni_options, auto_load_libs=True)
|
|
# check if libmixedjava.so was loaded
|
|
loaded_libs = [lib.provides for lib in project.loader.all_elf_objects]
|
|
assert "libmixedjava.so" in loaded_libs
|
|
|
|
# Test 2: test loading without load path
|
|
# => the folder of the JAR is implicitly used as an additional load path
|
|
binary_dir = os.path.join(self.test_location, "misc", "loading2")
|
|
project = self.create_project(binary_dir)
|
|
# check if libmixedjava.so was loaded
|
|
loaded_libs = [lib.provides for lib in project.loader.all_elf_objects]
|
|
assert "libmixedjava.so" in loaded_libs
|
|
|
|
#
|
|
# SimStates
|
|
#
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_toggling_of_simstate(self):
|
|
binary_dir = os.path.join(self.test_location, "misc", "simstates")
|
|
project = self.create_project(binary_dir)
|
|
|
|
state = project.factory.entry_state()
|
|
assert state.ip_is_soot_addr
|
|
assert isinstance(state.arch, ArchSoot)
|
|
assert isinstance(state.memory, JavaVmMemory)
|
|
assert isinstance(state.registers, KeyValueMemory)
|
|
|
|
state.regs.ip = 1
|
|
assert not state.ip_is_soot_addr
|
|
assert isinstance(state.arch, ArchAMD64)
|
|
assert isinstance(state.memory, DefaultMemory)
|
|
assert isinstance(state.registers, DefaultMemory)
|
|
|
|
state.regs._ip = project.entry
|
|
|
|
assert state.ip_is_soot_addr
|
|
assert isinstance(state.arch, ArchSoot)
|
|
assert isinstance(state.memory, JavaVmMemory)
|
|
assert isinstance(state.registers, KeyValueMemory)
|
|
|
|
state.ip = 1
|
|
assert not state.ip_is_soot_addr
|
|
assert isinstance(state.arch, ArchAMD64)
|
|
assert isinstance(state.memory, DefaultMemory)
|
|
assert isinstance(state.registers, DefaultMemory)
|
|
|
|
state_copy = state.copy()
|
|
assert not state_copy.ip_is_soot_addr
|
|
assert isinstance(state_copy.arch, ArchAMD64)
|
|
assert isinstance(state_copy.memory, DefaultMemory)
|
|
assert isinstance(state_copy.registers, DefaultMemory)
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def test_object_tracking(self):
|
|
binary_dir = os.path.join(self.test_location, "object_tracking")
|
|
project = self.create_project(binary_dir, load_native_libs=False)
|
|
bootstrap_state = project.factory.blank_state(addr=SootAddressTerminator())
|
|
mylib_object = SimSootValue_ThisRef.new_object(bootstrap_state, "MyLib", symbolic=True, init_object=False)
|
|
|
|
soot_method = resolve_method(
|
|
bootstrap_state, "testGetterAndSetterConcrete", "MixedJava", ("mylib.MyLib",), init_class=False
|
|
).address()
|
|
|
|
call_state = project.factory.call_state(
|
|
soot_method,
|
|
SootArgument(mylib_object, mylib_object.type, is_this_ref=False),
|
|
base_state=bootstrap_state,
|
|
ret_addr=SootAddressTerminator(),
|
|
)
|
|
|
|
call_state.options.add(angr.options.JAVA_IDENTIFY_GETTER_SETTER)
|
|
call_state.options.add(angr.options.JAVA_TRACK_ATTRIBUTES)
|
|
|
|
simgr = project.factory.simgr(call_state)
|
|
simgr.run()
|
|
|
|
assert len(simgr.deadended) == 1
|
|
|
|
final_state = simgr.deadended[0]
|
|
|
|
assert final_state.solver.eval(mylib_object.get_field(final_state, "myInt", "int")) == 1
|
|
assert final_state.solver.eval(mylib_object.get_field(final_state, "myShort", "short")) == 1
|
|
assert final_state.solver.eval(mylib_object.get_field(final_state, "myChar", "char")) == ord("c")
|
|
assert final_state.solver.eval(mylib_object.get_field(final_state, "myLong", "long")) == 2
|
|
assert final_state.solver.eval(mylib_object.get_field(final_state, "myFloat", "float")) == 1.5
|
|
assert final_state.solver.eval(mylib_object.get_field(final_state, "myDouble", "double")) == 1.5
|
|
string_ref = mylib_object.get_field(final_state, "myString", "java.lang.String")
|
|
assert final_state.solver.eval(final_state.memory.load(string_ref)) == "Hello!"
|
|
array_ref = mylib_object.get_field(final_state, "myArray", "int[]")
|
|
assert final_state.solver.eval(array_ref.size) == 3
|
|
object_ref = mylib_object.get_field(final_state, "myObject", "java.lang.Object")
|
|
assert final_state.solver.eval(object_ref.get_field(final_state, "a", "int")) == 1
|
|
|
|
assert ("myInt", "int") in mylib_object.attributes
|
|
assert ("myChar", "char") in mylib_object.attributes
|
|
assert ("myShort", "short") in mylib_object.attributes
|
|
assert ("myLong", "long") in mylib_object.attributes
|
|
assert ("myFloat", "float") in mylib_object.attributes
|
|
assert ("myDouble", "double") in mylib_object.attributes
|
|
assert ("myString", "java.lang.String") in mylib_object.attributes
|
|
assert ("myArray", "int[]") in mylib_object.attributes
|
|
assert ("myObject", "java.lang.Object") in mylib_object.attributes
|
|
|
|
#
|
|
# Helper
|
|
#
|
|
|
|
def run_method(self, project, method, assert_locals=None, assertions=None):
|
|
end_state = self.get_last_state_of_method(project, method)
|
|
# print_java_memory(end_state)
|
|
|
|
if assert_locals:
|
|
for symbol_name, assert_value in assert_locals.items():
|
|
symbol = self.load_value_from_stack(end_state, symbol_name)
|
|
val = end_state.solver.eval(symbol)
|
|
assert val == assert_value
|
|
|
|
if assertions:
|
|
for test in assertions.values():
|
|
assert test(end_state)
|
|
|
|
return end_state
|
|
|
|
@unittest.skipUnless(pysoot, "pysoot not available")
|
|
def create_project(self, binary_dir, load_native_libs=True):
|
|
jar_path = os.path.join(self.test_location, binary_dir, "mixedjava.jar")
|
|
if load_native_libs:
|
|
jni_options = {"jni_libs": ["libmixedjava.so"]}
|
|
project = angr.Project(jar_path, main_opts=jni_options, auto_load_libs=True)
|
|
else:
|
|
project = angr.Project(jar_path, auto_load_libs=True)
|
|
return project
|
|
|
|
def load_string(self, state, local_name):
|
|
str_ref = self.load_value_from_stack(state, local_name)
|
|
return state.memory.load(str_ref)
|
|
|
|
def load_value_from_stack(self, state, symbol_name):
|
|
try:
|
|
return state.memory.stack.load(symbol_name)
|
|
except KeyError:
|
|
return None
|
|
|
|
def get_entry_state_of_method(self, project, method_fullname):
|
|
# get SootAddressDescriptor of method entry
|
|
soot_method = project.loader.main_object.get_soot_method(method_fullname)
|
|
method = SootMethodDescriptor.from_soot_method(soot_method)
|
|
addr = SootAddressDescriptor(method, 0, 0)
|
|
# create call state
|
|
return project.factory.blank_state(addr=addr, add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY})
|
|
|
|
def get_last_state_of_method(self, project, method_fullname):
|
|
state = self.get_entry_state_of_method(project, method_fullname)
|
|
# run until no successors exists
|
|
# Note: this does not work if conditional branches are present
|
|
states = [state]
|
|
succ = states[-1].step()
|
|
while len(succ.successors) == 1:
|
|
states += succ
|
|
succ = states[-1].step()
|
|
# last state is the 'Terminator' state
|
|
# => return the state before
|
|
return states[-2]
|
|
|
|
def get_winning_paths(self, project, method_fullname):
|
|
state = self.get_entry_state_of_method(project, method_fullname)
|
|
simgr = project.factory.simgr(state)
|
|
simgr.run()
|
|
paths = simgr.deadended
|
|
|
|
# winning paths output a single 'W' on stdout
|
|
winnning_paths = []
|
|
for pp in paths:
|
|
stdout_packets = pp.posix.stdout.content
|
|
read_byte, _ = stdout_packets[0]
|
|
# a winning path is printing 'W'
|
|
pp.solver.add(read_byte == claripy.BVV(ord("W"), 8))
|
|
if pp.satisfiable():
|
|
winnning_paths.append(pp)
|
|
|
|
return winnning_paths
|
|
|
|
def get_winning_path(self, project, method_fullname):
|
|
winning_paths = self.get_winning_paths(project, method_fullname)
|
|
assert len(winning_paths) != 0
|
|
assert len(winning_paths) == 1
|
|
return winning_paths[0]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|