Android Anti-Instrumentation & SSL Pinning Bypass (Frida/Objection)

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

이 페이지는 인스트루먼테이션을 탐지하거나 루트로 차단하는 Android 앱, 또는 TLS pinning을 강제하는 앱에 대해 동적 분석 접근을 회복하는 실무 워크플로를 제공합니다. 빠른 초기 분류(fast triage), 흔한 탐지 기법, 그리고 가능한 경우 재패키징 없이 복사·붙여넣기 가능한 훅/전술에 중점을 둡니다.

Detection Surface (what apps check)

  • Root checks: su binary, Magisk paths, getprop values, common root packages
  • Frida/debugger checks (Java): Debug.isDebuggerConnected(), ActivityManager.getRunningAppProcesses(), getRunningServices(), scanning /proc, classpath, loaded libs
  • Native anti‑debug: ptrace(), syscalls, anti‑attach, breakpoints, inline hooks
  • Early init checks: Application.onCreate() or process start hooks that crash if instrumentation is present
  • TLS pinning: custom TrustManager/HostnameVerifier, OkHttp CertificatePinner, Conscrypt pinning, native pins

Bypassing Anti-Frida Detection / Stealth Frida Servers

phantom-frida는 Frida를 소스에서 재빌드하고 약 90개의 패치를 적용하여 일반적인 Frida 지문을 제거하면서도 기본 Frida 프로토콜과의 호환성(frida-tools로의 연결 가능)을 유지합니다. 대상은 /proc(cmdline, maps, task comm, fd readlink), D-Bus 서비스 이름, 기본 포트 또는 내보낸 심볼을 grep으로 검사하는 앱입니다.

단계:

  • Source patches: frida 식별자(server/agent/helper)의 전역 이름 변경 및 Java 패키지가 변경된 rebuilt helper DEX.
  • Targeted build/runtime patches: meson 조정, memfd 레이블을 jit-cache로 변경, SELinux 레이블(예: frida_file) 이름 변경, hook 탐지를 피하기 위해 libc의 exit/signal 후킹 비활성화.
  • Post-build rename: 초기 컴파일 후 내보낸 심볼 frida_agent_main을 재명명(첫 컴파일에서 Vala가 해당 심볼을 생성하므로 두 번째 증분 빌드 필요).
  • Binary hex patches: 스레드 이름(gmain, gdbus, pool-spawner) 교체; 선택적 스윕으로 남아있는 frida/Frida 문자열 제거.

커버되는 탐지 벡터:

  • Base (1–8): process name frida-server, mapped libfrida-agent.so, thread names, memfd label, exported frida_agent_main, SELinux labels, libc hook side-effects, D-Bus service re.frida.server 등을 이름 변경/무력화.
  • Extended (9–16): 리스닝 포트 변경(--port), D-Bus 인터페이스/내부 C 심볼/GType 이름 재명명, .frida/frida- 같은 임시 경로 변경, 바이너리 문자열 스윕, 빌드 시 define 및 자산 경로(libdir/frida) 재명명. D-Bus 인터페이스 이름 중 wire 프로토콜의 일부인 것들은 stock 클라이언트 호환성을 깨지 않기 위해 base 모드에서는 변경하지 않습니다.

Build/usage (Android arm64 example):

python3 build.py --version 17.7.2 --name myserver --port 27142 --extended --verify
adb push output/myserver-server-17.7.2-android-arm64 /data/local/tmp/myserver-server
adb shell chmod 755 /data/local/tmp/myserver-server
adb shell /data/local/tmp/myserver-server -D &
adb forward tcp:27142 tcp:27142
frida -H 127.0.0.1:27142 -f com.example.app

플래그: --skip-build (patch only), --skip-clone, --arch, --ndk-path, --temp-fixes; WSL 도우미: wsl -d Ubuntu bash build-wsl.sh.

1단계 — 빠른 해결: Magisk DenyList로 루트 숨기기

  • Magisk에서 Zygisk 활성화
  • DenyList 활성화, 대상 패키지 추가
  • 재부팅 후 재검증

많은 앱들은 명백한 징후(su/Magisk paths/getprop)만 확인합니다. DenyList는 종종 단순한 검사들을 무력화합니다.

참조:

  • Magisk (Zygisk & DenyList): https://github.com/topjohnwu/Magisk

Play Integrity / Zygisk detections (post‑SafetyNet)

최근의 은행/신분증 앱들은 런타임 체크를 Google Play Integrity (SafetyNet 대체)와 연동하고, Zygisk가 존재하면 충돌할 수도 있습니다. 빠른 트리아지 팁:

  • 일시적으로 Zygisk를 비활성화(토글 오프 + 재부팅)하고 다시 시도하세요; 일부 앱은 Zygote injection이 로드되는 즉시 크래시합니다.
  • 만약 attestation이 로그인 차단을 일으키면, Google Play Services를 PlayIntegrityFix/Fork + TrickyStore로 패치하거나 테스트 시에만 ReZygisk/Zygisk‑Next를 사용하세요. 대상은 DenyList에 남겨두고 leak props하는 LSPosed 모듈은 피하세요.
  • 일회성 실행의 경우 KernelSU/APatch(무 Zygote injection)를 사용해 Zygisk 휴리스틱을 피한 다음 Frida를 연결하세요.

2단계 — 30초 Frida Codeshare 테스트

심층 분석 전에 일반적인 drop-in 스크립트를 시도하세요:

  • anti-root-bypass.js
  • anti-frida-detection.js
  • hide_frida_gum.js

예시:

frida -U -f com.example.app -l anti-frida-detection.js

이들은 일반적으로 Java의 root/debug 검사, process/service 스캔, 및 native ptrace()를 스텁 처리합니다. 방어가 약한 앱에서는 유용하지만, 보안이 강화된 대상은 맞춤형 훅이 필요할 수 있습니다.

  • Codeshare: https://codeshare.frida.re/

Medusa (Frida framework)로 자동화

Medusa는 SSL unpinning, root/emulator detection bypass, HTTP comms logging, crypto key interception 등과 같은 작업을 위한 90개 이상의 즉시 사용 가능한 모듈을 제공합니다.

git clone https://github.com/Ch0pin/medusa
cd medusa
pip install -r requirements.txt
python medusa.py

# Example interactive workflow
show categories
use http_communications/multiple_unpinner
use root_detection/universal_root_detection_bypass
run com.target.app

팁: Medusa는 custom hooks를 작성하기 전에 빠른 성과를 얻기에 매우 유용합니다. 또한 modules를 선택적으로 골라 자신의 scripts와 결합할 수 있습니다.

Step 3 — init-time detectors를 늦게 attaching해서 우회하기

많은 탐지들은 process spawn/onCreate() 동안에만 실행됩니다. Spawn‑time injection (-f)이나 gadgets는 감지되기 쉽고; UI가 로드된 후 attaching하면 우회할 수 있습니다.

# Launch the app normally (launcher/adb), wait for UI, then attach
frida -U -n com.example.app
# Or with Objection to attach to running process
aobjection --gadget com.example.app explore  # if using gadget

이 방법이 작동하면 세션을 안정적으로 유지하고 map and stub checks를 진행하세요.

4단계 — Jadx와 string hunting을 통해 탐지 로직 매핑

Jadx에서의 정적 분류 키워드:

  • “frida”, “gum”, “root”, “magisk”, “ptrace”, “su”, “getprop”, “debugger”

전형적인 Java 패턴:

public boolean isFridaDetected() {
return getRunningServices().contains("frida");
}

검토/후킹할 공통 API:

  • android.os.Debug.isDebuggerConnected
  • android.app.ActivityManager.getRunningAppProcesses / getRunningServices
  • java.lang.System.loadLibrary / System.load (native bridge)
  • java.lang.Runtime.exec / ProcessBuilder (probing commands)
  • android.os.SystemProperties.get (root/emulator heuristics)

단계 5 — Frida (Java)를 사용한 런타임 스텁

repacking 없이 안전한 값을 반환하도록 커스텀 가드를 오버라이드:

Java.perform(() => {
const Checks = Java.use('com.example.security.Checks');
Checks.isFridaDetected.implementation = function () { return false; };

// Neutralize debugger checks
const Debug = Java.use('android.os.Debug');
Debug.isDebuggerConnected.implementation = function () { return false; };

// Example: kill ActivityManager scans
const AM = Java.use('android.app.ActivityManager');
AM.getRunningAppProcesses.implementation = function () { return java.util.Collections.emptyList(); };
});

초기 크래시를 분류하나요? 죽기 직전에 클래스를 덤프해서 탐지 가능성이 높은 네임스페이스를 찾아보세요:

Java.perform(() => {
Java.enumerateLoadedClasses({
onMatch: n => console.log(n),
onComplete: () => console.log('Done')
});
});

빠른 루트 감지 스텁 예시 (대상 패키지/클래스 이름에 맞게 조정):

Java.perform(() => {
try {
const RootChecker = Java.use('com.target.security.RootCheck');
RootChecker.isDeviceRooted.implementation = function () { return false; };
} catch (e) {}
});

의심스러운 메서드를 로깅하고 무력화하여 실행 흐름을 확인하세요:

Java.perform(() => {
const Det = Java.use('com.example.security.DetectionManager');
Det.checkFrida.implementation = function () {
console.log('checkFrida() called');
return false;
};
});

에뮬레이터/VM 탐지 우회 (Java stubs)

일반적인 휴리스틱: Build.FINGERPRINT/MODEL/MANUFACTURER/HARDWARE에 generic/goldfish/ranchu/sdk가 포함되어 있는지; QEMU 아티팩트(예: /dev/qemu_pipe, /dev/socket/qemud); 기본 MAC 02:00:00:00:00:00; 10.0.2.x NAT; telephony/sensors 누락.

Build 필드 빠른 스푸핑:

Java.perform(function(){
var Build = Java.use('android.os.Build');
Build.MODEL.value = 'Pixel 7 Pro';
Build.MANUFACTURER.value = 'Google';
Build.BRAND.value = 'google';
Build.FINGERPRINT.value = 'google/panther/panther:14/UP1A.231105.003/1234567:user/release-keys';
});

파일 존재 검사 및 식별자 (TelephonyManager.getDeviceId/SubscriberId, WifiInfo.getMacAddress, SensorManager.getSensorList)에 대한 스텁을 추가하여 현실적인 값을 반환하도록 보완하세요.

SSL pinning bypass quick hook (Java)

사용자 정의 TrustManagers를 무효화하고 허용적인 SSL contexts를 강제합니다:

Java.perform(function(){
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var SSLContext = Java.use('javax.net.ssl.SSLContext');

// No-op validations
X509TrustManager.checkClientTrusted.implementation = function(){ };
X509TrustManager.checkServerTrusted.implementation = function(){ };

// Force permissive TrustManagers
var TrustManagers = [ X509TrustManager.$new() ];
var SSLContextInit = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;','[Ljavax.net.ssl.TrustManager;','java.security.SecureRandom');
SSLContextInit.implementation = function(km, tm, sr){
return SSLContextInit.call(this, km, TrustManagers, sr);
};
});

참고

  • OkHttp에 대해 확장: 필요에 따라 okhttp3.CertificatePinner와 HostnameVerifier를 hook하거나, CodeShare의 universal unpinning script를 사용하세요.
  • 실행 예: frida -U -f com.target.app -l ssl-bypass.js --no-pause

OkHttp4 / gRPC / Cronet pinning (2024+)

최신 스택은 더 새로운 API 내부에서 pin을 수행합니다 (OkHttp4+, gRPC over Cronet/BoringSSL). 기본 SSLContext hook이 작동하지 않을 때 이러한 hooks를 추가하세요:

Java.perform(() => {
try {
const Pinner = Java.use('okhttp3.CertificatePinner');
Pinner.check.overload('java.lang.String', 'java.util.List').implementation = function(){};
Pinner.check$okhttp.implementation = function(){};
} catch (e) {}

try {
const CronetB = Java.use('org.chromium.net.CronetEngine$Builder');
CronetB.enablePublicKeyPinningBypassForLocalTrustAnchors.overload('boolean').implementation = function(){ return this; };
CronetB.setPublicKeyPins.overload('java.lang.String', 'java.util.Set', 'boolean').implementation = function(){ return this; };
} catch (e) {}
});

그래도 TLS가 여전히 실패하면, native로 내려가 Cronet/gRPC에서 사용하는 BoringSSL 검증 진입점을 패치하세요:

const customVerify = Module.findExportByName(null, 'SSL_CTX_set_custom_verify');
if (customVerify) {
Interceptor.attach(customVerify, {
onEnter(args){
// arg0 = SSL_CTX*, arg1 = mode, arg2 = callback
args[1] = ptr(0); // SSL_VERIFY_NONE
args[2] = NULL;  // disable callback
}
});
}

6단계 — Java hooks가 실패할 때 JNI/native 경로를 따라가세요

JNI 엔트리 포인트를 추적하여 native loaders와 detection init을 찾아내세요:

frida-trace -n com.example.app -i "JNI_OnLoad"

번들된 .so 파일의 빠른 네이티브 선별:

# List exported symbols & JNI
nm -D libfoo.so | head
objdump -T libfoo.so | grep Java_
strings -n 6 libfoo.so | egrep -i 'frida|ptrace|gum|magisk|su|root'

인터랙티브/네이티브 리버싱:

  • Ghidra: https://ghidra-sre.org/
  • r2frida: https://github.com/nowsecure/r2frida

예시: libc에서 간단한 anti‑debug를 무력화하기 위해 ptrace를 무력화:

const ptrace = Module.findExportByName(null, 'ptrace');
if (ptrace) {
Interceptor.replace(ptrace, new NativeCallback(function () {
return -1; // pretend failure
}, 'int', ['int', 'int', 'pointer', 'pointer']));
}

참고: Reversing Native Libraries

7단계 — Objection patching (embed gadget / strip basics)

repacking을 runtime hooks보다 선호한다면, 시도해보세요:

objection patchapk --source app.apk

노트:

  • apktool 필요; 빌드 문제를 피하려면 공식 가이드에서 최신 버전을 사용하세요: https://apktool.org/docs/install
  • Gadget injection은 root 없이 instrumentation을 가능하게 하지만 더 강력한 init‑time 검사에 의해 여전히 탐지될 수 있습니다.

선택적으로 LSPosed 모듈과 Shamiko를 추가하여 Zygisk 환경에서 더 강력한 root 숨김을 적용하고, DenyList를 관리하여 자식 프로세스까지 포함시키세요.

script-mode Gadget 구성 및 Frida 17+ agent를 APK에 번들링하는 전체 워크플로우는 다음을 참조하세요:

Frida Tutorial — Self-contained agent + Gadget embedding

참고자료:

  • Objection: https://github.com/sensepost/objection

Step 8 — 폴백: 네트워크 가시성을 위한 TLS pinning 패치

instrumentation이 차단된 경우, pinning을 정적으로 제거하여 트래픽을 검사할 수 있습니다:

apk-mitm app.apk
# Then install the patched APK and proxy via Burp/mitmproxy
  • 도구: https://github.com/shroudedcode/apk-mitm
  • 네트워크 구성 CA‑trust 트릭(및 Android 7+ user CA trust)은 다음을 참조하세요:

Make APK Accept CA Certificate

Install Burp Certificate

유용한 명령 치트시트

# List processes and attach
frida-ps -Uai
frida -U -n com.example.app

# Spawn with a script (may trigger detectors)
frida -U -f com.example.app -l anti-frida-detection.js

# Trace native init
frida-trace -n com.example.app -i "JNI_OnLoad"

# Objection runtime
objection --gadget com.example.app explore

# Static TLS pinning removal
apk-mitm app.apk

Universal proxy forcing + TLS unpinning (HTTP Toolkit Frida hooks)

Modern apps often ignore system proxies and enforce multiple layers of pinning (Java + native), making traffic capture painful even with user/system CAs installed. A practical approach is to combine universal TLS unpinning with proxy forcing via ready-made Frida hooks, and route everything through mitmproxy/Burp.

Workflow

  • 호스트에서 mitmproxy(또는 Burp)를 실행합니다. 디바이스가 호스트의 IP/포트에 접근할 수 있는지 확인하세요.
  • HTTP Toolkit의 통합된 Frida 훅을 로드하여 TLS를 unpin하고 OkHttp/OkHttp3, HttpsURLConnection, Conscrypt, WebView 등 공통 스택 전반에서 proxy 사용을 강제합니다. 이 훅은 CertificatePinner/TrustManager 검사를 우회하고 proxy selectors를 재정의하므로, 앱이 명시적으로 proxies를 비활성화하더라도 트래픽은 항상 당신의 proxy를 통해 전송됩니다.
  • Frida와 훅 스크립트로 대상 앱을 실행한 다음, mitmproxy에서 요청을 캡처합니다.

Example

# Device connected via ADB or over network (-U)
# See the repo for the exact script names & options
frida -U -f com.vendor.app \
-l ./android-unpinning-with-proxy.js \
--no-pause

# mitmproxy listening locally
mitmproxy -p 8080

노트

  • 가능한 경우 adb shell settings put global http_proxy <host>:<port>로 시스템 전체 프록시와 함께 사용하세요. Frida 후크는 앱이 글로벌 설정을 우회하더라도 프록시 사용을 강제합니다.
  • 이 기법은 pinning/proxy 회피가 흔한 mobile-to-IoT 온보딩 흐름을 MITM해야 할 때 이상적입니다.
  • 후크: https://github.com/httptoolkit/frida-interception-and-unpinning

참고자료

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기