Bypass SSL Pinning in Flutter Android App với Ghidra

Bypass SSL Pinning in Flutter Android App với Ghidra

1. Vấn đề

Trong hầu hầu hết các dự án trước đây mình làm, khi gặp 1 app Android có SSL Pinning thì mình sẽ thường sử dụng luôn Objection để patch lại app và thực hiện bypass pinning luôn. Mọi thứ sẽ không có gì thay đổi lắm nếu mới đây, một người bạn của mình phải thực hiện pentest 1 app Android sử dụng Flutter.

Flutter là 1 framework khá phổ biến hiện tại khi dev 1 app mobile. Nó cho phép build app cho cả Android và iOS - 1 tính năng vô cùng tiết kiệm thời gian. Do các tính năng bảo mật tích hợp của Flutter, các mobile app được build Flutter không tuân theo cài đặt proxy được thiết lập trên thiết bị di động và sử dụng kho chứng chỉ của riêng nó. Điều này đặt ra một vấn đề khi thực hiện pentest các ứng dụng như vậy, vì vậy không thể sử dụng phương pháp add system cert truyền thống để intercept request bằng Burp Proxy.

Sau một ngày nghiên cứu và mày mò thì mình cũng đã có thể thực hiện bypass ssl pinning giúp người bạn của mình. Concept chính hướng đi của mình sẽ là thực hiện phân tích thư viện Flutter mà app sử dụng từ đó ép hàm thực hiện việc validate certificate của Flutter luôn luôn trả về true bằng Frida. Từ đó có thể intercept request bằng Burp Proxy

2. Phân tích App

Quay trở lại với app mà người bạn mình phải thực hiện pentest. Vì khách hàng chỉ cung cấp link cài đặt app internal test trực tiếp từ Play store nên việc đầu tiên mình thực hiện sau khi cài app là export file apk của app thông qua adb và emulator.

adb shell pm list packages -f -3
adb shell pm path vn.frt.********.app.ci
adb pull /data/app/~~2oCVkWCcrcL8NBhYSnXt4A==/vn.frt.*********.app.ci-zZqd3cUNfJkCgkGL5uSsaw==/split_config.x86_64.apk

Đoạn này mình sẽ giải thích 1 chút về lý do tại sao app lại có nhiều file apk như trên hình. Mặc dù việc build app thành 1 file apk được goole khuyến khích, tuy nhiên điều đó có thể dẫn tới việc file apk có dung lượng rất lớn do sẽ phải có các file support cho nhiều kiểu màn hình hoặc nhiều dạng CPU khác nhau ở device của người dùng. Trong trường hợp này, mình sẽ lấy ra file split_config.x86_64.apk do đây là file sẽ chứa file libflutter.so - thư viện Flutter mà app sử dụng tương ứng với device x86_64 của mình

Sau khi lấy được file, đổi đuôi file thành zip và đi đến \\lib\\x86_64 để lấy file libflutter.so

Quay lại với app, sau khi cài đặt proxy và bật app lên, trong Burpsuite sẽ có 1 error log không thể tạo TLS connection với API như sau:

Đến bước này khi check logcat thì không hiểu sao mình lại không thấy bất cứ error log nào về việc không thể tạo TLS connection với API. Lục lọi 1 hồi thì mình thấy blog của NVISO cũng về 1 case tương tự Intercepting traffic from Android Flutter applications – NVISO Labs.

Trong bài có thể thấy rằng với app demo do NVISO tự code, khi thực hiện intercept bằng Burpsuite sẽ suất hiện 1 error log trong logcat về về việc không thể tạo TLS connection:

3. Giải quyết vấn đề

Đoạn error log trên xuất hiện do exception từ file handshake.cc trong thư viện BoringSSL. Flutter sử dụng thư viện này để handle mọi thứ liên quan đến SSL và BoringSSL cũng là opensource. Dựa theo blog, mình tìm được đoạn code thực hiện việc xứ lý khi verify certificate thất bại như sau:

Biến ret trong đoạn code trên được define như sau:

Function session_verify_cert_chain được define tại boringssl/ssl_x509.cc at master · google/boringssl (github.com). Logic của đoạn xử lý này khá đơn giản: Nếu function session_verify_cert_chain trả về true thì biến ret sẽ có giá trị là ssl_verify_ok ⇒ có thể tạo kết nối. Ngược lại nếu function trả về false thì biến ret sẽ có giá trị ssl_verify_invalid ⇒ tạo ra exception như trên. Như vậy, đến đây có thể xác định được mục tiêu cần thực hiện là sử dụng firda để thay đổi giá trị trả về của function session_verify_cert_chain luôn luôn là true ⇒ có thể thực hiện intercept request

Sau khi xác định được mục tiêu thì việc đầu tiên để thực hiện hook được với frida là xác định được offset của function session_verify_cert_chain. Trước tiên ném file libflutter.so vào Ghidra và analyze file.

Trước hết, mình sẽ sử dụng chức năng Search string để tìm kiếm các reference link đến file ssl_x509.cc:

Double click vào kết quả tìm kiếm sẽ thấy được có 9 reference link đến file này

Tiếp tục double click vào XREF[9] để kiểm tra từng cross reference đến ssl_x509.cc:

Với file code C boringssl/ssl_x509.cc at master · google/boringssl (github.com), ta biết được rằng function ssl_crypto_x509_session_verify_cert_chain có 3 tham số cần phải truyền vào. Dựa vào đó mình xác định được hàm ssl_crypto_x509_session_verify_cert_chain trong file libflutter.so như sau:

Từ đây mình có thể xác định address của function này trong Ghidra là 0x5319a6. Tuy nhiên Ghidra sử dụng 0x100000 là base address ⇒ Để xác định offset của function ta sẽ cần phải trừ đi 0x100000 ⇒ Offset cần tìm là 0x4319a6

Với offset tìm được, mình sử dụng script sau để thực hiện hook và confirm được rằng có thể intercept được request bằng Burp proxy:

function hook_ssl_verify_result(address)
{
  Interceptor.attach(address, {
    onEnter: function(args) {
      console.log("Disabling SSL validation")
    },
    onLeave: function(retval)
    {
      console.log("Retval: " + retval)
      retval.replace(0x1);

    }
  });
}
function disablePinning(){
    var address = Module.findBaseAddress('libflutter.so').add(0x4319a6)
    hook_ssl_verify_result(address);
}
setTimeout(disablePinning, 1000)

4. Tham khảo

Guide to Intercepting Trafffic from Flutter-based Apps (horangi.com)

Bypassing SSL pinning on Android Flutter Apps with Ghidra | by Raphael Denipotti | Medium

Intercepting traffic from Android Flutter applications – NVISO Labs