1. 연구 배경
1.1 서론
Flutter 애플리케이션의 네트워크 통신을 분석하고 디버깅하기 위해서는 HTTPS 트래픽을 intercept할 수 있어야 한다. 하지만 일반적으로 사용했던 Burp CA 시스템 경로 로드 방식으로 intercept가 동작하지 않는다. 이 포스팅에서는 Flutter로 개발된 앱에서 HTTPS 통신을 가로채는 방법과 그 원리를 심층 분석한다.
1.2 연구 동기
- 개발/디버깅 필요성: API 통신 내용 확인, 에러 디버깅
- 테스트 환경 구축: Mock 서버 연동, 네트워크 시나리오 테스트
- 보안 분석: 앱의 보안 취약점 점검, 데이터 전송 검증
- 역공학/분석: 기존 앱의 통신 프로토콜 분석
1.3 Flutter HTTPS 통신의 특징
Flutter는 모바일 애플리케이션을 진단할 때 일반적인 네이티브 앱과 다른 특성을 보인다:
- Dart의
HttpClient기반 네트워크 통신 - 기본적으로 Certificate Pinning 미적용
dart:io라이브러리를 통한 저수준 제어 가능- iOS/Android 플랫폼별 네트워크 스택 차이
- 시스템 CA 인증서 저장소를 우회할 수 있는 구조
이러한 특성으로 인해 기존의 Burp Suite CA 인증서를 시스템에 설치하는 방식만으로는 HTTPS 트래픽 intercept가 어렵다.
2. 문제 파악: 왜 Flutter 앱은 Intercept가 어려운가?
2.1 네이티브 앱 vs Flutter 앱: 아키텍처 비교
개발자는 네이티브 Android 앱과 Flutter 앱에서 동일한 HTTPS 통신 기능을 구현할 수 있다. 하지만 플랫폼에 따라 intercept 성공 여부가 달라지는 이유는 내부 구조의 근본적인 차이 때문이다.
2.1.1 네이티브 Android 앱의 네트워크 스택
[Java/Kotlin 앱 코드]
↓
[OkHttp / HttpURLConnection]
↓
[Android Framework]
↓
[System CA Store] ← Burp CA 설치 위치
↓
[OpenSSL / BoringSSL]
↓
[Linux Kernel Network Stack]
↓
[Network Interface]
특징:
- Android 시스템의 CA 인증서 저장소를 직접 사용
/system/etc/security/cacerts/또는 사용자 CA 저장소 참조- Burp Suite CA를 시스템에 설치하면 앱이 이를 신뢰
- 따라서 프록시 intercept가 정상 동작
2.1.2 Flutter 앱의 네트워크 스택
[Dart 앱 코드]
↓
[http package / dio]
↓
[dart:io HttpClient]
↓
[Dart VM (libflutter.so 내장)] ← 독립적인 SSL/TLS 구현
↓
[Flutter Engine (C++)]
↓
[BoringSSL (자체 번들)]
↓
[Platform Channel] (필요시)
↓
[Native Network Stack]
특징:
- Flutter Engine이 자체 SSL/TLS 스택 보유
- Android 시스템 CA 저장소를 우회 가능
- Dart VM에 내장된 인증서 검증 로직 사용
- Burp CA를 시스템에 설치해도 Flutter Engine이 이를 무시할 수 있음
- 따라서 기본 방식으로는 intercept 실패
2.2 Flutter Engine의 역할
Flutter는 크로스플랫폼 프레임워크로, Android와 iOS 모두에서 동작해야 한다. 이를 위해 플랫폼 독립적인 통합 계층이 필요하며, 이것이 바로 Flutter Engine이다.
2.2.1 Flutter Engine의 구성
- 언어: 대부분 C++로 작성
- 위치: Android APK 내부의
lib/[arch]/libflutter.so - 크기: 약 8~12MB (architecture별 상이)
- 역할:
- Dart 코드 실행을 위한 Dart VM 포함
- 렌더링 엔진 (Skia)
- 플랫폼별 추상화 계층
- 네트워크 통신 및 SSL/TLS 처리
2.2.2 libflutter.so에 포함된 주요 기능
- Dart VM (간소화된 버전)
- Dart 코드의 JIT/AOT 컴파일 및 실행
- 메모리 관리 및 가비지 컬렉션
- Isolate 관리
- 네트워크 기능
- HTTP/HTTPS 클라이언트 구현
- SSL/TLS Handshake 처리
- 인증서 검증 로직
- Certificate Pinning 지원 (구현 시)
- 암호화 라이브러리
- BoringSSL (Google의 OpenSSL fork) 번들
- 자체 CA 인증서 저장소 (필요시)
- 암호화/복호화 기능
- 플랫폼 인터페이스
- Platform Channels
- 네이티브 코드와의 통신
2.3 SSL/TLS 인증서 검증 과정의 차이
네이티브 Android 앱
// Android 네이티브 코드
HttpsURLConnection connection =
(HttpsURLConnection) url.openConnection();
// 시스템 CA 저장소 자동 사용
// /system/etc/security/cacerts/ 참조
connection.connect();
→ 시스템에 설치된 Burp CA를 자동으로 신뢰
Flutter 앱
// Flutter/Dart 코드
final response = await http.get(
Uri.parse('https://example.com')
);
// 내부적으로:
// 1. dart:io HttpClient 호출
// 2. libflutter.so의 SSL 구현 사용
// 3. 자체 인증서 검증 (시스템 CA 우회 가능)
→ 시스템 CA와 독립적으로 검증
2.4 왜 Flutter는 이런 구조를 선택했는가?
1. 크로스플랫폼 일관성
- iOS와 Android에서 동일한 네트워크 동작 보장
- 플랫폼별 차이로 인한 버그 최소화
2. 성능 최적화
- JNI/Platform Channel 호출 오버헤드 감소
- C++로 작성된 고성능 네트워크 스택
3. 보안 제어
- Certificate Pinning 등 고급 보안 기능 구현 용이
- 플랫폼 제약 없이 보안 정책 적용
4. 독립성
- Android/iOS 시스템 업데이트에 영향 받지 않음
- 일관된 TLS 버전 및 암호 스위트 사용
2.5 핵심 문제 요약
┌─────────────────────────────────────┐
│ 네이티브 Android 앱 │
├─────────────────────────────────────┤
│ System CA에 Burp 추가 → 성공 ✓ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Flutter 앱 │
├─────────────────────────────────────┤
│ System CA에 Burp 추가 → 실패 ✗ │
│ (libflutter.so가 독립 검증) │
└─────────────────────────────────────┘
결론:
Flutter 앱의 HTTPS intercept를 위해서는 libflutter.so 내부의 인증서 검증 로직을 우회하거나, Dart 코드 레벨에서 검증을 무력화해야 한다.
3. 테스트 앱 개발
3.1 테스트 앱 개요
본 연구를 위해 HTTPS 통신을 수행하는 간단한 Flutter 앱을 개발했다.
https://github.com/PeanutKingPeanut/flutter-https-sample-app
GitHub - PeanutKingPeanut/flutter-https-sample-app
Contribute to PeanutKingPeanut/flutter-https-sample-app development by creating an account on GitHub.
github.com


- 이름: HTTPS Intercept Test App
- 목적: Flutter HTTPS 통신 intercept 기법 검증
- API: JSONPlaceholder (공개 REST API)
- 빌드 크기: 약 19.1MB (release APK)
- 소스코드:
sample_app/디렉토리
3.2 주요 기능
앱은 세 가지 주요 HTTPS 통신 기능을 제공한다:
1) GET 요청 - 게시물 조회
엔드포인트: https://jsonplaceholder.typicode.com/posts/1
메서드: GET
응답: 단일 게시물의 ID, Title, Body 정보
2) POST 요청 - 게시물 생성
엔드포인트: https://jsonplaceholder.typicode.com/posts
메서드: POST
Content-Type: application/json
요청 본문:
{
"title": "Flutter Test",
"body": "HTTPS Intercept Test",
"userId": 1
}
3) GET 요청 - 사용자 목록 조회
엔드포인트: https://jsonplaceholder.typicode.com/users
메서드: GET
응답: 전체 사용자 목록 (앱에서는 최초 5명만 표시)
3.3 앱 동작 구조
3.3.1 아키텍처 다이어그램
[UI Layer]
↓
[State Management] (StatefulWidget)
↓
[HTTP Client] (http package)
↓
[Dart VM Network Stack]
↓
[Platform Channel] (Android/iOS)
↓
[Native Network Stack]
↓
[HTTPS/TLS Connection]
3.3.2 핵심 코드 구조
HTTP 패키지 사용
import 'package:http/http.dart' as http;
import 'dart:convert';
// GET 요청 예시
Future<void> _fetchGetData() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
// 데이터 처리
}
}
// POST 요청 예시
Future<void> _fetchPostData() async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body: jsonEncode({
'title': 'Flutter Test',
'body': 'HTTPS Intercept Test',
'userId': 1,
}),
);
}
3.3.3 네트워크 통신 흐름
- UI 이벤트 발생: 사용자가 버튼 클릭
- State 업데이트:
setState()로 로딩 상태 변경 - HTTP 요청 생성:
http패키지를 통한 요청 생성 - Dart HttpClient 실행:
dart:io의 HttpClient가 실제 요청 수행 - TLS Handshake:
- 서버와 TLS 연결 수립
- 서버 인증서 검증 (여기서 intercept 문제 발생)
- 암호화 세션 수립
- 데이터 전송/수신: 암호화된 데이터 교환
- 응답 처리: JSON 파싱 및 UI 업데이트
3.3.4 인증서 검증 프로세스
Flutter의 기본 인증서 검증 과정:
1. TLS Handshake 시작
↓
2. 서버 인증서 수신
↓
3. Dart VM 내장 CA 검증
↓
4. 인증서 체인 검증
↓
5. 호스트네임 확인
↓
6. 유효기간 확인
↓
7. 검증 통과 → 연결 수립
검증 실패 → HandshakeException
문제점: Flutter는 시스템 CA 저장소 대신 자체 CA 저장소를 우선 사용하기 때문에, Android 시스템에 Burp CA를 설치해도 Flutter 앱에서는 인증되지 않는다.
3.4 테스트 앱의 특징
3.4.1 의도적인 취약점
본 테스트 앱은 다음과 같은 특징을 가진다:
- ✅ Certificate Pinning 없음: intercept 테스트를 위해 의도적으로 제외
- ✅ 기본 HttpClient 사용: 표준
http패키지 사용 - ✅ 인증서 검증 우회 미적용: 초기 상태는 표준 검증 사용
- ✅ 실제 HTTPS 통신: 공개 API를 통한 실제 트래픽 생성
3.4.2 기술 스택
- Flutter: 3.32.8
- Dart: 3.8.1
- HTTP 패키지: ^1.1.0
- UI Framework: Material Design 3
- State Management: StatefulWidget
3.4.3 APK 구조 분석
실제 빌드된 APK를 압축 해제한 결과:
app-release.apk (19.1MB)
├── lib/
│ ├── arm64-v8a/
│ │ ├── libapp.so (3.5MB) ← Dart AOT 컴파일 코드
│ │ └── libflutter.so (11.3MB) ← Flutter Engine
│ ├── armeabi-v7a/
│ │ ├── libapp.so (4.0MB)
│ │ └── libflutter.so (8.0MB)
│ └── x86_64/
│ ├── libapp.so (3.7MB)
│ └── libflutter.so (12.4MB)
├── assets/
│ └── flutter_assets/
│ ├── kernel_blob.bin
│ └── fonts/, packages/ 등
└── classes.dex
주요 파일 설명:
libflutter.so: Flutter Engine 핵심 (Dart VM, SSL/TLS, Skia 등)libapp.so: 컴파일된 Dart 애플리케이션 코드 (AOT)- Flutter Engine이 앱 크기의 절반 이상을 차지함
4. libflutter.so 심층 분석
4.1 분석 환경 및 도구
분석 대상:
- 파일:
lib/x86_64/libflutter.so - 크기: 12,402,736 bytes (약 12.4MB)
- MD5:
83b572909d33baf7619dfb8b836a723a - SHA256:
41005a06c513ea7f8489c95bb8aee21425b1d0ecec0c22b44a93fcfae137ca73
사용 도구:
- IDA Pro 8.x (x86_64 바이너리 분석)
- objdump / nm (심볼 테이블 분석)
- strings (문자열 추출)
4.2 SSL/TLS 관련 심볼 확인
# objdump 또는 nm 도구로 분석
$ nm libflutter.so | grep -i ssl
# 결과 예시:
_ZN4dart8bin17SSLCertContext...
_ZN4dart8bin10X509Helper...
BoringSSL_SSL_CTX_new
BoringSSL_SSL_connect
이러한 심볼들은 Flutter Engine이 자체적으로 SSL/TLS를 처리함을 증명한다.
4.3 IDA 를 통한 정적 분석
4.3.1 분석 방법론
- 문자열 기반 분석: SSL/TLS 관련 에러 메시지 검색
- Xref 추적: 문자열 참조 함수 역추적
- 함수 디컴파일: 핵심 검증 로직 분석
- 호출 그래프 분석: 함수 간 호출 관계 매핑
4.3.2 주요 발견 문자열
IDA Pro에서 strings 윈도우를 통해 다음 문자열들을 확인:
BoringSSL 소스 파일 경로:
../../../flutter/third_party/boringssl/src/ssl/ssl_x509.cc
../../../flutter/third_party/boringssl/src/crypto/x509/a_verify.cc
../../../flutter/third_party/boringssl/src/crypto/x509/x509_vfy.cc
→ Flutter Engine이 BoringSSL을 번들로 포함하고 있음을 확인
인증서 검증 관련 에러 메시지:
- "CERTIFICATE_VERIFY_FAILED"
- "unable to verify the first certificate"
- "certificate chain too long"
- "self signed certificate"
- "invalid CA certificate"
- "certificate has expired"
- "certificate not trusted"
- "certificate rejected"
- "certificate revoked"
Dart 네트워크 관련:
- "dart:io/secure_socket.dart"
- "SecureSocket_Connect"
- "SecureSocket_Init"
- "SecureSocket_Handshake"
- "SecureSocket_Destroy"
- "X509Certificate"
- "HandshakeException"
- "TlsException"
- "Connection terminated during handshake"
4.4 SSL 검증 프로세스 역공학
IDA Pro를 통해 실제 libflutter.so (x86_64)를 분석한 결과, 다음과 같은 정확한 호출 체인을 확인:
1. TLS Handshake 시작
↓
2. 서버 인증서 수신
↓
3. ssl_verify_peer_cert() 호출 ← 패턴 매칭으로 식별
↓
4. sub_7FEB3B / sub_7FEDFB 호출 ← ssl_x509.cc (SSL 래퍼)
│ 크기: 561 bytes / 350 bytes
↓
5. sub_7DCD69 호출 ← x509_vfy.cc (인증서 체인 검증 메인 함수)
│ 크기: 8,854 bytes
│ 이것이 session_verify_cert_chain()의 역할
│
├─ 인증서 체인 순회 및 검증
├─ CA certificate 검증
├─ 날짜 유효성 검증
└─ sub_7D417A 호출 ← a_verify.cc (서명 검증)
크기: 1,158 bytes
↓
6. 검증 결과 반환
실패 시: CERTIFICATE_VERIFY_FAILED 에러 (코드 17)
성공 시: ssl_verify_ok (0) 반환
4.4.1 핵심 함수 상세 분석

1) sub_7DCD69 (0x7DCD69) - 인증서 체인 검증 메인 함수
// IDA Pro 디컴파일 결과 (간소화)
__int64 __fastcall sub_7DCD69(__int64 a1)
{
// Line 341: 신뢰 저장소 확인
if ( !v6 || !sub_7D3D5C(v6, *(_QWORD *)(a1 + 8)) )
{
*(_DWORD *)(a1 + 76) = 17; // CERTIFICATE_VERIFY_FAILED
return 0LL;
}
// Line 407, 409: 자체 서명 인증서 감지
if ( !(unsigned int)sub_7DEFFF(v25, &v305) )
goto LABEL_141; // 실패
// Line 813: RSA/ECDSA 서명 검증 호출
if ( (unsigned int)sub_7D417A(
(__int64)&unk_BB8490, // 검증 컨텍스트
v96[1], // 공개키
(int *)v96[2], // 서명 알고리즘
*v96, // 인증서 데이터
v271) ) // 서명 값
{
goto LABEL_631; // 검증 성공
}
// 실패 처리
*((_DWORD *)v1 + 19) = 7; // 서명 검증 실패
return 0LL;
}
주요 로직:
- Line 341: BoringSSL 내장 신뢰 저장소에서 인증서 조회
sub_7D3D5C(): 신뢰 저장소 조회 함수- Burp CA가 없으면 여기서 실패
- Line 407: 자체 서명 인증서 확인
sub_7DEFFF(): 자체 서명 여부 판단- Burp CA는 자체 서명이므로 이 경로로 진입
- Line 813: 서명 검증 수행
sub_7D417A()호출- Burp CA 서명이 신뢰할 수 없음으로 판단되면 실패
2) sub_7D417A (0x7D417A) - 서명 검증 함수
- 역할: RSA/ECDSA 등 공개키 알고리즘 기반 서명 검증
- 입력: 인증서 데이터, 공개키, 서명 알고리즘, 서명 값
- 출력: 검증 성공(1) / 실패(0)
- 소스:
a_verify.cc(BoringSSL)
3) sub_7FEB3B, sub_7FEDFB - SSL 컨텍스트 브리지 함수
- 역할: SSL 컨텍스트와 X.509 검증 로직 연결
- 크기: 561 bytes, 350 bytes
- 소스:
ssl_x509.cc(BoringSSL)
4.5 Burp CA가 거부되는 정확한 지점
TLS Handshake 시작
↓
Burp Proxy CA 인증서 수신
↓
sub_7DCD69() 호출
│
├─ Line 341: sub_7D3D5C() - 신뢰 저장소 조회
│ └─ Burp CA가 BoringSSL 신뢰 저장소에 없음 → 실패 가능성
│
├─ Line 407: sub_7DEFFF() - 자체 서명 확인
│ └─ Burp CA는 자체 서명 인증서 → 플래그 설정
│
├─ Line 813: sub_7D417A() - 서명 검증
│ └─ Burp CA 서명을 신뢰할 수 없음으로 판단 → 실패
│
└─ Line 329 or 344: 에러 코드 설정
*(_DWORD *)(a1 + 76) = 17; ← CERTIFICATE_VERIFY_FAILED
↓
검증 실패 → HandshakeException 발생 → 연결 거부
핵심 문제:
- Burp CA는 자체 서명(Self-Signed) 인증서
- BoringSSL의 내장 신뢰 저장소에 Burp CA가 없음
- Android 시스템 CA 저장소를 참조하지 않음
- 따라서
sub_7DCD69내부에서 검증 실패 → 에러 코드 17 반환
4.6 검증 결과 요약
| 항목 | 값 | 비고 |
|---|---|---|
| 메인 검증 함수 | sub_7DCD69 (0x7DCD69) |
8,854 bytes |
| 서명 검증 함수 | sub_7D417A (0x7D417A) |
1,158 bytes |
| SSL 브리지 함수 | sub_7FEB3B (0x7FEB3B) |
561 bytes |
| SSL 브리지 함수 | sub_7FEDFB (0x7FEDFB) |
350 bytes |
| 실패 에러 코드 | 17 | CERTIFICATE_VERIFY_FAILED |
| 성공 반환값 | 0 or 1 | ssl_verify_ok |
5. 우회 스크립트 작성
5.1 Frida 동적 계측 기법
5.1.1 Frida 개요
Frida는 동적 계측 도구로, 런타임에 앱의 메모리를 직접 수정하여 동작을 변경할 수 있다.
5.2 IDA 분석 결과 기반 후킹 전략
5.2.1 후킹 포인트 선정
IDA 분석을 통해 확인한 함수들을 우선순위별로 정리:
우선순위 1 (권장): ssl_verify_peer_cert 패턴 매칭
// ARM64/x86_64 공통 패턴으로 최상위 검증 함수 찾기
// 가장 효과적이며 모든 하위 검증을 우회
우선순위 2: sub_7DCD69 (인증서 체인 검증 메인 함수)
// 0x7DCD69 오프셋 직접 후킹
// 체인 검증 전체를 우회
우선순위 3: sub_7D417A (서명 검증)
// 0x7D417A 오프셋 직접 후킹
// 서명 검증만 우회 (더 정교한 접근)
우선순위 4: sub_7FEB3B, sub_7FEDFB (SSL 브리지)
// 0x7FEB3B, 0x7FEDFB 오프셋 후킹
// SSL 컨텍스트 레벨 우회
5.2.2 Frida 스크립트: 메인 검증 함수 우회
방법 1: sub_7DCD69 (x509_vfy.cc - 체인 검증 메인 함수) 후킹
// frida_bypass_chain_verification.js
// x86_64 libflutter.so 기준
function hook_certificate_verification() {
var moduleName = "libflutter.so";
var baseAddr = Module.findBaseAddress(moduleName);
if (baseAddr == null) {
console.log("[-] libflutter.so not found!");
return;
}
console.log("[+] libflutter.so base address: " + baseAddr);
// sub_7DCD69: 인증서 체인 검증 메인 함수
var verify_chain_offset = 0x7DCD69;
var verify_chain_addr = baseAddr.add(verify_chain_offset);
console.log("[+] Hooking certificate chain verification at: " + verify_chain_addr);
Interceptor.replace(verify_chain_addr, new NativeCallback(function(a1) {
console.log("[*] Certificate chain verification bypassed!");
console.log(" Context: " + a1);
return 1; // 검증 성공 반환
}, 'int', ['pointer']));
console.log("[+] Successfully hooked sub_7DCD69");
}
// 스크립트 시작
setTimeout(function() {
console.log("[*] Starting Frida hooking script...");
hook_certificate_verification();
}, 1000);
사용 방법:
# 디바이스에 연결된 앱에 적용
frida -U -f com.example.sample_app -l frida_bypass_chain_verification.js --no-pause
# 실행 중인 앱에 attach
frida -U com.example.sample_app -l frida_bypass_chain_verification.js
5.2.3 Frida 스크립트: 서명 검증 우회
방법 2: sub_7D417A (a_verify.cc - 서명 검증) 후킹
// frida_bypass_signature_verification.js
function hook_signature_verification() {
var moduleName = "libflutter.so";
var baseAddr = Module.findBaseAddress(moduleName);
if (baseAddr == null) {
console.log("[-] libflutter.so not found!");
return;
}
// sub_7D417A: RSA/ECDSA 서명 검증 함수
var sig_verify_offset = 0x7D417A;
var sig_verify_addr = baseAddr.add(sig_verify_offset);
console.log("[+] Hooking signature verification at: " + sig_verify_addr);
Interceptor.replace(sig_verify_addr, new NativeCallback(
function(ctx, pubkey, algo, data, sig) {
console.log("[*] Certificate signature verification bypassed!");
console.log(" Context: " + ctx);
console.log(" Public Key: " + pubkey);
console.log(" Algorithm: " + algo);
return 1; // 서명 유효
},
'int',
['pointer', 'pointer', 'pointer', 'pointer', 'pointer']
));
console.log("[+] Successfully hooked sub_7D417A");
}
setTimeout(hook_signature_verification, 1000);
5.2.4 종합 후킹 스크립트
방법 3: 다중 레벨 후킹 (최대 호환성)
// frida_bypass_comprehensive.js
// 여러 함수를 동시에 후킹하여 최대 호환성 확보
function comprehensive_ssl_bypass() {
var moduleName = "libflutter.so";
var baseAddr = Module.findBaseAddress(moduleName);
if (baseAddr == null) {
console.log("[-] Module not found!");
return;
}
console.log("[+] libflutter.so found at: " + baseAddr);
// 1. 체인 검증 우회
try {
var chain_verify = baseAddr.add(0x7DCD69);
Interceptor.replace(chain_verify, new NativeCallback(
function(a1) {
console.log("[*] Chain verification bypassed");
return 1;
}, 'int', ['pointer']
));
console.log("[+] Hooked chain verification (0x7DCD69)");
} catch(e) {
console.log("[-] Failed to hook chain verification: " + e);
}
// 2. 서명 검증 우회
try {
var sig_verify = baseAddr.add(0x7D417A);
Interceptor.replace(sig_verify, new NativeCallback(
function(ctx, key, algo, data, sig) {
console.log("[*] Signature verification bypassed");
return 1;
}, 'int', ['pointer', 'pointer', 'pointer', 'pointer', 'pointer']
));
console.log("[+] Hooked signature verification (0x7D417A)");
} catch(e) {
console.log("[-] Failed to hook signature verification: " + e);
}
// 3. SSL 브리지 함수 우회
try {
var ssl_bridge1 = baseAddr.add(0x7FEB3B);
Interceptor.replace(ssl_bridge1, new NativeCallback(
function() {
console.log("[*] SSL bridge 1 bypassed");
return 1;
}, 'int', []
));
console.log("[+] Hooked SSL bridge 1 (0x7FEB3B)");
} catch(e) {
console.log("[-] Failed to hook SSL bridge 1: " + e);
}
try {
var ssl_bridge2 = baseAddr.add(0x7FEDFB);
Interceptor.replace(ssl_bridge2, new NativeCallback(
function() {
console.log("[*] SSL bridge 2 bypassed");
return 1;
}, 'int', []
));
console.log("[+] Hooked SSL bridge 2 (0x7FEDFB)");
} catch(e) {
console.log("[-] Failed to hook SSL bridge 2: " + e);
}
console.log("[+] Comprehensive SSL bypass completed!");
}
setTimeout(comprehensive_ssl_bypass, 1000);
5.3 실행 및 검증
5.3.1 Frida 설치 및 준비
# Frida 설치 (PC)
pip install frida-tools
# Android 디바이스에 frida-server 설치
# 1. GitHub에서 frida-server 다운로드 (architecture 맞춰서)
# 2. adb push frida-server /data/local/tmp/
# 3. adb shell "chmod 755 /data/local/tmp/frida-server"
# 4. adb shell "/data/local/tmp/frida-server &"
5.3.2 스크립트 실행
# 방법 1: 앱 실행과 동시에 후킹
frida -U -f com.example.sample_app -l frida_bypass_comprehensive.js --no-pause
# 방법 2: 실행 중인 앱에 attach
frida -U com.example.sample_app -l frida_bypass_comprehensive.js
# 방법 3: spawn 모드 (앱을 재시작하면서 후킹)
frida -U -f com.example.sample_app --no-pause -l frida_bypass_comprehensive.js
5.4 우회 원리 요약
기존 플로우 (실패):
TLS Handshake → Burp CA 수신 → sub_7DCD69 검증 → 실패 → HandshakeException
Frida 우회 플로우 (성공):
TLS Handshake → Burp CA 수신 → sub_7DCD69 호출 → Frida가 가로챔 → return 1 → 성공 → 연결 수립
효과:
- ✅ 코드 수정 없이 런타임에서 인증서 검증 우회
- ✅ Certificate Pinning이 적용된 앱에서도 동작
- ✅ 앱 재빌드 불필요
- ✅ Burp Suite로 모든 HTTPS 트래픽 intercept 가능
6. 결론
6.1 연구 성과 요약
본 연구를 통해 다음을 달성했다:
- Flutter HTTPS 통신 구조 이해
- Flutter Engine(libflutter.so)의 독립적인 SSL/TLS 스택 확인
- 네이티브 앱과의 근본적인 차이점 파악
- 테스트 앱 개발 및 검증
- HTTPS 통신을 수행하는 Flutter 테스트 앱 개발
- 실제 API를 통한 GET/POST 요청 구현
- libflutter.so 정적 분석
- IDA Pro를 통한 x86_64 바이너리 역공학
- 인증서 검증 핵심 함수 식별:
sub_7DCD69(0x7DCD69): 8,854 bytes - 체인 검증 메인 함수sub_7D417A(0x7D417A): 1,158 bytes - 서명 검증 함수sub_7FEB3B(0x7FEB3B): 561 bytes - SSL 브리지sub_7FEDFB(0x7FEDFB): 350 bytes - SSL 브리지
- Frida 동적 후킹 스크립트 개발
- IDA 분석 결과를 바탕으로 정확한 오프셋 기반 후킹
- 다중 레벨 우회 전략 수립
- Burp Suite 연동 성공
6.2 Burp CA 거부 메커니즘 규명
핵심 발견:
Burp Proxy CA 거부 메커니즘:
1. Flutter Engine은 BoringSSL을 자체 번들
└─ Android 시스템 CA 저장소와 독립적
2. TLS Handshake 시 sub_7DCD69 함수 호출
└─ BoringSSL 내장 신뢰 저장소에서 Burp CA 조회
3. Burp CA는 자체 서명 인증서
└─ 신뢰 저장소에 없음 → 검증 실패
4. 에러 코드 17 (CERTIFICATE_VERIFY_FAILED) 반환
└─ HandshakeException 발생 → 연결 거부
해결 방법:
- Frida를 통한
sub_7DCD69함수 후킹 - 검증 결과를 강제로 성공(1)으로 반환
- 시스템 CA 저장소 우회 없이 intercept 성공
6.3 실전 적용 가이드
6.3.1 개발 단계
방법 1: HttpOverrides 사용 (권장)
// 개발 환경에서만 활성화
if (kDebugMode) {
HttpOverrides.global = MyHttpOverrides();
}
장점:
- 코드 레벨 제어
- 안정적인 동작
- 디버깅 용이
6.3.2 분석/역공학 단계
방법 2: Frida 동적 후킹 (권장)
frida -U -f com.example.app -l frida_bypass_comprehensive.js --no-pause
장점:
- 코드 수정 불필요
- 실시간 디버깅
- 다양한 앱에 적용 가능
6.5 최종 권고사항
개발자를 위한 권장사항:
- 개발 환경에서는
HttpOverrides사용 - 프로덕션에서는 Certificate Pinning 구현
- 민감한 데이터는 추가 암호화 계층 적용
- 네트워크 보안 로깅 및 모니터링
보안 분석가를 위한 권장사항:
- IDA Pro를 통한 정적 분석 선행
- Frida 동적 분석으로 검증
부록
A. 검증 환경
- IDA Pro: 9.x
- Frida: 16.x
- Android: 11+
- Flutter: 3.32.8
- 분석 대상: libflutter.so (x86_64)
- 크기: 12,402,736 bytes
- MD5: 83b572909d33baf7619dfb8b836a723a
B. 주요 함수 오프셋 요약
| 함수 | 오프셋 | 크기 (bytes) | 역할 |
|---|---|---|---|
| sub_7DCD69 | 0x7DCD69 | 8,854 | 인증서 체인 검증 메인 |
| sub_7D417A | 0x7D417A | 1,158 | RSA/ECDSA 서명 검증 |
| sub_7FEB3B | 0x7FEB3B | 561 | SSL 브리지 1 |
| sub_7FEDFB | 0x7FEDFB | 350 | SSL 브리지 2 |
C. 참고 자료
면책 조항: 본 연구는 교육 및 개인 학습 목적으로 작성되었습니다. 실제 운영 환경에서는 적절한 보안 조치를 취해야 하며, 타인의 권리를 침해하지 않도록 주의해야 합니다. 본 자료를 활용한 모든 행위에 대한 책임은 사용자에게 있습니다.
'Security > └ 안드로이드 취약점 진단' 카테고리의 다른 글
| [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 |
| [안드로이드 모의해킹] 코드 패치와 앱 무결성 검증 (NDK 코드 분석) (0) | 2024.11.17 |
