添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

大家好呀,欢迎来到我的博客!!! 本期我将带来b站so层sign算法实现

设备: pixel4 android10

下载地址: aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzI4MTI5MS9oaXN0b3J5X3Y2MTgwNTAw

版本: 6.18.0

工具: charles(抓包) socksdroid(流量转发) jadx(反编译dex) ida(反编译so)

我看了下b站这算法并没有更新,但是so层做了混淆,新版的不太好分析,所以干脆分析老版本的,最新版的so层混淆如下图

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!

定位sign位置

用32位的ida把它转为汇编


本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!

1 抓包没什么说的,往上滑或往下滑都能刷新抓到包

2 翻页后文本对比下发现只有两个时间戳是不同的然后加密得到不同的sign,其他参数可以暂时固定

定位sign位置

3 首先把apk拖到jadx反编译一下,这个时候大部分都会去搜字符串,但是由于很多地方都使用了sign关键字,直接搜的话太麻烦了,所以也可以搜查询参数中比较特殊的字符串,比如ad_extra,banner_hash,statistics,他们最终肯定会参与params的组装,然后可以顺着找到sign  我这里采用的是hook hashmap的put方法,这样比搜索快一些,代码如下

 Java.perform(function (){
        function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
var hashMap = Java.use("java.util.HashMap");
    hashMap.put.implementation = function (a, b) {
        if(a.equals("ad_extra")){
            showStacks();
            console.log("hashMap.put: ", a, b);
        return this.put(a, b);

4 只有一处,顺着堆栈找可以找到com.bilibili.okretro.f.a.d 

5 如下图

6 点击h这个方法里

7 再点进g这个方法 ,g加载s这个native方法

8 s来源于libbili.so这个文件

9 找到对应的so文件,只有32位的arm架构,下面那个是模拟器的,所以选择第一个里面的libbili.so

用32位的ida把它转为汇编

10 在导出表里面没有发现java的字眼,所以是动态注册,同时可以看到标志JNI_OnLoad,代表动态注册

11 点进去JNI_OnLoad 发现里面嵌套了很多函数,找到RegisterNatives动态注册很麻烦,这里可以用hook脚本,输出当前类下的所有native方法对应c中方法的偏移量,然后再找到c中的函数代码

// 获取 RegisterNatives 函数的内存地址,并赋值给addrRegisterNatives。
var addrRegisterNatives = null;
// 列举 libart.so 中的所有导出函数(成员列表)
var symbols = Module.enumerateSymbolsSync("libart.so");
for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols[i];
    console.log(symbol.name)
    //_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);
        //break
if (addrRegisterNatives) {
    // RegisterNatives(env, 类型, Java和C的对应关系,个数)
    Interceptor.attach(addrRegisterNatives, {
        onEnter: function (args) {
            var env = args[0];        // jni对象
            var java_class = args[1]; // 类
            var class_name = Java.vm.tryGetEnv().getClassName(java_class);
            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++) {
                    // Java中函数名字的
                    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));
                    // C中的函数内存地址
                    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);
                    // 地址、偏移量、基地址
                    var offset = ptr(fnPtr_ptr).sub(find_module.base);
                    //console.log("name:", name, "name:", sig, "module_name:", find_module.name, "offset:", offset);
                    console.log("name:", name, "name:", sig, "offset:", offset);

 12 这里因为app一开始加载的时候已经加载了所有的so文件,所以需要以重新启动app的方式来hook,而不能通过附加形式 hook,如下图

frida -U -f tv.danmaku.bili -l hook_code.js

13 这里在ida里按G就能弹出运行框,输入偏移量就可以调到指定函数位置

14 转化JNIEnv对象后如下图

15 往下滑看返回值 这里其实是进行了一堆md5加密,参数1是url路径后面的参数(除了sign),参数2就是加密的sign

16 通过java层hook s方法即可看出 

 Java.perform(function (){
     let LibBili = Java.use("com.bilibili.nativelibrary.LibBili");
LibBili["s"].implementation = function (sortedMap) {
    console.log(`LibBili.s is called: sortedMap=${sortedMap}`);
    var map = Java.use("java.util.TreeMap")
    var dict = Java.cast(sortedMap,map)
    console.log('传入的对象====>',dict)
    let result = this["s"](sortedMap);
    console.log(`LibBili.s result=${result}`);
    return result;

17 最终sign python算法,z最终生成的与params中的一样,验证算法是正确的

import hashlib
from urllib.parse import quote, unquote
params = {
    "ad_extra": "E86F4CFF1F8FA890A75155EEAA51E6AE4FA9DBE62FCE708186D0CE5EF37B86948620D8BA1D991685B1288E2EDE09C6D52F8C2D33D59872EAE1EB776D11F71523CE1AF2112D8A950B98F6A1A48F848BC6871A849C3ED14308F46431A85625726A929A8906FA0C16FEE2CEB33209AE6F1E0C6856961045F53A0FE3470E4E223F48DAE7923040EAC4541BE6F728DEA350329AC40887CB773083BB4D6D91DCCCDF8D16C5672A5E344293F5EFD2F3654B88602781A8869076E96FF8359FC76D3CD5851A733D0CF38E11DC869D660D1624928815C2A13497B215CCEA52053B302039B9B93DFABDD6A71A16AC8898285A37C7DEB5AB5ADD788C2456B5D9B2F8FDB1ACD334E8127D56B144B523155DE8AB49A1D1173CB590E379CCF33EFAE8C388100D5CEA7AD220E2AAA2256FF16D4BE28C8AA3D7BAE19B1FE6AA860276BB86B27ACCA34B8E081D67E8C699CF4ED4D7A45E8556B05584B35B1E11E80B9B41DC51C47B260C602E07B1936C73DDB8D7FFBBD148894822C5F7C9A688C5A25DB2CA92D77CA7C35E3AFD807D0AE95967943A42B30D0F0EF8EAD9D2E74A20BB4EA72014B5A3BFD53B2ECFB15B47455D97A4FBFDDFB4A3E30853E0B9CBF16AC70F25CB6B939540328256BF42AB6DF9D3DD4649E3F0B340376B162F859D5EE92D99A778FB313E28BCAD195FB59A30EC374436735A9732BF013A78FE3F606425B48A74137C267DAB91A00962C5FFECB7E798AC130FCF7F9428A05082C6D717D13F129F809818E05DB11EA3DE2EA80728D30DB7EECAE1231085C4E3B47B98506F261D89D15997AC09FB46DBA3444A438F43A59D232385F3C5548DFF3F51733A80A80880E7945035A18DCDDCDEB85A2DCCF755CD1AEBCA759CB2BE4AF6D5AB3A9FA8F7429DD37B740E33D80E1F11B8BD4DD312DEAECEEBAB7DC6FF57EFC5A81D3D7D02E798AA5CDCD387EAD885EE8D89368FA301463658FA52",
    "appkey": "1d8b6e7d45233436",
    "autoplay_card": "11",
    "autoplay_timestamp": "0",
    "build": "7500300",
    "c_locale": "zh-Hans_CN",
    "channel": "alifenfa",
    "column": "2",
    "column_timestamp": "0",
    "device_name": "Pixel 4",
    "device_type": "0",
    "disable_rcmd": "0",
    "flush": "8",
    "fnval": "464",
    "fnver": "0",
    "force_host": "0",
    "fourk": "1",
    "guidance": "0",
    "https_url_req": "0",
    "idx": "1698066410",
    "inline_danmu": "2",
    "inline_sound": "1",
    "interest_id": "0",
    "login_event": "0",
    "mobi_app": "android",
    "network": "wifi",
    "open_event": "",
    "platform": "android",
    "player_net": "1",
    "pull": "false",
    "qn": "32",
    "recsys_mode": "0",
    "s_locale": "zh-Hans_CN",
    "splash_id": "",
    "statistics": "{\"appId\":1,\"platform\":3,\"version\":\"7.50.0\",\"abtest\":\"\"}",
    "ts": "1698066416",
    "video_mode": "1",
    "voice_balance": "0",
    # "sign": "002c2395f37e8800095c41e08b652517"
sorted_params = sorted(params.items(), key=lambda x: x[0])
sorted_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
original_string = sorted_str
# 分割字符串,然后仅对值进行编码
parts = original_string.split("&")
encoded_parts = []
for part in parts:
    key, value = part.split("=")
    encoded_value = quote(value)
    encoded_parts.append(f"{key}={encoded_value}")
# 重新组合编码后的部分
encoded_string = "&".join(encoded_parts)
# 盐值 560c52ccd288fed045859ed18bffd973
str = encoded_string+'560c52ccd288fed045859ed18bffd973'
sign = hashlib.md5(str.encode('utf-8')).hexdigest()
print(sign)

1出于安全考虑,本章未提供完整流程,调试环节省略较多,只提供大致思路,具体细节要你自己还原,相信你也能调试出来.

2本人写作水平有限,如有讲解不到位或者讲解错误的地方,还请各位大佬在评论区多多指教,共同进步,也可加本人微信lyaoyao__i(两个_)

B站java定位并不难找,直接搜索大法即可定位到生成sign的native函数。 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客: 全新的界面设计 ,将会带来全新的写作体验; 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示; 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编 # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.# yarn lockfile v1"@babel/code-frame@^7.0.0":version "7.0.0"resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.... SO逆向入门实战教程六:s_白龙~的博客-CSDN博客还是那句,我会借鉴龙哥的文章,以一个初学者的角度,加上自己的理解,把内容丰富一下,尽量做到不在龙哥的基础上画蛇添足,哈哈。1.treemap需要注意下,在unidbg里需要继承map,abstractmap2.varArg.getObjectArg(0)可以获取该方法的参数,0,指第一个,1指第二个。 Native逆向指北之BiliBili Sign一、前言二、背景介绍三、分析3.1 定位native函数位置3.2 JNItrace辅助分析3.3 IDA分析四、总结 哈哈,用这个显眼的好位置说一下闲话 1.有没有杭州的大佬招小弟,Android逆向方面的,只会Java和不难的Natvie,抱抱我。 2.为什么这么久不写文章:去年忙考研,失败了,打算重拾逆向技能,冲冲冲。 3.为什么叫“逆向指北”,因为我的水平很一般,远远称不上能给大家分享一些真正的知识的地步,可能不仅没法指南还会南辕北辙,所 一、目标我们来分析某站 App的sign签名算法,先搜索一下 游戏 ,抓包结果:1:charlesb二、步骤这个sign依然是32位的字符串都9020年了,这种规模用户的App应该是不会裸奔在java了,我们就直接一点,在so里面搜索sign=可惜没有结果……藏起来的东西一定是重要的东西so导出函数给java调用,有两种方法,一种是静态注册,直接会体现在so的导出表里。一种是Re... data_781493040D68C2C5E8335D457F93416ADFD07D74EA6ACC04E3DF17D87EAC8F5D 6291D9764B7C323D2215919BEA3ECB3ABB6F1C71FF2311EB32B995AD82E4B89F FCC4F93C5C4EF638C65ED7FC21DC1AFB77E20B78098343A6259A4895B1C0741 # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.# yarn lockfile v1abbrev@1:version "1.1.0"resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f8... g_func_[size]125222[/size] data_78149D8DA6602124AD6E3DA7BE045809F937F79DCF95F33649FFB8FE37BC5EFC 45C4AA991AB785914A5EC67D9F0FDE495DF2A47FE5499BA63CCF7B23655C3660 7807E3FEBB3BBBBFFBC9B2535EE5BE44098 # iOS 6680 # app_key = '27eb53fc9058f8c3' # app_secret = 'c2ed53a74eeefe3cf99fbd01d8c9c375' # 云视听 TV # app_key = '4409e2ce8ffd12b8' 前言该项目仿照B站的Android客户端进行开发,初衷是想学习流行的Android技术,但是没有数据资源。因为喜欢逛B站,而且B站的Android客户端又是Google推荐的MD设计规范,于是花了些时间研究B站的网络请求,最后成功搞定,与@Android_ZzT同学达成一致,合作开发这个模仿B站的练习项目。该项目在工作之余开发,所以更新时间不定。前期准备一、B站网络请求抓包首先是对B站的网络请求进...