Một real case (maybe đơn giản) mình được ông anh ném cho vọc.
Ban đầu khi cài đặt và mở app trên con android đã ẩn root và bật sẵn frida-server thì bị văng app sau 30s. Do khá tự tin về khả năng ẩn root, ẩn unlock botloader , ẩn usb debug trên máy của mình nên khả năng chỉ có thể là frida đã bị detech.
Thử tắt frida-server đi và thử lại thì không còn bị văng app nữa nên chắc chắn app có detech frida.
Decompile với JADX nhận thấy đây là 1 app Flutter.
Và đã được obfuscate
Search thử 1 số keyword, phát hiện ở đây đã sử dụng Talsec RASP, cụ thể là freeRASP
Cùng với namespace com.aheaditec.talsec_security, có thể xác nhận đây là thư viện Talsec.
Đầu tiên thử sử dụng các script bypass Talsec RASP sẵn nhưng không được cơm cháo gì. Ok fine.Bắt buộc phải nhảy vào rev.
Sau 1 hồi trace theo keyword X509Certificate nhưng không thấy đoạn code nào liên quan đến việc triển khai SSL Pinning, quay lại kiểm tra các lib.
Có 1 số lib khả nghi ở đây :
libsecurity.so : là thư viện Talsec Security dùng cho RASP: anti-Frida, root detection, threat checks.
libpolarssl.so : Chưa có thông tin.
Bypass Anti-Frida
Bắt đầu với libsecurity.so. Sử dụng IDA Provà 100% Claude để rev lib này, tìm thấy cách app detech Frida:
afld() @ 0x5bc4 : mở /proc/self/maps, đọc từng dòng bằng fgets , tìm substring "frida" bằng strstr . Nếu trong memory map có module / path chứa chữ frida thì coi như bị instrumentation.
ifpip() @ 0x5910 : mở /proc/self/maps,parse các memory region có quyền r-x, quét trực tiếp vùng nhớ thực thi và tìm chuỗi LIBFRIDA.
ifsl() @ 0x5b24 : tạo socket rồi connect tới 127.0.0.1:27042 (Port mặc định của Frida), nếu connect được thì kết luận đang có Frida server hoạt động.
Ok đã tìm thấy hàm detech frida, tiến hành hook vào để bypass:
var libc = Process.findModuleByName("libc.so"); // Hook strstr - hide "frida" string if (libc) { var strstrExport = libc.findExportByName("strstr"); if (strstrExport) { Interceptor.attach(strstrExport, { onEnter: function (args) { this.needle = args[1].readUtf8String(); }, onLeave: function (retval) { if (this.needle && this.needle.toLowerCase().indexOf("frida") !== -1) { retval.replace(ptr(0)); } }, }); console.log("[+] Hooked strstr (frida string hiding)"); } // Hook connect - block port 27042 var connectExport = libc.findExportByName("connect"); if (connectExport) { Interceptor.attach(connectExport, { onEnter: function (args) { this.block = false; var addrPtr = args[1]; var addrLen = args[2].toInt32(); if (addrLen >= 16) { var family = addrPtr.readU16(); if (family === 2) { var port = (addrPtr.add(2).readU8() << 8) | addrPtr.add(3).readU8(); if (port === 27042) { console.log("[+] Blocked connect() to 127.0.0.1:" + port); this.block = true; } } } }, onLeave: function (retval) { if (this.block) retval.replace(-1); }, }); console.log("[+] Hooked connect (port 27042 blocking)"); } }
// Hook afld/ifpip/ifsl in libsecurity.so functionhookSecurityModule() { var mod = Process.findModuleByName("libsecurity.so"); if (mod) { console.log("[+] Found libsecurity.so @ " + mod.base);
Interceptor.attach(mod.base.add(0x5bc4), { // afld onLeave: function (retval) { if (retval.toInt32() === 1) { console.log("[+] afld(): blocked frida in /proc/self/maps"); retval.replace(2); } }, }); Interceptor.attach(mod.base.add(0x5910), { // ifpip onLeave: function (retval) { if (retval.toInt32() === 1) { console.log("[+] ifpip(): blocked LIBFRIDA scan"); retval.replace(2); } }, }); Interceptor.attach(mod.base.add(0x5b24), { // ifsl onLeave: function (retval) { if (retval.toInt32() === 1) { console.log("[+] ifsl(): blocked port 27042 detection"); retval.replace(2); } }, }); console.log("[+] Native anti-Frida hooks installed"); } else { setTimeout(hookSecurityModule, 100); } }
Thử hook script này và test, lúc này không bị văng app nữa.
Cũng ở libsecurity.so, app có 1 số cơ chế để check root:
Tìm su trong PATH : đọc biến môi trường PATH ,thử ghép từng directory với /su, nếu tồn tại thì báo root.
Đọc /proc/self/mounts,tìm string magisk, tìm core/mirror, tìm core/img để phát hiện Magisk / root environment.
Quét /system/app , so tên package trong danh sách APK decode sẵn, tìm app root manager / suspicious app.
Tìm file nhị phân root phổ biến : Danh sách đã được obfuscate nhưng khi chạy thực tế thấy app kiểm các path kiểu: /system/app/Superuser.apk,/sbin/su, /system/bin/su, /system/xbin/su,/data/local/xbin/su,/data/local/bin/su,/data/local/su,/su/bin/su,/cache/su ,/dev/su,…
Sau khi đã bypass anti-frida và check root thành công, cần bypass nốt SSL Pinning để bắt request.
Do khi rev libsecurity.so không thấy dấu hiệu gì của việc triển khai SSL Pinning ở đây nên thử rev libpolarssl.so,tuy nhiên lib này chỉ chứa crypto/hash, không phải chỗ xử lý TLS chính của app.
Lúc này có 2 khả năng:
Flutter engine (libflutter.so) dùng BoringSSL
native custom TLS library riêng
Đã thử reverse libflutter.so:
có string liên quan SecurityContext, SecureSocket, handshake.cc
tìm được một số candidate của BoringSSL
nhưng hook các hàm candidate như verify_cert_chain, ssl_verify_peer_cert không hề trigger trong runtime
Điều này chứng tỏ app không đi qua luồng TLS mặc định của Flutter cho request thực tế.
không dùng TLS stack mặc định của Flutter cho network chính
Do đó thử Claude rev libclib.so và thấy rất nhiều symbol OpenSSL còn nguyên:
SSL_CTX_set_verify
SSL_set_verify
SSL_CTX_set_cert_verify_callback
SSL_get_verify_result
SSL_do_handshake
Đến đây chắc chắn app dùng OpenSSL custom và SSL/TLS pinning và verify chính nằm ở libclib.so.
TLS client flow
SSL_connect @ 0x3eeffc
gọi tiếp SSL_do_handshake @ 0x3eef18
App triển khai verify từ 3 hàm:
SSL_CTX_set_verify @ 0x3f115c
SSL_set_verify @ 0x3eeac4
SSL_CTX_set_cert_verify_callback @ 0x3f1154
Ta thấy app có 2 cơ chế:
set verify mode
set callback verify custom
Đây là đúng pattern của app dùng OpenSSL để tự kiểm soát cert verification / pinning.
Sau handshake app hoặc thư viện có thể đọc verify status
SSL_get_verify_result @ 0x3f2108
Tiến hành hook vào các hàm
SSL_CTX_set_verify : ép mode thành SSL_VERIFY_NONE (0)
SSL_set_verify : ép mode thành 0
SSL_CTX_set_cert_verify_callback : thay callback verify bằng callback custom luôn return success
SSL_get_verify_result : nếu return khác 0 thì ép thành 0 (X509_V_OK)
SSL_do_handshake : debug xem handshake có thực sự đi qua OpenSSL hay không
Ở đây app dùng raw OpenSSL/curl để nói chuyện TLS trực tiếp với server.
Nên ta sẽ rewrite TCP destination từ 18.x.x.x:443 / 18.x.x.x:9243 thành 172.20.10.2:8080 (Proxy của Burp), nên cần bật invisible proxy trong Burp để bắt được request.