mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
Fall back to pure Java analysis when JNI libraries are not loaded
Naming jni_libs in main_opts sets jni_support on the loaded Soot object, but CLE reaches those libraries through the object's dependencies, which it only follows when auto_load_libs is enabled. With it disabled no native library is ever mapped, and SimJavaVM raised AngrSimOSError before the project finished constructing, so the Java bytecode could not be analyzed at all. Decide JNI support from the libraries the loader actually mapped. Keep the error for auto_load_libs=True, where an empty result means the libraries were looked for and not found, and warn instead when the caller asked for no dependencies. Without JNI support a native method has no Soot block for CFGFastSoot to walk, and SimJavaVM.native_symbols, which the Soot engine reads to resolve a native invoke, was never assigned. End the CFG at the JNI boundary and leave the symbol table empty, so the engine skips the invoke and keeps going.
This commit is contained in:
parent
460e551ea9
commit
9606d76558
3 changed files with 65 additions and 14 deletions
|
|
@ -157,9 +157,13 @@ class CFGFastSoot(CFGFast):
|
|||
# soot method
|
||||
method = self.project.loader.main_object.get_soot_method(function_id)
|
||||
|
||||
# native method has no soot block
|
||||
if self.support_jni and block is None:
|
||||
return self._native_method_successors(addr, method)
|
||||
if block is None:
|
||||
# A method without a Soot block has no bytecode body, which is what a native method looks like: its
|
||||
# implementation lives in a JNI library. With JNI support we follow the call into the native code;
|
||||
# without it there is nothing left to analyze here.
|
||||
if self.support_jni:
|
||||
return self._native_method_successors(addr, method)
|
||||
return []
|
||||
|
||||
block_id = block.idx
|
||||
|
||||
|
|
|
|||
|
|
@ -32,18 +32,32 @@ class SimJavaVM(SimOS):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, name="JavaVM", **kwargs)
|
||||
|
||||
# is the binary using JNI libraries?
|
||||
self.is_javavm_with_jni_support = self.project.loader.main_object.jni_support
|
||||
# Step 1: find all native libs
|
||||
# CLE pulls JNI libraries in as dependencies of the Soot object, so they are only mapped when the loader
|
||||
# follows dependencies at all.
|
||||
self.native_libs = [
|
||||
obj for obj in self.project.loader.initial_load_objects if not isinstance(obj.arch, ArchSoot)
|
||||
]
|
||||
|
||||
# JNI entry points exported by those libraries. Without JNI support it stays empty, so a native invoke finds
|
||||
# no implementation and the Soot engine skips it instead of entering code that was never mapped.
|
||||
self.native_symbols = {}
|
||||
|
||||
# JNI support needs the native libraries themselves, not merely a request for them.
|
||||
jni_libs_requested = self.project.loader.main_object.jni_support
|
||||
self.is_javavm_with_jni_support = jni_libs_requested and bool(self.native_libs)
|
||||
|
||||
if jni_libs_requested and not self.native_libs:
|
||||
if self.project.loader.auto_load_libs:
|
||||
# The loader looked for the libraries and came back empty-handed, which is a misconfiguration.
|
||||
raise AngrSimOSError("No JNI lib was loaded. Is the jni_libs_ld_path set correctly?")
|
||||
# The caller asked for no dependencies at all, so analyze the Java side alone instead of failing.
|
||||
l.warning(
|
||||
"No JNI library was loaded because auto_load_libs is disabled. Analyzing the Java bytecode alone; "
|
||||
"enable auto_load_libs to load the native libraries."
|
||||
)
|
||||
|
||||
if self.is_javavm_with_jni_support:
|
||||
# Step 1: find all native libs
|
||||
self.native_libs = [
|
||||
obj for obj in self.project.loader.initial_load_objects if not isinstance(obj.arch, ArchSoot)
|
||||
]
|
||||
|
||||
if len(self.native_libs) == 0:
|
||||
raise AngrSimOSError("No JNI lib was loaded. Is the jni_libs_ld_path set correctly?")
|
||||
|
||||
# Step 2: determine and set the native SimOS
|
||||
|
||||
# for each native library get the Arch
|
||||
|
|
@ -66,7 +80,6 @@ class SimJavaVM(SimOS):
|
|||
raise AngrSimOSError("Cannot instantiate SimOS for native libraries: No compatible SimOS found.")
|
||||
|
||||
# Step 3: Match static JNI symbols from native libs
|
||||
self.native_symbols = {}
|
||||
for lib in self.native_libs:
|
||||
for name, symbol in lib.symbols_by_name.items():
|
||||
if name.startswith("Java"):
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ from archinfo.arch_soot import (
|
|||
SootArgument,
|
||||
SootMethodDescriptor,
|
||||
)
|
||||
from cle import Jar
|
||||
|
||||
import angr
|
||||
from angr.engines.soot.method_dispatcher import resolve_method
|
||||
from angr.engines.soot.values import SimSootValue_ArrayRef, SimSootValue_ThisRef
|
||||
from angr.simos import SimJavaVM
|
||||
from angr.storage.memory_mixins import DefaultMemory, JavaVmMemory, KeyValueMemory
|
||||
|
||||
try:
|
||||
|
|
@ -518,6 +520,38 @@ class TestJava(unittest.TestCase):
|
|||
loaded_libs = [lib.provides for lib in project.loader.all_elf_objects]
|
||||
assert "libmixedjava.so" in loaded_libs
|
||||
|
||||
@unittest.skipUnless(pysoot, "pysoot not available")
|
||||
def test_loading_jni_libs_without_auto_load_libs(self):
|
||||
# JNI libraries reach the loader as dependencies of the JAR, so auto_load_libs=False keeps them out even
|
||||
# though the caller named them. Analysis then has to fall back to the Java bytecode on its own.
|
||||
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")
|
||||
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=False)
|
||||
assert isinstance(project.loader.main_object, Jar)
|
||||
assert project.loader.main_object.jni_support
|
||||
assert not project.loader.all_elf_objects
|
||||
assert isinstance(project.simos, SimJavaVM)
|
||||
assert not project.simos.is_javavm_with_jni_support
|
||||
assert not project.is_java_jni_project
|
||||
|
||||
cfg = project.analyses.CFGFastSoot()
|
||||
# MixedJava.get_version is declared native, so it has no Soot block. Without a JNI library behind it, the
|
||||
# CFG has to end there instead of resolving a native callee.
|
||||
native_method = cfg.kb.functions["MixedJava.get_version()"]
|
||||
native_nodes = [node for node in cfg.graph if node.function_address == native_method.addr]
|
||||
assert len(native_nodes) == 1
|
||||
assert list(cfg.graph.predecessors(native_nodes[0]))
|
||||
assert not list(cfg.graph.successors(native_nodes[0]))
|
||||
|
||||
# Execution reaches the same method. With no native implementation to enter, the invoke is skipped and the
|
||||
# remaining bytecode still runs to the end.
|
||||
simgr = project.factory.simgr(project.factory.entry_state())
|
||||
simgr.run()
|
||||
assert len(simgr.deadended) == 1
|
||||
assert type(simgr.deadended[0].addr) is SootAddressTerminator
|
||||
|
||||
#
|
||||
# SimStates
|
||||
#
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue