
1. 개요
Frida 17.6.0(2026.01.18)부터 Android 동적 분석의 핵심 메커니즘이 근본적으로 변경되었다. 기존 ptrace/child-gating 기반 Zygote 주입 방식에서 /proc/$pid/mem 기반의 경량 외부 패칭(Zymbiote) 방식으로 전환되었다. 이후 17.6.1~17.6.2, 17.7.0~17.7.3 까지 빠른 연속 릴리즈로 안정화 작업이 진행 중이다.
앱 취약점 진단에서 Frida는 사실상 필수 도구이다. 이번 변경은 내부 동작 방식의 전면 교체이므로, ptrace 기반 탐지 우회나 Magisk 충돌 등 기존에 겪던 문제들의 원인과 해결 여부가 달라진다. 변경사항을 정리한다.
2. 기존 방식: ptrace + child-gating 기반 Zygote 주입
2.1 Zygote란
Android에서 모든 앱 프로세스는 Zygote라는 부모 프로세스로부터 fork()되어 생성된다. Zygote는 ART 런타임과 공통 라이브러리를 미리 로드한 상태로 대기하며, 새 앱 실행 요청이 오면 자신을 복제(fork)하여 자식 프로세스를 만든다. 이 구조 덕분에 앱 시작 시간이 단축된다.
2.2 기존 동작 흐름
기존 Frida의 Android 계측은 다음 순서로 동작했다.
frida-server가 Zygote 프로세스에ptrace()로 연결(attach)frida-agent를 Zygote 내부에 주입- child-gating으로
fork()이벤트를 감시 - 새로 생성된 자식 프로세스(앱)에 대해 계측 수행
ptrace()는 Linux 디버깅 인터페이스로, 대상 프로세스의 메모리 읽기/쓰기와 실행 제어가 가능하다. frida-server는 이를 이용해 Zygote 내부에 에이전트 코드를 삽입하고, fork() 시점을 잡아내어 자식 프로세스에 대한 계측을 수행했다.
2.3 문제점
이 방식에는 구조적인 문제가 있었다.
- ptrace 점유 충돌: ptrace()는 하나의 프로세스에 대해 하나의 tracer만 허용한다. frida-server가 Zygote를 점유하면 Magisk, Riru 등 다른 도구가 Zygote에 접근할 수 없다.
- Zygote 크래시 위험: 주입 타이밍에 따라 앱이나 시스템 서비스가 크래시할 수 있다. Zygote가 죽으면 사용자 영역(userspace) 전체가 재시작된다.
- 유휴 syscall 판단 버그: ptrace 대기 중 Zygote가 유휴 상태인지 판단하는 로직에 버그가 있었다.
- SELinux/OEM 커널 차단: SELinux 정책이나 OEM 커널에서 ptrace를 차단하는 경우 동작 자체가 불가능했다.
- fd 은닉 필요: Zygote는 fork 시 예상치 못한 file descriptor가 있으면 abort()를 호출한다. frida-agent가 보유한 fd를 숨기는 로직이 필요했다.
3. 변경된 방식: /proc/$pid/mem 기반 Zymbiote 페이로드
3.1 핵심 변경
17.6.0부터 ptrace()를 완전히 제거하고, /proc/$pid/mem을 통해 외부에서 메모리를 직접 조작하는 방식으로 전환되었다. 이 기법의 원리는 erfur의 블로그(Code injection without ptrace)에서 상세히 다루고 있다.
/proc/$pid/mem은 Linux에서 프로세스의 가상 메모리를 파일처럼 읽고 쓸 수 있는 인터페이스이다. 적절한 권한(같은 UID 또는 root)이 있으면 ptrace 없이도 대상 프로세스의 메모리를 조작할 수 있다.
3.2 동작 흐름
변경된 방식의 동작 흐름은 다음과 같다.
/proc/$pid/mem으로 Zygote 메모리를 스캔한다.- Gum의 ELF 파서로
android.os.Process.setArgV0Native()메서드의 ArtMethod 구조체 위치를 파악한다. - 해당 메서드의 함수 포인터를 Zymbiote 페이로드로 교체한다.
- 페이로드가 실행되면 abstract UNIX 소켓으로 frida-core에 PID, PPID, 패키지명을 전송한다.
- frida-core가 응답 후 원본 코드를 복원하고
SIGCONT를 전송한다. - 소켓 연결 실패 시에도 프로세스 크래시 없이 안전하게 복귀한다.
3.3 setArgV0Native를 후킹 지점으로 선택한 이유
setArgV0Native()는 Zygote가 fork()한 후 자식 프로세스에서 호출되는 네이티브 메서드이다. 이 시점에 후킹하면 앱의 실제 코드가 실행되기 전에 계측 진입점을 확보할 수 있다. 기존의 child-gating이 fork() 자체를 감시했다면, 변경된 방식은 fork() 이후 자식 프로세스 내부에서 자연스럽게 호출되는 메서드를 이용한다.
3.4 Zymbiote 페이로드 구조
Zymbiote 페이로드는 arm64 기준 920바이트(295줄 C코드)로 구성된다. 페이로드의 역할을 정리하면 다음과 같다.
- 실행 시 abstract UNIX 소켓을 생성하여 frida-core에 연결
- PID, PPID, 패키지명을 전송하여 어떤 앱이 시작되었는지 알림
- frida-core의 응답을 대기한 후 원본 코드 복원
- 연결 실패 시 크래시 없이 정상 흐름으로 복귀 (graceful degradation)
기존 방식에서 frida-agent가 Zygote 내부에 상주하며 지속적으로 동작했던 것과 달리, Zymbiote는 외부에서 패칭하고 실행 후 즉시 원복하는 일회성 구조이다.
3.5 erfur의 /proc/pid/mem 주입 기법
이 변경의 기반이 된 erfur의 기법은 세 단계로 구성된다.
Stage 1: 함수 하이재킹
대상 프로세스에서 자주 호출되는 함수(예: malloc)를 식별하고, /proc/maps에서 가상 주소를 계산한다. 해당 함수를 셸코드로 덮어써서 활성 스레드에서 실행되도록 한다.
Stage 2: 1차 셸코드
원자적 연산(arm64의 ldxrb/stxrb)으로 스레드를 동기화하고, mmap으로 새 메모리를 할당한다. 할당된 주소를 injector에게 전달한다.
Stage 3: 2차 셸코드
injector가 실제 페이로드를 새 메모리에 기록한다. dlopen으로 공유 라이브러리를 로드하거나 커스텀 코드를 실행할 수 있다.
arm64에서는 데이터 캐시와 명령어 캐시가 분리되어 있으므로, 코드를 쓴 후 dsb ish와 isb 배리어로 캐시를 플러시해야 한다.
4. system_server 변경사항
system_server에 대한 접근 방식도 함께 변경되었다.
| 항목 | 기존 | 변경 |
|---|---|---|
| 접근 방식 | system_server에 코드 주입 | frida-helper.dex 사용 |
| 의존성 | frida-java-bridge | 없음 |
| 기능 | 제한적 | 액티비티 실행, 브로드캐스트 전송 등 지원 |
기존에는 system_server에 직접 코드를 주입하고 frida-java-bridge에 의존했다. frida-java-bridge는 libart.so의 내부 구조에 의존하므로, ART 버전이 바뀔 때마다 호환성 문제가 발생했다.
변경 후에는 frida-helper.dex를 Linux/Droidy 백엔드 간에 공유하여 사용한다. frida-java-bridge 의존성을 제거함으로써 libart.so 호환성 문제로부터 자유로워졌다.
5. 후속 릴리즈 (17.6.1 ~ 17.7.3)
* 2026-02-20 기준 작성되었습니다.
17.6.0의 대규모 변경 이후 빠른 속도로 안정화 릴리즈가 이어졌다.
17.6.1
- BTI(Branch Target Identification) 활성화된 arm64 시스템에서 크래시 수정
- Zymbiote 페이로드를 더 안전한 주소 범위에 배치
BTI는 최신 arm64 프로세서의 보안 기능으로, 간접 분기 대상이 올바른지 검증한다. 17.6.0의 페이로드가 BTI 정책을 위반하여 크래시가 발생했고, 17.6.1에서 수정되었다.
17.6.2
- memfd 제한 환경(Termux 등) 지원
- ELF 모듈 안정성 개선
- BTI 활성 환경의 간접 분기 크래시 추가 수정
17.7.0 ~ 17.7.3 (2026.02.13 ~ 16)
4일간 4개 버전이 연속 릴리즈되었다. 주요 변경사항은 다음과 같다.
- Android 14/15 호환성 복구
- Zygote/Exceptor/ART 진입점 수정
- ptrace 의존도 추가 감소
17.7.x에서 Android 14/15 대상으로 Zygote 진입에 실패하는 문제가 보고되었고, ART 내부 구조 변경에 맞춰 진입점을 수정하는 작업이 이루어졌다. 4일간 4번의 릴리즈가 나왔다는 것 자체가 이 영역의 안정화가 아직 진행 중임을 보여준다.
6. 앱 진단 관점에서의 영향
6.1 긍정적 변화
- Magisk 등 다른 도구와의 충돌 감소: ptrace 점유가 해소되었으므로, Magisk/Riru/Zygisk 등과 동시에 사용할 때 충돌이 줄어든다.
- Zygote 크래시 확률 감소: 외부 패칭 + 즉시 원복 방식이므로 Zygote 내부에 에이전트가 상주하지 않는다.
- SELinux 강화 환경에서의 동작 가능성 향상: ptrace 차단 정책의 영향을 받지 않는다.
6.2 주의사항
- frida-server를 17.6.0 이상으로 업데이트해야 한다.
- 기존 child-gating 기반 스크립트의 내부 동작 방식이 달라졌으나, 사용자 API(
spawn,attach등)는 유지된다. 대부분의 스크립트는 수정 없이 동작한다. - BTI 활성화된 최신 기기에서는 17.6.1 이상이 필수이다.
- Android 14/15 대상 진단 시 17.7.x를 권장한다. 17.7.0~17.7.3은 안정화 과정이므로 가능한 최신 버전을 사용한다.
- 일부 OEM 환경에서
/proc/$pid/mem접근도 제한될 수 있다. ptrace 차단과 마찬가지로, 보안 강화된 커널에서는 이 경로도 막힐 가능성이 있다.
6.3 탐지 관점 변화
이번 변경은 Frida 탐지/우회 관점에서도 의미가 크다.
기존 탐지 방식의 무력화
기존에는 /proc/self/status의 TracerPid 값을 확인하여 ptrace 연결 여부로 Frida를 탐지하는 방식이 널리 사용되었다. 17.6.0부터 ptrace를 사용하지 않으므로 TracerPid는 항상 0이다. 이 탐지 방식은 더 이상 유효하지 않다.
# 기존: frida-server가 ptrace로 연결된 경우
$ cat /proc/<pid>/status | grep TracerPid
TracerPid: 12345
# 17.6.0 이후: ptrace를 사용하지 않음
$ cat /proc/<pid>/status | grep TracerPid
TracerPid: 0
새로운 탐지 벡터
ptrace 기반 탐지가 무력화되었으므로, 보안 솔루션은 다른 벡터를 탐지에 활용할 수 있다.
- abstract UNIX 소켓의 존재 여부 확인
- 메모리 영역에서 Zymbiote 페이로드 패턴 탐지
frida-helper.dex의 존재 여부- frida-server 프로세스 및 포트 탐지 (기존과 동일)
다만, Zymbiote 페이로드는 실행 후 즉시 원복되므로 메모리 패턴 탐지의 시간 윈도우가 매우 짧다.
7. 기존 vs 변경 비교 요약
| 항목 | 기존 (~ 17.5.x) | 변경 (17.6.0 ~) |
|---|---|---|
| 주입 방식 | ptrace() + agent 주입 | /proc/$pid/mem + Zymbiote |
| Zygote 내부 진입 | frida-agent 상주 | 외부 패칭, 실행 후 원복 |
| child-gating | fork() 감시 | setArgV0Native() 후킹 |
| system_server | 코드 주입 + frida-java-bridge | frida-helper.dex |
| 페이로드 크기 | frida-agent 전체 | 920바이트 (arm64) |
| 타 도구 호환 | ptrace 점유로 충돌 | 충돌 없음 |
| TracerPid 탐지 | 탐지 가능 | 탐지 불가 |
| 실패 시 영향 | Zygote 크래시 가능 | 안전 복귀 |
8. 참고 자료
'Security > └ 안드로이드 취약점 진단' 카테고리의 다른 글
| Flutter HTTPS 통신 Intercept 위한 로직 분석 (0) | 2025.10.24 |
|---|---|
| [JADX-AI-MCP] jadx 와 MCP 연동하여 APK 분석하기 (0) | 2025.09.29 |
| [Androidmeda] LLM Powered Deobfuscation for Android Apps (1) | 2025.09.10 |
| [Fridump3] Frida 17 이상 버전에서 Not a function 에러 해결 (1) | 2025.08.13 |
| [안드로이드 모의해킹] 메모리 내 중요정보 평문 노출 (1) | 2024.11.26 |
