下了一个app,抓包后发现有ssl校验
https://github.com/WooyunDota/DroidSSLUnpinning/blob/master/ObjectionUnpinningPlus/hooks.js 试了这个unsslpinning脚本,把常用的网络框架都给hook上了也是没用,dump证书也是一样失败 既然都抓不到那试试r0capture从上一层ssl抓包看看, hook了更深层次点的函数SSL_write也发现没有调用 这种既没走标准的http框架和系统ssl的请求,再深入一层就到libc的网络库了,SSL_write函数内部会调用libc提供的send、sendmsg或sendto函数来实际完成数据的网络传输,直接hook这三个函数,查看其调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function main ( ){ var sendaddr = Module .findExportByName (null , "send" ); console .log ("sendaddr: " + sendaddr); Interceptor .attach (sendaddr, { onEnter : function (args ) { console .log (Thread .backtrace (this .context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ("\n\t" )) console .log ("send called: " + args[1 ]); }, onLeave : function (retval ) { console .log ("send return: " + retval); } }) var sendmsgaddr = Module .findExportByName (null , "sendmsg" ); console .log ("sendmsgaddr: " + sendmsgaddr); Interceptor .attach (sendmsgaddr, { onEnter : function (args ) { console .log (Thread .backtrace (this .context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ("\n\t" )) console .log ("sendmsg called: " + hexdump (args[1 ]), args[2 ]); }, onLeave : function (retval ) { console .log ("sendmsg return: " + retval); } }) var sendtoaddr = Module .findExportByName (null , "sendto" ); console .log ("sendtoaddr: " + sendtoaddr); Interceptor .attach (sendtoaddr, { onEnter : function (args ) { console .log (Thread .backtrace (this .context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ("\n\t" )) console .log ("sendto called: " + args[1 ], args[2 ]); }, onLeave : function (retval ) { console .log ("sendto return: " + retval); } }) } setImmediate (main);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ... sendmsg called : 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 7b488320b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 7b488320c8 08 21 83 48 7b 00 00 00 02 00 00 00 00 00 00 00 .!.H {........... 7b488320d8 f0 20 83 48 7b 00 00 00 18 00 00 00 00 00 00 00 . .H {........... 7b488320e8 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 ................ 7b488320f8 01 00 00 00 01 00 00 00 45 01 00 00 00 00 00 00 ........E ....... 7b48832108 a0 21 83 48 7b 00 00 00 0c 00 00 00 00 00 00 00 .!.H {........... 7b48832118 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 7b48832128 56 32 7d 8d a4 6c 62 5f 00 00 00 00 00 00 00 00 V2 }..lb_ ........ 7b48832138 80 c9 dd 4d 7b 00 00 00 88 35 83 48 7b 00 00 00 ...M{....5 .H {... 7b48832148 88 35 83 48 7b 00 00 00 68 24 83 48 7b 00 00 00 .5 .H {...h$.H{... 7b48832158 10 00 00 00 00 00 00 00 00 7b 27 6c 7b 00 00 00 .........{'l{... 7b48832168 45 01 00 00 00 00 00 00 10 22 83 48 7b 00 00 00 E........".H{... 7b48832178 30 a3 6a f8 7b 00 00 00 88 35 83 48 7b 00 00 00 0.j.{....5.H{... 7b48832188 88 35 83 48 7b 00 00 00 46 01 00 00 7b 00 00 00 .5.H{...F...{... 7b48832198 60 c8 8e 50 7b 00 00 00 01 00 00 00 00 00 00 00 `..P{........... 7b488321a8 00 00 00 00 7b 00 00 00 c0 21 83 48 7b 00 00 00 ....{....!.H{... 0x0 sendmsg return: 0xc 0x7bf86aa0a0 libnetd_client.so!_ZN12FwmarkClient4sendEP13FwmarkCommandiP17FwmarkConnectInfo+0x128 0x7bf86aa0a0 libnetd_client.so!_ZN12FwmarkClient4sendEP13FwmarkCommandiP17FwmarkConnectInfo+0x128 0x7bf86aa42c libnetd_client.so!0x142c 0x7b4afcbd18 libsscronet.so!0x305d18 0x7b4afcbd18 libsscronet.so!0x305d18 ...
其中libnetd_client.so是系统库,就只能怀疑请求是从 libsscronet.so 出来的了,好奇既然没用系统的ssl库,那是用了哪里的ssl库?尝试搜了以下app已加载的so库
1 2 3 4 5 6 7 8 9 marlin :/ # cat / proc/12902 /maps |grep ssl 7b23df5000-7b23e49000 r--p 00000000 103 :13 134190 /data/app/com.f100 .android -Hq36 t_d8nNvaQKbyqrIsXA==/lib/ arm64/libttboringssl.so 7b28180000-7b281c8000 r--p 00000000 103 :12 210 /system/lib64/libssl.so 7b4b795000-7b4b7e6000 r-xp 00000000 103 :13 134190 /data/app/com.f100 .android -Hq36 t_d8nNvaQKbyqrIsXA==/lib/ arm64/libttboringssl.so 7b4b7e6000-7b4b7e9000 r--p 00050000 103 :13 134190 /data/app/com.f100 .android -Hq36 t_d8nNvaQKbyqrIsXA==/lib/ arm64/libttboringssl.so 7b4b7e9000-7b4b7ea000 rw-p 00053000 103 :13 134190 /data/app/com.f100 .android -Hq36 t_d8nNvaQKbyqrIsXA==/lib/ arm64/libttboringssl.so 7b6d255000-7b6d298000 r-xp 00000000 103 :12 210 /system/lib64/libssl.so 7b6d298000-7b6d29b000 r--p 00042000 103 :12 210 /system/lib64/libssl.so 7b6d29b000-7b6d29c000 rw-p 00045000 103 :12 210 /system/lib64/libssl.so
查了下这个boringssl是由 Google 开发和维护的 BoringSSL 项目的动态链接库文件,尝试hook boringssl中的SSL_write函数打印调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function hook_ssl ( ){ var boringssl = Process .getModuleByName ("libttboringssl.so" ); var SSL_write = boringssl.findExportByName ("SSL_write" ); Interceptor .attach (SSL_write, { onEnter : function (args ) { console .log ("Calling SSL_write" ); console .log (Thread .backtrace (this .context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ("\n\t" )) console .log (" ssl:" , ptr (args[0 ])); this .ssl = args[0 ].toString (); this .buf = ptr (args[1 ]); }, onLeave : function (retval ) { console .log ("SSL_write returned:" , retval.toInt32 ()); } }); } setImmediate (hook_ssl)
1 2 3 4 5 6 7 8 ... Calling SSL_write0x7b4af25308 libsscronet.so !0x25f308 0x7b4af25308 libsscronet.so !0x25f308 0x7b4af25308 libsscronet.so !0x25f308 ssl : 0x7b476c9f08 SSL_write returned : 2861 ...
可以看到 libsscronet.so 调用了 libttboringssl.so 的 SSL_write,到这里证实了上面请求从 libsscronet.so 发出的猜想 在cronet库中搜索判断是否有调用boringssl中涉及证书验证的函数 有两个函数比较可疑 SSL_CTX_set_custom_verify和SSL_CTX_set_reverify_on_resume 定位到了调用的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 unsigned __int64 sub_25FF94 () { unsigned __int64 v0; __int64 v1; __int64 v2; __int64 v3; __int64 v4; __int64 v5; v0 = atomic_load (&qword_3C07F8); if ( v0 <= 1 ) { if ( (sub_1ED810(&qword_3C07F8) & 1 ) != 0 ) { v0 = sub_22BC9C(24LL ); *(_QWORD *)(v0 + 8 ) = 0LL ; *(_QWORD *)(v0 + 16 ) = 0LL ; CRYPTO_library_init(); *(_DWORD *)v0 = SSL_get_ex_new_index(0LL , 0LL , 0LL , 0LL , 0LL ); v1 = TLS_with_buffers_method(); v2 = SSL_CTX_new(v1); sub_149BDC(v0 + 8 , v2); SSL_CTX_set_cert_cb(*(_QWORD *)(v0 + 8 ), sub_260114, 0LL ); SSL_CTX_set_reverify_on_resume(); SSL_CTX_set_custom_verify(*(_QWORD *)(v0 + 8 ), 1LL , sub_25F7E4); SSL_CTX_set_session_cache_mode(*(_QWORD *)(v0 + 8 ), 769LL ); SSL_CTX_sess_set_new_cb(*(_QWORD *)(v0 + 8 ), sub_2602E4); SSL_CTX_set_timeout(*(_QWORD *)(v0 + 8 ), 3600LL ); v3 = SSL_CTX_set_grease_enabled(*(_QWORD *)(v0 + 8 ), 1LL ); v4 = *(_QWORD *)(v0 + 8 ); v5 = sub_2516C4(v3); SSL_CTX_set0_buffer_pool(v4, v5); SSL_CTX_set_msg_callback(*(_QWORD *)(v0 + 8 ), sub_2604B4); SSL_CTX_add_cert_compression_alg(*(_QWORD *)(v0 + 8 ), 2LL , 0LL , sub_2605F4); if ( (sub_1E60D4(&off_3B7840) & 1 ) != 0 ) SSL_CTX_set1_curves(*(_QWORD *)(v0 + 8 ), &unk_B49B4, 4LL ); sub_1ED89C(&qword_3C07F8, v0, 0LL , 0LL ); } else { return atomic_load (&qword_3C07F8); } } return v0; }
这里也去Chrome Code Search搜索(在线的Chrome源码浏览网页,提供了强大的交叉引用和全文搜索功能)https://source.chromium.org/chromium 看了SSL_CTX_set_reverify_on_resume源码也没什么
1 2 3 void SSL_CTX_set_reverify_on_resume (SSL_CTX *ctx, int enabled) { ctx->reverify_on_resume = !!enabled; }
而且下一行就调用了SSL_CTX_set_custom_verify,所以重点放在SSL_CTX_set_custom_verify上 搜索函数名SSL_CTX_set_custom_verify,定位到
1 2 3 4 5 6 void SSL_CTX_set_custom_verify ( SSL_CTX *ctx, int mode, enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert)) { ctx->verify_mode = mode; ctx->custom_verify_callback = callback; }
第一个参数应该是上下文管理SSL/TLS的会话指针, 第二个参数可以随便找调用 SSL_CTX_set_custom_verify 函数的地方,查看参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define SSL_VERIFY_NONE 0x00 #define SSL_VERIFY_PEER 0x01 #define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02 #define SSL_VERIFY_PEER_IF_NO_OBC 0x04
值为0x0时不校验证书,hook修改参数为0即可 第三个参数是回调函数,就是上面的 SSL_CTX_set_custom_verify(*(_QWORD *)(v0 + 8), 1LL, sub_25F7E4); 跟进刚才的源码enum ssl_verify_result_t,第三个参数回调函数的返回值就只有以下三种类型,
1 2 3 4 5 enum ssl_verify_result_t BORINGSSL_ENUM_INT { ssl_verify_ok, ssl_verify_invalid, ssl_verify_retry, };
看了一些调用的地方,ssl_verify_ok的值是0,所以可以hook回调函数修改返回值为0即可,就不用管函数内部在做什么了 这个so会在加载后立即进行证书绑定的判断,只执行一次,所以需要在so加载的时候找到它,并且在so执行第一次的时候进行hook frida hook代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function main ( ){ var android_dlopen_ext = Module .findExportByName (null , 'android_dlopen_ext' ); console .log ("android_dlopen_ext: " , android_dlopen_ext) if (android_dlopen_ext != null ) { Interceptor .attach (android_dlopen_ext, { onEnter : function (args ){ var soName = args[0 ].readCString (); if (soName.indexOf ("libsscronet.so" ) != -1 ) { this .hook = true ; } }, onLeave : function (retval ){ if (this .hook ) { hook_cronetssl () } } }) } } function hook_callback (p ){ var fun = new NativeFunction (p, 'int' , ['pointer' , 'pointer' ]); var new_fun = new NativeCallback (function (arg1, arg2 ){ console .log ("cononono" , fun (arg1, arg2)); return 0 ; }, 'int' , ['pointer' , 'pointer' ]) Interceptor .replace (fun, new_fun) } function hook_cronetssl ( ) { var cronet = Module .findBaseAddress ("libsscronet.so" ); console .log ("cronet: " , cronet) var verify = Module .findExportByName ("libsscronet.so" , "SSL_CTX_set_custom_verify" ); console .log ("verify: " , verify) var custom_verify = new NativeFunction (verify, 'pointer' , ['pointer' , 'int' , 'pointer' ]); var new_fun = new NativeCallback (function (arg1, arg2, arg3 ) { hook_callback (arg3) console .log ("SSL_CTX_set_custom_verify called: " + arg1 + " " + arg2 + " " + arg3) return custom_verify (arg1, 0 , arg3) }, 'pointer' , ['pointer' , 'int' , 'pointer' ]); Interceptor .replace (verify, new_fun) } setImmediate (main)
验证抓包 后面通过调用栈和查询发现这是byteDance下的产品,脚本应该是通用的,也有大佬给出了其它方法https://github.com/LanBaiCode/FridaScripts/blob/main/byteDance.js 大佬文章:[原创] 更高更妙的抓包——从Chrome源码学习使用Cronet 通讯组件的app的通用抓包方法