◼︎ 개요
Flutter 앱은 Flutter 프레임워크(https://flutter.dev/)를 사용해 개발된 앱으로, Flutter 프레임워크는 Dart 언어(https://dart.dev)를 사용합니다.
다양한 앱들이 Flutter 로 개발됩니다. Google Ads, BMW, 네이버 블로그, 네이버 지식인, 삼쩜삼, Demaecan, Nubank 등이 있죠.
Flutter 는 빠르게 네이티브 성능의 앱을, 그것도 멀티플랫폼으로 개발할 수 있습니다.
개발자들에게는 Flutter 가 좋은 옵션이 되겠네요.
음...하지만, 보안인에게는 Flutter 앱이 꽤나 골치가 아픕니다.
분석을 하려고 하는데 심볼은 전부 날라가 있고, 일반적인 방식으로 네트워크 프록시도 안잡히기 때문이죠.
본 글에서는 안드로이드 Flutter 앱의 http 패킷을 BurpSuite 으로 잡기 위해 수행한 분석에 대해서 다뤄보고자 합니다.
성격 급하신 분들을 위해, 제가 작성한 frida script 공유합니다.
https://github.com/hackcatml/frida-flutterproxy
◼︎ BurpSuite 으로 http 패킷 보내기
내 컴퓨터에서 돌아가고 있는 Burp 에 http 패킷을 보내야 프록시를 잡을 수 있습니다.
그런데, Flutter 의 경우 Flutter 엔진 내부에서 네트워크를 담당하고 있어서 아래 그림처럼 단말기에서 프록시를 설정하여도 패킷이 Burp 로 날라가지 않습니다.
Flutter 엔진에서 "Socket_CreateConnect" 라는 함수가 서버와의 연결을 담당합니다. (출처: https://blog.weghos.com/flutter/engine/third_party/dart/runtime/bin/socket.cc.html)
음...IDA 에서 해당 함수를 찾아봐야 겠군요.
안드로이드에서 Flutter 엔진 파일은 libflutter.so 입니다.
"Socket_CreateConnect" 문자열을 검색해서 클릭, 클릭 따라 들어가면 쉽게 해당 함수의 주소를 찾아낼 수 있습니다.
해당 함수 sub_6FD704 의 pseudo code 의 생김새를 보아하니, 두번째 함수 sub_70371c 가 "SocketAddress::GetSockAddr" 함수 이겠군요.
sub_70371c 의 두번째 인자는 sockaddr 구조체입니다.
아하! sub_70371C 를 후킹하면 sockaddr 구조체 주소를 얻을 수 있겠군요. sockaddr 구조체에는 IP 및 PORT 정보가 담겨져 있기 때문에, 이를 조작해서 Burp 로 패킷을 보낼 수 있겠습니다.
음 좀더 살펴보니 "Socket_CreateConnect" 함수는 내부적으로 socket 함수를 사용하고 있습니다.
아하! sockaddr 구조체 조작 시점을 socket 함수 사용 시점으로 하면 되겠네요. socket 함수 사용 시점이 네트워크 통신을 위한 socket 생성 시점이니, 이때 Burp IP 와 PORT 로 조작한 sockaddr 구조체를 준비해 놓으면 되겠습니다.
그런데 문제는 Flutter 앱이 업데이트 되거나 다른 Flutter 앱을 마주치면 매번 IDA로 디컴파일해서 "SocketAddress::GetSockAddr" 함수 주소를 찾아내고 후킹을 해서 sockaddr 구조체 주소를 얻어야 하는 건가 싶습니다.
그래서 여러 Flutter 앱들을 디컴파일해서 살펴봤는데요, 모두 "Socket_CreateConnect" 함수의 두번째 BL 인스트럭션이 가리키는 주소가 "SocketAddress::GetSockAddr" 함수 주소였습니다.
여러 Flutter 앱들에서 동일 패턴이 발견되었으니, 이는 frida script 로 충분히 구현 할 수 있습니다. 다음과 같은 흐름이 되겠죠.
libflutter.so 메모리에서 "Socket_CreateConnect" 문자열을 메모리 스캔 -->
"Socket_CreateConnect" 문자열이 저장된 주소를 참조하는 곳을 메모리 스캔 -->
(발견된 주소 - 0x10) 이 "Socket_CreateConnect" 함수 주소임 -->
"Socket_CreateConnect" 함수 주소에서 두 번째 BL 지시어가 가리키는 곳이 "SocketAddress::GetSockAddr" 함수 주소임 -->
"SocketAddress::GetSockAddr" 함수를 후킹해서 sockaddr 구조체 주소를 얻을 수 있음 -->
socket 함수를 후킹해서 socket 함수 사용 시점에 sockaddr 구조체의 정보를 Burp IP 와 PORT 로 변조
자 여기까지 frida 로 구현하고 앱을 실행해보면, Burp로 패킷이 날라가기는 하지만 패킷이 잡히지는 않습니다.
Burp 에서 "Remote host terminated the handshake" 에러가 발생합니다.
◼︎Verify cert chain 우회
위 에러의 원인을 찾아보니, Flutter 엔진의 경우 자체적으로 boringssl 을 사용해서 인증서 검증을 하고 있습니다.
그러다 보니 Burp 인증서가 검증을 통과하지 못해서 에러가 발생한 것이지요.
Flutter 엔진에서 인증서 검증을 담당하는 함수는 "ssl_crypto_x509_session_verify_cert_chain" 입니다. (출처: https://blog.weghos.com/flutter/engine/third_party/boringssl/src/ssl/ssl_x509.cc.html)
음 함수 내부에 "ssl_client" 라는 문자열이 보이네요. IDA 에서 해당 문자열을 검색하면 verify_cert_chain 함수를 쉽게 찾을 수 있겠습니다.
여러 Flutter 앱들을 살펴보았는데, 모두 "ssl_client" 문자열이 노출되었으며 이를 통해 "ssl_crypto_x509_session_verify_cert_chain" 함수 주소를 찾아낼 수 있었습니다.
따라서 이것도 충분히 frida script 로 구현 가능합니다. 이런 흐름이 되겠죠
libflutter.so 메모리에서 "ssl_client" 문자열을 메모리 스캔 -->
"ssl_client" 문자열이 저장된 주소를 참조하는 곳을 메모리 스캔 -->
발견된 주소가 속해 있는 함수가 "ssl_crypto_x509_session_verify_cert_chain" 임 -->
"ssl_crypto_x509_session_verify_cert_chain" 함수 후킹해서 return 값 변조
◼︎결과
"BurpSuite 으로 http 패킷 보내기" 에서의 분석결과와 "Verify cert chain 우회" 에서의 분석결과를 종합해서 frida script 를 작성하였습니다. 사용방법은 Github 주소 참고 바랍니다. https://github.com/hackcatml/frida-flutterproxy
테스트한 모든 앱들에서 정상적으로 프록시를 잡을 수 있었습니다.
◼︎참고
https://github.com/Impact-I/reFlutter (reFlutter)
https://blog.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
https://swarm.ptsecurity.com/fork-bomb-for-flutter/
'Information Security > Android' 카테고리의 다른 글
Android SO File Dump (0) | 2024.08.31 |
---|---|
Frida Build & Debug Using Logs For Android (0) | 2024.08.31 |
버sucker 키우기 (0) | 2024.01.27 |
Frida-portal 사용법 (0) | 2023.12.01 |
Attach Frida Using Frida-gadget Zygisk Module (0) | 2023.11.27 |