Native逆向指北(一)——BiliBili Sign

2 篇文章 5 订阅
2 篇文章 4 订阅

一、前言

一个简单的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_jclassPK15JNINativeMethodi
        if (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* envjobject 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->DeleteLocalRef
  20620 ms |- JNIEnv*          : 0xd6b9c540
  20620 ms |- jobject          : 0x19

  20620 ms ------------------------Backtrace------------------------
  20620 ms |-> 0xb8a261b1: libbili.so!0x31b1 (libbili.so:0xb8a23000)


           /* TID 24017 */
  20638 ms [+] JNIEnv->NewObject
  20638 ms |- JNIEnv*          : 0xb6de0840
  20638 ms |- jclass           : 0x326a    { com/bilibili/nativelibrary/SignedQuery }
  20638 ms |- jmethodID        : 0xbd1a78e8    { <init>(Ljava/lang/String;Ljava/lang/String;)V }
  20638 ms |: jstring          : 0x35
  20638 ms |: jstring          : 0x59
  20638 ms |= jobject          : 0x15

  20638 ms ------------------------Backtrace------------------------
  20638 ms |-> 0xb8a261cb: libbili.so!0x31cb (libbili.so:0xb8a23000)


           /* TID 23816 */
  20654 ms [+] JNIEnv->NewObject
  20654 ms |- JNIEnv*          : 0xd6b9c540
  20654 ms |- jclass           : 0x326a    { com/bilibili/nativelibrary/SignedQuery }
  20654 ms |- jmethodID        : 0xbd1a78e8    { <init>(Ljava/lang/String;Ljava/lang/String;)V }
  20654 ms |: jstring          : 0x35
  20654 ms |: jstring          : 0x59
  20654 ms |= jobject          : 0x11

  20654 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->CallBooleanMethod
  50384 ms |- JNIEnv*          : 0xdd70f140
  50384 ms |- jobject          : 0xc55bc310
  50384 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->ExceptionCheck
  50399 ms |- JNIEnv*          : 0xdd70f140
  50399 ms |= jboolean         : 0    { false }

  50399 ms ------------------------Backtrace------------------------
  50399 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50414 ms [+] JNIEnv->NewStringUTF
  50414 ms |- JNIEnv*          : 0xdd70f140
  50414 ms |- char*            : 0xb8eb21e4
  50414 ms |:     appkey
  50414 ms |= jstring          : 0x5

  50414 ms ------------------------Backtrace------------------------
  50414 ms |-> 0xb8eb2019: libbili.so!0x3019 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50429 ms [+] JNIEnv->CallObjectMethod
  50429 ms |- JNIEnv*          : 0xdd70f140
  50429 ms |- jobject          : 0xc55bc310
  50429 ms |- jmethodID        : 0x6fd28a54    { get(Ljava/lang/Object;)Ljava/lang/Object; }
  50429 ms |: jobject          : 0x5
  50429 ms |= jobject          : 0x15    { java/lang/Object }

  50429 ms ------------------------Backtrace------------------------
  50429 ms |-> 0xb8eb54dd: libbili.so!0x64dd (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50444 ms [+] JNIEnv->ExceptionCheck
  50444 ms |- JNIEnv*          : 0xdd70f140
  50444 ms |= jboolean         : 0    { false }

  50444 ms ------------------------Backtrace------------------------
  50444 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50458 ms [+] JNIEnv->GetStringUTFChars
  50458 ms |- JNIEnv*          : 0xdd70f140
  50458 ms |- jstring          : 0x15
  50458 ms |- jboolean*        : 0x0
  50458 ms |= char*            : 0xb78ff748

  50458 ms ------------------------Backtrace------------------------
  50458 ms |-> 0xb8eb203d: libbili.so!0x303d (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50473 ms [+] JNIEnv->NewStringUTF
  50473 ms |- JNIEnv*          : 0xdd70f140
  50473 ms |- char*            : 0xb8eb24ac
  50473 ms |:     ts
  50473 ms |= jstring          : 0x25

  50473 ms ------------------------Backtrace------------------------
  50473 ms |-> 0xb8eb2439: libbili.so!0x3439 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50488 ms [+] JNIEnv->CallObjectMethod
  50488 ms |- JNIEnv*          : 0xdd70f140
  50488 ms |- jobject          : 0xc55bc310
  50488 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->ExceptionCheck
  50502 ms |- JNIEnv*          : 0xdd70f140
  50502 ms |= jboolean         : 0    { false }

  50502 ms ------------------------Backtrace------------------------
  50502 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50516 ms [+] JNIEnv->DeleteLocalRef
  50516 ms |- JNIEnv*          : 0xdd70f140
  50516 ms |- jobject          : 0x25

  50516 ms ------------------------Backtrace------------------------
  50516 ms |-> 0xb8eb2485: libbili.so!0x3485 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50530 ms [+] JNIEnv->DeleteLocalRef
  50530 ms |- JNIEnv*          : 0xdd70f140
  50530 ms |- jobject          : 0x31

  50530 ms ------------------------Backtrace------------------------
  50530 ms |-> 0xb8eb248d: libbili.so!0x348d (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50544 ms [+] JNIEnv->CallStaticObjectMethod
  50544 ms |- JNIEnv*          : 0xdd70f140
  50544 ms |- jclass           : 0x3146    { com/bilibili/nativelibrary/SignedQuery }
  50544 ms |- jmethodID        : 0xbd1a7958    { r(Ljava/util/Map;)Ljava/lang/String; }
  50544 ms |: jobject          : 0xc55bc310
  50544 ms |= jobject          : 0x29    { java/lang/Object }

  50544 ms ------------------------Backtrace------------------------
  50544 ms |-> 0xb8eb2077: libbili.so!0x3077 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50559 ms [+] JNIEnv->ExceptionCheck
  50559 ms |- JNIEnv*          : 0xdd70f140
  50559 ms |= jboolean         : 0    { false }

  50559 ms ------------------------Backtrace------------------------
  50559 ms |-> 0xb8eb3379: libbili.so!0x4379 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50573 ms [+] JNIEnv->GetStringUTFChars
  50573 ms |- JNIEnv*          : 0xdd70f140
  50573 ms |- jstring          : 0x29
  50573 ms |- jboolean*        : 0x0
  50573 ms |= char*            : 0x9fa9a300

  50573 ms ------------------------Backtrace------------------------
  50573 ms |-> 0xb8eb209b: libbili.so!0x309b (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50588 ms [+] JNIEnv->ReleaseStringUTFChars
  50588 ms |- JNIEnv*          : 0xdd70f140
  50588 ms |- jstring          : 0xb78ff748
  50588 ms |- char*            : 0xb78ff748
  50588 ms |:     1d8b6e7d45233436

  50588 ms ------------------------Backtrace------------------------
  50588 ms |-> 0xb8eb20b7: libbili.so!0x30b7 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50602 ms [+] JNIEnv->NewStringUTF
  50602 ms |- JNIEnv*          : 0xdd70f140
  50602 ms |- char*            : 0xc55bc240
  50602 ms |:     b3b287f86cc0a057658edbac904c6624
  50602 ms |= jstring          : 0x35

  50602 ms ------------------------Backtrace------------------------
  50602 ms |-> 0xb8eb21a5: libbili.so!0x31a5 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50616 ms [+] JNIEnv->DeleteLocalRef
  50616 ms |- JNIEnv*          : 0xdd70f140
  50616 ms |- jobject          : 0x5

  50616 ms ------------------------Backtrace------------------------
  50616 ms |-> 0xb8eb21b1: libbili.so!0x31b1 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50630 ms [+] JNIEnv->NewObject
  50630 ms |- JNIEnv*          : 0xdd70f140
  50630 ms |- jclass           : 0x3146    { com/bilibili/nativelibrary/SignedQuery }
  50630 ms |- jmethodID        : 0xbd1a78e8    { <init>(Ljava/lang/String;Ljava/lang/String;)V }
  50630 ms |: jstring          : 0x29
  50630 ms |: jstring          : 0x35
  50630 ms |= jobject          : 0x9

  50630 ms ------------------------Backtrace------------------------
  50630 ms |-> 0xb8eb21cb: libbili.so!0x31cb (libbili.so:0xb8eaf000)

如果JNI方法以及其具体功能你有些陌生,建议先花半天浏览和查看JNI函数的介绍以及NDK开发指南,并尝试自己写几个小DEMO。

我们以如下的GetStringUTFChars函数为例,解释一下如何看JNItrace的输出结果。

  74204 ms [+] JNIEnv->GetStringUTFChars
  74204 ms |- JNIEnv*          : 0xdd713560
  74204 ms |- jstring          : 0xb7360c20
  74204 ms |- jboolean*        : 0x0
  74204 ms |= char*            : 0xdd7058c0

  74204 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->CallBooleanMethod
  50384 ms |- JNIEnv*          : 0xdd70f140
  50384 ms |- jobject          : 0xc55bc310
  50384 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
voidCallVoidMethod( )(无)
引用类型CallObjectMethod( )jobect
booleanCallBooleanMethod( )jboolean
byteCallByteMethod( )jbyte
charCallCharMethod( )jchar
shortCallShortMethod( )jshort
intCallIntMethod( )jint
longCallLongMethod( )jlong
doubleCallDoubleMethod( )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->ExceptionCheck
  50399 ms |- JNIEnv*          : 0xdd70f140
  50399 ms |= jboolean         : 0    { false }

  50399 ms ------------------------Backtrace------------------------
  50399 ms |-> 0xb8eb339b: libbili.so!0x439b (libbili.so:0xb8eaf000)

异常处理和检查,不去管它。

           /* TID 27207 */
  50414 ms [+] JNIEnv->NewStringUTF
  50414 ms |- JNIEnv*          : 0xdd70f140
  50414 ms |- char*            : 0xb8eb21e4
  50414 ms |:     appkey
  50414 ms |= jstring          : 0x5

  50414 ms ------------------------Backtrace------------------------
  50414 ms |-> 0xb8eb2019: libbili.so!0x3019 (libbili.so:0xb8eaf000)

由c字符串得到对应的 java中的字符串对象,“appkey”,毫无疑问,这是因为它后续要使用java方法,或者就是要传回java层。

           /* TID 27207 */
  50429 ms [+] JNIEnv->CallObjectMethod
  50429 ms |- JNIEnv*          : 0xdd70f140
  50429 ms |- jobject          : 0xc55bc310
  50429 ms |- jmethodID        : 0x6fd28a54    { get(Ljava/lang/Object;)Ljava/lang/Object; }
  50429 ms |: jobject          : 0x5
  50429 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->GetStringUTFChars
  50458 ms |- JNIEnv*          : 0xdd70f140
  50458 ms |- jstring          : 0x15
  50458 ms |- jboolean*        : 0x0
  50458 ms |= char*            : 0xb78ff748

  50458 ms ------------------------Backtrace------------------------
  50458 ms |-> 0xb8eb203d: libbili.so!0x303d (libbili.so:0xb8eaf000)

这个jstring 0x15 即我们上面取到的appkey value,这儿把它转换成c字符串,对应于IDA中if逻辑的第一句。

           /* TID 27207 */
  50473 ms [+] JNIEnv->NewStringUTF
  50473 ms |- JNIEnv*          : 0xdd70f140
  50473 ms |- char*            : 0xb8eb24ac
  50473 ms |:     ts
  50473 ms |= jstring          : 0x25

  50473 ms ------------------------Backtrace------------------------
  50473 ms |-> 0xb8eb2439: libbili.so!0x3439 (libbili.so:0xb8eaf000)


           /* TID 27207 */
  50488 ms [+] JNIEnv->CallObjectMethod
  50488 ms |- JNIEnv*          : 0xdd70f140
  50488 ms |- jobject          : 0xc55bc310
  50488 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->DeleteLocalRef
  50516 ms |- JNIEnv*          : 0xdd70f140
  50516 ms |- jobject          : 0x25

  50516 ms ------------------------Backtrace------------------------
  50516 ms |-> 0xb8eb2485: libbili.so!0x3485 (libbili.so:0xb8eaf000)
  
        /* TID 27207 */
         50530 ms [+] JNIEnv->DeleteLocalRef
  50530 ms |- JNIEnv*          : 0xdd70f140
  50530 ms |- jobject          : 0x31

  50530 ms ------------------------Backtrace------------------------
  50530 ms |-> 0xb8eb248d: libbili.so!0x348d (libbili.so:0xb8eaf000)

45c8函数改个名看起来更清晰

在这里插入图片描述

       /* TID 27207 */
         50544 ms [+] JNIEnv->CallStaticObjectMethod
  50544 ms |- JNIEnv*          : 0xdd70f140
  50544 ms |- jclass           : 0x3146    { com/bilibili/nativelibrary/SignedQuery }
  50544 ms |- jmethodID        : 0xbd1a7958    { r(Ljava/util/Map;)Ljava/lang/String; }
  50544 ms |: jobject          : 0xc55bc310
  50544 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->GetStringUTFChars
  50573 ms |- JNIEnv*          : 0xdd70f140
  50573 ms |- jstring          : 0x29
  50573 ms |- jboolean*        : 0x0
  50573 ms |= char*            : 0x9fa9a300

  50573 ms ------------------------Backtrace------------------------
  50573 ms |-> 0xb8eb209b: libbili.so!0x309b (libbili.so:0xb8eaf000)

即使不看ida,也可以根据0xb78ff748往上检索,原来这就是我们的appkey value,现在它被用完了,所以释放内存。

       /* TID 27207 */
         50588 ms [+] JNIEnv->ReleaseStringUTFChars
  50588 ms |- JNIEnv*          : 0xdd70f140
  50588 ms |- jstring          : 0xb78ff748
  50588 ms |- char*            : 0xb78ff748
  50588 ms |:     1d8b6e7d45233436

  50588 ms ------------------------Backtrace------------------------
  50588 ms |-> 0xb8eb20b7: libbili.so!0x30b7 (libbili.so:0xb8eaf000)

对比主动调用的结果可以发现,这就是Sign

       /* TID 27207 */
        50602 ms [+] JNIEnv->NewStringUTF
  50602 ms |- JNIEnv*          : 0xdd70f140
  50602 ms |- char*            : 0xc55bc240
  50602 ms |:     b3b287f86cc0a057658edbac904c6624
  50602 ms |= jstring          : 0x35

  50602 ms ------------------------Backtrace------------------------
  50602 ms |-> 0xb8eb21a5: libbili.so!0x31a5 (libbili.so:0xb8eaf000)

标记一下它
在这里插入图片描述
后面的jni调用就是创建SignedQuery对象,返回给Java层。

       /* TID 27207 */
         50630 ms [+] JNIEnv->NewObject
  50630 ms |- JNIEnv*          : 0xdd70f140
  50630 ms |- jclass           : 0x3146    { com/bilibili/nativelibrary/SignedQuery }
  50630 ms |- jmethodID        : 0xbd1a78e8    { <init>(Ljava/lang/String;Ljava/lang/String;)V }
  50630 ms |: jstring          : 0x29
  50630 ms |: jstring          : 0x35
  50630 ms |= jobject          : 0x9

  50630 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,原因有两个

  1. 这个SO没经过ollvm混淆,逻辑比较清晰
  2. 我们要分析的代码块里函数较少,试错成本不高,而从前往后翻译是累人的。

代码块中"_“开头或者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                                               7

Length:0x4e1

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                          560c52cc

Length:0x8

contents:
           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                          d288fed0

Length:0x8

contents:
           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                          45859ed1

Length:0x8

contents:
           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

Length:0x8

contents:
           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:0x37

contents:
           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                          560c52cc

Length:0x8

contents:
           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                          d288fed0

Length:0x8

contents:
           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                          45859ed1

Length:0x8

contents:
           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星,适合新手学习,下期再见。之后会是几篇硬货,如果这篇文章给了您帮助,可以请我喝杯咖啡。

  • 31
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值