找到
8
篇与
安卓逆向
相关的结果
-
安卓逆向-Bilibili播放量接口 一、Bilibili 1. 前置 [!sana] 版本:6.24.0 包名:tv.danmaku.bili so文件:libbili.so 目标:增加视频播放量算法 2. 抓包分析 使用socksdroid转发+charles抓包即可; 一共两个接口比较重要: https://api.bilibili.com/x/report/heartbeat/mobile https://api.bilibili.com/x/report/click/android2 抓包示例如下: image-20241222162234422图片 第一个是心跳包,熟悉的朋友可能知道,控制完播率这方面的,剩下一个就是增加播放量的; 3. click android2接口 首先看一下接口需要哪些参数: 请求地址: https://api.bilibili.com/x/report/click/android2 请求方式: POST 请求头: buvid XY88A4185B06248EA82BB0101C2ADC5DAD33B device-id bFpoC2lebA9uWm5ZJVkl fp_local d9c996c3fb98c1808bbf64ee054dda5420241222161236dee9d93eca085cd19e fp_remote d9c996c3fb98c1808bbf64ee054dda542024112221174303fb9f17f667dc29f3 session_id f815bbd9 请求体: ùouO\#âèhA^>o(](©ú······Ã<xxÚý \@aèyd 是一串乱码样式的内容3.1 请求体分析 直接反编译搜索地址的后部分即可,这样比较唯一,搜索到后直接点过去; image-20241222163602968图片 这里可以发现心跳接口其实也在这里,并且它是一个接口,那就去找它的实现,右键查找用例或者按x,也只有一个位置,点进去看看; image-20241222164440857图片 这里其实也跟明了,看不懂的可以去了解一下retrofit,这里调用reportClick方法,传入了create这个参数,参考接口定义的位置,这个变量应该就是请求体; 那么我们就应该去看create这个参数怎来的; public final void a() { long j2; long i = c2.f.f.c.j.a.i() / 1000; c2.f.b0.c.b.b.a.a E = c2.f.b0.c.b.b.a.a.E(); x.h(E, "EnvironmentPrefHelper.getInstance()"); long A = E.A(); if (A == -1) { c2.f.b0.c.b.b.a.a E2 = c2.f.b0.c.b.b.a.a.E(); x.h(E2, "EnvironmentPrefHelper.getInstance()"); E2.V(i); j2 = i; } else { j2 = A; } c0 create = c0.create(w.d(com.hpplay.sdk.source.protocol.h.E), d.this.H7(this.b.a(), this.b.b(), this.b.h(), i, j2, this.b.n(), this.b.m(), this.b.k(), this.b.c(), this.b.e(), this.b.l(), this.b.f())); x.h(create, "RequestBody.create(Media…ion/octet-stream\"), body)"); l<String> execute = ((tv.danmaku.biliplayerimpl.report.heartbeat.a) com.bilibili.okretro.c.a(tv.danmaku.biliplayerimpl.report.heartbeat.a.class)).reportClick(create).execute(); int b = execute.b(); String h = execute.h(); BLog.i("HeartBeatTracker", "player report click(vv): responseCode:" + b + ", responseMsg:" + h + ", responseBody:" + execute.a()); } 分析它的源码; x1 = w.d(com.hpplay.sdk.source.protocol.h.E) x2 = d.this.H7(this.b.a(), this.b.b(), this.b.h(), i, j2, this.b.n(), this.b.m(), this.b.k(), this.b.c(), this.b.e(), this.b.l(), this.b.f()) c0 create = c0.create(x1,x2); 其实他就两个参数,第一个参数是一个请求格式的常量,那么第二个参数看着这么长,就有可能是我们需要的请求体参数了; 进入H7方法看看; public final byte[] H7(long j2, long j4, int i, long j5, long j6, int i2, int i3, long j7, String str, int i4, String str2, String str3) throws Exception { long j8; int i5; Application f2 = BiliContext.f(); com.bilibili.lib.accounts.b client = com.bilibili.lib.accounts.b.f(f2); AccountInfo h = BiliAccountInfo.f.a().h(); if (h != null) { j8 = h.getMid(); i5 = h.getLevel(); } else { j8 = 0; i5 = 0; } TreeMap treeMap = new TreeMap(); treeMap.put("aid", String.valueOf(j2)); treeMap.put("cid", String.valueOf(j4)); treeMap.put("part", String.valueOf(i)); treeMap.put(EditCustomizeSticker.TAG_MID, String.valueOf(j8)); treeMap.put("lv", String.valueOf(i5)); treeMap.put("ftime", String.valueOf(j6)); treeMap.put("stime", String.valueOf(j5)); treeMap.put("did", com.bilibili.lib.biliid.utils.f.a.c(f2)); treeMap.put("type", String.valueOf(i2)); treeMap.put("sub_type", String.valueOf(i3)); treeMap.put("sid", String.valueOf(j7)); treeMap.put("epid", str); treeMap.put("auto_play", String.valueOf(i4)); x.h(client, "client"); if (client.r()) { treeMap.put("access_key", client.g()); } treeMap.put("build", String.valueOf(com.bilibili.api.a.f())); treeMap.put("mobi_app", com.bilibili.api.a.l()); treeMap.put("spmid", str2); treeMap.put("from_spmid", str3); StringBuilder sb = new StringBuilder(); for (Map.Entry entry : treeMap.entrySet()) { String str4 = (String) entry.getValue(); sb.append((String) entry.getKey()); sb.append('='); if (str4 == null) { str4 = ""; } sb.append(str4); sb.append('&'); } sb.deleteCharAt(sb.length() - 1); String sb2 = sb.toString(); x.h(sb2, "builder.toString()"); String b2 = t3.a.i.a.a.a.b.e.b(sb2); BLog.i("HeartBeatTracker", "player report click(vv), params: " + sb2 + " & sign=" + b2); sb.append("&sign="); sb.append(b2); String sb3 = sb.toString(); x.h(sb3, "builder.toString()"); return t3.a.i.a.a.a.b.e.a(sb3); } 看起来很庞大,但是貌似也有些比较敏感的参数,这个方法的主要目的是构建一个包含多个参数的请求,对这些参数进行签名,然后将签名后的参数字符串转换为字节数组,它会把map的参数逐一添加到sb中,中间以&符号拼接; 这里很明显就是看这个sign嘛,去看看b方法; public final String b(String params) { x.q(params, "params"); Charset charset = com.bilibili.commons.c.b; x.h(charset, "Charsets.UTF_8"); byte[] bytes = params.getBytes(charset); x.h(bytes, "(this as java.lang.String).getBytes(charset)"); String str = d; Charset charset2 = com.bilibili.commons.c.b; x.h(charset2, "Charsets.UTF_8"); if (str != null) { byte[] bytes2 = str.getBytes(charset2); x.h(bytes2, "(this as java.lang.String).getBytes(charset)"); String g = com.bilibili.commons.m.a.g(bytes, bytes2); x.h(g, "DigestUtils.sha256(param…yteArray(Charsets.UTF_8))"); Locale locale = Locale.US; x.h(locale, "Locale.US"); if (g != null) { String lowerCase = g.toLowerCase(locale); x.h(lowerCase, "(this as java.lang.String).toLowerCase(locale)"); return lowerCase; } throw new TypeCastException("null cannot be cast to non-null type java.lang.String"); } throw new TypeCastException("null cannot be cast to non-null type java.lang.String"); } 那这里的主要内容就是这个g方法了,同样进去看看; public static String g(byte[] bArr, byte[] bArr2) { try { MessageDigest messageDigest = MessageDigest.getInstance(AaidIdConstant.SIGNATURE_SHA256); messageDigest.reset(); messageDigest.update(bArr); if (bArr2 != null) { messageDigest.update(bArr2); } return g.H(messageDigest.digest()); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } } 打眼一看应该就是一个sha256算法; 那么接下来就是需要hook验证了,先去hook H7看看位置对不对; function hook_h7() { Java.perform(function () { var ByteString = Java.use("com.android.okhttp.okio.ByteString"); let d = Java.use("tv.danmaku.biliplayerimpl.report.heartbeat.d"); d["H7"].implementation = function (j2, j4, i, j5, j6, i2, i3, j7, str, i4, str2, str3) { console.log(`d.H7 is called: j2=${j2}, j4=${j4}, i=${i}, j5=${j5}, j6=${j6}, i2=${i2}, i3=${i3}, j7=${j7}, str=${str}, i4=${i4}, str2=${str2}, str3=${str3}`); let result = this["H7"](j2, j4, i, j5, j6, i2, i3, j7, str, i4, str2, str3); console.log(`d.H7 result=${ByteString.of(result).hex()}`); return result; }; }) } hook_h7() hook的结果如下,转成16进制输出; [Pixel 4::哔哩哔哩 ]-> d.H7 is called: j2=1205801029, j4=1593015651, i=1, j5=1734867854, j6=1734855270, i2=3, i3=0, j7=0, str=, i4=0, str2=main.ugc-video-detail.0.0, str3=main.ugc-video-detail.0.0 d.H7 result=36c64b4e8b138184f3c14a734642b5a2168e1cd05181eed73655abdddca85f7d8d7379750746b64e55e8285d4ba87747f78439f6c9f629cc9fcd35edb2748d2ef7ad4e4221bb01363c86624dc7fc8256627fe6315ea001c3dcc701c6facd7dd46621225582b367c2ec7844035a20be2c9218c58c39d6641b90a7d2fb91e6dad2a1124c848d27cfcab1541714f1f8390e80de2a4ab0ce13dfb4210a75df6792286fe7029b7d11d1362add404442bedeee3e6911f36fa360a2d1cad53be0fce4aada0eb373f5f065a36e35db91242923ac23a3eac4b5c5d08f79298a840249a674b746af2273f97a3d3d07522061eb3e324824fe9701cf583e85c73ab4997ed06bcce957773dd909af1531e2bf2a477b4f2f7901137c7d96e82835d3a96b79952428a5f4474ae1b54ce901c9649eb397f12523b9b536d099acaa62043db34f3d59 抓包的结果拿去对比一下; image-20241222194700257图片 可以发现位置是没有错的,那么就可以去看sign怎么来的了,上面知道了sign怎么来的了,那我们也直接去hook一下; function hook_sign() { Java.perform(function () { let a = Java.use("com.bilibili.commons.m.a"); a["g"].implementation = function (bArr, bArr2) { console.log(`a.g is called: bArr=${Bytes2utf8String(bArr)}, bArr2=${Bytes2utf8String(bArr2)}`); let result = this["g"](bArr, bArr2); console.log(`a.g result=${result}`); return result; }; }) } hook_sign(); 输出结果如下: [Pixel 4::哔哩哔哩 ]-> a.g is called: bArr=aid=1002480418&auto_play=0&build=6240300&cid=1487958098&did=bFpoC2lebA9uWm5ZJVkl&epid=&from_spmid=main.ugc-video-detail.0.0&ftime=1734855270&lv=0&mid=0&mobi_app=android&part=1&sid=0&spmid=main.ugc-video-detail.0.0&stime=1734869003&sub_type=0&type=3, bArr2=9cafa6466a028bfb a.g result=9d5a27d1f98c1a9ae66558db3c2a3804419f696511cc9c1b037a6c86965267f4 上面我们看源码觉得像sha256算法,这里它传了两个参数,本身是字节数组,我这里直接转成字符串,因为我们知道传进来的是什么,那么这个参数2在源码里是这样用的: if (bArr2 != null) { messageDigest.update(bArr2); } 这毫无疑问就是盐嘛,也可以搜索一下看看; image-20241222200924850" style="zoom:80%;" /> 这里还有key iv等关键词,那么这个可以留个心眼了,那就看看是不是标准算法; 是对的上的,那就证明没问题,只是提交的时候是原格式而不是hex; 那么明文参数也需要看一看,aid和cid其实是视频相关的id,ftime是视频打开时间,stime是视频开始播放时间,剩下的就是一个did了; 经过多次清除数据尝试,最终定位到其实就是在取mac地址;