77 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flutter
{{#include ../../banners/hacktricks-training.md}}
# Flutter
Flutter는 **Google의 크로스 플랫폼 UI 툴킷**으로, 개발자가 단일 Dart 코드베이스를 작성하면 **Engine** (네이티브 C/C++)가 이를 Android 및 iOS에 맞는 플랫폼 특정 머신 코드로 변환합니다. Engine은 **Dart VM**, **BoringSSL**, Skia 등을 포함하고, 공유 라이브러리 **libflutter.so** (Android) 또는 **Flutter.framework** (iOS)로 배포됩니다. 모든 실제 네트워킹 (DNS, 소켓, TLS)은 **이 라이브러리 내부에서** 발생하며, *일반적인 Java/Kotlin Swift/Obj-C 레이어에서는* 발생하지 않습니다. 이러한 격리된 설계 때문에 일반적인 Java 레벨 Frida 훅이 Flutter 앱에서 실패합니다.
## Flutter에서 HTTPS 트래픽 가로채기
이것은 이 [블로그 포스트](https://sensepost.com/blog/2025/intercepting-https-communication-in-flutter-going-full-hardcore-mode-with-frida/)의 요약입니다.
### Flutter에서 HTTPS 가로채기가 어려운 이유
* **SSL/TLS 검증은 BoringSSL의 두 레이어 아래에 존재**하므로 Java SSLpinning 우회는 이를 건드리지 않습니다.
* **BoringSSL은 libflutter.so 내부에 *자체* CA 저장소를 사용**하므로, Burp/ZAP CA를 Android의 시스템 저장소에 가져와도 아무런 변화가 없습니다.
* libflutter.so의 기호는 **제거되고 변형되어** 동적 도구에서 인증서 검증 기능을 숨깁니다.
### 정확한 Flutter 스택 지문 찍기
버전을 아는 것은 올바른 바이너리를 재구성하거나 패턴 매칭하는 데 도움이 됩니다.
Step | Command / File | Outcome
----|----|----
Get snapshot hash | ```bash\npython3 get_snapshot_hash.py libapp.so\n``` | `adb4292f3ec25…`
Map hash → Engine | **enginehash** 목록에서 reFlutter | Flutter 3 · 7 · 12 + engine commit `1a65d409…`
Pull dependent commits | 해당 엔진 커밋의 DEPS 파일 | • `dart_revision` → Dart v2 · 19 · 6<br>• `dart_boringssl_rev` → BoringSSL `87f316d7…`
[여기에서 get_snapshot_hash.py를 찾으세요](https://github.com/Impact-I/reFlutter/blob/main/scripts/get_snapshot_hash.py).
### Target: `ssl_crypto_x509_session_verify_cert_chain()`
* **BoringSSL의 `ssl_x509.cc`**에 위치합니다.
* **`bool`을 반환**합니다 단일 `true`가 전체 인증서 체인 검사를 우회하는 데 충분합니다.
* 모든 CPU 아키텍처에 동일한 함수가 존재하며, 오직 opcode만 다릅니다.
### Option A **reFlutter**를 이용한 바이너리 패칭
1. 앱의 Flutter 버전에 맞는 정확한 Engine 및 Dart 소스를 **클론**합니다.
2. 두 개의 핫스팟을 **정규 표현식 패치**합니다:
* `ssl_x509.cc`에서 `return 1;`로 강제합니다.
* (선택 사항) `socket_android.cc`에서 프록시를 하드코딩합니다 (`"10.0.2.2:8080"`).
3. libflutter.so를 **재컴파일**하고, APK/IPA에 다시 넣고, 서명하고, 설치합니다.
4. 일반 버전용 **사전 패치된 빌드**가 reFlutter GitHub 릴리스에 배포되어 빌드 시간을 절약합니다.
### Option B **Frida**를 이용한 라이브 훅킹 (“하드코어” 경로)
기호가 제거되었기 때문에, 로드된 모듈의 첫 바이트를 패턴 스캔한 다음, 반환 값을 즉석에서 변경합니다.
```javascript
// attach & locate libflutter.so
var flutter = Process.getModuleByName("libflutter.so");
// x86-64 pattern of the first 16 bytes of ssl_crypto_x509_session_verify_cert_chain
var sig = "55 41 57 41 56 41 55 41 54 53 48 83 EC 38 C6 02";
Memory.scan(flutter.base, flutter.size, sig, {
onMatch: function (addr) {
console.log("[+] found verifier at " + addr);
Interceptor.attach(addr, {
onLeave: function (retval) { retval.replace(0x1); } // always 'true'
});
},
onComplete: function () { console.log("scan done"); }
});
```
죄송합니다. 요청하신 내용을 처리할 수 없습니다.
```bash
frida -U -f com.example.app -l bypass.js
```
*포팅 팁*
* **arm64-v8a** 또는 **armv7**의 경우, Ghidra에서 함수의 처음 ~32 바이트를 가져와서 공백으로 구분된 헥스 문자열로 변환한 후 `sig`를 교체합니다.
* **Flutter 릴리스당 하나의 패턴**을 유지하고, 빠른 재사용을 위해 치트 시트에 저장합니다.
### 프록시를 통한 트래픽 강제 전송
Flutter 자체는 **장치 프록시 설정을 무시합니다**. 가장 쉬운 옵션:
* **Android Studio 에뮬레이터:** 설정 ▶ 프록시 → 수동.
* **물리적 장치:** 악성 Wi-Fi AP + DNS 스푸핑, 또는 Magisk 모듈 편집 `/etc/hosts`.
## 참조
- [https://sensepost.com/blog/2025/intercepting-https-communication-in-flutter-going-full-hardcore-mode-with-frida/](https://sensepost.com/blog/2025/intercepting-https-communication-in-flutter-going-full-hardcore-mode-with-frida/)
{{#include ../../banners/hacktricks-training.md}}