从此刻开始,知识不断涌进你的脑海。
一、登录接口分析
1.1 必要参数
![图片[1]-APP逆向-B站登录请求接口-木兮知识库](https://www.mzsec.cn/wp-content/uploads/2026/03/image-20260131145725875-300x160.png)
请求包如下:
POST /x/passport-login/oauth2/login HTTP/2
Host: passport.bilibili.com
Accept: */*
Accept-Encoding: gzip, deflate, br
App-Key: android64
Bili-Http-Engine: ignet
Buvid: XXB3185B425AAA299364F148DC0184B7ED3BD
Content-Length: 32657
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Env: prod
Fp_local: 7f5f43c6aebed517c4c0894227378c942026020422533758d63cc519ec5450dc
Fp_remote: 7f5f43c6aebed517c4c0894227378c9420260130211330139a8ffacfc2282e04
Guestid: 25822977116578
Session_id: 873bd744
User-Agent: Mozilla/5.0 BiliDroid/8.81.0 (bbcallen@gmail.com) 8.81.0 os/android model/Pixel 5 mobi_app/android build/8810200 channel/bili innerVer/8810210 osVer/11 network/2
X-Bili-Locale-Bin: Cg4KAnpoEgRIYW5zGgJDThIOCgJ6aBIESGFucxoCQ04iDUFzaWEvU2hhbmdoYWkqBiswODowMA
X-Bili-Metadata-Ip-Region: CN
X-Bili-Network-Bin: CAEqEQ0AAAA/ENjOhgMYx6S87sIz
X-Bili-Redirect: 1
X-Bili-Ticket: eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NzAzMDI0OTMsImlhdCI6MTc3MDI3MzM5MywiYnV2aWQiOiJYWEIzMTg1QjQyNUFBQTI5OTM2NEYxNDhEQzAxODRCN0VEM0JEIn0.P8UzE073Rs6-4-cZOqpwuycZjaMI4NiMatOPkzeCNM8
X-Bili-Trace-Id: 77d53c94b64ce1f0885c23e8cc698493:885c23e8cc698493:0:0
appkey=783bbb7264451d82&bili_local_id=7f5f43c6aebed517c4c0894227378c942026020422533758d63cc519ec5450dc&build=8810200&buvid=XXB3185B425AAA299364F148DC0184B7ED3BD&c_locale=zh-Hans_CN&channel=bili&device=phone&device_id=7f5f43c6aebed517c4c0894227378c9420260130211330139a8ffacfc2282e04&device_meta=B2BA76669234183A901296510D5843DE0DE741C1BE90F109C8B817C54D31955D7D63AC6C0F079465E413E8ED10EE48BEFCA118998C712266F2BF666EEF49A722CF57115747CC45F332389CBA6FACF590F85551CF56A78FCA6D1509EA173A7297DCEAC12B79A
......(这里device_meta的值太长了,省略一部分)
FC119474BEF758E26F6515289FC359B8C67FAE7E76E20E22E13B7CF8915A14822925E40EB2ECFD41C929C4F39422505276D2F29535E734F7E6BEF174BAE34E46BFD9AE26A02F09BDFF26B&device_name=GooglePixel%205&device_platform=Android11GooglePixel%205&device_tourist_id=25822977116578&disable_rcmd=0&dt=BNHO887i5YbWjFfR29XfR8rw4oSx3Y1ecFtuJXQvJdK5auILOObOYiVUTS%2FKvLxsf2zolhrgjh8M%0AdzqIoUOzsCiZ2Lf48rMX%2F0QAa18CBbAymWlPLO2XgFHq8xe1%2Bffv%2BpFXx4OpFygMZwha0n7tRe0E%0A7XkUxI8CyAAmf7EOfzc%3D%0A&extend=&from_pv=&from_url=&local_id=XXB3185B425AAA299364F148DC0184B7ED3BD&login_session_id=42b1d085fed3f95e2c246cb22b57e60b&mobi_app=android&password=wfbpzbKhlGuLc9LBpwfNtYuoNf9OjUH8vQzVaCNVRA3lJHzcuuckV0j67SkEVmIwHg3uuy4CtRpJ%0A2C6QNWfXTnQcTs1h5LtD4dp86xHZ3HcrX9NdhGsE%2FEpyXr4nCfcpMMiXL%2B6AwzJQZdedn7Xost6z%0AeyEIT71ofewP5aOcxO8%3D%0A&platform=android&s_locale=zh-Hans_CN&spm_id=main.homepage.bottombar.myinfo&statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%228.81.0%22%2C%22abtest%22%3A%22%22%7D&ts=1770296130&username=175XXXXXXXX&sign=e579fcea7306d0130a61e00791967ed9
分解请求包的必要参数
// 请求头
App-Key: android64
Bili-Http-Engine: ignet
Buvid: XXB3185B425AAA299364F148DC0184B7ED3BD
Env: prod
Fp_local: 7f5f43c6aebed517c4c0894227378c942026020422533758d63cc519ec5450dc
Fp_remote: 7f5f43c6aebed517c4c0894227378c9420260130211330139a8ffacfc2282e04
Guestid: 25822977116578
Session_id: 873bd744
X-Bili-Locale-Bin: Cg4KAnpoEgRIYW5zGgJDThIOCgJ6aBIESGFucxoCQ04iDUFzaWEvU2hhbmdoYWkqBiswODowMA
X-Bili-Metadata-Ip-Region: CN
X-Bili-Network-Bin: CAEqEQ0AAAA/ENjOhgMYx6S87sIz
X-Bili-Redirect: 1
X-Bili-Ticket: eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NzAzMDI0OTMsImlhdCI6MTc3MDI3MzM5MywiYnV2aWQiOiJYWEIzMTg1QjQyNUFBQTI5OTM2NEYxNDhEQzAxODRCN0VEM0JEIn0.P8UzE073Rs6-4-cZOqpwuycZjaMI4NiMatOPkzeCNM8
X-Bili-Trace-Id: 77d53c94b64ce1f0885c23e8cc698493:885c23e8cc698493:0:0
-----------------------------------------------------
// 请求体
appkey=783bbb7264451d82
bili_local_id=7f5f43c6aebed517c4c0894227378c942026020422533758d63cc519ec5450dc
build=8810200
buvid=XXB3185B425AAA299364F148DC0184B7ED3BD
c_locale=zhHans_CN
channel=bili
device=phone
device_id=7f5f43c6aebed517c4c0894227378c9420260130211330139a8ffacfc2282e04
device_meta=B2BA76669234183A901296510D5843DE0DE741C1BE90F109C8B817C54D31955D7D63AC6C0F079465E413E8ED10EE48BEFCA118998C712266F2BF666EEF49A722CF57115747CC45F332389CBA6FACF590F85551CF56A78FCA6D1509EA173A7297DCEAC12B79A
......(这里device_meta的值太长了,省略一部分)
FC119474BEF758E26F6515289FC359B8C67FAE7E76E20E22E13B7CF8915A14822925E40EB2ECFD41C929C4F39422505276D2F29535E734F7E6BEF174BAE34E46BFD9AE26A02F09BDFF26B
device_name=GooglePixel%205
device_platform=Android11GooglePixel%205
device_tourist_id=25822977116578
disable_rcmd=0
dt=BNHO887i5YbWjFfR29XfR8rw4oSx3Y1ecFtuJXQvJdK5auILOObOYiVUTS%2FKvLxsf2zolhrgjh8M%0AdzqIoUOzsCiZ2Lf48rMX%2F0QAa18CBbAymWlPLO2XgFHq8xe1%2Bffv%2BpFXx4OpFygMZwha0n7tRe0E%0A7XkUxI8CyAAmf7EOfzc%3D%0A
extend=
from_pv=
from_url=
local_id=XXB3185B425AAA299364F148DC0184B7ED3BD
login_session_id=42b1d085fed3f95e2c246cb22b57e60b
mobi_app=android
password=wfbpzbKhlGuLc9LBpwfNtYuoNf9OjUH8vQzVaCNVRA3lJHzcuuckV0j67SkEVmIwHg3uuy4CtRpJ%0A2C6QNWfXTnQcTs1h5LtD4dp86xHZ3HcrX9NdhGsE%2FEpyXr4nCfcpMMiXL%2B6AwzJQZdedn7Xost6z%0AeyEIT71ofewP5aOcxO8%3D%0A
platform=android
s_locale=zhHans_CN
spm_id=main.homepage.bottombar.myinfo
statistics=%7B%22appId%22%3A1%2C%22platform%22%3A3%2C%22version%22%3A%228.81.0%22%2C%22abtest%22%3A%22%22%7D
ts=1770296130
username=175XXXXXXXX
sign=e579fcea7306d0130a61e00791967ed9
1.2 请求接口定位
将bilibili的apk拖入jadx,根据字符串”/x/passport-login/oauth2/login”定位到登录接口位置
// 代码段1.1
@FormUrlEncoded
@POST("/x/passport-login/oauth2/login")
@RequestInterceptor(AuthInterceptor.class)
BiliCall<GeneralResponse<AuthInfo>> login(@Field("username") String str, @Field("password") String str2, @Field("device_meta") String str3, @Field("dt") String str4, @Field("from_pv") String str5, @Field("from_url") String str6, @Field("login_session_id") String str7, @Field("spm_id") String str8, @Field("device_tourist_id") String str9, @Field("extend") String str10, @FieldMap Map<String, String> map);
用例
// 代码段1.2
@JvmStatic
@NotNull
public static final AuthInfo M(@Nullable String str, @Nullable String str2, @Nullable Map<String, String> map, @Nullable String str3, @Nullable String str4, @Nullable String str5, @Nullable String str6, @Nullable String str7, @Nullable String str8, @Nullable final DeviceMetaDelegate deviceMetaDelegate) throws AccountException {
String str9;
try {
BiliPassportApi biliPassportApi = f141067a;
AuthKey authKeyP = biliPassportApi.p();
String strEncryptPassword = authKeyP != null ? authKeyP.encryptPassword(str2) : null;
Pair<String, String> pairZ = biliPassportApi.z(authKeyP, new Function0() { // from class: com.bilibili.lib.accounts.z
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BiliPassportApi.N(deviceMetaDelegate);
}
});
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
biliCallLogin.setApiTracker(new AccountApiTracker(biliCallLogin.getApiTracker()));
Response<GeneralResponse<AuthInfo>> responseExecute = biliCallLogin.execute();
if (!responseExecute.isSuccessful()) {
biliPassportApi.h0(responseExecute);
}
GeneralResponse<AuthInfo> generalResponseBody = responseExecute.body();
if (generalResponseBody == null) {
throw new AccountException(-2);
}
int i17 = generalResponseBody.code;
if (i17 != 0 && i17 != -105) {
throw new AccountException(generalResponseBody.code, generalResponseBody.message);
}
AuthInfo authInfo = generalResponseBody.data;
if (authInfo == null) {
throw new AccountException(-2);
}
if (i17 == -105) {
AccountException accountException = new AccountException(generalResponseBody.code, generalResponseBody.message);
AuthInfo authInfo2 = generalResponseBody.data;
if (authInfo2 == null || (str9 = authInfo2.url) == null) {
str9 = "";
}
accountException.payLoad = str9;
throw accountException;
}
AuthInfo authInfo3 = authInfo;
if (authInfo3 == null) {
throw new AccountException(-2);
}
AccessToken accessToken = authInfo3.accessToken;
if (accessToken != null) {
Date date = responseExecute.headers().getDate("Date");
if (date != null) {
accessToken.mExpires += (date.getTime() / 1000) + accessToken.mExpiresIn;
} else {
accessToken.mExpires = (System.currentTimeMillis() / 1000) + accessToken.mExpiresIn;
}
}
return authInfo3;
} catch (BiliApiParseException e17) {
throw new AccountException(e17);
} catch (IOException e18) {
throw new AccountException(e18);
}
二、请求体字段分析
根据接口以及用例对比,可确认用例中每个入参的意义
// 代码段2.1
@FormUrlEncoded
@POST("/x/passport-login/oauth2/login")
@RequestInterceptor(AuthInterceptor.class)
BiliCall<GeneralResponse<AuthInfo>> login(@Field("username") String str, @Field("password") String str2, @Field("device_meta") String str3, @Field("dt") String str4, @Field("from_pv") String str5, @Field("from_url") String str6, @Field("login_session_id") String str7, @Field("spm_id") String str8, @Field("device_tourist_id") String str9, @Field("extend") String str10, @FieldMap Map<String, String> map);
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
可以看到有一下参数需要确认:
str ===> 对应请求包的username
strEncryptPassword ===> 对应请求包的password
pairZ.component2()
pairZ.component1()
str3
str4
str5
str6
str7
str8
PassportCommParams.attachDeviceParams(map)
2.1 strEncryptPassword逆向
先确认strEncryptPassword是怎么来的,在用例里找到相关代码
// 代码段2.2
AuthKey authKeyP = biliPassportApi.p();
String strEncryptPassword = authKeyP != null ? authKeyP.encryptPassword(str2) : null;
strEncryptPassword来自authKeyP.encryptPassword(str2)
authKeyP又来自于biliPassportApi.p(),查找声明
// 代码段2.3
private final AuthKey p() throws AccountException {
return (AuthKey) v(E().getKeyV2());
}
可以看到biliPassportApi.p()实际上还是调用了v(E().getKeyV2()),再去查找声明,发现是由”/x/passport-login/web/key”这个get请求得到的
// 代码段2.4
@GET("/x/passport-login/web/key")
@RequestInterceptor(AuthInterceptor.class)
BiliCall<GeneralResponse<AuthKey>> getKeyV2();
很显然这个是一个请求公钥的接口
![图片[2]-APP逆向-B站登录请求接口-木兮知识库](https://www.mzsec.cn/wp-content/uploads/2026/03/image-20260131203721053-300x140.png)
现在知道了v(E().getKeyV2())实际上就是app向服务器请求公钥以及hash等字段,最后 (AuthKey) v(E().getKeyV2())这个操作是需要响应的data数据强制转换到AuthKey类实例里this.key以及this.hash
this.key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjb4V7EidX/ym28t2ybo0U6t0n\n6p4ej8VjqKHg100va6jkNbNTrLQqMCQCAYtXMXXp2Fwkk6WR+12N9zknLjf+C9sx\n/+l48mjUU8RqahiFD1XT/u2e0m2EN029OhCgkHx3Fc/KlFSIbak93EH/XlYis0w+\nXl69GV6klzgxW6d2xQIDAQAB\n-----END PUBLIC KEY-----\n"
this.hash = "0a667db92035769f"
回顾上面的代码段2.2,这里的str入参实际上是用户密码的明文字符串加上this.hash
下面的java代码可以看出在用户密码在通过i.b()方法加密之前,把公钥头尾的标识去掉,只需要公钥中间部分
// 代码段2.5
package com.bilibili.lib.accounts.model;
import androidx.annotation.Keep;
import c92.i;
/* compiled from: BL */
@Keep
/* loaded from: classes12.dex */
public class AuthKey {
public String hash;
public String key;
public String encrypt(String str) {
return i.b(str, this.key.replaceFirst("-----BEGIN PUBLIC KEY-----\n", "").replace("\n-----END PUBLIC KEY-----\n", ""));
}
public String encryptPassword(String str) {
return encrypt(this.hash + str);
}
}
再来看i.b是如何加密的,定位代码:
// 代码段2.6
/* compiled from: BL */
@RestrictTo({RestrictTo.Scope.LIBRARY})
/* loaded from: classes12.dex */
public class i {
public static byte[] a(String str, String str2) throws InvalidKeySpecException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
try {
PublicKey publicKeyGeneratePublic = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(str2.getBytes(), 0)));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(1, publicKeyGeneratePublic);
return cipher.doFinal(str.getBytes());
} catch (Exception unused) {
return null;
}
}
public static String b(String str, String str2) throws InvalidKeySpecException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
byte[] bArrA = a(str, str2);
if (bArrA != null) {
return Base64.encodeToString(bArrA, 0);
}
return null;
}
}
发现在i.b方法中 byte[] bArrA = a(str, str2) 调用i.a方法,真正的操作在i.a方法中,从上面的代码可知是”RSA/ECB/PKCS1PADDING”加密方式;加密完后通过i.b方法转换成base64编码;最后i.b方法调用返回AuthKey.encrypt方法,再返回AuthKey.encryptPassword方法,最后赋值给strEncryptPassword字符串,最后通过login方法将加密后的密码传输给服务器
// 代码段2.7
public String encrypt(String str) {
return i.b(str, this.key.replaceFirst("-----BEGIN PUBLIC KEY-----\n", "").replace("\n-----END PUBLIC KEY-----\n", ""));
}
public String encryptPassword(String str) {
return encrypt(this.hash + str);
}
}
----------------------------------------------------------------------------
private final AuthKey p() throws AccountException {
return (AuthKey) v(E().getKeyV2());
}
-----------------------------------------------------------------------------
AuthKey authKeyP = biliPassportApi.p();
String strEncryptPassword = authKeyP != null ? authKeyP.encryptPassword(str2) : null;
------------------------------------------------------------------------------
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
2.2 逆向pairZ
// 代码段2.8
BiliPassportApi biliPassportApi = f141067a;
AuthKey authKeyP = biliPassportApi.p();
String strEncryptPassword = authKeyP != null ? authKeyP.encryptPassword(str2) : null;
Pair<String, String> pairZ = biliPassportApi.z(authKeyP, new Function0() { // from class: com.bilibili.lib.accounts.z
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BiliPassportApi.N(deviceMetaDelegate);
}
});
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
biliCallLogin.setApiTracker(new AccountApiTracker(biliCallLogin.getApiTracker()));
Response<GeneralResponse<AuthInfo>> responseExecute = biliCallLogin.execute();
由定位的代码克制pairZ来自于biliPassportApi.z方法
// 代码段2.9
Pair<String, String> pairZ = biliPassportApi.z(authKeyP, new Function0() { // from class: com.bilibili.lib.accounts.z
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BiliPassportApi.N(deviceMetaDelegate);
}
});
再次定位biliPassportApi.z方法,注意:Pair类是java中提供存储、处理键值对的一种类
// 代码段2.10
private final Pair<String, String> z(AuthKey authKey, Function0<String> function0) {
Pair<String, String> pair = new Pair<>(null, null);
if (authKey == null) { // 若authKey为空,尝试重新去biliPassportApi.p()方法重新获取
try {
authKey = p();
if (authKey == null) { //若还是为空的话,只能返回空的pair实例对象
return pair;
}
} catch (Exception e17) {
AccountLog.Delegate log = AccountRuntime.getLog();
String message = e17.getMessage();
if (message == null) {
message = "";
}
log.e("BiliPassportApi", message, e17);
return pair;
}
}
String strInvoke = function0.invoke();
if (strInvoke != null && strInvoke.length() != 0) {
Pair<String, String> pairC = c92.j.c(strInvoke.getBytes(Charsets.UTF_8));
String strComponent1 = pairC.component1();
return new Pair<>(authKey.encrypt(strComponent1), pairC.component2());
}
return pair;
}
String strInvoke = function0.invoke(); 这一句代码中function0.invoke()的实际值来自于代码2.9中BiliPassportApi.N(deviceMetaDelegate);
再去定位BiliPassportApi.N(deviceMetaDelegate)
public static final String N(DeviceMetaDelegate deviceMetaDelegate) {
return h0.a(deviceMetaDelegate);
}
BiliPassportApi.N再去调用h0.a方法,再定位
public final class h0 {
@Nullable
public static final String a(@Nullable DeviceMetaDelegate deviceMetaDelegate) {
if (!b() || deviceMetaDelegate == null) {
return null;
}
return GsonKt.toJsonString(deviceMetaDelegate.getDeviceMeta());
}
private static final boolean b() {
Function2<String, Boolean, Boolean> ab6 = AccountConfig.INSTANCE.getAb();
Boolean bool = Boolean.TRUE;
return ab6.invoke("api.enable-upload-device-meta", bool) == bool;
}
}
实际上h0.a方法就是deviceMetaDelegate.getDeviceMeta()方获取安卓设备的一些元数据,并转换成字符串进行展示,以下是通过frida hook脚本获取到的GsonKt.toJsonString(deviceMetaDelegate.getDeviceMeta())文本值
var h0 = Java.use("com.bilibili.lib.accounts.h0");
h0.a.implementation = function(deviceMetaDelegate){
console.log('传入参数deviceMetaDelegate:',JSON.stringify(deviceMetaDelegate, null, 2));
var res = this.a(deviceMetaDelegate);
console.log('h0.a返回值是:',res);
return res;
}
输出
[Pixel 5::哔哩哔哩 ]->
传入参数deviceMetaDelegate: "<instance: com.bilibili.lib.accounts.DeviceMetaDelegate, $className: com.bilibili.gripper.legacy.h$a$c>"
h0.a返回值是: {"ui_version":"rd1a.200810.022.a4","virtualproc":"[]","sensors_info":"...","batteryState":"BATTERY_STATUS_FULL","aaid":"","model":"Pixel 5","band":"g7250-00016-200911-B-6826885","app_id":"1","brand":"google","cpuCount":"8","sys_ts":"1770275231599",
"battery_technology":"Unknown","props":{"net.hostname":"","ro.boot.hardware":"redfin","gsm.sim.state":"ABSENT","ro.build.date.utc":"1600153174","ro.product.device":"redfin","persist.sys.language":"","ro.debuggable":"0","ro.build.type":"user","net.gprs.local-ip":"","ro.build.tags":"release-keys","http.proxy":"","ro.serialno":"","ro.boot.flash.locked":"1","persist.sys.country":"","ro.boot.serialno":"",
......
......
......
"gadid":"","virtual":"0","memory":"7820144640","emu":"000","is_root":"true","sys":{"product":"redfin","cpu_model_name":"","display":"RD1A.200810.022.A4","cpu_abi_list":"arm64-v8a,armeabi-v7a,armeabi","cpu_abi_libc":"ARM","manufacturer":"Google","cpu_hardware":"Qualcomm Technologies, Inc LITO","cpu_processor":"AArch64 Processor rev 14 (aarch64)","cpu_abi_libc64":"ARM64","cpu_abi":"arm64-v8a","serial":"unknown","cpu_features":"fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp","fingerprint":"google/redfin/redfin:11/RD1A.200810.022.A4/6835977:user/release-keys","cpu_abi2":"","device":"redfin","hardware":"redfin"},"mem":"7820144640","root":true,"sdkver":"0.2.4","boot":"61795307","udid":"7.....b4","user_agent":"Mozilla/5.0 BiliDroid/8.81.0 (xxx@gmail.com)","os":"android","free_memory":"3619889152","androidapp20":"","last_dump_ts":"1770275223026","brightness":"45","kernel_version":"4.19.110-g9ceb3bf92e0a-ab6790968","usb_connected":"1","cpuFreq":"1804800","wifimaclist":[],"gps_sensor":"1","battery_health":"2","buvid_local":"...3185B42.....C0184....D"}
从脚本输出结果可知deviceMetaDelegate类型为com.bilibili.lib.accounts.DeviceMetaDelegate,deviceMetaDelegate是com.bilibili.gripper.legacy.h$a$c这个具体实现类的实例,定位com.bilibili.gripper.legacy.h$a$c这个实现类
private final long c() {
try {
Object systemService = BiliContext.application().getSystemService("activity");
ActivityManager activityManager = systemService instanceof ActivityManager ? (ActivityManager) systemService : null;
if (activityManager == null) {
return 0L;
}
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo.availMem;
} catch (Throwable unused) {
return 0L;
}
}
根据js脚本输出以及com.bilibili.gripper.legacy.h$a$c类的代码,deviceMetaDelegate对象实际上存储了大量的android系统信息(包括安装的应用,系统版本,是否root,cpu运行情况,内存及存储大小等等)
通过h0.a通过deviceMetaDelegate获取系统信息后,最后转换成字符串,把字符串结果返回给BiliPassportApi.N()方法,最后再返回给biliPassportApi.z()方法的function0.invoke()进行处理,也就是下面代码中的strInvoke字符串
//biliPassportApi.z部分代码段
String strInvoke = function0.invoke();
if (strInvoke != null && strInvoke.length() != 0) {
Pair<String, String> pairC = c92.j.c(strInvoke.getBytes(Charsets.UTF_8));
String strComponent1 = pairC.component1();
return new Pair<>(authKey.encrypt(strComponent1), pairC.component2());
}
继续看代码中的pairC的产生过程,来源于c92.j.c方法,其入参为strInvoke字符串的字节数组。定位c92.j.c方法
@NotNull
public static final Pair<String, String> c(@NotNull byte[] bArr) {
String strB = b(16);
return new Pair<>(strB, a(d(bArr, strB.getBytes(Charsets.UTF_8))));
}
//strB不知道是什么,再定位b()的代码
private static final String b(int i17) {
Random random = new Random();
StringBuffer stringBuffer = new StringBuffer();
for (int i18 = 0; i18 < i17; i18++) {
stringBuffer.append(f39707a.charAt(random.nextInt(r3.length() - 1)));
}
return stringBuffer.toString();
}
//典型的通过StringBuffer生成字符串,根据strB = b(16),以及b方法定义,生成16个的随机的字符串赋值给strB
---------------------------------------------------------------------------------------------
@NotNull
public static final Pair<String, String> c(@NotNull byte[] bArr) {
String strB = b(16);
return new Pair<>(strB, a(d(bArr, strB.getBytes(Charsets.UTF_8))));
}
// a(d(bArr, strB.getBytes(Charsets.UTF_8))) ,传入两个参数,先经过d()方法处理,返回后再通过a()方法处理
//定位 d() 方法 发现是aes-cbc模式加密 PKCS5填充
@Nullable
public static final byte[] d(@Nullable byte[] bArr, @NotNull byte[] bArr2) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
if (bArr == null) {
return null;
}
try {
//strB作为d的bArr2参数传入,生成IV
IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr2);
//aes-cbc模式加密 PKCS5填充
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 经过定位,代码为public static final String KEY_AES = "AES";
// cipher.init(1, new SecretKeySpec(bArr2, 'AES'), ivParameterSpec);
// 相当于还是用通过strB生成AES加密的KEY,说明KEY以及IV是相同的
cipher.init(1, new SecretKeySpec(bArr2, ConstantInternal.KEY_AES), ivParameterSpec);
// 最后执行加密
return cipher.doFinal(bArr);
} catch (Exception e17) {
e17.printStackTrace();
return null;
}
}
//定位a方法
//经典的使用StringBuffer类,将字节数组转换成十六进制字符串,最后转成大写输出
private static final String a(byte[] bArr) {
if (bArr == null) {
return null;
}
StringBuffer stringBuffer = new StringBuffer(bArr.length * 2);
for (byte b17 : bArr) {
String hexString = Integer.toHexString(b17 & 255);
if (hexString.length() == 1) {
stringBuffer.append("0");
}
stringBuffer.append(hexString);
}
return stringBuffer.toString().toUpperCase(Locale.getDefault());
}
综上,c92.j.c()方法(代码如下),strB为十六位随机字符串组合,同时作为c92.j.d() – AES加密方法的KEY以及IV,最后通过a方法将加密后的字节数组转换成十六进制字符串大写形式
c92.j.c()方法返回了一个key-value键值对,key为strB , value为十六进制大写形式的字符串
@NotNull
public static final Pair<String, String> c(@NotNull byte[] bArr) {
String strB = b(16);
return new Pair<>(strB, a(d(bArr, strB.getBytes(Charsets.UTF_8))));
}
回顾biliPassportApi.z方法,c92.j.c()方法以strInvoke字符串(存储android设备系统信息)为入参,将android设备系统信息经过一系列的加密以及十六进制文本转换处理,结合加密的strB(实际上就是加密用到的key以及iv),生成Pair对象赋值给pairC
Key-iv代码key与iv相同,说明bilibili的aes加密使用的key以及iv存在重用的问题
pairC > (Key-iv, AES加密的经过十六进制文本处理的android设备系统信息)
//biliPassportApi.z部分代码段
String strInvoke = function0.invoke();
if (strInvoke != null && strInvoke.length() != 0) {
Pair<String, String> pairC = c92.j.c(strInvoke.getBytes(Charsets.UTF_8));
String strComponent1 = pairC.component1();
//回顾2.1的strEncryptPassword逆向,authKey.encrypt()方法实际上是RSA公钥加密
// strComponent1 ==> pairC.component1() ==> Key-Value键值对的Key ===> strB ===> AES加密的KEY以及IV
// 避免直接暴露AES加密的KEY以及IV
return new Pair<>(authKey.encrypt(strComponent1), pairC.component2());
}
// 从这个Pair类的定义来看,component1代表A,即Key-Value键值对的Key;component2代表B,即Key-Value键值对的Value
public final class Pair<A, B> implements Serializable {
private final A first;
private final B second;
public Pair(A a17, B b17) {
this.first = a17;
this.second = b17;
}
public final A component1() {
return this.first;
}
public final B component2() {
return this.second;
}
...
...
...
}
biliPassportApi.z > (RSA加密后的Key-iv,AES加密的AES加密的经过十六进制文本处理的android设备系统信息)
用hook脚本进行验证
Java.perform(function () {
var BiliPassportApi = Java.use("com.bilibili.lib.accounts.BiliPassportApi");
BiliPassportApi.z.implementation = function(authKey,function0){
console.log('\n传入参数str:',authKey);
console.log('\n传入参数str1:',function0);
var res = this.z(authKey,function0);
console.log('\nBiliPassportApi.z返回值是:',res);
return res;
}
});
输出
BiliPassportApi.z返回值是:
(cvWM03RvxdoGDc6/P+ov+P2I8eFOhSlNpbuqnlExhxNiOgRlAMqbCm6PwhHALG7F7yhbCM8+MgoS
v3yTbxYzQRXjteDKY0aJSpOcQlRhxI8TztwMN20yF8A+hKDz3dczdcw4yuTMXgy9eJVrk41QJDPa
OWZqs8gzsrECuo5GETM=
, 15C0ABDD55A99E2FD01C32553EAB553282BE6A90C53D6F5A971936C29C0073B769E987103CC16D89471E9D2534522CF5AEC37589BD6483753017A51FA5B9BD37716402121472B485F168791B4D82FA5D9486C22B21306B3CF239A3B3F250AE8AAAD697F
.......
.......
.......
03ED06D3AFB4CE14AA69D28028AF7E44BF8352E28775C1BBEA9E5F7CB03C9FFD6E0E1A816056FF342E0FF100C1CEC8184684B493CE108F85476B485811532D85D714070A15CB16252CAC09A96)
再次回顾登录请求login方法, 就知道pairZ = biliPassportApi.z = (RSA加密后的Key-iv,AES加密的AES加密的经过十六进制文本处理的android设备系统信息)
pairZ.component1() => RSA加密后的Key-iv
pairZ.component2() => AES加密的AES加密的经过十六进制文本处理的android设备系统信息
public static final AuthInfo M(@Nullable String str, @Nullable String str2, @Nullable Map<String, String> map, @Nullable String str3, @Nullable String str4, @Nullable String str5, @Nullable String str6, @Nullable String str7, @Nullable String str8, @Nullable final DeviceMetaDelegate deviceMetaDelegate) throws AccountException {
String str9;
try {
BiliPassportApi biliPassportApi = f141067a;
AuthKey authKeyP = biliPassportApi.p();
String strEncryptPassword = authKeyP != null ? authKeyP.encryptPassword(str2) : null;
Pair<String, String> pairZ = biliPassportApi.z(authKeyP, new Function0() { // from class: com.bilibili.lib.accounts.z
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BiliPassportApi.N(deviceMetaDelegate);
}
});
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
......
.....
.....
}
}
@FormUrlEncoded
@POST("/x/passport-login/oauth2/login")
@RequestInterceptor(AuthInterceptor.class)
BiliCall<GeneralResponse<AuthInfo>> login(@Field("username") String str, @Field("password") String str2, @Field("device_meta") String str3, @Field("dt") String str4, @Field("from_pv") String str5, @Field("from_url") String str6, @Field("login_session_id") String str7, @Field("spm_id") String str8, @Field("device_tourist_id") String str9, @Field("extend") String str10, @FieldMap Map<String, String> map);
-----------------------------------------------------------------------------------------------
public static final AuthInfo M(@Nullable String str, @Nullable String str2, @Nullable Map<String, String> map, @Nullable String str3, @Nullable String str4, @Nullable String str5, @Nullable String str6, @Nullable String str7, @Nullable String str8, @Nullable final DeviceMetaDelegate deviceMetaDelegate) throws AccountException {
String str9;
try {
BiliPassportApi biliPassportApi = f141067a;
AuthKey authKeyP = biliPassportApi.p();
String strEncryptPassword = authKeyP != null ? authKeyP.encryptPassword(str2) : null;
Pair<String, String> pairZ = biliPassportApi.z(authKeyP, new Function0() { // from class: com.bilibili.lib.accounts.z
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BiliPassportApi.N(deviceMetaDelegate);
}
});
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
......
.....
.....
}
}
---------------------------------------------------------------------------------------------
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
可以看到有一下参数需要确认:
str ===> 对应请求包的username
strEncryptPassword ===> 对应请求包的password
pairZ.component2() ===> 补充:AES加密的AES加密的经过十六进制文本处理的android设备系统信息
pairZ.component1() ===> 补充:RSA加密后的Key-iv
str3
str4
str5
str6
str7
str8
PassportCommParams.attachDeviceParams(map)
2.3 逆向str3—str8
先用js 脚本hook参数值
Java.perform(function () {
var BiliPassportApi = Java.use("com.bilibili.lib.accounts.BiliPassportApi");
BiliPassportApi.M.implementation = function(str,str2,map,str3,str4,str5,str6,str7,str8,deviceMetaDelegate){
console.log('\n传入参数str:',str);
console.log('传入参数str1:',str2);
console.log('传入参数map:',JSON.stringify(map));
console.log('传入参数str3:',str3);
console.log('传入参数str4:',str4);
console.log('传入参数str5:',str5);
console.log('传入参数str6:',str6);
console.log('传入参数str7:',str7);
console.log('传入参数str8:',str8);
console.log('传入参数deviceMetaDelegate:',deviceMetaDelegate);
var res = this.M(str,str2,map,str3,str4,str5,str6,str7,str8,deviceMetaDelegate);
console.log('BiliPassportApi.M返回值是:',res);
return res;
}
});
// 输出
传入参数str: (明文用户名,一般是手机号)
传入参数str1: (明文密码)
传入参数map: "<instance: java.util.Map, $className: kotlin.collections.EmptyMap>"
// 代表map是EmptyMap实现类的对象实例,内部为空
传入参数str3: null
传入参数str4: null
传入参数str5: 51087d85e05308b379266ea66ae7fae1
传入参数str6: main.homepage.bottombar.myinfo
传入参数str7: 25822977116578
传入参数str8: null
传入参数deviceMetaDelegate: com.bilibili.gripper.legacy.h$a$c@60f41ed
这里不知道map str3-str8是什么,可以直接向下追踪BiliPassportApi.M这个方法的调用链(调用堆栈),确定最原始调用函数
package com.bilibili.lib.accounts;
public class u {
......
......
public VerifyBundle R(String str, String str2, String str3, String str4, @NonNull Map<String, String> map, String str5, String str6, String str7, String str8) throws AccountException {
AccountRuntime.log.i("Bili_Accounts", "passwordLogin status");
// 这里的v()实际上是之前2.2小结中已经逆向追踪出来的deviceMetaDelegate
AuthInfo authInfoM = BiliPassportApi.M(str, str2, map, str3, str4, str5, str6, str7, str8, v());
Z(authInfoM);
VerifyBundle verifyBundle = new VerifyBundle();
AccessToken accessToken = authInfoM.accessToken;
verifyBundle.accessKey = accessToken == null ? null : accessToken.mAccessKey;
verifyBundle.verifyURL = authInfoM.url;
verifyBundle.status = authInfoM.status;
verifyBundle.msg = authInfoM.msg;
AccountRuntime.log.i("Bili_Accounts", "passwordLogin status = " + authInfoM.status);
return verifyBundle;
}
......
......
}
protected DeviceMetaDelegate v() {
return AccountConfig.INSTANCE.getDeviceMetaDelegate();
}
// u.R方法调用BiliPassportApi.M
--------------------------------------------------------------------
package com.bilibili.lib.accounts;
public final class k implements IAccountRepo {
......
......
@Nullable
public VerifyBundle loginV3(@Nullable String str, @Nullable String str2, @Nullable String str3, @Nullable String str4, @NotNull Map<String, String> map, @Nullable String str5, @Nullable String str6, @Nullable String str7, @Nullable String str8) {
return u.p(this.f141126a).R(str, str2, str3, str4, map, str5, str6, str7, str8);
}
......
......
}
// k.loginV3方法调用u.R方法
--------------------------------------------------------------------
package com.bilibili.lib.accounts;
public class BiliAccounts {
......
@WorkerThread
public VerifyBundle loginV3(String str, String str2, String str3, String str4, @NonNull Map<String, String> map, String str5, String str6, String str7, String str8) throws AccountException {
return b().loginV3(str, str2, str3, str4, map, str5, str6, str7, str8);
}
......
......
}
// BiliAccounts.loginV3方法调用k.loginV3(因为代码混淆的原因,这里显示是b().k.loginV3)
---------------------------------------------------------------------
package tv.danmaku.bili.fullscreen.service;
final class PasswordLoginService$login$2 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super l>, Object> {
public final Object invokeSuspend(Object obj) {
Object objD;
Object objD2;
Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
int i17 = this.label;
try {
} catch (AccountException e17) {
BLog.e("FullscreenLogin", "Failed login for account " + this.$account, e17);
LoginResultService loginResultService = LoginResultService.f403722a;
tv5.e eVar = this.$loginWay;
this.label = 2;
objD = LoginResultService.d(loginResultService, eVar, null, e17, this, 2, null);
if (objD == coroutine_suspended) {
return coroutine_suspended;
}
}
if (i17 == 0) {
ResultKt.throwOnFailure(obj);
// 在这里!!!!!!!!!!!!!!!!
VerifyBundle verifyBundleLoginV3 = BiliAccounts.get(FoundationAlias.getFapp()).loginV3(this.$account, this.$password, this.$loginReportParams.d(), this.$loginReportParams.e(), this.$captcha, this.$loginReportParams.c(), this.$loginReportParams.b(), this.$loginReportParams.f(), this.$loginReportParams.a());
BLog.i("FullscreenLogin", "Finish login for account " + this.$account);
LoginResultService loginResultService2 = LoginResultService.f403722a;
tv5.e eVar2 = this.$loginWay;
this.label = 1;
objD2 = LoginResultService.d(loginResultService2, eVar2, verifyBundleLoginV3, null, this, 4, null);
if (objD2 == coroutine_suspended) {
return coroutine_suspended;
}
} else {
if (i17 != 1) {
if (i17 != 2) {
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
ResultKt.throwOnFailure(obj);
objD = obj;
return (l) objD;
}
ResultKt.throwOnFailure(obj);
objD2 = obj;
}
return (l) objD2;
}
}
// PasswordLoginService$login$2.invokeSuspend调用BiliAccounts.loginV3
至此整个调用过程为
PasswordLoginService$login$2.invokeSuspend --> BiliAccounts.loginV3 --> k.loginV3 --> u.R --> BiliPassportApi.M --> login()
单独将PasswordLoginService$login$2.invokeSuspend中的登录代码拿出来
BiliAccounts.get(FoundationAlias.getFapp()).loginV3(
this.$account, ---> str1
this.$password, ---> str2
this.$loginReportParams.d(), ---> str3
this.$loginReportParams.e(), ---> str4
this.$captcha, ---> map
this.$loginReportParams.c(), ---> str5
this.$loginReportParams.b(), ---> str6
this.$loginReportParams.f(), ---> str7
this.$loginReportParams.a() ---> str8
);
再定位this.$loginReportParams的a、b、c、d、e、f()方法
package tv.danmaku.bili.fullscreen.service;
import androidx.compose.runtime.internal.StabilityInferred;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/* compiled from: BL */
@StabilityInferred(parameters = 1)
/* loaded from: classes29.dex */
public final class b0 {
/* renamed from: a, reason: collision with root package name */
@NotNull
private final String f403738a;
/* renamed from: b, reason: collision with root package name */
@NotNull
private final String f403739b;
/* renamed from: c, reason: collision with root package name */
@NotNull
private final String f403740c;
/* renamed from: d, reason: collision with root package name */
@NotNull
private final String f403741d;
/* renamed from: e, reason: collision with root package name */
@NotNull
private final String f403742e;
/* renamed from: f, reason: collision with root package name */
@NotNull
private final String f403743f;
/* renamed from: g, reason: collision with root package name */
@NotNull
private final String f403744g;
public b0(@NotNull String str, @NotNull String str2, @NotNull String str3, @NotNull String str4, @NotNull String str5, @NotNull String str6, @NotNull String str7) {
this.f403738a = str;
this.f403739b = str2;
this.f403740c = str3;
this.f403741d = str4;
this.f403742e = str5;
this.f403743f = str6;
this.f403744g = str7;
}
@NotNull
public final String a() {
return this.f403743f;
}
@NotNull
public final String b() {
return this.f403741d;
}
@NotNull
public final String c() {
return this.f403740c;
}
@NotNull
public final String d() {
return this.f403738a;
}
@NotNull
public final String e() {
return this.f403739b;
}
......
@NotNull
public final String f() {
return this.f403742e;
}
......
}
这个b0类里并没有找到相关的set方法,应该是直接通过b0类的构造方法传值,这里通过hook脚本获取
Java.perform(function () {
var b0 = Java.use("tv.danmaku.bili.fullscreen.service.b0");
b0.$init.implementation = function(str,str2,str3,str4,str5,str6,str7){
console.log("str=",str);
console.log("str2=",str2);
console.log("str3=",str3);
console.log("str4=",str4);
console.log("str5=",str5);
console.log("str6=",str6);
console.log("str7=",str7);
var res = this.$init(str,str2,str3,str4,str5,str6,str7);
console.log("res=",res);
return res;
};
});
// 输出
str= ---> this.f403738a ---> d() ----> 对应login的str3 ---> null
str2= ---> this.f403739b ---> e() ----> 对应login的str4 ---> null
str3= 51087d85e05308b379266ea66ae7fae1 ---> this.f403740c ---> c() ----> 对应login的str5
str4= main.homepage.bottombar.myinfo ---> this.f403741d ---> b() ----> 对应login的str6
str5= 25822977116578 ---> this.f403742e ---> f() ----> 对应login的str7
str6= ---> this.f403743f ---> a() ----> 对应login的str8 ---> null
str7= pwd_fullscreen_new ---> this.f403744g --->
经过多次执行,发现str3、str4、str5以及str7的值是固定的
str3= 51087d85e05308b379266ea66ae7fae1 ---> this.f403740c ---> c() ----> 对应login的str5
str4= main.homepage.bottombar.myinfo ---> this.f403741d ---> b() ----> 对应login的str6
str5= 25822977116578 ---> this.f403742e ---> f() ----> 对应login的str7
// BiliPassportApi.M
// 输出
传入参数str: (明文用户名,一般是手机号)
传入参数str1: (明文密码)
传入参数map: "<instance: java.util.Map, $className: kotlin.collections.EmptyMap>"
// 代表map是EmptyMap实现类的对象实例,内部为空
传入参数str3: null
传入参数str4: null
传入参数str5: 51087d85e05308b379266ea66ae7fae1
传入参数str6: main.homepage.bottombar.myinfo
传入参数str7: 25822977116578
传入参数str8: null
传入参数deviceMetaDelegate: com.bilibili.gripper.legacy.h$a$c@60f41ed
补充login方法已经完成逆向的参数:
BiliCall<GeneralResponse<AuthInfo>> biliCallLogin = biliPassportApi.E().login(str, strEncryptPassword, pairZ.component2(), pairZ.component1(), str3, str4, str5, str6, str7, str8, PassportCommParams.attachDeviceParams(map));
可以看到有一下参数需要确认:
str ===> 对应请求包的username
strEncryptPassword ===> 对应请求包的password
pairZ.component2() ===> AES加密的AES加密的经过十六进制文本处理的android设备系统信息
pairZ.component1() ===> RSA加密后的Key-iv
str3 ===> 补充:null
str4 ===> 补充:null
str5 ===> 补充:51087d85e05308b379266ea66ae7fae1
str6 ===> 补充:main.homepage.bottombar.myinfo
str7 ===> 补充:25822977116578
str8 ===> 补充:null
PassportCommParams.attachDeviceParams(map) ==> 已知map是空的,在本节开头第一个js脚本输出中获知map是空的
2.4 逆向PassportCommParams.attachDeviceParams
定位PassportCommParams.attachDeviceParams,看类定义:
package com.bilibili.lib.accounts;
import android.os.Build;
import com.bilibili.column.base.ColumnMainProcessProvider;
import com.facebook.devicerequests.internal.DeviceRequestsHelper;
import java.util.HashMap;
import java.util.Map;
/* compiled from: BL */
/* loaded from: classes12.dex */
public class PassportCommParams {
public static Map<String, String> attachDeviceParams(Map<String, String> map) {
HashMap map2 = new HashMap();
if (map != null) {
map2.putAll(map);
}
map2.put(DeviceRequestsHelper.DEVICE_INFO_DEVICE, "phone");
map2.put("local_id", getLocalId());
map2.put("bili_local_id", getBiliLocalId());
map2.put(ColumnMainProcessProvider.EXTRA_KEY_DEVICE_ID, getDeviceId());
map2.put("buvid", AccountConfig.paramDelegate.getBuvid());
map2.put("device_name", getDeviceName());
map2.put("device_platform", getDevicePlatFrom());
if (BiliAccounts.get(AccountRuntime.getApp()).isLogin()) {
map2.put("from_access_key", BiliAccounts.get(AccountRuntime.getApp()).getAccessKey());
}
return map2;
}
public static Map<String, String> createDeviceParams() {
return attachDeviceParams(null);
}
public static String getBiliLocalId() {
return AccountConfig.paramDelegate.getBiliLocalID();
}
public static String getDeviceId() {
return AccountConfig.paramDelegate.getDeviceID();
}
public static String getDeviceName() {
return Build.MANUFACTURER + Build.MODEL;
}
public static String getDevicePlatFrom() {
return "Android" + Build.VERSION.RELEASE + Build.MANUFACTURER + Build.MODEL;
}
public static String getLocalId() {
return AccountConfig.paramDelegate.getBuvid();
}
}
重点在这里
public static Map<String, String> attachDeviceParams(Map<String, String> map) {
HashMap map2 = new HashMap();
if (map != null) {
map2.putAll(map);
}
map2.put(DeviceRequestsHelper.DEVICE_INFO_DEVICE, "phone");
map2.put("local_id", getLocalId());
map2.put("bili_local_id", getBiliLocalId());
map2.put(ColumnMainProcessProvider.EXTRA_KEY_DEVICE_ID, getDeviceId());
map2.put("buvid", AccountConfig.paramDelegate.getBuvid());
map2.put("device_name", getDeviceName());
map2.put("device_platform", getDevicePlatFrom());
if (BiliAccounts.get(AccountRuntime.getApp()).isLogin()) {
map2.put("from_access_key", BiliAccounts.get(AccountRuntime.getApp()).getAccessKey());
}
return map2;
}
// 入参map是空的,2.3小结已总结
// 创建了一个hashMap对象,往里面添加了几个key-value键值对
下面将对map2中的相关参数及参数值进行追踪定位逆向
2.4.1 DeviceRequestsHelper.DEVICE_INFO_DEVICE
定位DeviceRequestsHelper.DEVICE_INFO_DEVICE,代码如下:
package com.facebook.devicerequests.internal;
public final class DeviceRequestsHelper {
@NotNull
public static final String DEVICE_INFO_DEVICE = "device";
......
......
}
可知map2.put(DeviceRequestsHelper.DEVICE_INFO_DEVICE, "phone")等价于map2.put("device", "phone")
2.4.2 getLocalId()
2.4.2.1 反复定位CommonParamDelegate接口实现位置
定位getLocalId(),代码如下
public static String getLocalId() {
return AccountConfig.paramDelegate.getBuvid();
}
getLocalId()返回值来源于getBuvid()方法,定位AccountConfig.paramDelegate.getBuvid(),实质上是CommonParamDelegate这个接口定义了这些getBuvid方法,我们的目的是要找到CommonParamDelegate的实现类,只有找到CommonParamDelegate的实现类,才能确定getBuvid方法是怎么定义的
package com.bilibili.lib.accounts;
public interface CommonParamDelegate {
......
@NotNull
String getBuvid();
......
}
由于是接口,没有具体是实现步骤,定位AccountConfig.paramDelegate,代码如下
package com.bilibili.lib.accounts;
public final class AccountConfig {
public static CommonParamDelegate paramDelegate;
......
......
public final void setParamDelegate(@NotNull CommonParamDelegate commonParamDelegate) {
paramDelegate = commonParamDelegate;
}
......
}
找到了初始化paramDelegate的setParamDelegate()方法,可以进一步定位到AccountConfig.init()方法调用了setParamDelegate()方法
package com.bilibili.lib.accounts;
public final class AccountConfig {
public final void init(@NotNull Application application, @NotNull CommonParamDelegate commonParamDelegate, @Nullable DeviceMetaDelegate deviceMetaDelegate, @Nullable BiliAccountsReporter.ReportDelegate reportDelegate, @Nullable IConfigDelegate iConfigDelegate) {
if (iConfigDelegate != null) {
f141047a = iConfigDelegate.ab();
f141048b = iConfigDelegate.abWithDefault();
}
if (iConfigDelegate != null) {
f141049c = iConfigDelegate.config();
}
// 在这里调用了setParamDelegate()方法
setParamDelegate(commonParamDelegate);
f141050d = deviceMetaDelegate;
BiliAccountsReporter.INSTANCE.setReportDelegate(reportDelegate);
f141051e = true;
}
}
再次定位,c.initAccount()调用了AccountConfig.init()方法
package com.bilibili.gripper.account;
public final class c {
@RestrictedProcesses({StandardExecutor.MAIN, "web"})
@Nullable
@PreTrigger(trigger = StandardTriggers.ON_PRIVACY_ALLOWED)
@Producer(dependsOn = {"infra.initAccountContainer"}, id = "infra.initAccount", singleton = true)
public static final Object initAccount(@NotNull GFoundation gFoundation, @NotNull GEnvironment gEnvironment, @NotNull Continuation<? super Unit> continuation) {
AccountConfig accountConfig = AccountConfig.INSTANCE;
Application app = gFoundation.getApp();
h.a aVar = h.f140133a;
accountConfig.init(app, aVar.d(gFoundation.getInternalVersionCode()), aVar.g(gFoundation.getApp()), aVar.k(), aVar.e());
return Unit.INSTANCE;
}
......
......
}
2.4.2.2 确认接口实现类C1168a
其中aVar.d(gFoundation.getInternalVersionCode())就是传给accountConfig.init()方法的commonParamDelegate
通过定位可确定gFoundation.getInternalVersionCode()是一个整数,再确定aVar.d()方法
package com.bilibili.gripper.api.foundation;
public interface GFoundation {
......
int getInternalVersionCode();
......
}
再确定aVar.d()方法,返回一个C1168a对象
package com.bilibili.gripper.legacy;
public final class h {
.......
@NotNull
public final CommonParamDelegate d(int i17) {
return new C1168a(i17);
}
......
}
再定位C1168a位置,由public static final class C1168a implements CommonParamDelegate这行代码,可以看出C1168a实现了CommonParamDelegate这个接口,且实现了CommonParamDelegate所有接口方法
package com.bilibili.gripper.legacy;
public final class h {
public static final class a {
/* compiled from: BL */
/* renamed from: com.bilibili.gripper.legacy.h$a$a */
public static final class C1168a implements CommonParamDelegate {
/* renamed from: a */
final /* synthetic */ int f140135a;
C1168a(int i17) {
this.f140135a = i17;
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public long compatSwitchDelayTimeMillis() {
Long longOrNull;
String str = ConfigManager.Companion.config().get("account.compat_account_switch_delay", "2500");
if (str == null || (longOrNull = StringsKt.toLongOrNull(str)) == null) {
return 2500L;
}
return longOrNull.longValue();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ boolean enableAppSecretSign() {
return com.bilibili.lib.accounts.f.b(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getAppDefaultUA() {
return BiliConfig.getAppDefaultUA();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getAppKey() {
return h.f140133a.o() ? LibBili.getAppKey(BiliConfig.getMobiApp(), 1, 0) : BiliConfig.getAppKey();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getAppSecret() {
return com.bilibili.lib.accounts.f.c(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getAppVersionCode() {
return String.valueOf(BiliConfig.getBiliVersionCode());
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getBiliLocalID() {
return BiliIds.buvidLocal();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getBuvid() {
return BuvidHelper.getBuvid();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getChannel() {
return w36.d.f431144a.b();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getCurrentLocale() {
return BiliConfig.getCurrentLocale();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getDeviceID() {
return BiliIds.fingerprint();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getFacebookAppId() {
return com.bilibili.lib.accounts.f.d(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getGoogleClientId() {
return com.bilibili.lib.accounts.f.e(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public int getInternalVersion() {
return this.f140135a;
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getMobiApp() {
return Foundation.Companion.instance().getApps().getMobiApp();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getMobileAppID() {
return com.bilibili.lib.accounts.f.g(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getMobileAppSecret() {
return com.bilibili.lib.accounts.f.h(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public int getNetType() {
return PhoneUtils.netType(FoundationAlias.getFapp());
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public int getNetwork() {
int network = ConnectivityMonitor.getInstance().getNetwork();
if (network != 1) {
return network != 2 ? 0 : 1;
}
return 2;
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getNonAccountAppKey() {
return BiliConfig.getAppKey();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getSystemLocale() {
return BiliConfig.getSystemLocale();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getTelecomAppID() {
return com.bilibili.lib.accounts.f.k(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getTelecomAppSecret() {
return com.bilibili.lib.accounts.f.l(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getUnicomAppID() {
return com.bilibili.lib.accounts.f.m(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getUnicomAppSecret() {
return com.bilibili.lib.accounts.f.n(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ String getUnicomOnlineAppID() {
return com.bilibili.lib.accounts.f.o(this);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public boolean isLanguageSimplifiedChinese() {
return Intrinsics.areEqual("1", LanguageUtil.getCurrentLanguage(Foundation.Companion.instance().getApp()));
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public boolean isNightTheme(Context context) {
return MultipleThemeUtils.isNightTheme(context);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public int maxUserSize() {
Integer intOrNull;
String str = ConfigManager.Companion.config().get("account.multi_user_max_size", "5");
if (str == null || (intOrNull = StringsKt.toIntOrNull(str)) == null) {
return 5;
}
return intOrNull.intValue();
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public boolean reportTokenDiff() {
return ConfigManager.Companion.ab2().getWithDefault("ff_account_report_change_v2", false);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public boolean supportFacialRecognition() {
Contract<Boolean> contractAb = ConfigManager.Companion.ab();
Boolean bool = Boolean.TRUE;
return contractAb.get("ff_login_control_facial_recognition_enable", bool) == bool;
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public boolean useUnicomOnline() {
return ConfigManager.Companion.ab2().getWithDefault("ff_account_unicom_online_enable", true);
}
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public /* synthetic */ boolean webOrientationAuto() {
return com.bilibili.lib.accounts.f.v(this);
}
}
.......
......
}
在C1168a类中找到了getBuvid()实现方法,是直接返回的BuvidHelper.getBuvid(),说明C1168a.getBuvid()直接调用的BuvidHelper.getBuvid()
@Override // com.bilibili.lib.accounts.CommonParamDelegate
public String getBuvid() {
return BuvidHelper.getBuvid();
}
再定位BuvidHelper.getBuvid(),也是直接返回!INSTANCE.a() ? "" : f142128a.getBuvid(),这是一个三元表达式
package com.bilibili.lib.biliid.api;
public final class BuvidHelper {
@NotNull
public static final BuvidHelper INSTANCE = new BuvidHelper();
/* renamed from: a, reason: collision with root package name */
@NotNull
private static final IBuvid f142128a = (IBuvid) TribeApi.getBundles().a().loadClass("com.bilibili.lib.biliid.api.BuvidHelperImpl").newInstance();
private BuvidHelper() {
}
private final boolean a() {
return BiliGlobalPreferenceHelper.getBLKVSharedPreference(FoundationAlias.getFapp()).getBoolean(PerformanceConfigKey.PRIVACY_ALLOWED, false);
}
@JvmStatic
@NotNull
public static final String getBuvid() {
return !INSTANCE.a() ? "" : f142128a.getBuvid();
}
private final boolean a() {
return BiliGlobalPreferenceHelper.getBLKVSharedPreference(FoundationAlias.getFapp()).getBoolean(PerformanceConfigKey.PRIVACY_ALLOWED, false);
}
@JvmStatic
@NotNull
public static final String getBuvid() {
return !INSTANCE.a() ? "" : f142128a.getBuvid();
}
......
......
}
2.4.2.3 SharedPreference知识点讲解
1、INSTANCE.a()---> BuvidHelper.a() 直接返回BiliGlobalPreferenceHelper.getBLKVSharedPreference(FoundationAlias.getFapp()).getBoolean(PerformanceConfigKey.PRIVACY_ALLOWED, false);,看到类似SharedPreference的字样,应当知道这是确认存储状态的一行代码,
再简单讲解下什么是SharedPreference:SharedPreference是Android平台上一个轻量级的存储类,主要用于保存应用的一些常用配置。它通过键值对的形式将数据保存在xml文件中,通常用于存储简单的数据持久化缓存。
通俗来讲,Android会通过SharedPreference把一些必要的数据以key-vlaue键值对形式存储在xml文件中,这些xml文件通常位于Android系统的/data/data/<包名>/shared_prefs/路径下
以哔哩哔哩的android应用为例,哔哩哔哩的apk包名为tv.danmaku.bili,
![图片[3]-APP逆向-B站登录请求接口-木兮知识库](https://www.mzsec.cn/wp-content/uploads/2026/03/image-20260208123844959-300x123.png)
说明哔哩哔哩应用会把一些数据存储在/data/data/tv.danmaku.bili/shared_prefs/路径下,可以看到很多xml文件
![图片[4]-APP逆向-B站登录请求接口-木兮知识库](https://www.mzsec.cn/wp-content/uploads/2026/03/image-20260208124137727-157x300.png)
同时也注意到哔哩哔哩应用目录下也有一些blkv文件,与上面的BiliGlobalPreferenceHelper.getBLKVSharedPreference中的BLKV字符相对应
可能是哔哩哔哩自定义的一些存储格式
![图片[5]-APP逆向-B站登录请求接口-木兮知识库](https://www.mzsec.cn/wp-content/uploads/2026/03/image-20260208130154600-157x300.png)
2.4.2.4 逆向f142128a.getBuvid()
回到!INSTANCE.a() ? "" : f142128a.getBuvid(); 已经确认了INSTANCE.a()是应用程序确认存储状态的一个方法,如果:
INSTANCE.a()为false或者NULL,!INSTANCE.a()都会返回true,!INSTANCE.a() ? "" : f142128a.getBuvid()等于""
INSTANCE.a()为true或者不为空,!INSTANCE.a()都会返回false,!INSTANCE.a() ? "" : f142128a.getBuvid()等于f142128a.getBuvid()
最终还是需要定位f142128a.getBuvid(),定位源位置后,发现又是一个接口,名为IBuvid,重新跳回BuvidHelper类,查看是哪里实现了IBuvid接口
package com.bilibili.lib.biliid.api;
public interface IBuvid {
@NotNull
String getBuvid();
@NotNull
String getLocalBuvid();
@NotNull
String getRemoteBuvid();
@Nullable
String softGetBuvid();
}
--------------------------------------------------------------------
public final class BuvidHelper {
@NotNull
public static final BuvidHelper INSTANCE = new BuvidHelper();
/* renamed from: a, reason: collision with root package name */
@NotNull
private static final IBuvid f142128a = (IBuvid) TribeApi.getBundles().a().loadClass("com.bilibili.lib.biliid.api.BuvidHelperImpl").newInstance();
private BuvidHelper() {
}
}
从private static final IBuvid f142128a = (IBuvid) TribeApi.getBundles().a().loadClass("com.bilibili.lib.biliid.api.BuvidHelperImpl").newInstance();
这行代码可知,f142128a的值是直接通过反射机制,使用类加载器loadClass加载com.bilibili.lib.biliid.api.BuvidHelperImpl
这个类,并通过newInstance()创建实例,赋值给f142128a
2.4.2.4.1 BuvidHelperImpl –> IBuvid
现在定位com.bilibili.lib.biliid.api.BuvidHelperImpl这个类,这个类实现了IBuvid与IHelper两个接口
package com.bilibili.lib.biliid.api;
public final class BuvidHelperImpl implements IBuvid, IHelper {
@NotNull
private final Function0<Executor> f142129a;
@NotNull
private final d f142130b;
@NotNull
private final l f142131c;
@NotNull
private final p f142132d;
private final boolean f142133e;
private boolean f142134f;
public BuvidHelperImpl() {
Function0<Executor> function0 = new Function0() { // from class: x92.a
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BuvidHelperImpl.b();
}
};
this.f142129a = function0;
d dVar = new d();
this.f142130b = dVar;
l lVar = new l(dVar, new x92.b(null, 1, null), function0);
this.f142131c = lVar;
this.f142132d = new p(dVar, lVar);
this.f142133e = StringsKt.indexOf$default((CharSequence) FoundationAlias.getFapps().getProcessName(), AbstractJsonLexerKt.COLON, 0, false, 6, (Object) null) == -1;
}
/* JADX INFO: Access modifiers changed from: private */
public static final Executor b() {
return Executors.newCachedThreadPool();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void bindReporter(@NotNull Function1<? super Map<String, String>, Unit> function1, @NotNull Function1<? super Map<String, String>, Unit> function12) {
this.f142131c.b(function1);
this.f142132d.b(function12);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void fetchRemote(long j17, boolean z17, @Nullable RemoteBuvidCallback remoteBuvidCallback) {
this.f142132d.c(this.f142129a.invoke(), new c(), j17, z17, remoteBuvidCallback);
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
String strE = this.f142132d.e();
return strE.length() == 0 ? this.f142131c.e() : strE;
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getLocalBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
return this.f142131c.e();
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getRemoteBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
return this.f142132d.e();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public synchronized void init() {
if (this.f142133e && !this.f142134f) {
this.f142131c.h();
this.f142134f = true;
}
}
@Override // com.bilibili.lib.biliid.api.IHelper
public boolean isRemoteBuvidRequestOver() {
return this.f142132d.f();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void remoteBuvidRequestOver() {
e.a().putBoolean("remote_buvid_request_over", true);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void saveBadBuvidToBLKV(@NotNull String str) {
y92.c.d(str);
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@Nullable
public String softGetBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
String strE = this.f142132d.e();
return strE.length() == 0 ? this.f142131c.k() : strE;
}
}
抽取BuvidHelperImpl类中的getBuvid()方法
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
String strE = this.f142132d.e();
return strE.length() == 0 ? this.f142131c.e() : strE;
}
简化一下BuvidHelperImpl.getBuvid()方法,根据简化结果,跟f142132d.e()以及f142131c.e()有关
public String getBuvid() {
return this.f142132d.e().length() == 0 ? this.f142131c.e() : this.f142132d.e();
}
定位f142132d.e()以及f142131c.e()
先定位f142132d.e(),
package y92;
public final class p {
@NotNull
public final String e() {
return this.f443697a.f();
}
}
// ---------------------------------------------------
package y92;
public final class d {
@NotNull
public String f() {
return this.f443666a.e();
}
}
//---------------------------------------------------
package y92;
final class a {
@NotNull
public final String e() {
return RawKV.DefaultImpls.getString$default(e.a(), "buvid_remote", null, 2, null);
}
}
//---------------------------------------------------
package y92;
public final class e {
@NotNull
private static final RawKV f443668a = BLKV.getKvs$default(FoundationAlias.getFapp(), FoundationAlias.getFapp().getPackageName() + "_buvid", true, 0, 4, null);
@NotNull
public static final RawKV a() {
return f443668a;
}
}
//定位到最后 发现还是去某存储文件中去拿数据
package com.bilibili.lib.blkv;
@JvmName(name = "BLKV")
public final class BLKV {
@NotNull
public static final RawKV getKvs(@NotNull Context context, @NotNull String str, boolean z17, int i17) {
return toKvs(new File(context.getDir("blkv", 0), a(str) + ".raw_kv"), z17, i17);
}
}
再定位f142131c.e(), 无论是strA = this.f443685a.a()、strA = f();、strA = g()同上面都是去存储文件里面找
package y92;
public final class l {
@NotNull
public final String e() {
String strA = this.f443685a.a();
if (strA.length() > 0 || !i()) {
return strA;
}
if (strA.length() == 0) {
strA = f();
}
if (strA.length() == 0) {
strA = g();
}
this.f443685a.h(strA);
return strA;
}
}
// ---------------------------------------------------
package y92;
public final class d {
@NotNull
public final String a() {
return this.f443666a.a();
}
}
// ---------------------------------------------------
package y92;
final class a {
@NotNull
public static final C4817a f443664a = new C4817a(null);
/* compiled from: BL */
/* renamed from: y92.a$a, reason: collision with other inner class name */
public static final class C4817a {
public /* synthetic */ C4817a(DefaultConstructorMarker defaultConstructorMarker) {
this();
}
private C4817a() {
}
}
@NotNull
public final String a() {
return RawKV.DefaultImpls.getString$default(e.a(), "buvid", null, 2, null);
}
@NotNull
public final String b() {
return RawKV.DefaultImpls.getString$default(e.a(), "buvid_local", null, 2, null);
}
@NotNull
public final String c() {
return RawKV.DefaultImpls.getString$default(e.a(), "buvid_compat", null, 2, null);
}
@NotNull
public final String d() {
return RawKV.DefaultImpls.getString$default(e.a(), "device_model", null, 2, null);
}
@NotNull
public final String e() {
return RawKV.DefaultImpls.getString$default(e.a(), "buvid_remote", null, 2, null);
......
......
}
}
2.4.2.4.2 BuvidHelperImpl –> IHelper
从上面实现IBuvid接口的BuvidHelperImpI方法来看,都是从Android的存储文件从找到buvid的值,都不是buvid的生成过程
现在跳回BuvidHelperImpI实现类代码,
package com.bilibili.lib.biliid.api;
public final class BuvidHelperImpl implements IBuvid, IHelper {
@NotNull
private final Function0<Executor> f142129a;
@NotNull
private final d f142130b;
@NotNull
private final l f142131c;
@NotNull
private final p f142132d;
private final boolean f142133e;
private boolean f142134f;
public BuvidHelperImpl() {
Function0<Executor> function0 = new Function0() { // from class: x92.a
@Override // kotlin.jvm.functions.Function0
public final Object invoke() {
return BuvidHelperImpl.b();
}
};
this.f142129a = function0;
d dVar = new d();
this.f142130b = dVar;
l lVar = new l(dVar, new x92.b(null, 1, null), function0);
this.f142131c = lVar;
this.f142132d = new p(dVar, lVar);
this.f142133e = StringsKt.indexOf$default((CharSequence) FoundationAlias.getFapps().getProcessName(), AbstractJsonLexerKt.COLON, 0, false, 6, (Object) null) == -1;
}
/* JADX INFO: Access modifiers changed from: private */
public static final Executor b() {
return Executors.newCachedThreadPool();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void bindReporter(@NotNull Function1<? super Map<String, String>, Unit> function1, @NotNull Function1<? super Map<String, String>, Unit> function12) {
this.f142131c.b(function1);
this.f142132d.b(function12);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void fetchRemote(long j17, boolean z17, @Nullable RemoteBuvidCallback remoteBuvidCallback) {
this.f142132d.c(this.f142129a.invoke(), new c(), j17, z17, remoteBuvidCallback);
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
String strE = this.f142132d.e();
return strE.length() == 0 ? this.f142131c.e() : strE;
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getLocalBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
return this.f142131c.e();
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@NotNull
public String getRemoteBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
return this.f142132d.e();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public synchronized void init() {
if (this.f142133e && !this.f142134f) {
this.f142131c.h();
this.f142134f = true;
}
}
@Override // com.bilibili.lib.biliid.api.IHelper
public boolean isRemoteBuvidRequestOver() {
return this.f142132d.f();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void remoteBuvidRequestOver() {
e.a().putBoolean("remote_buvid_request_over", true);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void saveBadBuvidToBLKV(@NotNull String str) {
y92.c.d(str);
}
@Override // com.bilibili.lib.biliid.api.IBuvid
@Nullable
public String softGetBuvid() {
if (this.f142133e && !this.f142134f) {
init();
}
String strE = this.f142132d.e();
return strE.length() == 0 ? this.f142131c.k() : strE;
}
}
从上面的完整代码中抽取属于实现IHelper接口的方法,观察方法名,都包含romteBuvid、saveBadBuvidToBLKV等字样,猜测这是从哔哩哔哩服务器远程加载buvid,并以特定形式将buvid存入到blkv文件中
package com.bilibili.lib.biliid.api;
public final class BuvidHelperImpl implements IBuvid, IHelper {
@Override // com.bilibili.lib.biliid.api.IHelper
public void bindReporter(@NotNull Function1<? super Map<String, String>, Unit> function1, @NotNull Function1<? super Map<String, String>, Unit> function12) {
this.f142131c.b(function1);
this.f142132d.b(function12);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void fetchRemote(long j17, boolean z17, @Nullable RemoteBuvidCallback remoteBuvidCallback) {
this.f142132d.c(this.f142129a.invoke(), new c(), j17, z17, remoteBuvidCallback);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public synchronized void init() {
if (this.f142133e && !this.f142134f) {
this.f142131c.h();
this.f142134f = true;
}
}
@Override // com.bilibili.lib.biliid.api.IHelper
public boolean isRemoteBuvidRequestOver() {
return this.f142132d.f();
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void remoteBuvidRequestOver() {
e.a().putBoolean("remote_buvid_request_over", true);
}
@Override // com.bilibili.lib.biliid.api.IHelper
public void saveBadBuvidToBLKV(@NotNull String str) {
y92.c.d(str);
}
}
其中最值得关注的就是BuvidHelperImpl.init()方法。根据个人经验,其他方法从方法名,顾名思义都与buvid的生成无太大关系
定位BuvidHelperImpl.init()代码,其中有this.f142131c.h();这样一行代码
@Override // com.bilibili.lib.biliid.api.IHelper
public synchronized void init() {
if (this.f142133e && !this.f142134f) {
this.f142131c.h();
this.f142134f = true;
}
}
定位this.f142131c.h()
package y92;
public final class l {
private final oc2.d f443686b
......
......
public final void h() {
// this.f443685a.f()在逆向实现IBuvid的BuvidHelperImpI类方法中出现过,本质上是去blkv文件中取出相应的值
String strF = this.f443685a.f();
//如果还是没取到值
if (strF.length() == 0) {
// this.f443685a.a()在逆向实现IBuvid的BuvidHelperImpI类方法中出现过,本质上是去blkv文件中取出相应的值
strF = this.f443685a.a();
}
// 依旧没取到值
if (strF.length() == 0) {
//定位 new i() 这个i对象的类定义
this.f443689e = new i(new n.c(""), this.f443685a, this.f443687c.invoke(), this.f443686b);
} else {
d dVar = this.f443685a;
String str = Build.MODEL;
if (dVar.g(str != null ? str : "")) {
c();
this.f443689e = new i(n.b.f443694b, this.f443685a, this.f443687c.invoke(), this.f443686b);
} else if (c.c().contains(strF)) {
c();
this.f443689e = new i(new n.a(strF), this.f443685a, this.f443687c.invoke(), this.f443686b);
}
}
if (this.f443685a.c().length() == 0) {
this.f443690f = new g(this.f443685a, this.f443687c.invoke());
}
}
......
......
}
在定位new i()这个i对象的类定义前,明确new i(new n.c(""), this.f443685a, this.f443687c.invoke(), this.f443686b);中几个入参的意义:
new i(new n.c(""), this.f443685a, this.f443687c.invoke(), this.f443686b);
//--------------------------------------------------------------------
// new n.c("") 定位n.c()方法 本质上是构造方法 这里的str就是new n.c("")传入的""空字符串
public static final class c extends n {
public c(@NotNull String str) {
super(str, null);
}
}
//--------------------------------------------------------------------
this.f443685a ---> 这个在逆向实现IBuvid的BuvidHelperImpI类方法中出现过,本质上是一个集合大量去blkv文件中取出相应的值的方法的类y92.d
//--------------------------------------------------------------------
// this.f443687c.invoke() 是一个接口方法 本质上是java反射机制里的,用于动态调用反射加载的类的方法
package kotlin.jvm.functions;
import kotlin.Function;
public interface Function0<R> extends Function<R> {
R invoke();
}
//--------------------------------------------------------------------
// this.f443686b --->根据private final oc2.d f443686b定位代码
package oc2;
public interface d {
@Nullable
a a();
@NotNull
Set<String> b();
@NotNull
List<c> c();
@Nullable
Function1<String, Unit> getLogger();
}
定位new i()这个i对象的类定义,this.f443675a 、this.f443676b、this.f443677c、this.f443678d都是接收i方法的入参,根据上面的分析,跟buvid没有关系;继续分析this.f443679e,值来自于oc2.b.f364071a.a(dVar2),定位oc2.b.f364071a.a方法
package y92;
public final class i {
......
......
public i(@NotNull n nVar, @NotNull d dVar, @NotNull Executor executor, @NotNull oc2.d dVar2) {
this.f443675a = nVar;
this.f443676b = dVar;
this.f443677c = executor;
this.f443678d = dVar2;
this.f443679e = oc2.b.f364071a.a(dVar2);
FutureTask<String> futureTask = new FutureTask<>(new Callable() { // from class: y92.h
@Override // java.util.concurrent.Callable
public final Object call() {
return i.e(this.f443674a);
}
});
this.f443683i = futureTask;
executor.execute(futureTask);
}
......
......
}
定位oc2.b.f364071a.a方法 ,确定与buvid无关;只能继续分析上面y92.i类代码
package oc2;
public interface b {
@NotNull
public static final a f364071a = a.f364072a;
public static final class a {
static final /* synthetic */ a f364072a = new a();
private a() {
}
@NotNull //定位到这里了
public final b a(@NotNull d dVar) {
return new pc2.c(dVar);
}
@NotNull
public final b b(@NotNull e eVar) {
return new pc2.e(eVar);
}
}
@NotNull
String a();
@NotNull
Map<String, String> b();
}
// 再定位pc2.c()
package pc2;
public final class c implements oc2.b {
@NotNull
private final oc2.d f371409b;
public c(@NotNull oc2.d dVar) {
this.f371409b = dVar;
}
......
.....
}
继续分析,发现这是个多线程任务,重写call方法,执行i.e(this.f443674a)
FutureTask<String> futureTask = new FutureTask<>(new Callable() { // from class: y92.h
@Override // java.util.concurrent.Callable
public final Object call() {
return i.e(this.f443674a);
}
});
定位y92.i.e()方法,第一行直接开始赋值String strG = iVar.g()
public static final String e(i iVar) {
String strG = iVar.g();
synchronized (iVar) {
iVar.f443680f = strG;
Unit unit = Unit.INSTANCE;
}
return strG;
}
再定位iVar.g(),实际上就是this.f443674a.g(),因为i.e()入参为this.f443674a,
private final String g() {
LinkedHashMap linkedHashMap = new LinkedHashMap();
// SystemClock.uptimeMillis提供了自系统启动以来的毫秒数,但不包括设备处于深度睡眠状态的时间。
long jUptimeMillis = SystemClock.uptimeMillis();
// this.f443675a instanceof n.c 确认this.f443675a类型是不是n.c ---> 回顾new i(new n.c(""), this.f443685a, this.f443687c.invoke(), this.f443686b); ---> 事实上是n.c类型
// 所以strE的值为this.f443676b.e()
String strE = this.f443675a instanceof n.c ? this.f443676b.e() : "";
if (strE.length() == 0) {
strE = this.f443679e.a();
this.f443676b.l(strE);
linkedHashMap.put("from", "calculate");
} else {
this.f443676b.i(strE);
linkedHashMap.put("from", "storage");
}
d dVar = this.f443676b;
String str = Build.MODEL;
if (str == null) {
str = "";
}
dVar.j(str);
linkedHashMap.put("time", String.valueOf(SystemClock.uptimeMillis() - jUptimeMillis));
linkedHashMap.put("reason", this.f443675a.a());
linkedHashMap.put("buvid_local", strE);
String processName = AndroidUtilsKt.getProcessName(FoundationAlias.getFapp());
if (processName == null) {
processName = "";
}
linkedHashMap.put("process", processName);
synchronized (this) {
try {
Function1<? super Map<String, String>, Unit> function1 = this.f443682h;
if (function1 == null) {
this.f443681g.putAll(linkedHashMap);
} else {
function1.invoke(MapsKt.toMap(linkedHashMap));
}
Unit unit = Unit.INSTANCE;
} catch (Throwable th6) {
throw th6;
}
}
return strE;
}
由于上面分析到strE的值为this.f443676b.e(),定位this.f443676b.e()以及后续跳转代码
@NotNull
public String e() {
return this.f443667b.a();
}
// 定位this.f443667b.a()
// -----------------------------------------------------
package y92;
final class j {
private final String d(String str) {
int length;
if (str == null || (length = str.length()) == 0 || length > 64) {
return null;
}
for (int i17 = 0; i17 < length; i17++) {
char cCharAt = str.charAt(i17);
if (('A' > cCharAt || cCharAt >= '[') && (('a' > cCharAt || cCharAt >= '{') && !(('0' <= cCharAt && cCharAt < ':') || cCharAt == '-' || cCharAt == '_'))) {
return null;
}
}
return str;
}
// 定位到这里了
public final String a() {
String strD = d(EnvironmentManager.getInstance().getBuvid2());
return strD == null ? "" : strD;
}
......
......
}
// 定位EnvironmentManager.getInstance().getBuvid2()
// -----------------------------------------------------
package com.bilibili.lib.biliid.api;
public class EnvironmentManager {
......
......
public String getBuvid2() {
String buvid2 = getPrefHelper().getBuvid2();
if (!TextUtils.isEmpty(buvid2)) {
ia2.a.q(buvid2);
return buvid2;
}
String strC = ia2.a.c();
if (!TextUtils.isEmpty(strC)) {
getPrefHelper().setBuvid2(strC);
}
return strC;
}
......
......
}
// 跟getPrefHelper().getBuvid2();有关 ,再定位
// -----------------------------------------------------
package com.bilibili.lib.biliid.internal.storage.prefs;
public final class EnvironmentPrefHelper extends SharedPreferencesHelper {
......
......
public String getBuvid2() {
return getSharedPreferences().getString("buvid2", "");
}
......
......
}
//到最够还是去通过SharedPreference类,去哔哩哔哩对应的存储文件中去获取,不是buvid生成的地方
分析strE = this.f443679e.a(),定位this.f443679e.a()
private final String g() {
LinkedHashMap linkedHashMap = new LinkedHashMap();
// SystemClock.uptimeMillis提供了自系统启动以来的毫秒数,但不包括设备处于深度睡眠状态的时间。
long jUptimeMillis = SystemClock.uptimeMillis();
// this.f443675a instanceof n.c 确认this.f443675a类型是不是n.c ---> 回顾new i(new n.c(""), this.f443685a, this.f443687c.invoke(), this.f443686b); ---> 事实上是n.c类型
// 所以strE的值为this.f443676b.e(),经过分析这里通过SharedPreference类,去哔哩哔哩对应的存储文件中去获取,不是buvid生成的地方
String strE = this.f443675a instanceof n.c ? this.f443676b.e() : "";
if (strE.length() == 0) { //没取到值
strE = this.f443679e.a(); //分析这里
this.f443676b.l(strE);
linkedHashMap.put("from", "calculate");
} else {
this.f443676b.i(strE);
linkedHashMap.put("from", "storage");
}
d dVar = this.f443676b;
String str = Build.MODEL;
if (str == null) {
str = "";
}
dVar.j(str);
linkedHashMap.put("time", String.valueOf(SystemClock.uptimeMillis() - jUptimeMillis));
linkedHashMap.put("reason", this.f443675a.a());
linkedHashMap.put("buvid_local", strE);
String processName = AndroidUtilsKt.getProcessName(FoundationAlias.getFapp());
if (processName == null) {
processName = "";
}
linkedHashMap.put("process", processName);
synchronized (this) {
try {
Function1<? super Map<String, String>, Unit> function1 = this.f443682h;
if (function1 == null) {
this.f443681g.putAll(linkedHashMap);
} else {
function1.invoke(MapsKt.toMap(linkedHashMap));
}
Unit unit = Unit.INSTANCE;
} catch (Throwable th6) {
throw th6;
}
}
return strE;
}
定位this.f443679e.a(),定位到接口oc2.b,由于oc2.b.a()方法没有具体实现,定位oc2接口实现类
package oc2;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/* compiled from: BL */
/* loaded from: classes14.dex */
public interface b {
/* renamed from: a, reason: collision with root package name */
@NotNull
public static final a f364071a = a.f364072a;
/* compiled from: BL */
public static final class a {
/* renamed from: a, reason: collision with root package name */
static final /* synthetic */ a f364072a = new a();
private a() {
}
@NotNull
public final b a(@NotNull d dVar) {
return new pc2.c(dVar);
}
@NotNull
public final b b(@NotNull e eVar) {
return new pc2.e(eVar);
}
}
@NotNull //定位到这里
String a();
@NotNull
Map<String, String> b();
}
定位oc2.b接口实现类
package pc2;
public final class e implements oc2.b {
......
......
@Override // oc2.b
@WorkerThread
@NotNull
public String a() throws JSONException {
String strOptString;
String strA = "-1";
String str = "";
long jUptimeMillis = SystemClock.uptimeMillis();
LinkedHashMap linkedHashMap = new LinkedHashMap();
linkedHashMap.put("imei", this.f371412b.g());
linkedHashMap.put("mac", this.f371412b.i());
linkedHashMap.put("androidId", this.f371412b.j());
linkedHashMap.put("brand", Build.BRAND);
linkedHashMap.put("model", Build.MODEL);
linkedHashMap.put("oaid", this.f371412b.a());
linkedHashMap.put("drmId", this.f371412b.k());
linkedHashMap.put("buvid", this.f371412b.m());
linkedHashMap.put("neuronAppId", this.f371412b.d());
linkedHashMap.put("neuronPlatformId", this.f371412b.l());
linkedHashMap.put("fawkesAppKey", this.f371412b.c());
linkedHashMap.put("versionName", this.f371412b.p());
linkedHashMap.put("versionCode", this.f371412b.versionCode());
linkedHashMap.put("internalVersionCode", this.f371412b.o());
linkedHashMap.put(InAppPurchaseConstants.METHOD_BUILD, this.f371412b.build());
linkedHashMap.put("channel", this.f371412b.q());
linkedHashMap.put("appkey", this.f371412b.f());
linkedHashMap.put("first", this.f371412b.e().length() == 0 ? "1" : "0");
linkedHashMap.put("firstStart", this.f371412b.n() ? "1" : "0");
for (Map.Entry<String, String> entry : this.f371412b.b().entrySet()) {
linkedHashMap.put(entry.getKey(), entry.getValue());
}
Request requestBuild = new Request.Builder().url("https://app.bilibili.com/x/polymer/buvid/get").post(RequestBody.create(MediaType.get("application/x-www-form-urlencoded"), this.f371412b.h(linkedHashMap))).build();
OkHttpClient.Builder builderNewBuilder = this.f371412b.getOkHttpClient().newBuilder();
TimeUnit timeUnit = TimeUnit.SECONDS;
try {
Response responseExecute = builderNewBuilder.connectTimeout(3L, timeUnit).readTimeout(3L, timeUnit).writeTimeout(3L, timeUnit).build().newCall(requestBuild).execute();
this.f371413c.put("http_code", String.valueOf(responseExecute.code()));
if (responseExecute.isSuccessful()) {
ResponseBody responseBodyBody = responseExecute.body();
if (responseBodyBody == null) {
this.f371413c.put("code", "3");
this.f371413c.put("msg", "http null body");
} else {
try {
String strString = responseBodyBody.string();
try {
JSONObject jSONObject = new JSONObject(strString);
int i17 = jSONObject.getInt("code");
if (i17 == 0) {
JSONObject jSONObjectOptJSONObject = jSONObject.optJSONObject("data");
if (jSONObjectOptJSONObject != null && (strOptString = jSONObjectOptJSONObject.optString("buvid", "")) != null) {
str = strOptString;
}
this.f371413c.put("code", "0");
this.f371413c.put("msg", "success");
} else {
this.f371413c.put("code", "6");
this.f371413c.put("msg", jSONObject.optString("message", "api code error"));
this.f371413c.put("api_code", String.valueOf(i17));
}
} catch (Exception unused) {
this.f371413c.put("code", "5");
this.f371413c.put("msg", Intrinsics.stringPlus("parse json error: ", strString));
}
} catch (Exception e17) {
this.f371413c.put("code", "4");
this.f371413c.put("msg", Intrinsics.stringPlus("http read body, ", ExceptionsKt.stackTraceToString(e17)));
}
}
} else {
this.f371413c.put("code", "2");
this.f371413c.put("msg", "http code error");
}
} catch (IOException e18) {
this.f371413c.put("code", "1");
this.f371413c.put("msg", Intrinsics.stringPlus("http connect error, ", ExceptionsKt.stackTraceToString(e18)));
Map<String, String> map = this.f371413c;
try {
pc2.a aVarB = f.b(e18);
if (aVarB != null) {
strA = aVarB.a();
}
} catch (Exception unused2) {
}
map.put("conn_code", strA);
}
this.f371413c.put("time", String.valueOf(SystemClock.uptimeMillis() - jUptimeMillis));
this.f371413c.put("remote_buvid", str);
return str;
}
......
......
}
分析linkedHashMap.put("buvid", this.f371412b.m());,定位代码,发现还是从应用程序的存储文件中获取
public String m() {
return BuvidHelper.getLocalBuvid();
}
2.4.2.4.3 向服务器请求buvid
接着一堆linkedHashMap.put操作下面有一个Request请求,请求服务器的https://app.bilibili.com/x/polymer/buvid/get地址
Request requestBuild = new Request.Builder().url("https://app.bilibili.com/x/polymer/buvid/get").post(RequestBody.create(MediaType.get("application/x-www-form-urlencoded"), this.f371412b.h(linkedHashMap))).build();
通过抓包可看到buvid字段是从服务器请求过来的,请求中的必要参数为androidId、model、neuronAppId、neuronPlatformId
其中neuronAppId、neuronPlatformId固定
而androidId、model是绑定关系,任意一个字段错误,服务器返回的buvid结果为空
![图片[6]-APP逆向-B站登录请求接口-木兮知识库](https://www.mzsec.cn/wp-content/uploads/2026/03/image-20260208171851902-300x107.png)
请求服务器得到的buvid将会通过SharedPreference或者哔哩哔哩自定义的BLKV(即bilibili local Key Value)来进行缓存
至于androidId是怎么生成的,以及如何向服务器申请绑定androidId、model后续再进一步说明,目前如果要写脚本的话,这些请求包字段直接固定就好。
特别声明:本文所涉及的任何技术、信息或工具,仅供学习和参考之用。请勿利用本文提供的信息从事任何违法活动或不当行为。任何因使用本文所提供的信息或工具而导致的损失、后果或不良影响,均由使用者个人承担责任,与本文作者无关。作者不对任何因使用本文信息或工具而产生的损失或后果承担任何责任。使用本文所提供的信息或工具即视为同意本免责声明,并承诺遵守相关法律法规和道德规范。相关资源仅供学习和研究使用,请在下载后24小时内删除。



















暂无评论内容