Native逆向指北之BiliBili Sign
- 一、前言
- 二、背景介绍
- 三、分析
-
- 3.1 定位native函数位置
- 3.2 JNItrace辅助分析
- 3.3 IDA分析
- 四、总结
一、前言
一个简单的Native层逆向分享。
二、背景介绍
分析版本是6.18.0,SO为armeabi-v7a。
本篇重点在native层,所以Java部分的分析不去提它,直接说结论,目标Sign通过com.bilibili.nativelibrary.LibBili.s方法获取,以下是Objection Hook的结果,我注释或者额外补充的信息,采用”# “开头。
tv.danmaku.bili on (Xiaomi: 10) [usb] # android hooking watch class_method com.bilibili.nativelibrary.LibBili.s --dump-args --dump-backtrace --dump-return # Hook该方法,打印参数、返回值、调用栈
(agent) Attempting to watch class com.bilibili.nativelibrary.LibBili and method s.
(agent) Hooking com.bilibili.nativelibrary.LibBili.s(java.util.SortedMap) # 方法只有一个参数,是SortedMap对象
(agent) Registering job 5172847509985. Type: watch-method for: com.bilibili.nativelibrary.LibBili.s
tv.danmaku.bili on (Xiaomi: 10) [usb] # (agent) [5172847509985] Called com.bilibili.nativelibrary.LibBili.s(java.util.SortedMap)
(agent) [5172847509985] Backtrace:com.bilibili.nativelibrary.LibBili.s(Native Method)com.bilibili.nativelibrary.LibBili.g(BL:1)com.bilibili.okretro.f.a.h(BL:1)com.bilibili.okretro.f.a.d(BL:7)com.bilibili.okretro.f.a.a(BL:4)com.bilibili.okretro.d.a.execute(BL:24)com.bilibili.okretro.d.a$a.run(BL:2)java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)java.lang.Thread.run(Thread.java:919)(agent) [5172847509985] Arguments com.bilibili.nativelibrary.LibBili.s("<instance: java.util.SortedMap, $className: java.util.TreeMap>")
(agent) [5172847509985] Return Value: "<instance: com.bilibili.nativelibrary.SignedQuery>" # 返回SignedQuery 对象
我们发现,传入了一个map集合,输出一个com.bilibili.nativelibrary.SignedQuery的实例,Objection未能展示传入参数的具体内容,我们待会写Hook脚本进行打印,先用JADX看一下反编译的Java代码,看一下SignedQuery是怎么回事:
sign就在里面,这个对象重写了toString方法,用于返回拼接了sign之后的字符串,脑补容易出错,我们通过Hook脚本来一探究竟:
function hookSign(){
Java.perform(function() {
var ClassName = "com.bilibili.nativelibrary.LibBili";var Bilibili = Java.use(ClassName);var targetMethod = "s";Bilibili[targetMethod].implementation = function () {
var map = arguments[0];// 打印入参console.log("\nmap内容:", map.entrySet().toArray());var result = this[targetMethod](arguments[0]);// 打印结果,不需要做什么额外处理,Frida隐式调用toString,正好将我们想看的内容打印出来。console.log("\n返回结果:",result);return result;}});
}
CSDN中看代码块不是很舒服(也可能是我不知道怎么限制每行的长度),可以复制到文本编辑器中查看。
[MIX 2S::哔哩哔哩]->
[MIX 2S::哔哩哔哩]->
map内容: ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223,appkey=1d8b6e7d45233436,autoplay_card=11,banner_hash=10687342131252771522,build=6180500,c_locale=zh_CN,channel=shenma117,column=2,device_name=MIX2S,device_type=0,flush=6,fnval=464,fnver=0,force_host=0,fourk=1,guidance=0,https_url_req=0,idx=1612692508,inline_danmu=2,inline_sound=1,login_event=0,mobi_app=android,network=wifi,open_event=,platform=android,player_net=1,pull=true,qn=32,recsys_mode=0,s_locale=zh_CN,splash_id=,statistics={
"appId":1,"platform":3,"version":"6.18.0","abtest":""}返回结果: ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223&appkey=1d8b6e7d45233436&autoplay_card=11&banner_hash=10687342131252771522&build=6180500&c_locale=zh_CN&channel=shenma117&column=2&device_name=MIX%202S&device_type=0&flush=6&fnval=464&fnver=0&force_host=0&fourk=1&guidance=0&https_url_req=0&idx=1612692508&inline_danmu=2&inline_sound=1&login_event=0&mobi_app=android&network=wifi&open_event=&platform=android&player_net=1&pull=true&qn=32&recsys_mode=0&s_locale=zh_CN&splash_id=&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%226.18.0%22%2C%22abtest%22%3A%22%22%7D&ts=1612693177&sign=81a955e380e18f620098ba02f74d80f5
返回值似乎做了url encode,等号看起来不太顺眼,decode后再看看。
ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223&appkey=1d8b6e7d45233436&autoplay_card=11&banner_hash=10687342131252771522&build=6180500&c_locale=zh_CN&channel=shenma117&column=2&device_name=MIX2S&device_type=0&flush=6&fnval=464&fnver=0&force_host=0&fourk=1&guidance=0&https_url_req=0&idx=1612692508&inline_danmu=2&inline_sound=1&login_event=0&mobi_app=android&network=wifi&open_event=&platform=android&player_net=1&pull=true&qn=32&recsys_mode=0&s_locale=zh_CN&splash_id=&statistics={
"appId":1,"platform":3,"version":"6.18.0","abtest":""}&ts=1612693177&sign=81a955e380e18f620098ba02f74d80f5
可以相信,Sign”81a955e380e18f620098ba02f74d80f5“就是在这个方法中生成的。
三、分析
3.1 定位native函数位置
首先我们要确定com.bilibili.nativelibrary.LibBili.s 这个方法位于哪个so里,叫什么名儿。
先做如下约定,防止名称混淆。
- JNI 函数:Java函数所对应的native函数
- JNI 方法:JNI提供的两百多个API,比如GetStringUTFChars等等。
将一个JAVA方法和JNI函数连结在一起,有两种方式——静态绑定和动态绑定,也叫静态注册和动态注册,这是基础的知识,不太熟悉的同学可以看相关文章巩固一下。
我们并不能一眼确定某个函数是静态绑定还是动态绑定,过去我们会静态分析反编译的Java代码,找到代码中加载的SO,然后在SO里做JNI函数的分析和定位。得益于强大的Frida以及现在开源的各种工具和脚本,许多这种小麻烦就可以省却了。
如果一个函数采用静态绑定,那么对应native函数的命名遵照以下规则:
1、前缀 Java_
;
2、紧跟着类的全名(间隔符从由”.“替换成_
);
3、最后是方法名;
比如com.bilibili.nativelibrary.LibBili.s 如果采用静态注册,JNI函数名为Java_com_bilibili_nativelibrary_LibBili_s
我们可以使用frida自带的分析工具frida trace进行批量native hook,环境为cmd命令行。
frida-trace -UF -i "Java_com*"
-UF 意指附加到手机最前台的应用,即当前运行的应用,所以记得先打开App。
-i “Java_com*”意指Hook 该app当前加载的所有以"Java_com"开头的native函数,-i 后面双引号里是函数名,支持正则表达式的语法。
为什么不 -i "Java_com_bilibili_nativelibrary_LibBili_s"呢 ?因为静态注册的命名规则比我列出来的复杂一些,在遇到方法中包含“_”或者存在重载时,有额外的补充民命名规则,为了防止遗漏和错误,干脆扩大一下Hook范围,Hook所有“Java_com”开头的Native函数。
C:\Users\Lenovo>frida-trace -UF -i "Java_com*"
Java_com_tencent_tencentmap_lbssdk_service_e_v: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libtencentloc.so\\Java_com_tencent_tencentmap_lbss_747883c7.js"
Java_com_tencent_tencentmap_lbssdk_service_e_w: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libtencentloc.so\\Java_com_tencent_tencentmap_lbss_037fb351.js"
Java_com_tencent_tencentmap_lbssdk_service_e_b: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libtencentloc.so\\Java_com_tencent_tencentmap_lbss_6ea257ba.js"
Java_com_tencent_tencentmap_lbssdk_service_e_o: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libtencentloc.so\\Java_com_tencent_tencentmap_lbss_10132b07.js"
Java_com_tencent_tencentmap_lbssdk_service_e_r: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libtencentloc.so\\Java_com_tencent_tencentmap_lbss_731547de.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativePrefetch: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_1b4519f6.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeInitOnInitThread: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_e7574059.js"
Java_com_bilibili_lib_bilicr_BiliCrLibraryLoader_nativeBiliCrInitOnInitThread: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_bilicr_Bil_c7df419d.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeResolve: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_84b60f81.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeContains: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_3bc8f6c4.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeClearCache: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_311a7f60.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeCreateHttpDnsConfig: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_333b50a0.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeFallback: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_842be6f8.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeAddAliService: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_b3330bb0.js"
Java_com_bilibili_lib_bilicr_BiliCrLibraryLoader_nativeGetBiliCrVersion: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_bilicr_Bil_5bb6cdc8.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeDestroy: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_9e190dc6.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeAdd: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_deb23be8.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeProvider: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_27f6ef26.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeAddTencentService: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_d51745cc.js"
Java_com_bilibili_lib_httpdns_impl_NativeHttpDns_nativeCreateHttpDnsAdapter: Loaded handler at "C:\\Users\\Lenovo\\__handlers__\\libbilicr.81.0.4044.156.so\\Java_com_bilibili_lib_httpdns_im_965e4bfc.js"
Started tracing 20 functions. Press Ctrl+C to stop.
对app做一些操作,比如下拉刷新,发现并没有产生什么调用,即使零星有,看着也不是我们想要的函数调用。
那么大概率不是静态注册,接下来测试com.bilibili.nativelibrary.LibBili.s 是不是动态绑定,使用 yang 神的脚本 。
frida -U --no-pause -f tv.danmaku.bili -l path/hook_RegisterNatives.js
输出会有几百条,找起来有些累眼,修改代码限定一下类,重新运行。
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");var addrRegisterNatives = null;for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodiif (symbol.name.indexOf("art") >= 0 &&symbol.name.indexOf("JNI") >= 0 && symbol.name.indexOf("RegisterNatives") >= 0 && symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;console.log("RegisterNatives is at ", symbol.address, symbol.name);}}if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var env = args[0];var java_class = args[1];var class_name = Java.vm.tryGetEnv().getClassName(java_class);//console.log(class_name);// 只有类名为com.bilibili.nativelibrary.LibBili,才打印输出var taget_class = "com.bilibili.nativelibrary.LibBili";if(class_name === taget_class){
console.log("\n[RegisterNatives] method_count:", args[3]);var methods_ptr = ptr(args[2]);var method_count = parseInt(args[3]);for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));var name = Memory.readCString(name_ptr);var sig = Memory.readCString(sig_ptr);var find_module = Process.findModuleByAddress(fnPtr_ptr);console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));}}}});}
}setImmediate(hook_RegisterNatives);
C:\Users\Lenovo>frida -U --no-pause -f tv.danmaku.bili -l C:\Users\Lenovo\Desktop\2021\bilibili_sign分析\hook_RegisterNatives.js____/ _ | Frida 14.2.2 - A world-class dynamic instrumentation toolkit| (_| |> _ | Commands:/_/ |_| help -> Displays the help system. . . . object? -> Display information about 'object'. . . . exit/quit -> Exit. . . .. . . . More info at https://www.frida.re/docs/home/
Spawning `tv.danmaku.bili`...
RegisterNatives is at 0xec527091 _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
Spawned `tv.danmaku.bili`. Resuming main thread!
[MIX 2S::tv.danmaku.bili]->
[RegisterNatives] method_count: 0x8
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: a sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0xbabccc7d module_name: libbili.so module_base: 0xbabcb000 offset: 0x1c7d
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: ao sig: (Ljava/lang/String;II)Ljava/lang/String; fnPtr: 0xbabccc83 module_name: libbili.so module_base: 0xbabcb000 offset: 0x1c83
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: b sig: (Ljava/lang/String;)Ljavax/crypto/spec/IvParameterSpec; fnPtr: 0xbabccc91 module_name: libbili.so module_base: 0xbabcb000 offset: 0x1c91
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: s sig: (Ljava/util/SortedMap;)Lcom/bilibili/nativelibrary/SignedQuery; fnPtr: 0xbabccc97 module_name: libbili.so module_base: 0xbabcb000 offset: 0x1c97
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: so sig: (Ljava/util/SortedMap;II)Lcom/bilibili/nativelibrary/SignedQuery; fnPtr: 0xbabccc9d module_name: libbili.so module_base: 0xbabcb000 offset: 0x1c9d
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: so sig: (Ljava/util/SortedMap;[B)Lcom/bilibili/nativelibrary/SignedQuery; fnPtr: 0xbabcccab module_name: libbili.so module_base: 0xbabcb000 offset: 0x1cab
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: getCpuCount sig: ()I fnPtr: 0xbabcccb3 module_name: libbili.so module_base: 0xbabcb000 offset: 0x1cb3
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: getCpuId sig: ()I fnPtr: 0xbabcccb7 module_name: libbili.so module_base: 0xbabcb000 offset: 0x1cb7
显然这就是我们的目标
[RegisterNatives] java_class: com.bilibili.nativelibrary.LibBili name: s sig: (Ljava/util/SortedMap;)Lcom/bilibili/nativelibrary/SignedQuery; fnPtr: 0xbabccc97 module_name: libbili.so module_base: 0xbabcb000 offset: 0x1c97
module_name: libbili.so 即位于libbili.so中
offset: 0x1c97 偏移量为0x1c97,即从SO的开始处算,第0x1c97个字节开始是这个函数。
接下来分析SO,我这个版本是32位的SO,随着手机更新换代,越来越多的app会只提供arm64-v8a即64位的So,比如bilibili 18.0.1版就只包含arm64-v8a的SO。
让IDA加载和解析一会儿
接下来在IDA中做一些准备工作
-
快捷键G 跳转到目标函数 0x1c97
-
快捷键TAB 得到伪C代码
-
写一些自己的注释
-
看看流程图,如果流程图很复杂,就可能OLLVM混淆过
-
IDA无法正确识别参数类型和个数,Java_com_xxx_yyy()类型方法的前两个参数为JNIEnv* env与jobject thiz,从第三个参数开始为传入参数,因此本例应该有三个参数,且前两个参数为JNIEnv* env与**jobject thiz。
2F88是关键函数,参数一是JNIENV 指针,JNI指针主要用来使用JNI方法,参数二就是我们传入的集合对象,但它并不能直接使用,而是要通过JNI方法转换一下,参数三四均为0。
我们修改一下参数,再把伪代码中无意义的中间变量投影到真正的变量上。
3.2 JNItrace辅助分析
函数中大量使用了JNI方法,JNItrace会大大减轻这部分我们的工作量,忘了说了,JNItrace是一个基于Frida框架的Hook jni方法的库,它的源码写的非常好,想深入了解JNI方法的同学可以研究它的源码。
JNItrace提供了Spawn和Attach两种附加模式,但测试发现,在不少情况下,Attach模式存在BUG,会缺少输出或者无输出,具体原因未知,因此我采用默认的spawn模式对libbili.so里发生的所有JNI调用进行Hook,关于参数的具体意义,可以看JNItrace的介绍:
jnitrace -l libbili.so tv.danmaku.bili --ignore-vm
在启动成功并产生大量输出,截取一段,看着很不错:
/* TID 23816 */20620 ms [+] JNIEnv->DeleteLocalRef20620 ms |- JNIEnv* : 0xd6b9c54020620 ms |- jobject : 0x1920620 ms ------------------------Backtrace------------------------20620 ms |-> 0xb8a261b1: libbili.so!0x31b1 (libbili.so:0xb8a23000)/* TID 24017 */20638 ms [+] JNIEnv->NewObject20638 ms |- JNIEnv* : 0xb6de084020638 ms |- jclass : 0x326a {
com/bilibili/nativelibrary/SignedQuery }20638 ms |- jmethodID : 0xbd1a78e8 {
<init>(Ljava/lang/String;Ljava/lang/String;)V }20638 ms |: jstring : 0x3520638 ms |: jstring : 0x5920638 ms |= jobject : 0x1520638 ms ------------------------Backtrace------------------------20638 ms |-> 0xb8a261cb: libbili.so!0x31cb (libbili.so:0xb8a23000)/* TID 23816 */20654 ms [+] JNIEnv->NewObject20654 ms |- JNIEnv* : 0xd6b9c54020654 ms |- jclass : 0x326a {
com/bilibili/nativelibrary/SignedQuery }20654 ms |- jmethodID : 0xbd1a78e8 {
<init>(Ljava/lang/String;Ljava/lang/String;)V }20654 ms |: jstring : 0x3520654 ms |: jstring : 0x5920654 ms |= jobject : 0x1120654 ms ------------------------Backtrace------------------------20654 ms |-> 0xb8a261cb: libbili.so!0x31cb (libbili.so:0xb8a23000)
问题来了,这里面可能大部分都不是我们想分析的函数所产生的JNI调用,在一次点击或者刷新App的操作里,libbili.so中可能有多个方法被调用,可以断定其中含有不少噪音和干扰,甚至s方法本身也可能被调用多次,我们无法相信这个Hook结果。
尤其在样本的Native函数与JAVA层的交互尤其频繁的分析情景中,想着就非常麻烦。我们不妨再多写点代码,使用Frida主动调用这个函数,而不是每次依靠对App进行点击/滑动操作来触发此函数。
除此之外还有一个好处,主动调用函数可以总是传入固定的参数,这样更有助于对照和保持调试环境的稳定。
//C:\Users\Lenovo\Desktop\2021\bilibili_sign分析\hookAndCallFromJava.js
//Hook sign 函数
function hookSign(){
Java.perform(function() {
var ClassName = "com.bilibili.nativelibrary.LibBili";var Bilibili = Java.use(ClassName);var targetMethod = "s";Bilibili[targetMethod].implementation = function () {
var map = arguments[0];// 打印入参console.log("\nmap内容:", map.entrySet().toArray());var result = this[targetMethod](arguments[0]);// 打印结果,不需要做什么额外处理,这儿会隐式调用toString。console.log("\n返回结果:",result);return result;}});
}// Call Sign 函数
function callsign() {
Java.perform(function () {
var ClassName = "com.bilibili.nativelibrary.LibBili";var Bilibili = Java.use(ClassName);var targetMethod = "s";var TreeMap = Java.use("java.util.TreeMap");var map = TreeMap.$new();map.put("ad_extra", "E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223");map.put("appkey", "1d8b6e7d45233436");map.put("autoplay_card","11");map.put("banner_hash","10687342131252771522");map.put("build","6180500");map.put("c_locale","zh_CN");map.put("channel","shenma117");map.put("column","2");map.put("device_name","MIX2S");map.put("device_type","0");map.put("flush","6");map.put("ts","1612693177");var result = Bilibili.s(map);// 打印结果,不需要做什么额外处理,这儿会隐式调用toString。console.log("\n返回结果:",result);return result;});}
代码非常的简单,我们构造了一个简单的map作为入参,测试一下,一切顺利,sign 是三十二位十六进制数,且在参数固定的请况下Sign值固定。
frida -UF -l C:\Users\Lenovo\Desktop\2021\bilibili_sign分析\hookAndCallFromJava.js____/ _ | Frida 14.2.2 - A world-class dynamic instrumentation toolkit| (_| |> _ | Commands:/_/ |_| help -> Displays the help system. . . . object? -> Display information about 'object'. . . . exit/quit -> Exit. . . .. . . . More info at https://www.frida.re/docs/home/
[MIX 2S::哔哩哔哩]-> callsign()返回结果: ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223&appkey=1d8b6e7d45233436&autoplay_card=11&banner_hash=10687342131252771522&build=6180500&c_locale=zh_CN&channel=shenma117&column=2&device_name=MIX2S&device_type=0&flush=6&ts=1612693177&sign=b3b287f86cc0a057658edbac904c6624
[MIX 2S::哔哩哔哩]->
万事俱备,开始Frida 主动调用 + Jnitrace Hook 联动对JNI 函数的trace。
首先启动jnitrace
jnitrace -l libbili.so tv.danmaku.bili --ignore-vm
jnitrace启动后会产生大量输出,不去管它,另开一个命令行窗口,使用attach模式将hookAndCallFromJava.js注入App,这里不能用Spawn模式,否则Jnitrace就被断开了,也不能将我们的脚本和JNItrace的注入顺序颠倒,原因同理。
[我时常担心自己的表述不够清楚,如果你在实操的过程中遇到障碍,欢迎在评论里或者vx和我讨论。]
把新产生的Hook信息拷贝下来进行分析:
/* TID 27207 */50384 ms [+] JNIEnv->CallBooleanMethod50384 ms |- JNIEnv* : 0xdd70f14050384 ms |- jobject : 0xc55bc31050384 ms |- jmethodID : 0x6fd28aa8 {
isEmpty()Z }50384 ms |= jboolean : 0 {
false }50384 ms ------------------------Backtrace------------------------50384 ms |-> 0xb8eb5697: libbili.so!0x6697 (libbili.so:0xb8eaf000)/* TID 27207 */50399 ms [+] JNIEnv->ExceptionCheck50399 ms |- JNIEnv* : 0xdd70f14050399 ms |= jboolean : 0 {
false }50399 ms ------------------------Backtrace------------------------50399 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)/* TID 27207 */50414 ms [+] JNIEnv->NewStringUTF50414 ms |- JNIEnv* : 0xdd70f14050414 ms |- char* : 0xb8eb21e450414 ms |: appkey50414 ms |= jstring : 0x550414 ms ------------------------Backtrace------------------------50414 ms |-> 0xb8eb2019: libbili.so!0x3019 (libbili.so:0xb8eaf000)/* TID 27207 */50429 ms [+] JNIEnv->CallObjectMethod50429 ms |- JNIEnv* : 0xdd70f14050429 ms |- jobject : 0xc55bc31050429 ms |- jmethodID : 0x6fd28a54 {
get(Ljava/lang/Object;)Ljava/lang/Object; }50429 ms |: jobject : 0x550429 ms |= jobject : 0x15 {
java/lang/Object }50429 ms ------------------------Backtrace------------------------50429 ms |-> 0xb8eb54dd: libbili.so!0x64dd (libbili.so:0xb8eaf000)/* TID 27207 */50444 ms [+] JNIEnv->ExceptionCheck50444 ms |- JNIEnv* : 0xdd70f14050444 ms |= jboolean : 0 {
false }50444 ms ------------------------Backtrace------------------------50444 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)/* TID 27207 */50458 ms [+] JNIEnv->GetStringUTFChars50458 ms |- JNIEnv* : 0xdd70f14050458 ms |- jstring : 0x1550458 ms |- jboolean* : 0x050458 ms |= char* : 0xb78ff74850458 ms ------------------------Backtrace------------------------50458 ms |-> 0xb8eb203d: libbili.so!0x303d (libbili.so:0xb8eaf000)/* TID 27207 */50473 ms [+] JNIEnv->NewStringUTF50473 ms |- JNIEnv* : 0xdd70f14050473 ms |- char* : 0xb8eb24ac50473 ms |: ts50473 ms |= jstring : 0x2550473 ms ------------------------Backtrace------------------------50473 ms |-> 0xb8eb2439: libbili.so!0x3439 (libbili.so:0xb8eaf000)/* TID 27207 */50488 ms [+] JNIEnv->CallObjectMethod50488 ms |- JNIEnv* : 0xdd70f14050488 ms |- jobject : 0xc55bc31050488 ms |- jmethodID : 0x6fd28a54 {
get(Ljava/lang/Object;)Ljava/lang/Object; }50488 ms |: jobject : 0x25 {
java/lang/Object }50488 ms |= jobject : 0x31 {
java/lang/String }50488 ms ------------------------Backtrace------------------------50488 ms |-> 0xb8eb54dd: libbili.so!0x64dd (libbili.so:0xb8eaf000)/* TID 27207 */50502 ms [+] JNIEnv->ExceptionCheck50502 ms |- JNIEnv* : 0xdd70f14050502 ms |= jboolean : 0 {
false }50502 ms ------------------------Backtrace------------------------50502 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)/* TID 27207 */50516 ms [+] JNIEnv->DeleteLocalRef50516 ms |- JNIEnv* : 0xdd70f14050516 ms |- jobject : 0x2550516 ms ------------------------Backtrace------------------------50516 ms |-> 0xb8eb2485: libbili.so!0x3485 (libbili.so:0xb8eaf000)/* TID 27207 */50530 ms [+] JNIEnv->DeleteLocalRef50530 ms |- JNIEnv* : 0xdd70f14050530 ms |- jobject : 0x3150530 ms ------------------------Backtrace------------------------50530 ms |-> 0xb8eb248d: libbili.so!0x348d (libbili.so:0xb8eaf000)/* TID 27207 */50544 ms [+] JNIEnv->CallStaticObjectMethod50544 ms |- JNIEnv* : 0xdd70f14050544 ms |- jclass : 0x3146 {
com/bilibili/nativelibrary/SignedQuery }50544 ms |- jmethodID : 0xbd1a7958 {
r(Ljava/util/Map;)Ljava/lang/String; }50544 ms |: jobject : 0xc55bc31050544 ms |= jobject : 0x29 {
java/lang/Object }50544 ms ------------------------Backtrace------------------------50544 ms |-> 0xb8eb2077: libbili.so!0x3077 (libbili.so:0xb8eaf000)/* TID 27207 */50559 ms [+] JNIEnv->ExceptionCheck50559 ms |- JNIEnv* : 0xdd70f14050559 ms |= jboolean : 0 {
false }50559 ms ------------------------Backtrace------------------------50559 ms |-> 0xb8eb3379: libbili.so!0x4379 (libbili.so:0xb8eaf000)/* TID 27207 */50573 ms [+] JNIEnv->GetStringUTFChars50573 ms |- JNIEnv* : 0xdd70f14050573 ms |- jstring : 0x2950573 ms |- jboolean* : 0x050573 ms |= char* : 0x9fa9a30050573 ms ------------------------Backtrace------------------------50573 ms |-> 0xb8eb209b: libbili.so!0x309b (libbili.so:0xb8eaf000)/* TID 27207 */50588 ms [+] JNIEnv->ReleaseStringUTFChars50588 ms |- JNIEnv* : 0xdd70f14050588 ms |- jstring : 0xb78ff74850588 ms |- char* : 0xb78ff74850588 ms |: 1d8b6e7d4523343650588 ms ------------------------Backtrace------------------------50588 ms |-> 0xb8eb20b7: libbili.so!0x30b7 (libbili.so:0xb8eaf000)/* TID 27207 */50602 ms [+] JNIEnv->NewStringUTF50602 ms |- JNIEnv* : 0xdd70f14050602 ms |- char* : 0xc55bc24050602 ms |: b3b287f86cc0a057658edbac904c662450602 ms |= jstring : 0x3550602 ms ------------------------Backtrace------------------------50602 ms |-> 0xb8eb21a5: libbili.so!0x31a5 (libbili.so:0xb8eaf000)/* TID 27207 */50616 ms [+] JNIEnv->DeleteLocalRef50616 ms |- JNIEnv* : 0xdd70f14050616 ms |- jobject : 0x550616 ms ------------------------Backtrace------------------------50616 ms |-> 0xb8eb21b1: libbili.so!0x31b1 (libbili.so:0xb8eaf000)/* TID 27207 */50630 ms [+] JNIEnv->NewObject50630 ms |- JNIEnv* : 0xdd70f14050630 ms |- jclass : 0x3146 {
com/bilibili/nativelibrary/SignedQuery }50630 ms |- jmethodID : 0xbd1a78e8 {
<init>(Ljava/lang/String;Ljava/lang/String;)V }50630 ms |: jstring : 0x2950630 ms |: jstring : 0x3550630 ms |= jobject : 0x950630 ms ------------------------Backtrace------------------------50630 ms |-> 0xb8eb21cb: libbili.so!0x31cb (libbili.so:0xb8eaf000)
如果JNI方法以及其具体功能你有些陌生,建议先花半天浏览和查看JNI函数的介绍以及NDK开发指南,并尝试自己写几个小DEMO。
我们以如下的GetStringUTFChars函数为例,解释一下如何看JNItrace的输出结果。
74204 ms [+] JNIEnv->GetStringUTFChars74204 ms |- JNIEnv* : 0xdd71356074204 ms |- jstring : 0xb7360c2074204 ms |- jboolean* : 0x074204 ms |= char* : 0xdd7058c074204 ms ------------------------Backtrace------------------------74204 ms |-> 0xb8a26759: libbili.so!0x3759 (libbili.so:0xb8a23000)
Get开头的JNI方法用于从Java的类型中取数据,GetStringUTFChars取出Java字符串中内容,返回native中的字符串,严谨的说是将jstring指针转化成一个UTF-8格式的C字符串,除此之外,还有GetStringChars,它将jstring转换成为Unicode格式的C字符串。
传入native的数据,除了少部分基本类型,比如Int,long,short,char等等,都必须通过JNI方法GETxxxx的主动转换,才能在native中使用,用完后还必须要调用对应的Releasexxx释放资源,否则会导致JVM内存泄露,这是官方规定的开发规范。
先介绍一下GetStringUTFChars方法,它需要传入三个参数:
const jchar * GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy);
参数1:env
参数2:jstring 指针,可以简单理解成Java层传入的String对象
参数3:布尔值,一般false,这里不用深究
返回值是String对象对应的C字符串。
jnitrace记录中,”-“开头的即为参数,”=“开头的是返回值,十分清晰明了。
可以发现,JNItrace打印了前两个参数的指针,第三个参数因为是基本数据类型,所以直接显示了值,即0。
给了一堆指针,并没有告诉我们这个字符串内容究竟是什么,可以修改Jnitrace源码,增加打印功能,或者和我一样,通过指针来判断,除此之外,GetStringUTFChars后一定有ReleaseStringUTFChars,jnitrace可以顺利打印Release系列函数中的字符串,所以我们也可以通过对应的release来得知操作的是哪个Java层的内容。
除此之外,它提供了非常重要的信息——调用栈。
0xb8a26759: libbili.so!0x3759 (libbili.so:0xb8a23000)
0xb8a26759和0xb8a23000是内存中的虚拟地址,无法为我们所用,而libbili.so!0x3759意指”位于什么模块!偏移地址多少“,这也是Frida官方trace工具frida-trace中的表达方式,JNItrace进行了”沿用",我猜测JNItrace的初衷就是Frida trace的补充,即对JNI 的Hook和trace。(JNITrace在使用Unidbg或者AndroidNativeEmu进行模拟执行时,也很有帮助,毕竟模拟执行的头号敌人就是Native与Java类的交互,Jnitrace可以提供很好的指引功能。)
IDA中快捷键G跳转到0x3759,这是一个典型的JNI函数,但env没有被识别,参数也没被识别,我们稍作修改,我将最后两个参数命名为zero_x,这是很糟糕的命名习惯,而且“把伪代码中无意义的中间变量投影到真正的变量上”也并不是非做不可,有时候甚至会导致错误,希望大家随机应变。
做到这儿就够了,不用管此处的具体逻辑和参数,因为我们只是做演示
现在演示完了,我们开始对目标函数做这些事。
/* TID 27207 */50384 ms [+] JNIEnv->CallBooleanMethod50384 ms |- JNIEnv* : 0xdd70f14050384 ms |- jobject : 0xc55bc31050384 ms |- jmethodID : 0x6fd28aa8 {
isEmpty()Z }50384 ms |= jboolean : 0 {
false }50384 ms ------------------------Backtrace------------------------50384 ms |-> 0xb8eb5697: libbili.so!0x6697 (libbili.so:0xb8eaf000)
第一个JNI调用是CallBooleanMethod
CallxxxMethod系列方法用于调用在native中调用Java方法
Java层返回值 | 方法族 | 本地返回类型NativeType |
---|---|---|
void | CallVoidMethod( ) | (无) |
引用类型 | CallObjectMethod( ) | jobect |
boolean | CallBooleanMethod( ) | jboolean |
byte | CallByteMethod( ) | jbyte |
char | CallCharMethod( ) | jchar |
short | CallShortMethod( ) | jshort |
int | CallIntMethod( ) | jint |
long | CallLongMethod( ) | jlong |
double | CallDoubleMethod( ) | jdouble |
CallBooleanMethod即调用一个Java方法,这个Java方法的返回值是布尔型,在native中转换为jboolean,JNItrace 也证实了这一点,返回jboolean,值为False。
入参一JNIEnv* ,CallBooleanMethod参数二是待操作的对象,参数三是方法,再后面的参数是方法参数。
对于本例来说,即jobject.isEmpty() 结果为False,IDA中跳转到0x6697位置,函数有两处引用,一处与我们无关排除掉,发现另一处jobject正是我们传入的map对象。
GIF中,大家会发现有很多误操作,没错,这就是正常的试错。
把函数名改成j_isempty,即java层的isempty,map不为空,走else逻辑,继续往下看。
/* TID 27207 */50399 ms [+] JNIEnv->ExceptionCheck50399 ms |- JNIEnv* : 0xdd70f14050399 ms |= jboolean : 0 {
false }50399 ms ------------------------Backtrace------------------------50399 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)
异常处理和检查,不去管它。
/* TID 27207 */50414 ms [+] JNIEnv->NewStringUTF50414 ms |- JNIEnv* : 0xdd70f14050414 ms |- char* : 0xb8eb21e450414 ms |: appkey50414 ms |= jstring : 0x550414 ms ------------------------Backtrace------------------------50414 ms |-> 0xb8eb2019: libbili.so!0x3019 (libbili.so:0xb8eaf000)
由c字符串得到对应的 java中的字符串对象,“appkey”,毫无疑问,这是因为它后续要使用java方法,或者就是要传回java层。
/* TID 27207 */50429 ms [+] JNIEnv->CallObjectMethod50429 ms |- JNIEnv* : 0xdd70f14050429 ms |- jobject : 0xc55bc31050429 ms |- jmethodID : 0x6fd28a54 { get(Ljava/lang/Object;)Ljava/lang/Object; }50429 ms |: jobject : 0x550429 ms |= jobject : 0x15 { java/lang/Object }50429 ms ------------------------Backtrace------------------------50429 ms |-> 0xb8eb54dd: libbili.so!0x64dd (libbili.so:0xb8eaf000)
jobject的指针是一种线索,这儿入参jobject指针为0xc55bc310,就是我们上面说的map,参数四jobject 0x5,即上面appkey返回的jstring,翻译一下就是map.get(“appkey”),从map中取出appkey的值。
回到ida做一下反馈笔记
/* TID 27207 */50458 ms [+] JNIEnv->GetStringUTFChars50458 ms |- JNIEnv* : 0xdd70f14050458 ms |- jstring : 0x1550458 ms |- jboolean* : 0x050458 ms |= char* : 0xb78ff74850458 ms ------------------------Backtrace------------------------50458 ms |-> 0xb8eb203d: libbili.so!0x303d (libbili.so:0xb8eaf000)
这个jstring 0x15 即我们上面取到的appkey value,这儿把它转换成c字符串,对应于IDA中if逻辑的第一句。
/* TID 27207 */50473 ms [+] JNIEnv->NewStringUTF50473 ms |- JNIEnv* : 0xdd70f14050473 ms |- char* : 0xb8eb24ac50473 ms |: ts50473 ms |= jstring : 0x2550473 ms ------------------------Backtrace------------------------50473 ms |-> 0xb8eb2439: libbili.so!0x3439 (libbili.so:0xb8eaf000)/* TID 27207 */50488 ms [+] JNIEnv->CallObjectMethod50488 ms |- JNIEnv* : 0xdd70f14050488 ms |- jobject : 0xc55bc31050488 ms |- jmethodID : 0x6fd28a54 { get(Ljava/lang/Object;)Ljava/lang/Object; }50488 ms |: jobject : 0x25 { java/lang/Object }50488 ms |= jobject : 0x31 { java/lang/String }50488 ms ------------------------Backtrace------------------------50488 ms |-> 0xb8eb54dd: libbili.so!0x64dd (libbili.so:0xb8eaf000)
map.get(“ts”)
后续使用C代码进行了一系列操作,在目的达成后释放了“ts”以及具体的值。
/* TID 27207 */50516 ms [+] JNIEnv->DeleteLocalRef50516 ms |- JNIEnv* : 0xdd70f14050516 ms |- jobject : 0x2550516 ms ------------------------Backtrace------------------------50516 ms |-> 0xb8eb2485: libbili.so!0x3485 (libbili.so:0xb8eaf000)/* TID 27207 */50530 ms [+] JNIEnv->DeleteLocalRef50530 ms |- JNIEnv* : 0xdd70f14050530 ms |- jobject : 0x3150530 ms ------------------------Backtrace------------------------50530 ms |-> 0xb8eb248d: libbili.so!0x348d (libbili.so:0xb8eaf000)
45c8函数改个名看起来更清晰
/* TID 27207 */50544 ms [+] JNIEnv->CallStaticObjectMethod50544 ms |- JNIEnv* : 0xdd70f14050544 ms |- jclass : 0x3146 {
com/bilibili/nativelibrary/SignedQuery }50544 ms |- jmethodID : 0xbd1a7958 {
r(Ljava/util/Map;)Ljava/lang/String; }50544 ms |: jobject : 0xc55bc31050544 ms |= jobject : 0x29 {
java/lang/Object }50544 ms ------------------------Backtrace------------------------50544 ms |-> 0xb8eb2077: libbili.so!0x3077 (libbili.so:0xb8eaf000)
对照java代码
jobject = com.bilibili.nativelibrary.SignedQuery.r(map)
似乎就是把map拼接成字符串:{“ts”:123,“key”:456} → ts=123&key=456,注意,调用java’方法返回的是jstring,无法在native中使用,需要转成c字符串。
/* TID 27207 */50573 ms [+] JNIEnv->GetStringUTFChars50573 ms |- JNIEnv* : 0xdd70f14050573 ms |- jstring : 0x2950573 ms |- jboolean* : 0x050573 ms |= char* : 0x9fa9a30050573 ms ------------------------Backtrace------------------------50573 ms |-> 0xb8eb209b: libbili.so!0x309b (libbili.so:0xb8eaf000)
即使不看ida,也可以根据0xb78ff748往上检索,原来这就是我们的appkey value,现在它被用完了,所以释放内存。
/* TID 27207 */50588 ms [+] JNIEnv->ReleaseStringUTFChars50588 ms |- JNIEnv* : 0xdd70f14050588 ms |- jstring : 0xb78ff74850588 ms |- char* : 0xb78ff74850588 ms |: 1d8b6e7d4523343650588 ms ------------------------Backtrace------------------------50588 ms |-> 0xb8eb20b7: libbili.so!0x30b7 (libbili.so:0xb8eaf000)
对比主动调用的结果可以发现,这就是Sign
/* TID 27207 */50602 ms [+] JNIEnv->NewStringUTF50602 ms |- JNIEnv* : 0xdd70f14050602 ms |- char* : 0xc55bc24050602 ms |: b3b287f86cc0a057658edbac904c662450602 ms |= jstring : 0x3550602 ms ------------------------Backtrace------------------------50602 ms |-> 0xb8eb21a5: libbili.so!0x31a5 (libbili.so:0xb8eaf000)
标记一下它
后面的jni调用就是创建SignedQuery对象,返回给Java层。
/* TID 27207 */50630 ms [+] JNIEnv->NewObject50630 ms |- JNIEnv* : 0xdd70f14050630 ms |- jclass : 0x3146 { com/bilibili/nativelibrary/SignedQuery }50630 ms |- jmethodID : 0xbd1a78e8 { <init>(Ljava/lang/String;Ljava/lang/String;)V }50630 ms |: jstring : 0x2950630 ms |: jstring : 0x3550630 ms |= jobject : 0x950630 ms ------------------------Backtrace------------------------50630 ms |-> 0xb8eb21cb: libbili.so!0x31cb (libbili.so:0xb8eaf000)
0x29 往上翻,是map拼接的字符串,0x35是sign,逻辑已经疏通了。
3.3 IDA分析
JNI实现的那部分代码逻辑已经通过Jnitrace了解了,接下来我们要去IDA中分析由C/C++ 实现的那部分。
函数一:Sub_34b8
逻辑非常好懂,进入else逻辑
将你本机的appkey与一系列key进行对比,返回不同的数,按照我本机的情况,返回0。
函数二:Sub3414
在上一节我们知道,这儿取出了map中的时间戳,在时间戳为空的情况下,补全时间,想验证的小伙伴可以把主动调用里的时间键值对注释掉,测试一番。把这个函数改名为checktimestamp,继续往下走。
下面这几行也是上一节已经完成的工作,我们改一下名,“j_”开头意味着是jstring指针,“c_”开头则是转换成了c字符串。
if ( zero3 != -1 ){
s = (char *)c_mapText;v19 = zero3;((void (__fastcall *)(JNIEnv *, int, char *))(*env)->ReleaseStringUTFChars)(env, v15, c_appkey);v20 = malloc(0x10u);if ( v20 ){
v30 = j_mapText;if ( zero1 == 1 ){
v21 = &unk_96AC;if ( (zero2 | 1) != 3 )v21 = &unk_971C;}else{
v21 = &unk_978C;}zero2 = v21[v19];v22 = &v21[v19];v23 = v22[7];v24 = v22[14];v25 = v22[21];*v20 = zero2;v20[1] = v23;v20[2] = v24;v20[3] = v25;_aeabi_memclr8(&v34, 33);v26 = strlen(s);_aeabi_memclr8(v36, 24);_aeabi_memclr8(&v35, 88);sub_227C(&v35);sub_22B0(&v35, s, v26);sprintf(v36, "%08x", zero2);sub_22B0(&v35, v36, 8);v27 = 1;do{
sprintf(v36, "%08x", v20[v27]);sub_22B0(&v35, v36, 8);++v27;}while ( v27 != 4 );sub_2AE0(v36, &v35);v28 = &v34;v29 = 0;do{
sprintf(v28, "%02x", (unsigned __int8)v36[v29++]);v28 += 2;}while ( v29 != 16 );free(v20);j_mapText = v30;map = (int *)((int (__fastcall *)(JNIEnv *, char *))(*env)->NewStringUTF)(env, &v34);// v34 就是他妈的SIGN}else{
map = 0;}}
这一段看着有些麻烦,当你试图一行一行去翻译的时候感到迷茫和痛苦。这个时候你发现,上一节Jnitrace提供的帮助如此之小,对于这个sign的生成过程,我们几乎一无所知。
事实也确实如此,但我们不应该感觉被欺骗,线索常常藏在某个不起眼的角落里,逆向的绝大部分时间都是在试错,只不过对于高手来说,错误少些,弯路少些,而新手的错误和弯路会多一些。
言归正传,回到我们这个平平无奇的SO上,以下思路都是靠谱的:
- 从生成结果往前推,即从v34往前推来源
- 从传入参数往后推,s = (char *)c_mapText,看s的调用情况。
- 浏览一遍函数,对感兴趣的函数入参和出参Hook
每个思路都是可以走通的,但每个具体的SO都有最佳的那条路,我们这儿先试一下3,原因有两个
- 这个SO没经过ollvm混淆,逻辑比较清晰
- 我们要分析的代码块里函数较少,试错成本不高,而从前往后翻译是累人的。
代码块中"_“开头或者free/sprintf这些函数都是库函数,不属于我们关注的范畴,所以一共有三个自定义函数。
sub_227C
sub_22B0
sub_2AE0
一个个看
好家伙,不知道你会不会眼前一亮,我是很喜悦的。这四个数字很像初始化变量,也叫幻数,比如MD5就有四个醒目的初始化变量。
接下来就是解析了,在IDA中将其转成十六进制,因为幻数一般都被表示成十六进制,然后面向Google编程。
(下面这一段是我瞎叨叨,大家听着一乐就行)面向Google编程或者面向Github编程是逆向工程师的重要手段和技能,原因呢?逆向从抽象的角度上看,就是符号还原的过程。比如你遇到一个保护很好的APK,加密在JAVA层,但你没法彻底拖壳,关键函数的代码都被抽取了。想着很绝望吧,但离谱的是,它没有做函数名的混淆,而恰巧这个加密类又是样本的Android开发工程师在Github抄的,然后你通过函数名、包结构,惊喜的发现能对上去,然后Hook验证一下,竟然成了!这并不是很奇怪的事,我的逆向经验不算多,但也遇到过至少两次了。Native层也同理,即使部分大厂,加密也是用的开源库,在没有符号名的SO中大胆的试错,常常会惊喜的发现开源库的身影。
常客有这些:OpenSSL、Crypto++、Botan、Libtomcrypt等等。
疑似md5的初始化变量,那就好办了。
随便找一份md5 C代码对比一下
MD5Init(&context);MD5Update(&context, (unsigned char*)szText, strlen(szText));unsigned char dest[16] = {
0 };MD5Final(dest, &context);// 以十六进制输出结果int i = 0;char szMd5[33] = {
0 };for (i = 0; i < 16; i++){
sprintf(szMd5, "%s%02x", szMd5, dest[i]);}
IDA中修改名称,还原sub_222C参数名和函数名
MD5Init(&context);sub_22B0((int)&context, (int)s, mapText_length);sprintf(v36, "%08x", zero2);sub_22B0((int)&context, (int)v36, 8u);v27 = 1;do{
sprintf(v36, "%08x", v20[v27]);sub_22B0((int)&context, (int)v36, 8u);++v27;}while ( v27 != 4 );sub_2AE0((int)v36, (int)&context);
通过context的入参位置可以猜测,sub_22B0 也就是 MD5Update 了,sub_2AE0即MD5Final。
MD5Init(&context);MD5Update((int)&context, (int)s, mapText_length);sprintf(dest, "%08x", zero2);MD5Update((int)&context, (int)dest, 8u);v27 = 1;do{
sprintf(dest, "%08x", v20[v27]);MD5Update((int)&context, (int)dest, 8u);++v27;}while ( v27 != 4 );MD5Final((int)dest, (int)&context);v28 = &v34;v29 = 0;do{
sprintf(v28, "%02x", (unsigned __int8)dest[v29++]);v28 += 2;}
可以发现,几乎一模一样,除了MD5Update 似乎调用了多次,对于MD5哈希算法来说,将数据分块多次调用update()
和一次性update()
没有区别。
接下来使用Frida Hook 来确定入参
MD5Update 函数,我们需要打印它的参数二和参数三
参数二是字符串指针,参数三是字符串的长度
编写代码hook 这个函数
function hook_update() {
var libbili = Module.findBaseAddress("libbili.so");if(libbili){
// 0x22b0 是 MD5Update 函数的地址,+1是因为指令是thumb模式var md5_update = libbili.add(0x22b0 + 1);Interceptor.attach(md5_update,{
onEnter:function (args) {
console.log("\ncontents:");// 这儿必须指定hexdump的length,hexdump默认长度256不足以显示全部内容console.log(hexdump(args[1], {
length: args[2].toInt32()}));console.log("\nLength:"+args[2]);},onLeave:function (args) {
}})}
}
打开App,开两个命令行窗口,分别用attach 模式注入这两个脚本,并调用对应方法
结果如下
contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
b9dfc100 61 64 5f 65 78 74 72 61 3d 45 31 31 33 33 43 32 ad_extra=E1133C2
b9dfc110 33 46 33 36 35 37 31 41 33 46 31 46 44 45 36 42 3F36571A3F1FDE6B
b9dfc120 33 32 35 42 31 37 34 31 39 41 41 44 34 35 32 38 325B17419AAD4528
b9dfc130 37 34 35 35 45 35 32 39 32 41 31 39 43 46 35 31 7455E5292A19CF51
b9dfc140 33 30 30 45 41 46 30 46 32 36 36 34 43 38 30 38 300EAF0F2664C808
b9dfc150 45 32 43 34 30 37 46 42 44 39 45 35 30 42 44 34 E2C407FBD9E50BD4
b9dfc160 38 46 38 45 44 31 37 33 33 34 46 34 45 32 44 33 8F8ED17334F4E2D3
b9dfc170 41 30 37 31 35 33 36 33 30 42 46 36 32 46 31 30 A07153630BF62F10
b9dfc180 44 43 35 45 35 33 43 34 32 45 33 32 32 37 34 43 DC5E53C42E32274C
b9dfc190 36 30 37 36 41 35 35 39 33 43 32 33 45 45 36 35 6076A5593C23EE65
b9dfc1a0 38 37 46 34 35 33 46 35 37 42 38 34 35 37 36 35 87F453F57B845765
b9dfc1b0 34 43 42 33 44 43 45 39 30 46 41 45 39 34 33 45 4CB3DCE90FAE943E
b9dfc1c0 32 41 46 35 46 46 41 45 37 38 45 35 37 34 44 30 2AF5FFAE78E574D0
b9dfc1d0 32 42 38 42 42 44 46 45 36 34 30 41 45 39 38 42 2B8BBDFE640AE98B
b9dfc1e0 38 46 30 32 34 37 45 43 30 39 37 30 44 32 46 44 8F0247EC0970D2FD
b9dfc1f0 34 36 44 38 34 42 39 35 38 45 38 37 37 36 32 38 46D84B958E877628
b9dfc200 41 38 45 39 30 46 37 31 38 31 43 43 31 36 44 44 A8E90F7181CC16DD
b9dfc210 32 32 41 34 31 41 45 39 45 31 43 32 42 39 43 42 22A41AE9E1C2B9CB
b9dfc220 39 39 33 46 33 33 42 36 35 45 30 42 32 38 37 33 993F33B65E0B2873
b9dfc230 31 32 45 38 33 35 31 41 44 43 34 41 39 35 31 35 12E8351ADC4A9515
b9dfc240 31 32 33 39 36 36 41 43 46 38 30 33 31 46 46 34 123966ACF8031FF4
b9dfc250 34 34 30 45 43 34 43 34 37 32 43 37 38 43 38 42 440EC4C472C78C8B
b9dfc260 30 43 36 43 38 44 35 45 41 39 41 42 39 45 35 37 0C6C8D5EA9AB9E57
b9dfc270 39 39 36 36 41 44 34 42 39 44 32 33 46 36 35 43 9966AD4B9D23F65C
b9dfc280 34 30 36 36 31 41 37 33 39 35 38 31 33 30 45 34 40661A73958130E4
b9dfc290 44 37 31 46 35 36 34 42 32 37 43 34 35 33 33 43 D71F564B27C4533C
b9dfc2a0 31 34 33 33 35 45 41 36 34 44 44 36 45 32 38 43 14335EA64DD6E28C
b9dfc2b0 32 39 43 44 39 32 44 35 41 38 30 33 37 44 43 44 29CD92D5A8037DCD
b9dfc2c0 30 34 43 38 43 43 45 41 45 42 45 43 43 45 31 30 04C8CCEAEBECCE10
b9dfc2d0 45 41 41 45 30 46 41 43 39 31 43 37 38 38 45 43 EAAE0FAC91C788EC
b9dfc2e0 44 34 32 34 44 38 34 37 33 43 41 41 36 37 44 34 D424D8473CAA67D4
b9dfc2f0 32 34 34 35 30 34 33 31 34 36 37 34 39 31 42 33 24450431467491B3
b9dfc300 34 41 31 34 35 30 41 37 38 31 46 33 34 31 41 42 4A1450A781F341AB
b9dfc310 42 38 30 37 33 43 36 38 44 42 43 43 43 39 38 36 B8073C68DBCCC986
b9dfc320 33 46 38 32 39 34 35 37 43 37 34 44 42 44 38 39 3F829457C74DBD89
b9dfc330 43 37 41 38 36 37 43 38 42 36 31 39 45 42 42 32 C7A867C8B619EBB2
b9dfc340 31 46 33 31 33 44 33 30 32 31 30 30 37 44 32 33 1F313D3021007D23
b9dfc350 44 33 37 37 36 44 41 30 38 33 41 37 45 30 39 43 D3776DA083A7E09C
b9dfc360 42 41 35 41 39 38 37 35 39 34 34 43 37 34 35 42 BA5A9875944C745B
b9dfc370 42 36 39 31 39 37 31 42 46 45 39 34 33 42 44 34 B691971BFE943BD4
b9dfc380 36 38 31 33 38 42 44 37 32 37 42 46 38 36 31 38 68138BD727BF8618
b9dfc390 36 39 41 36 38 45 41 32 37 34 37 31 39 44 36 36 69A68EA274719D66
b9dfc3a0 32 37 36 42 44 32 43 33 42 42 35 37 38 36 37 46 276BD2C3BB57867F
b9dfc3b0 34 35 42 31 31 44 36 42 31 41 37 37 38 45 37 30 45B11D6B1A778E70
b9dfc3c0 35 31 42 33 31 37 39 36 37 46 38 41 35 45 41 46 51B317967F8A5EAF
b9dfc3d0 31 33 32 36 30 37 32 34 32 42 31 32 43 39 30 32 132607242B12C902
b9dfc3e0 30 33 32 38 43 38 30 41 31 42 42 42 46 32 38 45 0328C80A1BBBF28E
b9dfc3f0 32 45 32 32 38 43 38 43 37 43 44 41 43 44 31 46 2E228C8C7CDACD1F
b9dfc400 36 43 43 37 35 30 30 41 30 38 42 41 32 34 43 34 6CC7500A08BA24C4
b9dfc410 42 39 45 34 42 43 39 42 36 39 45 30 33 39 32 31 B9E4BC9B69E03921
b9dfc420 36 41 41 38 42 30 35 36 36 42 30 43 35 30 41 30 6AA8B0566B0C50A0
b9dfc430 37 46 36 35 32 35 35 43 45 33 38 46 39 32 31 32 7F65255CE38F9212
b9dfc440 34 43 42 39 31 44 31 43 31 43 33 39 41 33 43 35 4CB91D1C1C39A3C5
b9dfc450 46 37 44 35 30 45 35 37 44 43 44 32 35 43 36 36 F7D50E57DCD25C66
b9dfc460 38 34 41 35 37 45 31 46 35 36 34 38 39 41 45 33 84A57E1F56489AE3
b9dfc470 39 42 44 42 43 35 43 46 45 31 33 43 35 34 30 43 9BDBC5CFE13C540C
b9dfc480 41 30 32 35 43 34 32 41 33 46 30 46 33 44 41 39 A025C42A3F0F3DA9
b9dfc490 38 38 32 46 32 41 31 44 30 42 35 42 31 42 33 36 882F2A1D0B5B1B36
b9dfc4a0 46 30 32 30 39 33 35 46 44 36 34 44 35 38 41 34 F020935FD64D58A4
b9dfc4b0 37 45 46 38 33 32 31 33 39 34 39 31 33 30 42 39 7EF83213949130B9
b9dfc4c0 35 36 46 31 32 44 42 39 32 42 30 35 34 36 44 41 56F12DB92B0546DA
b9dfc4d0 44 43 31 42 36 30 35 44 39 41 33 45 44 32 34 32 DC1B605D9A3ED242
b9dfc4e0 43 38 44 37 45 46 30 32 34 33 33 41 36 43 38 45 C8D7EF02433A6C8E
b9dfc4f0 33 43 34 30 32 43 36 36 39 34 34 37 41 37 46 31 3C402C669447A7F1
b9dfc500 35 31 38 36 36 45 36 36 33 38 33 31 37 32 41 38 51866E66383172A8
b9dfc510 41 38 34 36 43 45 34 39 41 43 45 36 31 41 44 30 A846CE49ACE61AD0
b9dfc520 30 43 31 45 34 32 32 32 33 26 61 70 70 6b 65 79 0C1E42223&appkey
b9dfc530 3d 31 64 38 62 36 65 37 64 34 35 32 33 33 34 33 =1d8b6e7d4523343
b9dfc540 36 26 61 75 74 6f 70 6c 61 79 5f 63 61 72 64 3d 6&autoplay_card=
b9dfc550 31 31 26 62 61 6e 6e 65 72 5f 68 61 73 68 3d 31 11&banner_hash=1
b9dfc560 30 36 38 37 33 34 32 31 33 31 32 35 32 37 37 31 0687342131252771
b9dfc570 35 32 32 26 62 75 69 6c 64 3d 36 31 38 30 35 30 522&build=618050
b9dfc580 30 26 63 5f 6c 6f 63 61 6c 65 3d 7a 68 5f 43 4e 0&c_locale=zh_CN
b9dfc590 26 63 68 61 6e 6e 65 6c 3d 73 68 65 6e 6d 61 31 &channel=shenma1
b9dfc5a0 31 37 26 63 6f 6c 75 6d 6e 3d 32 26 64 65 76 69 17&column=2&devi
b9dfc5b0 63 65 5f 6e 61 6d 65 3d 4d 49 58 32 53 26 64 65 ce_name=MIX2S&de
b9dfc5c0 76 69 63 65 5f 74 79 70 65 3d 30 26 66 6c 75 73 vice_type=0&flus
b9dfc5d0 68 3d 36 26 74 73 3d 31 36 31 32 36 39 33 31 37 h=6&ts=161269317
b9dfc5e0 37 7Length:0x4e1contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 35 36 30 63 35 32 63 63 560c52ccLength:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 64 32 38 38 66 65 64 30 d288fed0Length:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 34 35 38 35 39 65 64 31 45859ed1Length:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 38 62 66 66 64 39 37 33 8bffd973Length:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
bae3e064 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bae3e074 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bae3e084 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bae3e094 00 00 00 00 00 00 00 .......Length:0x37contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c904 08 28 00 00 00 00 00 00 .(......Length:0x8
可以发现方法被调用了七次
但代码中只有五次,还有两次哪来的?事实上,MD5Final中会调用两次MD5Update,所以我们只用管前5次即可。
使用逆向之友Cyberchef验证结果
五个数据块拼接在一起md5结果正是sign,那这后四个参与md5的数据块哪来的?
contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 35 36 30 63 35 32 63 63 560c52ccLength:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 64 32 38 38 66 65 64 30 d288fed0Length:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 34 35 38 35 39 65 64 31 45859ed1Length:0x8contents:0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ac53c9c0 38 62 66 66 64 39 37 33 8bffd973
算半个硬编码,此处不深究了。
接下来真实操作手机,下拉刷新页面产生一次sign调用,看是否可以还原。
map内容: ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223,appkey=1d8b6e7d45233436,autoplay_card=11,banner_hash=17947875969974833536,build=6180500,c_locale=zh_CN,channel=shenma117,column=2,device_name=MIX 2S,device_type=0,flush=6,fnval=464,fnver=0,force_host=0,fourk=1,guidance=0,https_url_req=0,idx=1612774865,inline_danmu=2,inline_sound=1,login_event=0,mobi_app=android,network=wifi,open_event=,platform=android,player_net=1,pull=true,qn=32,recsys_mode=0,s_locale=zh_CN,splash_id=,statistics={
"appId":1,"platform":3,"version":"6.18.0","abtest":""}返回结果: ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223&appkey=1d8b6e7d45233436&autoplay_card=11&banner_hash=17947875969974833536&build=6180500&c_locale=zh_CN&channel=shenma117&column=2&device_name=MIX%202S&device_type=0&flush=6&fnval=464&fnver=0&force_host=0&fourk=1&guidance=0&https_url_req=0&idx=1612774865&inline_danmu=2&inline_sound=1&login_event=0&mobi_app=android&network=wifi&open_event=&platform=android&player_net=1&pull=true&qn=32&recsys_mode=0&s_locale=zh_CN&splash_id=&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%226.18.0%22%2C%22abtest%22%3A%22%22%7D&ts=1612777942&sign=d8448c31064d272dba213c6c0d9bc3b1
MD5 明文是拼接后的map(我们偷个懒直接用返回值里除去尾巴sign的部分)+ 560c52ccd288fed045859ed18bffd973
ad_extra=E1133C23F36571A3F1FDE6B325B17419AAD45287455E5292A19CF51300EAF0F2664C808E2C407FBD9E50BD48F8ED17334F4E2D3A07153630BF62F10DC5E53C42E32274C6076A5593C23EE6587F453F57B8457654CB3DCE90FAE943E2AF5FFAE78E574D02B8BBDFE640AE98B8F0247EC0970D2FD46D84B958E877628A8E90F7181CC16DD22A41AE9E1C2B9CB993F33B65E0B287312E8351ADC4A9515123966ACF8031FF4440EC4C472C78C8B0C6C8D5EA9AB9E579966AD4B9D23F65C40661A73958130E4D71F564B27C4533C14335EA64DD6E28C29CD92D5A8037DCD04C8CCEAEBECCE10EAAE0FAC91C788ECD424D8473CAA67D424450431467491B34A1450A781F341ABB8073C68DBCCC9863F829457C74DBD89C7A867C8B619EBB21F313D3021007D23D3776DA083A7E09CBA5A9875944C745BB691971BFE943BD468138BD727BF861869A68EA274719D66276BD2C3BB57867F45B11D6B1A778E7051B317967F8A5EAF132607242B12C9020328C80A1BBBF28E2E228C8C7CDACD1F6CC7500A08BA24C4B9E4BC9B69E039216AA8B0566B0C50A07F65255CE38F92124CB91D1C1C39A3C5F7D50E57DCD25C6684A57E1F56489AE39BDBC5CFE13C540CA025C42A3F0F3DA9882F2A1D0B5B1B36F020935FD64D58A47EF83213949130B956F12DB92B0546DADC1B605D9A3ED242C8D7EF02433A6C8E3C402C669447A7F151866E66383172A8A846CE49ACE61AD00C1E42223&appkey=1d8b6e7d45233436&autoplay_card=11&banner_hash=17947875969974833536&build=6180500&c_locale=zh_CN&channel=shenma117&column=2&device_name=MIX%202S&device_type=0&flush=6&fnval=464&fnver=0&force_host=0&fourk=1&guidance=0&https_url_req=0&idx=1612774865&inline_danmu=2&inline_sound=1&login_event=0&mobi_app=android&network=wifi&open_event=&platform=android&player_net=1&pull=true&qn=32&recsys_mode=0&s_locale=zh_CN&splash_id=&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%226.18.0%22%2C%22abtest%22%3A%22%22%7D&ts=1612777942560c52ccd288fed045859ed18bffd973
完全正确。
四、总结
此Sign算法无混淆,无魔改加密算法,无保护,也没有复杂流程,难度1星,适合新手学习,下期再见。之后会是几篇硬货,如果这篇文章给了您帮助,可以请我喝杯咖啡。