主页 抓包 版本6.18.0
Charles本地证书
系统证书目录:/system/etc/security/cacerts/
其中的每个证书的命名规则如下:. 文件名是一个Hash值,而后缀是一个数字。
文件名可以用下面的命令计算出来: openssl x509 -subject_hash_old -in
后缀名的数字是为了防止文件名冲突的,比如如果两个证书算出的Hash值是一样的话,那么一个证书的后缀名数字可以设置成0,而另一个证书的后缀名数字可以设置成1
安卓8
1 2 3 4 5 6 7 cd /data/misc/user/0/cacerts-added/ mount -o remount,rw / mount -o remount,rw /system chmod 777 * cp * /etc/security/cacerts/ mount -o remount,ro / mount -o remount,ro /system
安卓7
1 2 3 4 5 6 7 cd /data/misc/user/0/cacerts-added/ mount -o rw,remount /system mount -o rw,remount / chmod 777 * cp * /etc/security/cacerts/ mount -o ro,remount /system mount -o ro,remount /
安卓10
Move_Certificates-v1.9 直接面具插件安排,重启即可
重启
appkey 1d8b6e7d45233436 build 6180500 c_locale zh-Hans_CN channel shenma069 duration 0 fnval 400 fnver 0 force_host 0 fourk 1 from_source app_search highlight 1 is_org_query 0 keyword 666 local_time 8 mobi_app android platform android player_net 1 pn 1 ps 20 qn 32 recommend 1 s_locale zh-Hans_CN statistics {“appId”:1,”platform”:3,”version”:”6.18.0”,”abtest”:””} ts 1641789657 sign 057565a1251d32fec516abcf65b75123
分析 定位 1 frida -UF -l okhttp_poker.js 抓包
本app被混淆,尝试trace选中的类
1 frida -UF -l r0tracer.js -o bilibili.txt
查看方法 com.bilibili.okretro.f.a.a
查看 d(a0Var.k(), h);
查看 b(hashMap);
, 该方法中只是把部分参数放到map里,不包括sign
查看 SignedQuery h = h(hashMap);
看着像是生成sign的地方
1 2 3 public SignedQuery h(Map<String, String> map) { return LibBili.g(map); }
查看com.bilibili.nativelibrary.LibBili
,目测在libbili.so中
1 2 3 4 5 6 7 8 9 public static final String G = "bili"; static { c.c(PlayIndex.G); } public static SignedQuery g(Map<String, String> map) { return s(map == null ? new TreeMap() : new TreeMap(map)); } static native SignedQuery s(SortedMap<String, String> sortedMap);
定位com.bilibili.nativelibrary.LibBili.s
,直接hook之
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 biliNative.s.implementation = function (map) { var result = this.s(map); var keyStr = "" var keys = map.keySet(); var key_set = keys.iterator(); while (key_set.hasNext()) { var key = key_set.next().toString(); keyStr += "," + key } console.log("keyStr==="+keyStr) console.log(map.values().toArray()); console.log("biliNative s(" + map + ") = " + result); return result; }
frida-trace -UF -i “Java_com*” 判断是静态注册
frida -U -f tv.danmaku.bili -l hook_RegisterNatives.js –no-pause -o bilibili.txt 判断动态注册,搜索com.bilibili.nativelibrary.LibBili
如果太多可以尝试修改hook_RegisterNatives.js
Frida 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var threadef = Java.use('java.lang.Thread'); var threadinstance = threadef.$new(); Java.openClassFile("/data/local/tmp/r0gson.dex").load(); var Gson = Java.use('com.r0ysue.gson.Gson'); var LibBili = Java.use('com.bilibili.nativelibrary.LibBili'); LibBili.g.implementation = function (a) { console.log("SignedQuery param:" + Gson.$new().toJson(a)); var stack = threadinstance.currentThread().getStackTrace(); console.log("Full call stack:" + Where(stack)); var result = this.g(a); console.log("SignedQuery result:" + Gson.$new().toJson(a)); return result; }
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { "appkey": "1d8b6e7d45233436", "build": "6180500", "c_locale": "zh-Hans_CN", "channel": "shenma069", "duration": "0", "fnval": "400", "fnver": "0", "force_host": "0", "fourk": "1", "from_source": "app_search", "highlight": "1", "is_org_query": "0", "keyword": "666", "local_time": "8", "mobi_app": "android", "platform": "android", "player_net": "1", "pn": "1", "ps": "20", "qn": "32", "recommend": "1", "s_locale": "zh-Hans_CN", "statistics": "{\"appId\":1,\"platform\":3,\"version\":\"6.18.0\",\"abtest\":\"\"}" }
返回结果:
1 2 3 4 { "a": "appkey=1d8b6e7d45233436&build=6180500&c_locale=zh-Hans_CN&channel=shenma069&duration=0&fnval=400&fnver=0&force_host=0&fourk=1&from_source=app_search&highlight=1&is_org_query=0&keyword=666&local_time=8&mobi_app=android&platform=android&player_net=1&pn=1&ps=20&qn=32&recommend=1&s_locale=zh-Hans_CN&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=1641817162", "b": "2fafacb931ae2fa3c68818646bc41c47" }
在 SignedQuery h = h(hashMap);
之后,q.h(h.toString());
sign就是返回结果中的b 值
unidbg 主动调用s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void s(){ List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); // 第一个参数是env list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到 TreeMap<String, String> keymap = new TreeMap<String, String>(); keymap.put("appkey", "1d8b6e7d45233436"); ... // 封装字节数组 // byte[] inputByte = "666".getBytes(StandardCharsets.UTF_8); // ByteArray inputByteArray = new ByteArray(vm,inputByte); // 封装map→AbstractMap→TreeMap DvmClass Map = vm.resolveClass("java/util/Map"); DvmClass AbstractMap = vm.resolveClass("java/util/AbstractMap",Map); DvmObject<?> input_map = vm.resolveClass("java/util/TreeMap", AbstractMap).newObject(keymap); list.add(vm.addLocalObject(input_map)); // RegisterNative(com/bilibili/nativelibrary/LibBili, s(Ljava/util/SortedMap;)Lcom/bilibili/nativelibrary/SignedQuery;, RX@0x40001c97[libbili.so]0x1c97) Number number = module.callFunction(emulator, 0x1c97, list.toArray())[0]; DvmObject result = vm.getObject(number.intValue()); }
报错:java/util/Map->isEmpty()Z
1 2 3 4 5 6 7 8 @Override public boolean callBooleanMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { if ("java/util/Map->isEmpty()Z".equals(signature)) { TreeMap<String, String> treeMap = (TreeMap<String, String>)dvmObject.getValue(); return treeMap.isEmpty(); } return super.callBooleanMethod(vm, dvmObject, signature, varArg); }
报错:java/util/Map->get(Ljava/lang/Object;)Ljava/lang/Object;
1 2 3 4 5 6 7 8 9 10 11 12 @Override public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { switch (signature) { case "java/util/Map->get(Ljava/lang/Object;)Ljava/lang/Object;": StringObject keyobject = varArg.getObjectArg(0); String key = keyobject.getValue(); TreeMap<String, String> treeMap = (TreeMap<String, String>) dvmObject.getValue(); String value = treeMap.get(key); return new StringObject(vm, value); } return super.callObjectMethod(vm, dvmObject, signature, varArg); }
报错:com/bilibili/nativelibrary/SignedQuery->r(Ljava/util/Map;)Ljava/lang/String;
查看SignedQuery的r方法
还原如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 public class utils { private static final char[] f14934c = "0123456789ABCDEF".toCharArray(); public static final String KEY_VALUE_DELIMITER = "="; public static final String FIELD_DELIMITER = "&"; private static boolean a(char c2, String str) { return (c2 >= 'A' && c2 <= 'Z') || (c2 >= 'a' && c2 <= 'z') || !((c2 < '0' || c2 > '9') && "-_.~".indexOf(c2) == -1 && (str == null || str.indexOf(c2) == -1)); } static String r(Map<String, String> map) { String str; if (!(map instanceof SortedMap)) { map = new TreeMap(map); } StringBuilder sb = new StringBuilder(256); for (Map.Entry<String, String> entry : map.entrySet()) { String key = entry.getKey(); if (!key.isEmpty()) { sb.append(b(key)); sb.append(KEY_VALUE_DELIMITER); String value = entry.getValue(); if (value == null) { str = ""; } else { str = b(value); } sb.append(str); sb.append(FIELD_DELIMITER); } } int length = sb.length(); if (length > 0) { sb.deleteCharAt(length - 1); } if (length == 0) { return null; } return sb.toString(); } static String b(String str) { return c(str, null); } static String c(String str, String str2) { StringBuilder sb = null; if (str == null) { return null; } int length = str.length(); int i2 = 0; while (i2 < length) { int i3 = i2; while (i3 < length && a(str.charAt(i3), str2)) { i3++; } if (i3 != length) { if (sb == null) { sb = new StringBuilder(); } if (i3 > i2) { sb.append((CharSequence) str, i2, i3); } i2 = i3 + 1; while (i2 < length && !a(str.charAt(i2), str2)) { i2++; } byte[] bytes = str.substring(i3, i2).getBytes(StandardCharsets.UTF_8); int length2 = bytes.length; for (int i4 = 0; i4 < length2; i4++) { sb.append('%'); sb.append(f14934c[(bytes[i4] & 240) >> 4]); sb.append(f14934c[bytes[i4] & 15]); } } else if (i2 == 0) { return str; } else { sb.append((CharSequence) str, i2, length); return sb.toString(); } } return sb == null ? str : sb.toString(); } }
补环境
1 2 3 4 5 6 7 8 9 10 11 12 @Override public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature){ case "com/bilibili/nativelibrary/SignedQuery->r(Ljava/util/Map;)Ljava/lang/String;":{ DvmObject<?> mapObject = varArg.getObjectArg(0); TreeMap<String, String> mymap = (TreeMap<String, String>) mapObject.getValue(); String result = utils.r(mymap); return new StringObject(vm, result); } } return super.callStaticObjectMethod(vm, dvmClass, signature, varArg); }
报错:com/bilibili/nativelibrary/SignedQuery-><init>(Ljava/lang/String;Ljava/lang/String;)V
需要手动构建SignedQuery该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature) { case "com/bilibili/nativelibrary/SignedQuery-><init>(Ljava/lang/String;Ljava/lang/String;)V": StringObject stringObject1 = varArg.getObjectArg(0); StringObject stringObject2 = varArg.getObjectArg(1); String str1 = stringObject1.getValue(); String str2 = stringObject2.getValue(); return vm.resolveClass("com/bilibili/nativelibrary/SignedQuery").newObject(new SignedQuery(str1, str2)); } return super.newObject(vm, dvmClass, signature, varArg); } public class SignedQuery { public final String a; public final String b; public SignedQuery(String str, String str2) { this.a = str; this.b = str2; } }
init中有两个参数,参数2就是sign。
Unidbg提供了另外一种模拟Native调用JAVA的方式——缺啥补啥,其原理是JAVA的反射
LibBili
不继承自AbstractJni
vm.setJni(this);
改成vm.setDvmClassFactory(new ProxyClassFactory());
报错:com.bilibili.nativelibrary.SignedQuery
从jadx中将类拷贝出来,补充环境SignedQuery.java
算法还原 JNITrace **static native SignedQuery s(SortedMap<String, String> sortedMap);**,入参是map,返回是SignedQuery对象
根据上文动态注册的函数地址com.bilibili.nativelibrary.LibBili.s
的偏移量为0x1c97
,IDA打开G跳转到该方法
双击进入sub_2F88
修改sub_2F88
函数,int __fastcall sub_2F88(JNIEnv *env, int *map, int a3, int a4)
1 2 pip install jnitrace jnitrace -l libbili.so tv.danmaku.bili --ignore-vm
Get开头的JNI方法用于从Java的类型中取数据,GetStringUTFChars取出Java字符串中内容,jstring指针转化成一个UTF-8格式的C字符串,返回native中的字符串,用完后还必须要调用对应的ReleaseStringUTFChars释放资源,否则会导致JVM内存泄露。
1 const jchar * GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy);
libbili.so!0x309b 表示位于什么模块!偏移地址多少
1 2 3 4 5 6 7 8 function call_LibBili_s() { var keymap = Java.use("java.util.TreeMap").$new(); ... var result = Java.use("com.bilibili.nativelibrary.LibBili").s(keymap); // 打印结果,不需要做什么额外处理,这儿会隐式调用toString。 console.log("\n返回结果:", result); return result; }
启动jnitrace后,frida -UF tv.danmaku.bili -l bilibili.js
主动调用call_LibBili_s
,查看jnitrace的log,可以根据左侧的时间判断执行的顺序。
1 2 3 4 5 6 7 8 9 /* TID 12745 */ 116155 ms [+] JNIEnv->CallBooleanMethod 116155 ms |- JNIEnv* : 0xeb8c1c40 116155 ms |- jobject : 0xc9120210 116155 ms |- jmethodID : 0x6f69db60 { isEmpty()Z } 116155 ms |= jboolean : 0 { false } 116155 ms ------------------------Backtrace------------------------ 116155 ms |-> 0xbf86d697: libbili.so!0x6697 (libbili.so:0xbf867000)
第一个JNI调用是CallBooleanMethod,调用java中的方法返回布尔型在native中转换为jboolean,如上图显示为false。IDA G跳转到0x6697,F5进入伪代码,Y修改类型为JNIEnv*
X查看sub_6680
交叉的引用
一共有两处,其中有一处就是上文中的sub_2F88
函数,由于jnitrace时返回false,直接走下面框选的逻辑代码。
继续跟进jnitrace,ExceptionCheck异常处理和检查,NewStringUTF获取到appkey变量名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* TID 12745 */ 116167 ms [+] JNIEnv->ExceptionCheck 116167 ms |- JNIEnv* : 0xeb8c1c40 116167 ms |= jboolean : 0 { false } 116167 ms ------------------------Backtrace------------------------ 116167 ms |-> 0xbf86b39b: libbili.so!0x439b (libbili.so:0xbf867000) /* TID 12745 */ 116179 ms [+] JNIEnv->NewStringUTF 116179 ms |- JNIEnv* : 0xeb8c1c40 116179 ms |- char* : 0xbf86a1e4 116179 ms |: appkey 116179 ms |= jstring : 0x9 { appkey } 116179 ms ------------------------Backtrace------------------------ 116179 ms |-> 0xbf86a019: libbili.so!0x3019 (libbili.so:0xbf867000)
jobject的地址0xc9120210,即上文中的map参数,第四个参数0x9就是上文中的appkey,翻译一下就是map.get(“appkey”)
1 2 3 4 5 6 7 8 9 10 /* TID 12745 */ 116191 ms [+] JNIEnv->CallObjectMethod 116191 ms |- JNIEnv* : 0xeb8c1c40 116191 ms |- jobject : 0xc9120210 116191 ms |- jmethodID : 0x6f69db0c { get(Ljava/lang/Object;)Ljava/lang/Object; } 116191 ms |: jobject : 0x9 116191 ms |= jobject : 0x11 { java/lang/Object } 116191 ms ------------------------Backtrace------------------------ 116191 ms |-> 0xbf86d4dd: libbili.so!0x64dd (libbili.so:0xbf867000)
继续分析jnitrace,0x11
就是上文中appkey对应的值,就是if(v13)
, 而0x29
就是”ts”字符串,图中的框选就是map.get("ts")
,并返回0x35
,即ts的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /* TID 12745 */ 116215 ms [+] JNIEnv->GetStringUTFChars 116215 ms |- JNIEnv* : 0xeb8c1c40 116215 ms |- jstring : 0x11 { 1d8b6e7d45233436 } 116215 ms |- jboolean* : 0x0 116215 ms |= char* : 0xdc9954a0 116215 ms ------------------------Backtrace------------------------ 116215 ms |-> 0xbf86a03d: libbili.so!0x303d (libbili.so:0xbf867000) /* TID 12745 */ 116226 ms [+] JNIEnv->NewStringUTF 116226 ms |- JNIEnv* : 0xeb8c1c40 116226 ms |- char* : 0xbf86a4ac 116226 ms |: ts 116226 ms |= jstring : 0x29 { ts } 116226 ms ------------------------Backtrace------------------------ 116226 ms |-> 0xbf86a439: libbili.so!0x3439 (libbili.so:0xbf867000) /* TID 12745 */ 116240 ms [+] JNIEnv->CallObjectMethod 116240 ms |- JNIEnv* : 0xeb8c1c40 116240 ms |- jobject : 0xc9120210 116240 ms |- jmethodID : 0x6f69db0c { get(Ljava/lang/Object;)Ljava/lang/Object; } 116240 ms |: jobject : 0x29 { java/lang/Object } 116240 ms |= jobject : 0x35 { java/lang/Object } 116240 ms ------------------------Backtrace------------------------ 116240 ms |-> 0xbf86d4dd: libbili.so!0x64dd (libbili.so:0xbf867000)
进入sub_3414
操作完后通过DeleteLocalRef
释放ts和ts的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* TID 12745 */ 116264 ms [+] JNIEnv->DeleteLocalRef 116264 ms |- JNIEnv* : 0xeb8c1c40 116264 ms |- jobject : 0x29 116264 ms ------------------------Backtrace------------------------ 116264 ms |-> 0xbf86a485: libbili.so!0x3485 (libbili.so:0xbf867000) /* TID 12745 */ 116276 ms [+] JNIEnv->DeleteLocalRef 116276 ms |- JNIEnv* : 0xeb8c1c40 116276 ms |- jobject : 0x35 116276 ms ------------------------Backtrace------------------------ 116276 ms |-> 0xbf86a48d: libbili.so!0x348d (libbili.so:0xbf867000)
跟进jnitrace,调用java函数中的SignedQuery
的r方法,主要作用将map转为&连接的string
1 2 3 4 5 6 7 8 9 10 /* TID 12745 */ 116298 ms [+] JNIEnv->CallStaticObjectMethod 116298 ms |- JNIEnv* : 0xeb8c1c40 116298 ms |- jclass : 0x2ac6 { com/bilibili/nativelibrary/SignedQuery } 116298 ms |- jmethodID : 0xc1509ce4 { r(Ljava/util/Map;)Ljava/lang/String; } 116298 ms |: jobject : 0xc9120210 116298 ms |= jobject : 0x25 { java/lang/Object } 116298 ms ------------------------Backtrace------------------------ 116298 ms |-> 0xbf86a077: libbili.so!0x3077 (libbili.so:0xbf867000)
将java中的返回jstring转为native中的c字符串
1 2 3 4 5 6 7 8 9 /* TID 12745 */ 116324 ms [+] JNIEnv->GetStringUTFChars 116324 ms |- JNIEnv* : 0xeb8c1c40 116324 ms |- jstring : 0x25 116324 ms |- jboolean* : 0x0 116324 ms |= char* : 0xbf010240 116324 ms ------------------------Backtrace------------------------ 116324 ms |-> 0xbf86a09b: libbili.so!0x309b (libbili.so:0xbf867000)
搜索jnitrace返回的log中0xdc9954a0
是appkey的value,现在释放内存
1 2 3 4 5 6 7 8 9 /* TID 12745 */ 116337 ms [+] JNIEnv->ReleaseStringUTFChars 116337 ms |- JNIEnv* : 0xeb8c1c40 116337 ms |- jstring : 0xdc9954a0 { 1d8b6e7d45233436 } 116337 ms |- char* : 0xdc9954a0 116337 ms |: 1d8b6e7d45233436 116337 ms ------------------------Backtrace------------------------ 116337 ms |-> 0xbf86a0b7: libbili.so!0x30b7 (libbili.so:0xbf867000)
继续跟进jnitrace返回的595697e093d51d09be188caa5e393015
就是主动调用时返回的sign
1 2 3 4 5 6 7 8 9 /* TID 12745 */ 116351 ms [+] JNIEnv->NewStringUTF 116351 ms |- JNIEnv* : 0xeb8c1c40 116351 ms |- char* : 0xc9120140 116351 ms |: 595697e093d51d09be188caa5e393015 116351 ms |= jstring : 0x39 { 595697e093d51d09be188caa5e393015 } 116351 ms ------------------------Backtrace------------------------ 116351 ms |-> 0xbf86a1a5: libbili.so!0x31a5 (libbili.so:0xbf867000)
IDA跳转到0x31a5
,进入伪代码
后续只要调用创建SignedQuery,在init的时候的0x39返回sign,而0x25就是上面map拼接返回的字符串
1 2 3 4 5 6 7 8 9 10 11 /* TID 12745 */ 116376 ms [+] JNIEnv->NewObject 116376 ms |- JNIEnv* : 0xeb8c1c40 116376 ms |- jclass : 0x2ac6 { com/bilibili/nativelibrary/SignedQuery } 116376 ms |- jmethodID : 0xc1509c74 { <init>(Ljava/lang/String;Ljava/lang/String;)V } 116376 ms |: jstring : 0x25 116376 ms |: jstring : 0x39 { 595697e093d51d09be188caa5e393015 } 116376 ms |= jobject : 0x1 116376 ms ------------------------Backtrace------------------------ 116376 ms |-> 0xbf86a1cb: libbili.so!0x31cb (libbili.so:0xbf867000)
函数分析 sub_34B8 sub_34B8
函数的参数就是传入的appkey的值和IDA中定义的appkey的值对比
sub_3414 sub_3414
是操作ts的值并释放ts
sub_227C 通过h转为十六进制数,这四个是MD5状态变量,该方法就是MD5Init,初始化核心变量,装入标准的幻数。
常见的md5的c算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 void MD5Init (MD5Context *context) { context->count[0] = context->count[1] = 0; context->state[0] = 0x67452301; context->state[1] = 0xefcdab89; context->state[2] = 0x98badcfe; context->state[3] = 0x10325476; } void MD5Update (MD5Context *context, unsigned char *input, unsigned int inputLen) { unsigned int i, index, partLen; // Compute number of bytes mod 64 index = (unsigned int)((context->count[0] >> 3) & 0x3F); // Update number of bits if ((context->count[0] += ((u32)inputLen << 3)) < ((u32)inputLen << 3)) context->count[1]++; context->count[1] += ((u32)inputLen >> 29); partLen = 64 - index; //Transform as many times as possible. if (inputLen >= partLen) { memcpy((unsigned char *)&context->buffer[index], (unsigned char *)input, partLen); MD5Transform (context->state, context->buffer); for (i = partLen; i + 63 < inputLen; i += 64) { MD5Transform (context->state, &input[i]); } index = 0; } else i = 0; // Buffer remaining input memcpy((u8 *)&context->buffer[index], (u8 *)&input[i], inputLen-i); } void MD5Final (u8 digest[16], MD5Context *context) { unsigned char bits[8]; unsigned int index, padLen; // Save number of bits memcpy(bits, context->count, 8); // Pad out to 56 mod 64. index = (unsigned int)((context->count[0] >> 3) & 0x3f); padLen = (index < 56) ? (56 - index) : (120 - index); MD5Update (context, PADDING, padLen); // Append length (before padding MD5Update (context, bits, 8); //Store state in digest memcpy(digest, context->state, 16); } int MD5MessageDigest(u8 *digest, u8 *buf, int len) { MD5Context context; MD5Init (&context); MD5Update (&context, buf, len); MD5Final (digest, &context); return 0; }
sub_22B0 sub_22B0方法就是MD5Update,是MD5主计算过程,v36是context指针,v32是要变换的字节串,v27是长度。
1 sub_22B0(v36, v32, v27);
sub_2AE0 sub_2AE0就是MD5Final,是整理和填写输出结果,v36是context指针,v37就是加密后的字符串
尝试hook MD5Update
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function hook_22B0() { 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) { } }) } }
打印结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 contents: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF bd419000 61 64 5f 65 78 74 72 61 3d 33 36 38 31 45 38 43 ad_extra=3681E8C bd419010 33 41 43 35 33 37 45 42 30 39 33 39 46 34 35 31 3AC537EB0939F451 bd419020 30 37 38 37 37 43 36 33 44 35 31 45 37 38 45 36 07877C63D51E78E6 bd419030 43 39 39 34 41 39 35 30 45 31 34 41 44 43 41 39 C994A950E14ADCA9 bd419040 41 39 46 43 30 35 33 36 43 33 38 44 31 36 38 35 A9FC0536C38D1685 bd419050 31 43 31 35 31 33 31 32 37 44 30 38 43 30 37 34 1C1513127D08C074 bd419060 44 41 43 35 31 31 33 42 36 31 31 39 39 39 31 42 DAC5113B6119991B bd419070 42 33 38 45 34 37 36 37 44 44 32 44 30 32 37 43 B38E4767DD2D027C bd419080 42 41 44 33 30 36 35 43 46 41 35 42 38 42 36 45 BAD3065CFA5B8B6E bd419090 31 37 31 36 41 30 34 32 37 38 34 38 34 32 34 31 1716A04278484241 bd4190a0 46 43 43 45 42 43 31 44 31 30 36 37 38 34 33 31 FCCEBC1D10678431 bd4190b0 44 37 33 41 43 44 35 30 37 42 44 45 39 45 35 37 D73ACD507BDE9E57 bd4190c0 36 34 33 42 33 42 32 34 36 46 46 46 42 45 42 35 643B3B246FFFBEB5 bd4190d0 46 41 31 31 42 42 31 36 44 32 41 45 30 34 45 35 FA11BB16D2AE04E5 bd4190e0 38 42 43 45 41 41 38 42 34 37 46 41 42 35 34 33 8BCEAA8B47FAB543 bd4190f0 30 35 38 44 45 46 35 36 42 45 41 31 41 31 35 35 058DEF56BEA1A155 bd419100 39 32 41 34 44 43 45 44 35 37 37 36 44 45 39 45 92A4DCED5776DE9E bd419110 44 33 36 33 43 34 35 38 44 36 33 41 32 31 37 34 D363C458D63A2174 bd419120 33 36 33 30 46 46 37 38 32 33 42 30 34 31 45 37 3630FF7823B041E7 bd419130 41 31 42 35 33 39 33 38 35 42 41 42 43 41 43 44 A1B539385BABCACD bd419140 33 45 46 38 41 34 34 32 46 44 34 30 36 46 45 36 3EF8A442FD406FE6 bd419150 33 41 39 44 39 43 36 38 37 36 35 30 35 45 46 42 3A9D9C6876505EFB bd419160 44 32 43 34 44 32 31 38 46 34 43 33 36 39 33 41 D2C4D218F4C3693A bd419170 38 38 32 36 37 36 41 32 41 38 35 34 36 37 37 33 882676A2A8546773 bd419180 32 35 38 42 34 46 43 44 38 30 44 31 35 31 45 31 258B4FCD80D151E1 bd419190 43 35 31 30 35 44 34 31 37 43 33 41 46 45 35 43 C5105D417C3AFE5C bd4191a0 32 30 38 45 35 44 34 43 36 42 45 46 30 43 37 31 208E5D4C6BEF0C71 bd4191b0 44 44 31 36 32 39 31 38 38 38 39 41 30 45 31 39 DD162918889A0E19 bd4191c0 44 41 30 45 38 44 34 38 44 46 33 38 31 37 46 31 DA0E8D48DF3817F1 bd4191d0 30 41 30 43 30 41 30 44 33 45 38 43 32 38 45 35 0A0C0A0D3E8C28E5 bd4191e0 42 38 37 43 30 39 36 41 39 33 35 37 31 43 36 31 B87C096A93571C61 bd4191f0 32 33 42 30 35 34 41 45 39 41 34 34 39 33 46 31 23B054AE9A4493F1 bd419200 43 33 31 46 37 43 34 43 32 39 31 45 38 37 37 46 C31F7C4C291E877F bd419210 35 32 44 33 46 41 41 32 34 35 35 36 36 42 37 41 52D3FAA245566B7A bd419220 38 32 39 38 35 34 39 39 31 37 37 31 42 33 41 31 829854991771B3A1 bd419230 39 31 31 41 44 45 39 46 44 30 34 30 45 41 46 46 911ADE9FD040EAFF bd419240 41 31 33 44 34 33 35 39 43 37 46 43 45 46 32 30 A13D4359C7FCEF20 bd419250 39 45 32 42 30 43 45 39 31 35 32 34 31 31 46 46 9E2B0CE9152411FF bd419260 31 32 43 46 42 35 30 42 33 37 33 45 44 38 39 46 12CFB50B373ED89F bd419270 33 46 46 37 35 33 30 43 38 39 43 35 42 32 42 35 3FF7530C89C5B2B5 bd419280 41 44 42 36 34 33 32 36 43 44 43 45 42 46 34 36 ADB64326CDCEBF46 bd419290 32 31 32 32 42 42 34 45 44 36 37 41 31 39 32 30 2122BB4ED67A1920 bd4192a0 33 37 37 30 34 33 37 35 36 30 45 41 36 46 42 33 3770437560EA6FB3 bd4192b0 41 33 31 45 39 33 32 45 35 35 46 42 37 31 30 38 A31E932E55FB7108 bd4192c0 41 41 42 36 35 38 33 38 36 35 37 45 43 36 43 45 AAB65838657EC6CE bd4192d0 34 41 31 42 45 32 43 42 35 38 45 32 33 38 32 37 4A1BE2CB58E23827 bd4192e0 34 38 45 35 45 34 43 36 38 44 36 33 35 33 30 34 48E5E4C68D635304 bd4192f0 36 37 44 31 32 32 43 35 44 31 46 38 31 42 46 35 67D122C5D1F81BF5 bd419300 35 46 35 35 46 35 34 33 31 43 32 30 33 34 30 34 5F55F5431C203404 bd419310 39 33 43 30 37 39 43 44 41 46 41 46 38 46 31 33 93C079CDAFAF8F13 bd419320 41 42 37 46 31 45 43 42 39 37 43 34 33 42 39 42 AB7F1ECB97C43B9B bd419330 33 30 43 30 42 43 30 44 43 34 31 35 43 36 46 39 30C0BC0DC415C6F9 bd419340 37 36 46 42 31 36 41 33 38 39 43 37 34 30 41 46 76FB16A389C740AF bd419350 44 34 32 44 35 46 33 43 44 34 39 46 38 46 33 44 D42D5F3CD49F8F3D bd419360 37 35 45 42 34 34 46 45 45 38 31 34 30 38 41 32 75EB44FEE81408A2 bd419370 33 36 42 46 37 43 36 32 45 36 44 33 36 33 39 31 36BF7C62E6D36391 bd419380 35 31 30 45 43 38 34 36 38 31 41 43 45 30 30 38 510EC84681ACE008 bd419390 39 34 42 32 37 46 33 44 32 32 42 35 44 44 31 45 94B27F3D22B5DD1E bd4193a0 36 30 32 33 31 39 39 42 43 38 38 44 37 37 35 33 6023199BC88D7753 bd4193b0 46 32 37 38 43 37 34 43 44 30 34 35 42 39 34 31 F278C74CD045B941 bd4193c0 38 43 41 43 44 46 30 36 32 26 61 70 70 6b 65 79 8CACDF062&appkey bd4193d0 3d 31 64 38 62 36 65 37 64 34 35 32 33 33 34 33 =1d8b6e7d4523343 bd4193e0 36 26 61 75 74 6f 70 6c 61 79 5f 63 61 72 64 3d 6&autoplay_card= bd4193f0 31 31 26 62 61 6e 6e 65 72 5f 68 61 73 68 3d 36 11&banner_hash=6 bd419400 33 32 36 36 30 30 30 38 30 34 37 31 37 31 33 32 3266000804717132 bd419410 33 30 26 62 75 69 6c 64 3d 36 31 38 30 35 30 30 30&build=6180500 bd419420 26 63 5f 6c 6f 63 61 6c 65 3d 7a 68 2d 48 61 6e &c_locale=zh-Han bd419430 73 5f 43 4e 26 63 68 61 6e 6e 65 6c 3d 73 68 65 s_CN&channel=she bd419440 6e 6d 61 30 36 39 26 63 6f 6c 75 6d 6e 3d 32 26 nma069&column=2& bd419450 64 65 76 69 63 65 5f 6e 61 6d 65 3d 50 69 78 65 device_name=Pixe bd419460 6c 25 32 30 58 4c 26 64 65 76 69 63 65 5f 74 79 l%20XL&device_ty bd419470 70 65 3d 30 26 66 6c 75 73 68 3d 36 26 66 6e 76 pe=0&flush=6&fnv bd419480 61 6c 3d 34 30 30 26 66 6e 76 65 72 3d 30 26 66 al=400&fnver=0&f bd419490 6f 72 63 65 5f 68 6f 73 74 3d 30 26 66 6f 75 72 orce_host=0&four bd4194a0 6b 3d 31 26 67 75 69 64 61 6e 63 65 3d 30 26 68 k=1&guidance=0&h bd4194b0 74 74 70 73 5f 75 72 6c 5f 72 65 71 3d 30 26 69 ttps_url_req=0&i bd4194c0 64 78 3d 31 36 34 31 39 36 36 34 36 38 26 69 6e dx=1641966468&in bd4194d0 6c 69 6e 65 5f 64 61 6e 6d 75 3d 32 26 69 6e 6c line_danmu=2&inl bd4194e0 69 6e 65 5f 73 6f 75 6e 64 3d 31 26 6c 6f 67 69 ine_sound=1&logi bd4194f0 6e 5f 65 76 65 6e 74 3d 30 26 6d 6f 62 69 5f 61 n_event=0&mobi_a bd419500 70 70 3d 61 6e 64 72 6f 69 64 26 6e 65 74 77 6f pp=android&netwo bd419510 72 6b 3d 77 69 66 69 26 6f 70 65 6e 5f 65 76 65 rk=wifi&open_eve bd419520 6e 74 3d 26 70 6c 61 74 66 6f 72 6d 3d 61 6e 64 nt=&platform=and bd419530 72 6f 69 64 26 70 6c 61 79 65 72 5f 6e 65 74 3d roid&player_net= bd419540 31 26 70 75 6c 6c 3d 74 72 75 65 26 71 6e 3d 33 1&pull=true&qn=3 bd419550 32 26 72 65 63 73 79 73 5f 6d 6f 64 65 3d 30 26 2&recsys_mode=0& bd419560 73 5f 6c 6f 63 61 6c 65 3d 7a 68 2d 48 61 6e 73 s_locale=zh-Hans bd419570 5f 43 4e 26 73 70 6c 61 73 68 5f 69 64 3d 26 73 _CN&splash_id=&s bd419580 74 61 74 69 73 74 69 63 73 3d 25 37 42 25 32 32 tatistics=%7B%22 bd419590 61 70 70 49 64 25 32 32 25 33 41 31 25 32 43 25 appId%22%3A1%2C% bd4195a0 32 32 70 6c 61 74 66 6f 72 6d 25 32 32 25 33 41 22platform%22%3A bd4195b0 33 25 32 43 25 32 32 76 65 72 73 69 6f 6e 25 32 3%2C%22version%2 bd4195c0 32 25 33 41 25 32 32 36 2e 31 38 2e 30 25 32 32 2%3A%226.18.0%22 bd4195d0 25 32 43 25 32 32 61 62 74 65 73 74 25 32 32 25 %2C%22abtest%22% bd4195e0 33 41 25 32 32 25 32 32 25 37 44 26 74 73 3d 31 3A%22%22%7D&ts=1 bd4195f0 36 34 31 39 36 36 37 33 34 641966734 Length:0x5f9 contents: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF b9d11f70 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 b9d11f70 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 b9d11f70 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 b9d11f70 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 c92b8064 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ c92b8074 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............... Length:0x1f contents: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF b9d11eb4 c8 30 00 00 00 00 00 00 .0...... Length:0x8
两次同时起两个终端hook,启用hook_22B0和hook_LibBili_s,用来对比生成的sign。总共调用了7次hook_22B0,即MD5Update,代码中总共调用了2+3=5次,以下通过N重命名函数
MD5Final中会调用两次
将MD5Update得到的5次的字符串拼接起来
1 ad_extra=3681E8C3AC537EB0939F45107877C63D51E78E6C994A950E14ADCA9A9FC0536C38D16851C1513127D08C074DAC5113B6119991BB38E4767DD2D027CBAD3065CFA5B8B6E1716A04278484241FCCEBC1D10678431D73ACD507BDE9E57643B3B246FFFBEB5FA11BB16D2AE04E58BCEAA8B47FAB543058DEF56BEA1A15592A4DCED5776DE9ED363C458D63A21743630FF7823B041E7A1B539385BABCACD3EF8A442FD406FE63A9D9C6876505EFBD2C4D218F4C3693A882676A2A8546773258B4FCD80D151E1C5105D417C3AFE5C208E5D4C6BEF0C71DD162918889A0E19DA0E8D48DF3817F10A0C0A0D3E8C28E5B87C096A93571C6123B054AE9A4493F1C31F7C4C291E877F52D3FAA245566B7A829854991771B3A1911ADE9FD040EAFFA13D4359C7FCEF209E2B0CE9152411FF12CFB50B373ED89F3FF7530C89C5B2B5ADB64326CDCEBF462122BB4ED67A19203770437560EA6FB3A31E932E55FB7108AAB65838657EC6CE4A1BE2CB58E2382748E5E4C68D63530467D122C5D1F81BF55F55F5431C20340493C079CDAFAF8F13AB7F1ECB97C43B9B30C0BC0DC415C6F976FB16A389C740AFD42D5F3CD49F8F3D75EB44FEE81408A236BF7C62E6D36391510EC84681ACE00894B27F3D22B5DD1E6023199BC88D7753F278C74CD045B9418CACDF062&appkey=1d8b6e7d45233436&autoplay_card=11&banner_hash=6326600080471713230&build=6180500&c_locale=zh-Hans_CN&channel=shenma069&column=2&device_name=Pixel%20XL&device_type=0&flush=6&fnval=400&fnver=0&force_host=0&fourk=1&guidance=0&https_url_req=0&idx=1641966468&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-Hans_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=1641966734560c52ccd288fed045859ed18bffd973
md5结果如下,正是d45938b080b6e74c5a8f9e66719998ab
Python实现 以个人空间为例,如果请求中没有sign将报错API校验密匙错误
,针对入参中变量就是ts,vmid,sign进行拼接即可。
1 2 3 4 5 6 7 8 import hashlib import time import requests ts = str(int(time.time())) origin_txt = 'access_key=***&ad_extra=3681E8C3AC537EB0939F45107877C63D51E78E6C994A950E14ADCA9A9FC0536C38D16851C1513127D08C074DAC5113B6119991BB38E4767DD2D027CBAD3065CFA5B8B6E1716A04278484241FCCEBC1D10678431D73ACD507BDE9E57643B3B246FFFBEB5FA11BB16D2AE04E58BCEAA8B47FAB543058DEF56BEA1A15592A4DCED5776DE9ED363C458D63A21743630FF7823B041E7A1B539385BABCACD3EF8A442FD406FE63A9D9C6876505EFBD2C4D218F4C3693A882676A2A8546773258B4FCD80D151E1C5105D417C3AFE5C208E5D4C6BEF0C71DD162918889A0E19DA0E8D48DF3817F10A0C0A0D3E8C28E5B87C096A93571C6123B054AE9A4493F1C31F7C4C291E877F52D3FAA245566B7A829854991771B3A1911ADE9FD040EAFFA13D4359C7FCEF209E2B0CE9152411FF12CFB50B373ED89F3FF7530C89C5B2B5ADB64326CDCEBF462122BB4ED67A19203770437560EA6FB3A31E932E55FB7108AAB65838657EC6CE4A1BE2CB58E2382748E5E4C68D63530467D122C5D1F81BF55F55F5431C20340493C079CDAFAF8F13AB7F1ECB97C43B9B30C0BC0DC415C6F976FB16A389C740AFD42D5F3CD49F8F3D75EB44FEE81408A236BF7C62E6D36391510EC84681ACE00894B27F3D22B5DD1E6023199BC88D7753F278C74CD045B9418CACDF062&appkey=1d8b6e7d45233436&build=6180500&c_locale=zh-Hans_CN&channel=shenma069&fnval=400&fnver=0&force_host=0&fourk=1&from=0&mobi_app=android&platform=android&player_net=1&ps=10&qn=32&s_locale=zh-Hans_CN&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={}&vmid={}' sign = hashlib.md5(origin_txt.format(ts,'1493964352'+'560c52ccd288fed045859ed18bffd973').encode('utf-8')).hexdigest() print(requests.get('https://app.bilibili.com/x/v2/space?'+origin_txt.format(ts,'1493964352')+'&sign='+sign).json())
视频