TL;DR
Xin mở đầu bài viết bằng chiếc meme miêu tả xúc tích cảm giác của mình trong tháng qua. Do việc bypass SSL pinning cho flutter application tốn quá nhiều thời gian dẫn đến cuối tháng mà vẫn vừa nghiên cứu vừa viết blog :v
Dạo đôi vòng lịch sử blog mà chưa có bài viết nào nói về pentest mobile application nên hôm nay mình cũng mạo muội viết đôi dòng kiến thức về vùng kiến thức này.
Trong hầu hết các dự án mình làm trước đây đều gặp phải các moblie app check rooted/jailbreak, ssl pinning và đủ mọi thể loại check khác, lúc này thì có hai cách giải quyết cho vấn đề này là: dùng hooking với Frida hoặc patch lại mobile application. Tùy theo mỗi người mà sẽ có những lựa chọn, đối với mình thì cách nào ít tốn công và thời gian hơn thì làm :v.
Lúc đầu mình có ý định viết về lý thuyết thôi để lần sau có cái để cái cớ mà viết phần hai (phần ứng dụng lý thuyết) nhưng ngẫm lại viết blog mà chỉ nói lý thuyết thì nhạt vãi, thôi thì bỏ thêm nhiều buổi tối viết thêm phần ứng dụng kiến thức cho bypass ssl pinning cho flutter app luôn cho có tâm xíu.
Trong blog này mình có nói cơ bản về frida hooking, nếu bạn nào ok phần này rồi thì nhảy luôn đến phần Bypass SSL pinning trong Flutter application cho đỡ mất thời gian nhé.
- Giới thiệu Frida
Là một Dynamic instrumentation toolkit dành cho những developers, reverse engineers và security researchers. Anh em nào muốn tìm hiểu thêm thì vào đây
Frida có một vài tính năng như hình dưới
Như trong tiêu đề blog là sẽ nói về android hooking với Frida, vậy hooking là gì? → là chúng ta cung cấp cho Frida sử dụng các Javascript API để tương tác trực tiếp với Java functions, để thực hiện các thao tác như: lấy dữ liệu hay thay đổi luồng hoạt động của ứng dụng theo ý định
- Yêu cầu trong blog
Trong blog này mình sẽ cần các công cụ sau:
-
- Java decompiler (JD-GUI, Jadx, …)
- Thiết bị android/máy ảo (Genymotion, Nox Player, ..)
- Dynamic instrumentation toolkit (Frida)
- Ghidra hoặc ida
- Apktool.jar
- uber-apk-signer.jar
Việc cài đặt Frida khá dễ dàng:
pip install frida-tools
pip install frida
Tiếp theo, tải và cài đặt frida-server trên thiết bị android/máy ảo tại đây và cài đặt theo hướng dẫn sau đây. Hoặc nếu muốn nhanh gọn hơn thì cài đặt trực tiếp Frida Server từ Playstore :v
Dùng command sau để đảm bảo rằng thiết bị android/máy ảo đã kết nối với máy tính
adb devices -l
Tiếp theo, cần kiểm tra liệu có hoạt động:
frida-ps -U #List packages and processes
- Cơ bản về Frida Hooking
Các bạn cần đọc qua khái niệm về Activity-lifecycle nhé để biết được luồng hoạt động của một ứng dụng là như thế nào
Đầu tiên, kiểu cú pháp bắt buộc để cấp cho Frida, lưu ý rằng Javascript API sẽ được viết ở trong //do something
Java.perform(function () {
//do something
});
Trong document của Frida thì có nói như sau
Bây giờ mình sẽ giải thích một số Javascript API cơ bản kèm theo hook một mobile application của OWASP nhé (học đi đôi với hành mà :v)
Hình ảnh đầu tiên mở lên thì là dòng chữ Root detected! to tướng. Lúc này ứng dụng không thể sử dụng được, nhiệm vụ bây giờ là bypass check root.
Bước đầu tiên là mình cần phải biết được ứng dụng đang check root như thế nào rồi mới hook đúng không, vậy thì dùng Jadx – Java decompiler (hoặc dùng bất kỳ tool nào bạn cảm thấy thuận lợi là được) để đọc được java code.
Okay, sau hồi review thì đây là đoạn code check root
Class sg.vantagepoint.a.c
Việc check root của ứng dụng hoạt động bằng cách gọi 3 function a(), b(), c() từ class sg.vantagepoint.a.c
Chỉ cần một trong ba function trên trả về true thì sẽ gọi đến function a(str) để alert thông báo và thoát ứng dụng khi người dùng click vào OK button
Sau khi đã hiểu rõ chương trình check root ra sao thì bước tiếp theo là lên ý tưởng bypass, bằng cách hook vào 3 hàm a(), b(), c() của class sg.vantagepoint.a.c trả về false.
Đoạn code bypass check root khá là đơn giản như sau:
//frida -U –no-pause -l bypass_root.js -f owasp.mstg.uncrackable1
Java.perform(function () {
var root = Java.use(“sg.vantagepoint.a.c”);
root.a.implementation = function() {
console.log(“vao ham a”);
return false;
};
root.b.implementation = function() {
console.log(“vao ham b”);
return false;
};
root.c.implementation = function() {
console.log(“vao ham c”);
return false;
};
});
Trong đó:
-
- sg.vantagepoint.a.c là class name.
- Java.use(“class”) cho phép custom method trong class bằng cách gọi đến implementation
Muốn hook và custom method a() thuộc class sg.vantagepoint.a.c thì dùng cú pháp:
root.a.implementation = function() {
//do something
};
Ngoài ra, các bạn có thấy mình có đưa console.log(); vào trong code bypass không? Đó là một tip nhỏ mình thường dùng để debug biết được là đoạn liệu code đó có hoạt động?. Nếu các bạn code ngon thì khỏi cần thêm cũng được :v
Okay, sau thử thách bypass check root thành công sẽ là thử thách nhập vào đúng secret string của ứng dụng :v
Tiếp tục công việc đọc hiểu đoạn code xử lý chỗ này để giải quyết thôi
(1): Ứng dụng nhận chuỗi ký tự từ người dùng và lưu vào biến obj
(2): Truyền biến obj vào method a() (của class a nằm chung package với class MainActivity) để kiểm tra, nếu kết quả của method a() về là true thì ứng dụng sẽ alert chuỗi Success! This is the correct secret.
Như đoạn code trên sẽ so sánh giữa biến str và kết quả của hàm
sg.vantagepoint.a.a.a(b(“8d127684cbc37c17616d806cf50473cc”), Base64.decode(“5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=”, 0))
Sau khi hiểu cách thức hoạt động xong mình nghĩ ra được hai cách để bypass
Cách 1: Dùng Java.use() để thực hiện return true;
Đọc code đến đây là có thể dùng Frida để alert dòng chữ Success lên với đoạn code tí xíu này.
var check = Java.use(“sg.vantagepoint.uncrackable1.a”);
check.a.implementation = function() {
console.log(“vao ham check secret”);
return true;
};
Lúc này, bạn nhập bất kỳ chuỗi ký tự nào vào cũng sẽ alert Success lên
Cách 2: Là tìm ra chuỗi secret string luôn. Mình thường dùng cách này cần thu thập các chuỗi ký tự đã được mã hóa trong các ứng dụng mà mình gặp phải.
Việc cần làm gọi vào hàm a() ở trên để trả về mảng byte, lúc này cần phải thêm một bước nữa là chuyển mảng byte thành String
// cach 2
var check = Java.use(“sg.vantagepoint.a.a”);
check.a.implementation = function(x,y) {
console.log(“vao ham decrypt”);
var bte = this.a(x,y);
console.log(“byte: “+ bte);
var result;
for (var i = 0; i < bte.length; i++) {
result += String.fromCharCode(bte[i]);
}
console.log(“secret key: ” + result);
return bte;
};
Trong đó:
-
- Do hàm a() có hai arguments nên trong code js cần phải truyền vào 2 x, y
- Gọi hàm a(x,y) với cú pháp this.a(x,y) để lấy giá trị ở kiểu dữ liệu byte
Kết quả: vậy secret string cần tìm là I want to believe
Okay, nhập vào thôi
Ngoài ra có thể tham khảo thêm Javascript API thêm ở đây, với các hàm cơ bản như
Java.choose(): Tìm instance của className bằng cách scan trong Java heap, nếu trùng khớp sẽ gọi đến onMatch(), …
- Bypass SSL pinning trong Flutter application
Những kiến thức trên chỉ là phần cơ bản, hy vọng có thể giúp những bạn mới có thể làm quen với hooking với Frida.
Còn thực tế những mobile app mình gặp ở môi trường thực tế sẽ khó khăn hơn nhiều do lượng code lớn, hàm check root, mã hóa, … phức tạp hơn nhiều (mobile app của của các ngân hàng)
Phần lớn các mobile app hiện nay dùng các framework như Flutter hoặc React Native, có thể vừa buid android và iOS applications với cùng một codebase. Đặc biệt là thằng Flutter này không tuân theo cấu hình proxy của thiết bị được cài đặt bởi pentester mà sẽ sử dụng certificate của lập trình viên cài đặt. Do đó, không thể bắt được request theo cách thông thường được.
Hiện nay có tool reFlutter cho phép bypass SSL Pinning và ý tưởng của tool này khá đơn giản như sau: vì Flutter app thường sử dụng binary (libflutter.so bao gồm các Flutter engine) đã được complied (ahead-of-time – AOT), vậy nên tool này chỉ cần sử dụng bản patch của Flutter library đã được complied sẵn, sau đó re-packing app là ok. Nhưng sẽ có vấn đề xãy ra là nếu version Dart khác nhau sẽ không thể hoạt động được. Đây là lý do chính khiến mình viết blog này.
Ở phần này thì cần thêm tool rev như: Ghidra hoặc ida
Hình dưới cho thấy thư viện được load vào ứng dụng
Cấu hình proxy ở proxy-droid cho qua Burp Suite rồi mở app lên bạn sẽ được thông báo như sau ở tab Dashboard: The client failed to negotiate a TLS connection to test.com:443: Remote host terminated the handshake
Lúc này tất nhiên sẽ không thể bắt được http traffic
Đầu tiên cũng cực kỳ quan trọng là cần kiểm tra kiến trúc trên thiết bị để đảm bảo rằng load đúng thư viện bằng command sau:
adb shell getprop ro.product.cpu.abi
Bước tiếp theo: import libflutter.so vào vào Ghidra, thời gian import khá lâu, thôi để đó, bật logcat lên xem thử như nào
Nhận thấy rằng verify certificate đã thất bại tại file handshake.cc
Sau hồi đau đầu đọc blog của anh horangi thì nhận thấy rằng phần thực thi tại hàm session_verify_cert_chain và sẽ trả về kết quả ssl_verify_ok hoặc ssl_verify_invalid và lưu trong biến ret
Định nghĩa của hàm session_verify_cert_chain nằm trong file ssl_x509.cc
Okay, mục tiêu giờ phải thay đổi kết quả trả về của hàm trên thành true. Và cũng lưu ý rằng hàm trên có 3 arguments được truyền vào (sẽ giúp ta tìm được hàm chính xác khi decompile bằng Ghidra)
Chuyển qua Ghidra click Search -> For Strings… tìm ssl_x509.cc sẽ nhận được kết quả như hình bên dưới
Double click vào kết quả tìm được, bạn sẽ thấy được các References (như trong hình có 7 chỗ gọi đến string này)
Double click vào XREF[7] sẽ hiển thị như hình dưới
Lúc này phải duyệt qua tất cả các Xrefs để tìm được function có 3 arguments (đã nói lưu ý ở trên) và return 0
Tìm được đến đây thì có hai sự lựa chọn để giải quyết bài toán bypass SSL Pinning:
Cách thứ nhất: patch lại app chỉnh sửa 0x0 thành 0x1
Sau đó, lưu lại và chọn Export Program, dùng apktool.jar để compile app, tiếp theo dùng uber-apk-signer.jar để ký app
Cách thứ hai: dùng frida để hook, lấy mã hex rồi dùng mã để bypass
Ném vào đây và chạy thôi
- Phần tham khảo
Mình không thể tự nhiên mà biết được kiến thức này, mà qua đọc những bài blog, tài liệu và học từ những anh em đồng nghiệp. Nhân bài blog này, mình xin gửi lời cảm ơn đến tất cả!
Mình cũng hy vọng qua bài blog này có thể giúp được nhiều bạn pentesters mới làm quen với frida.
Tham khảo:
https://book.hacktricks.xyz/mobile-apps-pentesting/android-app-pentesting/frida-tutorial
https://frida.re/docs/javascript-api/
https://github.com/OWASP/owasp-mstg
https://www.horangi.com/blog/a-pentesting-guide-to-intercepting-traffic-from-flutter-apps
_ndtoan_Redteam