App信息 包名:com.douban.frodo
frida反调试绕过 首先尝试注入frida,发现直接就挂掉了,说明frida被检测到了
定位检测frida的so hook dlopen查看so加载流程,看在加载哪一个so的时候挂掉的,说明检测frida的函数在那个so里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function hook_dlopen ( ){ var android_dlopen_ext = Module .findExportByName (null ,"android_dlopen_ext" ); console .log ("addr_android_dlopen_ext" ,android_dlopen_ext); Interceptor .attach (android_dlopen_ext,{ onEnter :function (args ){ var pathptr = args[0 ]; if (pathptr!=null && pathptr != undefined ){ var path = ptr (pathptr).readCString (); console .log ("android_dlopen_ext:" ,path); } }, onLeave :function (retvel ){ console .log ("leave!" ); } }) } hook_dlopen ()
根据hook的输出可以知道,在加载libmsaoaidsec.so
之后,frida就挂掉了,说明是在libmsaoaidsec.so
里检测的frida。
定位检测frida的函数地址,并进行替换 根据so的加载流程,call_construction()
函数用来确保动态库中的函数得到加载并执行,在call_construction()
函数里会调用一个init_array()
函数,它包含了一组指向函数的指针,用于对函数进行初始化,在so加载完成之后,会调用JNI_OnLoad()
函数,用于进行 JNI 环境的初始化、注册本地方法。
hook pthread_create() 检测frida的函数通常不会在主线程中,我们可以通过hook pthread_create()函数来打印线程中创建的函数的地址
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 function hook_dlopen ( ){ var android_dlopen_ext = Module .findExportByName (null ,"android_dlopen_ext" ); console .log ("addr_android_dlopen_ext" ,android_dlopen_ext); Interceptor .attach (android_dlopen_ext,{ onEnter :function (args ){ var pathptr = args[0 ]; if (pathptr!=null && pathptr != undefined ){ var path = ptr (pathptr).readCString (); if (path.indexOf ("libmsaoaidsec.so" )!=-1 ){ console .log ("android_dlopen_ext:" ,path); hook_pthread () } } }, onLeave :function (retvel ){ } }) } function hook_pthread ( ) { var pth_create = Module .findExportByName ("libc.so" , "pthread_create" ); console .log ("[pth_create]" , pth_create); Interceptor .attach (pth_create, { onEnter : function (args ) { var module = Process .findModuleByAddress (args[2 ]); if (module != null ) { console .log ("address" , module .name , args[2 ].sub (module .base )); } }, onLeave : function (retval ) {} }); } function main ( ){ hook_dlopen () } main ()
结果如下,在libmsaoaidsec.so
里创建了三个函数
hook call_construction() 可以通过hook call_construction()这个函数来对pthread_create()中创建的函数进行替换
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 46 47 48 49 function hook_dlopen ( ){ var android_dlopen_ext = Module .findExportByName (null ,"android_dlopen_ext" ); console .log ("addr_android_dlopen_ext" ,android_dlopen_ext); Interceptor .attach (android_dlopen_ext,{ onEnter :function (args ){ var pathptr = args[0 ]; if (pathptr!=null && pathptr != undefined ){ var path = ptr (pathptr).readCString (); if (path.indexOf ("libmsaoaidsec.so" )!=-1 ){ console .log ("android_dlopen_ext:" ,path); hook_call_constructors () } } }, onLeave :function (retvel ){ } }) } function hook_call_constructors ( ) { var linker64_base_addr = Module .getBaseAddress ("linker64" ) var call_constructors_func_off = 0x2C274 var call_constructors_func_addr = linker64_base_addr.add (call_constructors_func_off) var listener = Interceptor .attach (call_constructors_func_addr, { onEnter : function (args ) { console .log ("hooked call_constructors" ) var module = Process .findModuleByName ("libmsaoaidsec.so" ) if (module != null ) { Interceptor .replace (module .base .add (0x1c544 ), new NativeCallback (function ( ) { console .log ("0x1c544:替换成功" ) }, "void" , [])) Interceptor .replace (module .base .add (0x1b924 ), new NativeCallback (function ( ) { console .log ("0x1B924:替换成功" ) }, "void" , [])) Interceptor .replace (module .base .add (0x26e5c ), new NativeCallback (function ( ) { console .log ("0x26e5c:替换成功" ) }, "void" , [])) listener.detach () } }, }) } function main ( ){ hook_dlopen () } main ()
结果如下,frida反调试绕过成功了!
_sign参数还原 抓包分析 通过抓包找到有关_sig
参数的数据包
jadx反编译apk 搜索字符串"_sig"
intercept 定位到intercept函数,如下可以看出来sig
的值为F3.first,F3的值为i0.d.F(request)
跟进到F方法里
F F方法内容如下
返回值是E方法
跟进到E方法
E E方法内容如下
分析流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 1.判断str是否为空 (TextUtils.isEmpty(str)) ↓ 2.获取str2 (StringBuilder l10 = android.support.v4.media.e.l(str2)) ↓ 3.获取URL的路径部分,并进行url编码 (String encodedPath = HttpUrl.parse(str).encodedPath()) ↓ 4.对进行过URL解码的路径部分进行URL解码 (decode = Uri.decode(encodedPath)) ↓ 5.如果路径URL的路径部分的末尾有/符号,则删除它 ↓ 6.在l10末尾追加一个&符号 ↓ 7.对解码的URL路径部分进行url编码,然后追加到l10末尾 ↓ 8.在l10末尾追加一个&符号 ↓ 9.在l10末尾追加当前时间戳 ↓ 10.进行HmacSHA1签名 ↓ 11.进行Base64编码
hook doFinal hook doFinal方法来查看参与加密的参数的格式
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 Java .perform (function ( ) { var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); function toBase64 (tag, data ) { console .log (tag + " Base64: " + ByteString .of (data).base64 ()); } function toHex (tag, data ) { console .log (tag + " Hex: " + ByteString .of (data).hex ()); } function toUtf8 (tag, data ) { console .log (tag + " Utf8: " + ByteString .of (data).utf8 ()); } var mac = Java .use ("javax.crypto.Mac" ); mac.init .overload ('java.security.Key' ).implementation = function (key ) { console .log ("Mac.init('java.security.Key') is called!" ); var algorithm = this .getAlgorithm (); var tag = algorithm + " init Key" ; var keyBytes = key.getEncoded (); toUtf8 (tag, keyBytes); toHex (tag, keyBytes); toBase64 (tag, keyBytes); return this .init (key); } mac.doFinal .overload ('[B' ).implementation = function (data ) { console .log ("Mac.doFinal.overload('[B') is called!" ); console .log ("data:" ,toUtf8 ("data:" ,data)) var result = this .doFinal (data) console .log ("result:" ,toBase64 ("result" ,result)) console .log ("=======================================================" ); return result; } });
hook 结果
1 2 3 4 5 6 7 8 9 10 Mac.init('java.security.Key') is called! HmacSHA1 init Key Utf8: bf7dddc7c9cfe6f7 HmacSHA1 init Key Hex: 62663764646463376339636665366637 HmacSHA1 init Key Base64: YmY3ZGRkYzdjOWNmZTZmNw== Mac.doFinal.overload('[B') is called! data: Utf8: GET&%2Fapi%2Fv2%2Fsearch&1736261466 data: undefined result Base64: S6+RLmdsNP1NdjQghxJebsST7Tc= result: undefined =======================================================
hook E 对E方法进行hook,可以查看参数和返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function hook_E ( ){ Java .perform (function ( ){ let d = Java .use ("i0.d" ); d.E .implementation = function (str1,str2,str3 ){ console .log ("----E----" ) console .log ("str1:" ,str1); console .log ("str2:" ,str2); console .log ("str3:" ,str3); let result = this .E (str1,str2,str3); console .log ("result:" ,result) console .log ("----over----" ) return result } }); } hook_E ()
hook 结果
1 2 3 4 5 6 7 8 9 10 11 12 ----E---- str1: https://frodo.douban.com/api/v2/search/suggestion?q=%E8%AF%AF%E6%9D%80&loc_id=108288&apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=ali_market&udid=ce0fb355c7f98012b1d6716853b6841ba7e35819&os_rom=android&timezone=Asia%2FShanghai str2: GET str3: null result: Pair{idc8eWIGKhtP6hTr9piPi5OSadQ= 1736098880} ----over---- ----E---- str1: https://frodo.douban.com/api/v2/search/found_words?screen_width=1080&screen_height=2028&wx_api_ver=0&opensdk_ver=638058496&webview_ua=Mozilla%2F5.0%20%28Linux%3B%20Android%209%3B%20Pixel%203%20Build%2FPD1A.180720.030%3B%20wv%29%20AppleWebKit%2F537.36%20%28KHTML%2C%20like%20Gecko%29%20Version%2F4.0%20Chrome%2F66.0.3359.158%20Mobile%20Safari%2F537.36&sugar=0&update_mark=1709887761.853333336&network=wifi&enable_sdk_bidding=1&timezone=Asia%2FShanghai str2: POST str3: null result: Pair{gYm5pJeYn7Wh/uv2QxJF6sh6sfA= 1736098863} ----over----
hook SecretKeySpec 对于hmacsha1算法,我们得知道密钥才行,可以hook SecretKeySpec得到密钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function hook_SecretKeySpec ( ){ Java .perform (function ( ){ let secretkeyspec = Java .use ("javax.crypto.spec.SecretKeySpec" ); secretkeyspec.$init .overload ('[B' , 'java.lang.String' ).implementation = function (str5,sha1 ){ let stringdata = Java .use ("java.lang.String" ).$new(str5); console .log ("str5:" ,stringdata); console .log ("sha1:" ,sha1); console .log ("====+++====" ) } }) } hook_SecretKeySpec ()>>> str5 : bf7dddc7c9cfe6f7sha1 : HmacSHA1
得到hmacsha1的密钥为bf7dddc7c9cfe6f7
参数还原 这里拿一份hook到的数据进行分析
1 2 3 4 5 6 ----E---- str1: https://frodo.douban.com/api/v2/search/suggestion?q=%E8%AF%AF%E6%9D%80&loc_id=108288&apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=ali_market&udid=ce0fb355c7f98012b1d6716853b6841ba7e35819&os_rom=android&timezone=Asia%2FShanghai str2: GET str3: null result: Pair{idc8eWIGKhtP6hTr9piPi5OSadQ= 1736098880} ----over----
首先需要的是请求方式,这里是GET
其次是URL的路径部分,还要进行url编码,这里拿到的是%2Fapi%2Fv2%2Fsearch%2Fsuggestion
然后是时间戳1736098880
进行HmacSHA1签名,在进行Base64编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import hashlibimport hmacimport base64def hmacsha1 (key,data ): key_bytes = key.encode() data_bytes = data.encode() mac = hmac.new(key_bytes,data_bytes,hashlib.sha1) sha1_result = mac.digest() return base64.b64encode(sha1_result).decode("utf-8" ) key = "bf7dddc7c9cfe6f7" data = "GET&%2Fapi%2Fv2%2Fsearch%2Fsuggestion&1736098880" print ("sig:" +hmacsha1(key,data))>>> sig:idc8eWIGKhtP6hTr9piPi5OSadQ=
搜索接口 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import requestsimport jsonimport timefrom urllib.parse import quoteimport hashlibimport hmacimport base64def hmacsha1 (key,data ): key_bytes = key.encode() data_bytes = data.encode() mac = hmac.new(key_bytes,data_bytes,hashlib.sha1) sha1_result = mac.digest() return base64.b64encode(sha1_result).decode("utf-8" ) url = "https://frodo.douban.com/api/v2/search" headers = { "user-agent" : "api-client/1 com.douban.frodo/7.89.0(307) Android/28 product/blueline vendor/Google model/Pixel 3 brand/google rom/android network/wifi udid/ce0fb355c7f98012b1d6716853b6841ba7e35819 platform/mobile" , "accept-encoding" : "br,gzip" } timestamp = int (time.time()) encode_data = "GET" +"&" +"%2Fapi%2Fv2%2Fsearch" +"&" +str (timestamp) key = "bf7dddc7c9cfe6f7" sig = hmacsha1(key,encode_data) print ("sig:" ,sig)query = input ("请输入:" ) params = { "q" : query, "sort" : "relevance" , "count" : "20" , "screen_width" : "1080" , "screen_height" : "2028" , "wx_api_ver" : "0" , "opensdk_ver" : "638058496" , "webview_ua" : "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PD1A.180720.030; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.158 Mobile Safari/537.36" , "sugar" : "0" , "update_mark" : "1709887761.853333336" , "network" : "wifi" , "enable_sdk_bidding" : "1" , "loc_id" : "108288" , "apikey" : "0dad551ec0f84ed02907ff5c42e8ec70" , "channel" : "ali_market" , "udid" : "ce0fb355c7f98012b1d6716853b6841ba7e35819" , "os_rom" : "android" , "oaid" : "EdGi3zYQCRzmwwB1YR7WKg==\n" , "timezone" : "Asia/Shanghai" , "_sig" :sig, "_ts" : timestamp } response = requests.get(url=url,headers=headers,params=params) print (response.status_code)text = json.loads(response.text) print (text)