SO逆向之某bili的sign分析

主页

抓包

版本6.18.0

Charles本地证书

image-20220106102345520

系统证书目录:/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 直接面具插件安排,重启即可

重启

image-20220110143929773

appkey1d8b6e7d45233436
build6180500
c_localezh-Hans_CN
channelshenma069
duration0
fnval400
fnver0
force_host0
fourk1
from_sourceapp_search
highlight1
is_org_query0
keyword666
local_time8
mobi_appandroid
platformandroid
player_net1
pn1
ps20
qn32
recommend1
s_localezh-Hans_CN
statistics{“appId”:1,”platform”:3,”version”:”6.18.0”,”abtest”:””}
ts1641789657
sign057565a1251d32fec516abcf65b75123

分析

定位

1
frida -UF -l okhttp_poker.js    抓包

image-20220110185056936

本app被混淆,尝试trace选中的类

image-20220110192720257

1
frida -UF -l r0tracer.js -o bilibili.txt

image-20220110185604693

查看方法 com.bilibili.okretro.f.a.a

image-20220110190143983

查看 d(a0Var.k(), h);

image-20220110191856362

查看 b(hashMap); , 该方法中只是把部分参数放到map里,不包括sign

image-20220110191755430

查看 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;
}

image-20220203205718794

frida-trace -UF -i “Java_com*” 判断是静态注册

image-20220111114305600

frida -U -f tv.danmaku.bili -l hook_RegisterNatives.js –no-pause -o bilibili.txt 判断动态注册,搜索com.bilibili.nativelibrary.LibBili

image-20220111114828447

如果太多可以尝试修改hook_RegisterNatives.js

image-20220111115246813

image-20220111115332647

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;
}

image-20220110195233020

参数:

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());

image-20220110195633979

sign就是返回结果中的b

image-20220110201242375

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

image-20220111102442623

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;

image-20220111102732976

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;

image-20220111103508642

查看SignedQuery的r方法

image-20220111103847189

还原如下:

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该方法

image-20220111104019992

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;
}
}

image-20220111104253862

init中有两个参数,参数2就是sign。

Unidbg提供了另外一种模拟Native调用JAVA的方式——缺啥补啥,其原理是JAVA的反射

  • LibBili不继承自AbstractJni
  • vm.setJni(this);改成vm.setDvmClassFactory(new ProxyClassFactory());

报错:com.bilibili.nativelibrary.SignedQuery

image-20220111111520085

从jadx中将类拷贝出来,补充环境SignedQuery.java

image-20220111111629599

image-20220111111739680

算法还原

JNITrace

**static native SignedQuery s(SortedMap<String, String> sortedMap);**,入参是map,返回是SignedQuery对象

根据上文动态注册的函数地址com.bilibili.nativelibrary.LibBili.s的偏移量为0x1c97,IDA打开G跳转到该方法

image-20220111124959476

双击进入sub_2F88

image-20220111125140039

修改sub_2F88函数,int __fastcall sub_2F88(JNIEnv *env, int *map, int a3, int a4)

image-20220111212343551

1
2
pip install jnitrace 
jnitrace -l libbili.so tv.danmaku.bili --ignore-vm

image-20220112101226451

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*

image-20220112103421156

image-20220112103448440

X查看sub_6680交叉的引用

image-20220112103539877

一共有两处,其中有一处就是上文中的sub_2F88函数,由于jnitrace时返回false,直接走下面框选的逻辑代码。

image-20220112103736437

继续跟进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)

image-20220112105114498

继续分析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的值

image-20220112105844008

image-20220112105921007

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)

image-20220112110620654

image-20220112111351865

将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)

image-20220112111832553

IDA跳转到0x31a5,进入伪代码

image-20220112112022039

后续只要调用创建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的值对比

1
v15 = sub_34B8(v33);

image-20220112112912208

image-20220112113003781

sub_3414

sub_3414是操作ts的值并释放ts

1
sub_3414(env, map);

image-20220112113642731

sub_227C

1
sub_227C(v36);

通过h转为十六进制数,这四个是MD5状态变量,该方法就是MD5Init,初始化核心变量,装入标准的幻数。

image-20220112131900999

常见的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);

image-20220112132404778

sub_2AE0

sub_2AE0就是MD5Final,是整理和填写输出结果,v36是context指针,v37就是加密后的字符串

1
sub_2AE0(v37, v36);

尝试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重命名函数

image-20220112140453685

MD5Final中会调用两次

image-20220112140531134

将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

image-20220112141742015

Python实现

以个人空间为例,如果请求中没有sign将报错API校验密匙错误,针对入参中变量就是ts,vmid,sign进行拼接即可。

image-20220113080806862

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())

image-20220113123556725

视频

文章作者: J
文章链接: http://onejane.github.io/2022/01/10/SO逆向之某bili的sign分析/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏