Skip to content
Open
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
74 changes: 74 additions & 0 deletions mobsf_ext/static/analyze_apk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
import sys, json
from pathlib import Path

from detectors.dex_hidden import find_hidden_dex
from detectors.dynamic_loading import detect_dynamic_loading_markers
from detectors.manifest_check import manifest_findings
from yara_scan import run_yara_scan

def main():
if len(sys.argv) < 2:
print("Usage: analyze_apk.py <path-to-apk>")
sys.exit(1)

apk_path = Path(sys.argv[1]).resolve()
if not apk_path.exists():
print(json.dumps({"error": "apk not found", "path": str(apk_path)}))
sys.exit(2)

result = {
"apk": str(apk_path),
"checks": {
"hidden_dex_paths": [],
"dynamic_loading_markers": [],
"manifest": {},
"yara_matches": []
},
"score": 0,
"summary": ""
}

# 1) 숨겨진/은닉 DEX
result["checks"]["hidden_dex_paths"] = find_hidden_dex(apk_path)

# 2) DexClassLoader 등 런타임 로딩 흔적
result["checks"]["dynamic_loading_markers"] = detect_dynamic_loading_markers(apk_path)

# 3) Manifest 위험 권한 / SDK 정보 (Androguard 사용)
result["checks"]["manifest"] = manifest_findings(apk_path)

# 4) YARA 매치
rules_dir = Path(__file__).resolve().parent / "yara" / "rules"
result["checks"]["yara_matches"] = run_yara_scan(apk_path, rules_dir)

# 5) 아주 러프한 점수화
score = 0
if result["checks"]["hidden_dex_paths"]:
score += 40
if result["checks"]["dynamic_loading_markers"]:
score += 30
if result["checks"]["manifest"].get("dangerous_permissions"):
score += min(30, 5 * len(result["checks"]["manifest"]["dangerous_permissions"]))
if result["checks"]["yara_matches"]:
score += 30
result["score"] = min(100, score)

flags = []
if result["checks"]["hidden_dex_paths"]:
flags.append("은닉/암호화 DEX 의심")
if result["checks"]["dynamic_loading_markers"]:
flags.append("런타임 DEX 로딩 흔적")
if result["checks"]["manifest"].get("dangerous_permissions"):
flags.append("위험 권한 요청")
if result["checks"]["yara_matches"]:
flags.append("YARA 매치 있음")

result["summary"] = " / ".join(flags) if flags else "특이점 미검출(정적 1차 패스)"
out_file = Path("outputs") / (apk_path.stem + ".json")
out_file.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"[+] 결과 저장 완료: {out_file}")
print(json.dumps(result, ensure_ascii=False, indent=2))

if __name__ == "__main__":
main()
Empty file.
24 changes: 24 additions & 0 deletions mobsf_ext/static/detectors/dex_hidden.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import zipfile
from pathlib import Path

DEX_HEADER = b"dex\n035"

COMMON_HIDE_PREFIX = ("assets/", "res/raw/", "lib/", "assets/bin/", "assets/obj/")

def find_hidden_dex(apk_path: Path):
"""APK 내부에서 classes.dex 외의 DEX 헤더를 탐색"""
hits = []
with zipfile.ZipFile(apk_path, "r") as z:
for name in z.namelist():
lower = name.lower()
if lower.endswith(".dex") and lower != "classes.dex":
hits.append(name)
continue
if lower.startswith(COMMON_HIDE_PREFIX) or lower.endswith((".dat", ".bin", ".jar", ".zip")):
try:
data = z.read(name)
except KeyError:
continue
if DEX_HEADER in data:
hits.append(name)
return sorted(set(hits))
38 changes: 38 additions & 0 deletions mobsf_ext/static/detectors/dynamic_loading.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import zipfile
from pathlib import Path

MARKERS = [
b"DexClassLoader",
b"PathClassLoader",
b"loadClass(",
b"Class.forName",
b"getMethod(",
b"invoke(",
b"Base64.decode",
]

TEXT_EXTS = (".xml", ".txt", ".properties", ".json")

def detect_dynamic_loading_markers(apk_path: Path):
"""DexClassLoader 등 문자열 흔적을 APK 내부에서 폭넓게 스캔"""
suspects = []
with zipfile.ZipFile(apk_path, "r") as z:
for name in z.namelist():
lname = name.lower()
if lname.endswith(TEXT_EXTS) or lname.startswith(("assets/", "res/raw/")) or lname.endswith((".dex", ".so", ".bin", ".dat")):
try:
data = z.read(name)
except KeyError:
continue
if any(m in data for m in MARKERS):
suspects.append(name)
return sorted(set(suspects))

def find_native_libs(apk_path: Path):
import zipfile
libs = []
with zipfile.ZipFile(apk_path, "r") as z:
for name in z.namelist():
if name.lower().startswith("lib/") and name.lower().endswith(".so"):
libs.append(name)
return sorted(libs)
51 changes: 51 additions & 0 deletions mobsf_ext/static/detectors/manifest_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from pathlib import Path

# 호환 import (여러 Androguard 버전 대응)
try:
from androguard.core.bytecodes.apk import APK
except Exception:
try:
from androguard.core.apk import APK # 일부 포크/버전
except Exception as e:
raise ImportError(
"Androguard APK import 실패. venv에서 `pip install 'androguard==3.3.5'` 후 다시 시도하세요."
) from e

from androguard.core.bytecodes.apk import APK

DANGEROUS = {
"android.permission.READ_SMS",
"android.permission.RECEIVE_SMS",
"android.permission.SEND_SMS",
"android.permission.RECORD_AUDIO",
"android.permission.READ_CALL_LOG",
"android.permission.WRITE_CALL_LOG",
"android.permission.CALL_PHONE",
"android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
"android.permission.SYSTEM_ALERT_WINDOW",
"android.permission.PACKAGE_USAGE_STATS",
"android.permission.BIND_ACCESSIBILITY_SERVICE",
}

def manifest_findings(apk_path: Path):
"""권한/SDK 정보와 대표 위험 권한 리포트"""
apk = APK(str(apk_path))
perms = set(apk.get_permissions() or [])
dangerous = sorted(p for p in perms if p in DANGEROUS)

sdk_info = {
"minSdkVersion": apk.get_min_sdk_version(),
"targetSdkVersion": apk.get_target_sdk_version(),
"maxSdkVersion": apk.get_max_sdk_version(),
}

return {
"package": apk.get_package(),
"sdk": sdk_info,
"requested_permissions": sorted(perms),
"dangerous_permissions": dangerous,
}
Binary file added mobsf_ext/static/samples/UnCrackable-Level1.apk
Binary file not shown.
Binary file added mobsf_ext/static/samples/UnCrackable-Level2.apk
Binary file not shown.
15 changes: 15 additions & 0 deletions mobsf_ext/static/yara/rules/anti_debug_root.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
rule ANDROID_AntiDebug_Root_Strings {
meta:
description = "Root/Anti-debug/frida indicators"
strings:
$su1 = "/system/xbin/su"
$su2 = "/system/bin/su"
$superuser = "Superuser.apk"
$testkeys = "test-keys"
$frida1 = "frida"
$frida2 = "gum-js-loop"
$ptrace = "android.os.Debug"
$dbg1 = "isDebuggerConnected"
condition:
any of ($su*) or $superuser or $testkeys or any of ($frida*) or $ptrace or $dbg1
}
11 changes: 11 additions & 0 deletions mobsf_ext/static/yara/rules/sample_android_rule.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
rule ANDROID_Generic_Signs {
meta:
author = "team"
description = "Generic suspicious Android strings"
strings:
$a = "DexClassLoader"
$b = "PathClassLoader"
$c = "Base64.decode"
condition:
any of them
}
24 changes: 24 additions & 0 deletions mobsf_ext/static/yara_scan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pathlib import Path
from typing import List
try:
import yara
except Exception:
yara = None

def run_yara_scan(apk_path: Path, rules_dir: Path) -> List[str]:
"""yara-python 기반 스캔. 룰 파일 여러 개를 합쳐 컴파일."""
if yara is None:
return []

rule_files = list(rules_dir.glob("*.yar")) + list(rules_dir.glob("*.yara"))
if not rule_files:
return []

# filepaths로 여러 룰을 묶어 컴파일
filemap = {f"rule_{i}": str(p) for i, p in enumerate(rule_files)}
try:
rules = yara.compile(filepaths=filemap)
matches = rules.match(str(apk_path))
return sorted({m.rule for m in matches})
except Exception:
return []
Empty file added outputs/.gitkeep
Empty file.
20 changes: 20 additions & 0 deletions outputs/UnCrackable-Level1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"apk": "D:\\MobSF\\OMT_Semi_project2_MobSF\\mobsf_ext\\static\\samples\\UnCrackable-Level1.apk",
"checks": {
"hidden_dex_paths": [],
"dynamic_loading_markers": [],
"manifest": {
"package": "owasp.mstg.uncrackable1",
"sdk": {
"minSdkVersion": "19",
"targetSdkVersion": "28",
"maxSdkVersion": null
},
"requested_permissions": [],
"dangerous_permissions": []
},
"yara_matches": []
},
"score": 0,
"summary": "특이점 미검출(정적 1차 패스)"
}