지난 글에서 플러터(Flutter) 앱의 프록시 설정에 대해서 다룬 바 있다. 하지만 해당 설정 만으로는 HTTPS 패킷을 캡처할 때 오류가 발생한다.
플러터가 사용하는 다트는 시스템 내 CA 인증서 저장소 대신 앱 자체 인증서 저장소를 사용하기 때문에 프레임워크 내 SSL 인증서 검증을 실시하는 함수에 대한 후킹을 통해 검증 절차 우회가 필요하다.
프록시 설정 이후 HTTPS 통신 발생 시 발생하는 'SSL handshake' 오류 내용을 확인하고 앱 내 어떠한 라이브러리에서 발생하고 있는지 분석하도록 한다.
위 Debugging 로그를 살펴보면 'SSL handshake' 오류는 구글의 SSL 라이브러리인 BoringSSL의 'handshake.cc' 파일에서 발생하고 있음을 확인할 수 있다.
오픈소스인 BoringSSL의 handshke.cc 내 오류 지점은 위와 같다. 해당 함수를 더 자세히 살펴보도록 하자.
해당 함수는 'ssl_x509.cc' 파일 내 선언되어 있으며 함수 명은 'ssl_crypto_x509_session_verify_cert_chain( )' 이다. 해당 함수가 플러터 앱에서 SSL 인증서의 유효성을 검증한다.
그리고 이 함수는 bool 타입으로 인증서의 유효성 검증에 성공하였을 땐 '1'을 검증에 실패했을 때는 '0'을 반환한다. 따라서 이 함수의 Offset을 찾아 함수가 호출될 때 반환 값을 변조하면 인증서 검증을 우회할 수 있다.
BoringSSL 라이브러리는 Flutter 프레임워크 파일에 내장되어 있다. 프록시 연결 대상 앱의 .ipa 파일에서 Flutter 파일을 추출하고 해당 파일에서 SSL 검증 함수의 offset을 찾도록 한다.
앞서 BoringSSL 오픈소스 분석 시 우리가 찾고자 하는 'ssl_crypto_x509_session_verify_cert_chain( )' 함수 내부에는 'ssl_client' 문자열이 하드코딩 되어 있음을 확인한 바 있다. 이를 바탕으로 해당 값을 참조하고 있는 함수를 찾아낸다.
그리고 이렇게 찾아낸 함수의 초기 Byte 패턴을 추출해낸다. 해당 함수의 offset을 가져와 직접 참조해도 되지만 패턴 검색을 통하면 새로운 앱을 점검할 때 마다 세팅할 수고가 줄어든다.
var m = Process.findModuleByName("Flutter");
var ragnes = "FF 03 05 D1 FC 6F 0F A9 F8 5F 10 A9 F6 57 11 A9 F4 4F 12 A9 FD 7B 13 A9 FD C3 04 91 08 0A 80 52 48 00 00 39"
Memory.protect (m.base,m.size,"rwx");
Memory.scan (m.base, m.size, ragnes, {
onMatch: function (address, size) {
console.log ("\n[+] 'ssl_crypto...' found match at: " + address.toString () + "(" + size.toString ()+")");
Interceptor.attach (address, {
onEnter: function(args) {
console.log("\n[*] Hooking func is called")
},
onLeave: function(retval) {
console.log ("[-] Original Return Value: " + retval)
retval.replace (0x1);
console.log ("[-] New Return Value: " + retval)
}
});
},
onError: function (err) {
console.log ("[!] Exception: " + err.message);
},
onComplete: function () {
console.log ("[*] Instance Finished");
}
});
그리고 Frida를 이용하여 해당 함수의 반환 값을 변조하도록 한다. 코드 전체 구조를 간단히 설명하자면 먼저 함수의 초기 Byte 패턴을 통해 offset을 찾아내고 인증서 검증에 실패했을 때 반환되는 값 '0'을 '1'로 변조하는 기능을 지닌다.
$ frida -Uf [App Name] -l sslBypass.js --no-pause
코드가 정상적으로 동작했다면 위와 같은 결과를 확인할 수 있다. 그리고 다시 HTTPS 통신을 발생시키면 패킷이 정상적으로 Intercept 됨을 확인할 수 있다.
'보안 > iOS' 카테고리의 다른 글
[iOS] 앱 바이너리 패치(SSL 인증서 검증 우회) (0) | 2022.02.27 |
---|---|
[iOS] AltStore 사용 순정폰 IPA 설치 (with 루팅) (0) | 2022.02.24 |
[iOS] 플러터(Flutter) 앱 프록시 설정 (0) | 2022.01.29 |
[iOS] SSL Pinning bypass (0) | 2021.11.10 |
[iOS] lldb 디버깅 (0) | 2021.11.08 |
댓글