本章节只用来教学,无其他不良指引
一.前言
今天还是上次的那个接口 https://api.bilibili.com/x/report/click/android2
我们今天需要破解的是请求头中的buvid session_id fp_local,这三个值,这三个值的破解还是有点难度的,所以今天我会尽可能的讲的详细一点,希望大家都能够听得懂
二.buvid破解
我们把让b站反编译之后,搜索report/click/android2,可以看到一个,然后我们带你进去
我们看到@RequestInterceptor,很能理解,请求头都是在拦截器中进行处理的,我们点进蓝七七里面的方法
很明显,这里我们还看不出来啥,但是杜义熙逻辑就知道,获得一个对象方法,然后e(h)对这个进行添加方法,那我们进一下e看看怎么个回事。
发现了这些东西,我们就确定了位置
我们点进去
我们点进来,发现这个个回事,就要一步步查找用例,这里我给出hook查看全部调用栈的代码
import frida
import sys
rdev = frida.get_remote_device()
pid = rdev.spawn(["tv.danmaku.bili"])
session = rdev.attach(pid)
scr = """
Java.perform(function () {
var c = Java.use("com.bilibili.api.c");
c.b.implementation = function(arg0){
console.log("buvid=",arg0);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
this.b(arg0);
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()
hook结果 ,这个就是全部调用栈信息了,方便我们筛选
我们最后一直查找用例,找到下面这个位置
这个str就是buvid了 ,那我们就是得找到这个this.a了,然后我们往下找找,可以发现
第一和第二个,他是从xml中拿,怎么判断的呢,因为点击去多几次可以看见SharedPreferences,这个就是之前和大家说的xml持久化存储 ,我们就可以知道,生成位置是在第三个uperCase里,那我们重点是看这个,我们点进去
可以发现这个就是生成逻辑,我这里先说一下生成逻辑
e是区字符串中2,12,22的位置
14 buvid生成方案四种--e是取字符串的2,12和22个位置值
1 "XZ" + e(d) + d;
XZ+e(手机状态字符串md5签名)+手机状态字符串md5签名
2 "XY" + e(d4) + d4;
XY+e(mac地址字符串md5签名)+mac地址态字符串md5签名
3 "XX" + e(d4) + d4;
XX+e(安卓id-md5签名)+安卓id-md5签名
4 "XW" + e(replace) + replace;
XW+e(uuid)+uuid
我就带大家看一下最简单的XW开头的,因为XW开头的python最好模拟
点进来就发现是uuid了,然后外面就是把uuid的-替换成空。
至此,buvid已经可以任意选择四套逻辑进行破解,这里我给出源代码
import uuid
import hashlib
import random
# mac地址生成方案
def create_random_mac(sep=":"):
# 00:90:4C:11:22:33
data_list = []
for i in range(1, 7):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
mac = sep.join(data_list)
return mac
def get_buvid_by_wifi_mac():
mac = create_random_mac()
md5 = hashlib.md5()
md5.update(mac.encode('utf-8'))
v0_1 = md5.hexdigest()
return "XY{}{}{}{}".format(v0_1[2], v0_1[12], v0_1[22], v0_1).upper()
if __name__ == '__main__':
buvid = get_buvid_by_wifi_mac()
print(buvid)
###uuid方案
# u=str(uuid.uuid4()).replace('-','')
# print("XW{}{}{}{}".format(u[2], u[12], u[22], u).upper())
至此,buvid破解完成
三.session_id破解
我们回到开始的位置
我们点到头发现下面这个
发现这个是一个接口,没有具体实现,那我们就要在外面找,我们退一层
发现这个就是赋值的地方,那我们是不是需要hook一下o函数,找到b的具体类,再去找这个类里面找到getSessionId的具体实现方法,这里给出hook代码
import frida
import sys
rdev = frida.get_remote_device()
pid = rdev.spawn(["tv.danmaku.bili"])
session = rdev.attach(pid)
scr = """
Java.perform(function () {
var a = Java.use("com.bilibili.api.a");
a.o.implementation = function(arg0){
console.log("obj=",arg0);
console.log("obj=",JSON.stringify(arg0));
this.o(arg0);
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()
JSON.stringfy就是打印出具体类型
可以看到:是com.bilibili.api.a$b接口类型
具体类型是:tv.danmaku.bili.utils.p$a
$符号代表的是p里面还有个a的子类
那我们就搜索这个类名
发现这个就是要找的位置,我们就接着点进去
发现还是一个接口,那我们就得确定 com.bilibili.lib.foundation.e.b()的具体类型,那我们们点进去
hook这个的返回值类型,这里给出hook代码
import frida
import sys
rdev = frida.get_remote_device()
pid = rdev.spawn(["tv.danmaku.bili"])
session = rdev.attach(pid)
scr = """
Java.perform(function () {
var e = Java.use("com.bilibili.lib.foundation.e");
e.b.implementation = function(){
var res = this.b();
console.log("res=",JSON.stringify(res));
return res;
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()
结果发现疯狂打印,但是实际只有一个,那我们就搜索 com.bilibili.lib.foundation.DefaultApps
搜到的结果点进去,然后找到getsessionid
这个的逻辑就是,去xml中找,找不到的话就用l,那我们就去找l
这个代码可以直接把中间的打印日志去掉,丢给gpt
清晰明了
这里给出生成代码
# python 3.9及以后
import random
session_id = "".join([hex(item)[2:] for item in random.randbytes(4)])
print(session_id)
# python 3.8及以前
import random
session_id = "".join([hex(random.randint(0,255))[2:] for i in range(4)])
print(session_id)
四.fp_local破解
Tips:这个是今天的重点和难点
我们再次回到最开始的位置
点进去
发现还是接口,那么就要回到上一步,还得是之前的额方法咯,但是很巧的是,b的类型我们已经确定过了 tv.danmaku.bili.utils.p$a
那我们再去搜索,点进去找到里面的F
然后一直点,找到这
我们发现理论上返回的就是a,那我们往上找,发现找不到a,看到下面这一大坨
我们就要猜测是新版的反编译不全,那我们就用老版的试试
0.老版jadx加内存
这里我单独加个教大家 老版本的jadx加内存
jadx-gui 反编译app的时候内存不足
1.使用记事本或者notpad++打开jadx-gui.bat
2.找到 set DEFAULT_JVM_OPTS="-Xms128M" "-Xmx4g"
3.将其修改为 set DEFAULT_JVM_OPTS="-Xms128M" "-Xmx16g" 后保存就ok了 (你要4g 提升到16g把-Xmx4g改成-Xmx16g)
在老版本里面找到这个
那我们在这里,再点进去
发现我们要破的东西特别多
str2:MiscHelperKt.a(f(str, aVar)) + h() + MiscHelperKt.a(g())
返回:str2+b(str2)
# 咱们要破的:
- f(str, aVar)
- h()
- g()
- MiscHelperKt.a()
- b()这里我就不一一和大家截图说了,直接给你们代码讲解
1.f(str, aVar)
buvid+手机型号+手机品牌 使 用md5签名
private static final byte[] f(String str, a aVar) {
# 1 传入的aVar 调用a方法,变成字典
Map<String, String> a2 = aVar.a();
# 2 e是md5签名,
# str通过hook得到就是buvid
# public static final String KEY_PUB_MODEL = "model"; 手机型号
# band:手机品牌
return e(str + a2.get(PersistEnv.KEY_PUB_MODEL) + a2.get("band"));
}
2.h()
时间戳
private static final String h() {
String format = a.format(new Date(System.currentTimeMillis()));
return format;
}
3.g()
随机生成8个字节
private static final byte[] g() {
# 核心是com.bilibili.commons.e.a(8),传了8进去
byte[] a2 = com.bilibili.commons.e.a(8);
return a2;
}
4.b()
把传入的字符串,按3个数字截取--》转成16进制--》g=0;h=60;i=2
public static final String b(String str) {
int i;
i iVar = q.S0(q.n1(0, Math.min(str.length() - 1, 62)), 2);
# 1 从 iVar中执行方法,拿到了 g h i3 三个int数字--》固定的
int g = iVar.g();
int h = iVar.h();
int i2 = iVar.i();
if (i2 < 0 ? g >= h : g <= h) {
i = 0;
while (true) {
# 2 把传入的字符串,取 g 到 g+2的长度--》本质按上面三个数字做截取
String substring = str.substring(g, g + 2);
i += Integer.parseInt(substring, b.a(16));
if (g == h) {
break;
}
g += i2;
}
} else {
i = 0;
}
e0 e0Var = e0.a;
#3 把字符串按三个数字截取完后--》转成16进制,如果不足2位用0 补齐
String format = String.format("%02x", Arrays.copyOf(new Object[]{Integer.valueOf(i % 256)}, 1));
return format;
}
5.MiscHelperKt.a()
这个就很复杂,来和大家说一下这个
我们点进来到这里,发现又无法跳到声明,原因是用的kotlin写的,jadx反编译不了
我们需要借助另外一款工具GDA(国产)
这里给出下载地址
GDA主页-亚洲首款交互式Android反编译器GDA:一款简洁、轻便、快速的交互式Android反编译分析工具.http://www.gda.wiki:9090/ 下载好之后,我们拖入,搜索 MiscHelperKt
发现在这里,return了一个 ArraysKt___ArraysKt.Fe(p0, str, p8, str1, i, str3, p6);
那我们再点进去
再点进去,最后位置在这里
到这里我就不带大家读了,直接告诉你们结论 把字符串转成16进制
给出python实现代码
import hashlib
import datetime
import random
def gen_local_v1(buvid, phone_model, phone_band):
"""
fp_local和fp_remote都是用这个算法来生成的,在手机初始化阶段生成 fp_local,
:param buvid: 根据算法生成的buvid,例如:"XYBA4F3B2789A879EA8AEEDBE2E4118F78303"
:param phone_model: 手机型号modal,例如:"Mate 10 Pro"
:param phone_band: 手机品牌band,在模拟器上是空字符串(我猜是程序员想要写成 brand )哈哈哈哈
:return:
"""
def misc_helper_kt(data_bytes):
data_list = []
v7 = len(data_bytes)
v0 = 0
while v0 < v7:
v2 = data_bytes[v0]
data_list.append("%02x" % v2)
v0 += 1
return ''.join(data_list)
data_string = "{}{}{}".format(buvid, phone_model, phone_band)
hash_object = hashlib.md5()
hash_object.update(data_string.encode('utf-8'))
data = hash_object.digest()
arg1 = misc_helper_kt(data)
arg2 = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
#arg3 = misc_helper_kt(random.randbytes(8)) # python3.9可以
arg3 = misc_helper_kt([random.randint(1, 255) for i in range(8)])
return "{}{}{}".format(arg1, arg2, arg3)
def a_b(arg8):
v3 = 0
v4 = 60
v0_1 = 2
v5 = 0
while True:
v6 = arg8[v3:v3 + 2]
v5 += int(v6, base=16)
if v3 != v4:
v3 += v0_1
continue
break
data = "%02x" % (v5 % 0x100,)
return data
str2 = gen_local_v1("XYBA4F3B2789A879EA8AEEDBE2E4118F78303", "Mate 10 Pro", "")
fp_local = str2 + a_b(str2)
print(fp_local)
补充
有不懂的地方可以咨询我,有求必应