Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions sample/tests/test_android_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,44 @@ def _meta_per_path(path):
f"expected warning mentioning libzbroken.so, got {result.warnings!r}",
)

def test_first_lief_failure_still_analyzes_later_libs(self):
"""A broken lex-first .so must not hide valid libraries in all mode."""
with tempfile.TemporaryDirectory() as td:
tdp = Path(td)
apk = _make_apk_with_lib(
tdp,
{"arm64-v8a": ["libaaa_broken.so", "libcrypto.so"]},
)
from venomhook.binary_meta import BinaryMetaError

def _meta_per_path(path):
name = Path(path).name
if name == "libaaa_broken.so":
raise BinaryMetaError(f"synthetic parse failure for {name}")
if name == "libcrypto.so":
return _stub_binary_meta(str(path), ["JNI_OnLoad"])
raise AssertionError(path)

with mock.patch(
"venomhook.android_pipeline.extract_binary_meta",
side_effect=_meta_per_path,
):
result = analyze_apk(
apk,
tdp / "work",
use_apktool=False,
use_jadx=False,
analyze_all_libs=True,
)

self.assertIsNotNone(result.so_meta)
self.assertEqual(result.so_meta.name, "libcrypto.so")
self.assertEqual(result.additional_so_metas, [])
self.assertTrue(
any("libaaa_broken.so" in w for w in result.warnings),
f"expected warning mentioning libaaa_broken.so, got {result.warnings!r}",
)


class TestAnalyzeApkAbiSelection(unittest.TestCase):
def test_explicit_abi(self):
Expand Down
47 changes: 29 additions & 18 deletions src/venomhook/android_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,35 @@ def analyze_apk(
)

# ----- Step 3: BinaryMeta of the .so (REQUIRED for JNI correlation) -----
if so_path is not None:
if analyze_all_libs and extracted_paths:
parsed: list[tuple[Path, BinaryMeta]] = []
failures: list[str] = []
for candidate_path in extracted_paths:
try:
parsed.append((candidate_path, extract_binary_meta(candidate_path)))
except BinaryMetaError as e:
failures.append(
f"{candidate_path}에 대한 binary_meta 추출 실패 "
f"(계속 진행): {e}"
)

if parsed:
# Pick the first successfully parsed library as the legacy
# primary. A corrupt lexicographic first .so must not prevent
# --apk-lib all from analysing the remaining valid libraries.
so_path, so_meta = parsed[0]
for extra_path, extra_meta in parsed[1:]:
additional_so_metas.append(extra_meta)
additional_so_paths.append(str(extra_path))
warnings.extend(failures)
else:
msg = "; ".join(failures) or (
f"{apk}의 {selected_abi} ABI에서 분석 가능한 .so가 없습니다"
)
if require_native:
raise AndroidPipelineError(msg)
warnings.append(f"{msg} — 네이티브 분석을 건너뜁니다")
elif so_path is not None:
try:
so_meta = extract_binary_meta(so_path)
except BinaryMetaError as e:
Expand All @@ -369,23 +397,6 @@ def analyze_apk(
raise AndroidPipelineError(msg) from e
warnings.append(f"{msg} — 네이티브 분석을 건너뜁니다")

if analyze_all_libs and so_meta is not None:
# Run lief on each remaining .so. A failure on a non-primary
# library is recorded as a warning and the rest of the
# analysis continues — losing one library's exports degrades
# JNI correlation but does not invalidate the report.
for extra_path in extracted_paths[1:]:
try:
extra_meta = extract_binary_meta(extra_path)
except BinaryMetaError as e:
warnings.append(
f"{extra_path}에 대한 binary_meta 추출 실패 "
f"(계속 진행): {e}"
)
continue
additional_so_metas.append(extra_meta)
additional_so_paths.append(str(extra_path))

# ----- Step 4: AndroidManifest decode (optional) -----
app_meta: Optional[AndroidAppMeta] = None
if use_apktool:
Expand Down
Loading