APP逆向 day16 BiliBili逆向 part1

一.前言

今天要给大家讲一个b站的刷播放量教程,很麻烦,因为有很多个接口需要逆向,我们需要为期三天的教程,,我们今天就先和大奖讲一个接口,但是这个接口的值其实并不需要逆向,但是作为学习,还是和大家讲应该如何逆向

二.app版本选择

这里app我们选择的版本是v6.24.0,可以直接去豌豆荚下载,这里给出地址

2021哔哩哔哩v6.24.0老旧历史版本安装包官方免费下载_豌豆荚 (wandoujia.com)icon-default.png?t=N7T8https://www.wandoujia.com/apps/281291/history_v6240300大家下载好之后,直接adb install 到手机就好了。

三.抓包分析 

这里并没有进行抓包的检测,我们可以直接使用正常代理就能抓到包,我们进入app,随即点开一个视频 ,这里给出我们今天要抓包的地址

https://api.bilibili.com/x/report/click/android2

 进来之后发现一堆乱码,大家肯定时以为是检测抓包,但是实际上这就是字节数组传过去的,我们今天就是要破解这个参数。

四.逆向参数

4.1 找到加密位置

我们先把apk拖入jadx进行反编译,然后搜索链接地址/x/report/click/android2,apk比较大,如果是老版的jadx就需要增加内存

tips: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)

确保内存有16个g

然后我们会发现一个地方,我们点进去

看到这里,这个肯定就是我们要的数据,我们就查找用例

 这个create就是我们要的参数,我们双击点进去 

 发先就在上面,我们发现create里面传来两个参数,第一个点进去一看,

发现是一个请求体类型,那我们重点关注的不就是d.this.H7吗?我们点进去

发现是这么个鬼代码 ,这里给大家总结一下逻辑吧

1 把一堆 key  和 value 放到了 TreeMap中
2 构建sb对象,把treeMap的内容,拼成字符串了
     aid=asdfasdf&cid=asadfasd&part=sadfasd&
3 把字符串最后 & 删除
sb=aid=asdfasdf&cid=asadfasd&part=sadfasd
4 调用: t3.a.i.a.a.a.b.e.b对 aid=asdfasdf&cid=asadfasd&part=sadfasd进行签名,得到字符串
5 把加密后的字符串拼接到 aid=asdfasdf&cid=asadfasd&part=sadfasd 后面
    sb=   aid=asdfasdf&cid=asadfasd&part=sadfasd&sign=签名后的字符串
6 调用t3.a.i.a.a.a.b.e.a(sb3)对 上述字符串加密
7 得到的就是 请求体的

 重点:
    1 t3.a.i.a.a.a.b.e.b 如何签名得到sign
    2 t3.a.i.a.a.a.b.e.a 整体请求体加密了,加密方式是什么

我们来hookH7 这个方法,因为这个hook比较特殊,这里给出hook代码

import frida
import sys

rdev = frida.get_remote_device()
session = rdev.attach("哔哩哔哩")

scr = """
Java.perform(function () {

    var d = Java.use("tv.danmaku.biliplayerimpl.report.heartbeat.d");
    var ByteString = Java.use("com.android.okhttp.okio.ByteString"); //这个
    d.H7.implementation = function(j2,  j4,  i2,  j5,  j6,  i3,  i4,  j7,  str,  i5,  str2,  str3){
        console.log("请求来了");
        var res = this.H7(j2,  j4,  i2,  j5,  j6,  i3,  i4,  j7,  str,  i5,  str2,  str3);
        console.log("字节数组", res);
        console.log("字节数组", JSON.stringify(res));
        console.log(ByteString.of(res).hex()); // 在hook时,直接把抓到的字符数组转成16进制
        console.log(ByteString.of(res).utf8());  //将字节转换成字符串

        return res;
    };

});
"""

script = session.create_script(scr)
def on_message(message, data):
    print(message, data)

script.on("message", on_message)
script.load()
sys.stdin.read()

 var ByteString = Java.use("com.android.okhttp.okio.ByteString");这个是引进java中将字节转换成字符串的方法

console.log(ByteString.of(res).hex()); // 在hook时,直接把抓到的字符数组转成16进制
console.log(ByteString.of(res).utf8());  //将字节转换成字符串

我们对比一下 发现他们的16进制是一样的。 那说明位置找正确了

4.2 破解sign 

 我们可以看见sb2就是前面的那一堆拼接起来的,而sign就是 t3.a.i.a.a.a.b.e.b(sb2)进行加密

那我们要破解sign,就得破解t3.a.i.a.a.a.b.e.b的加密方式,我们先点进去

我们发现主要就是调用这个方法com.bilibili.commons.m.a.g,里面传入两个加密参数,一个是b的字节类型,b是固定值,大家点进去看就知道了,还有一个是传入的字符串参数的字节数据,我们就点进去

发现是这个,我们发现这是个sha256加密,大家可以hook确定一下,这里我就不给大家hook了,这里就是sha256,两次append,一看就是对sha256进行加盐,最后进行g.H处理,我们再点进去看

发现这就是对结果进行16进制转换

至此,sign我们成功破解

4.3 破解请求体加密

我们破解完sign之后,我们就得破解请求体加密,这个时候我们往外退

我们就得对 t3.a.i.a.a.a.b.e.a(sb3)进行破解,我们点进去

发现这了AES,这不就明显了,这个是一个AES加密,而重要的是获取iv和key

发现这个是hook构造方法,之前我们是没见过的,其实这个在前面和之前sha256的盐是在一起的,但是为了大家能够知道如何hook构造方法,这里给出大家(因为这里new出来的

import frida
import sys

rdev = frida.get_remote_device()
session = rdev.attach("哔哩哔哩")

scr = """
Java.perform(function () {
    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    
    var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
    SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function(key,name){
        console.log("请求来了");
        console.log("key=",ByteString.of(key).utf8());
        console.log("name=",name);
        
        var res = this.$init(key,name);
        return res;
    };
    
    var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec");
    IvParameterSpec.$init.overload('[B').implementation = function(iv){
        console.log("iv=",ByteString.of(iv).utf8());
        var res = this.$init(iv);
        return res;
    };

});
"""

script = session.create_script(scr)


def on_message(message, data):
    print(message, data)


script.on("message", on_message)

script.load()
sys.stdin.read()

这个[B就是字节形式,大家可以去看看见面的jni对应表 

 大家可以看一下,和前面的一样

4.4 流程总结

# 1 根据请求地址:x/report/click/android2  ---》反编译查找
# 2 retrofit发送请求---》用例
    ResponseBody responseBody = retrofit.create(接口类a.class).reportClick(参数).execute().body()
# 3 破解参数,create
# 4 参数是通过:d.this.H7 传了一堆参数进去,得到的结果

# 5 H7的逻辑是:
'''
1 把一堆 key  和 value 放到了 TreeMap中
2 构建sb对象,把treeMap的内容,拼成字符串了
     aid=asdfasdf&cid=asadfasd&part=sadfasd&
3 把字符串最后 & 删除
sb=aid=asdfasdf&cid=asadfasd&part=sadfasd
4 调用: t3.a.i.a.a.a.b.e.b对 aid=asdfasdf&cid=asadfasd&part=sadfasd进行加密,得到字符串
5 把加密后的字符串拼接到 aid=asdfasdf&cid=asadfasd&part=sadfasd 后面
sb=aid=asdfasdf&cid=asadfasd&part=sadfasd&sign=加密串
6 调用t3.a.i.a.a.a.b.e.a(sb3)对 上述字符串加密
7 得到的就是 请求体的内容
'''

# 6 破解sign加密
    -sha256+盐 
    
# 7 得到最终的字符串,又加密了---》aes加密:秘钥,iv,明文

五. 破解请求体中aid,cid,did

# 1 请求体内容
aid=1003541346&auto_play=0&build=6240300&cid=1520243397&did=fU52TixILBQlEicfLk93Tw9fNk4rR3UtYQ&epid=&from_spmid=tm.recommend.0.0&ftime=1704811216&lv=0&mid=0&mobi_app=android&part=1&sid=0&spmid=main.ugc-video-detail.0.0&stime=1715778569&sub_type=0&type=3&sign=affed1c280f6fc6168ec5997dd186e7e4a595f99aff6029c6498e3df81f4916d

# 2 转成字典形式
{
    "aid":"1003541346",
    "cid":"1520243397",
    ---视频id和分级id---请求返回的
    
    ---需要破-----
    "did":"fU52TixILBQlEicfLk93Tw9fNk4rR3UtYQ",
    ---视频打开时间和播放时间----
    "ftime":"1704811216",
    "stime":"1715778569",
    -----下面都固定-----
    "auto_play":"0",  # 固定
    "build":"6240300", # 版本
    "epid":"",    # 空 
    "from_spmid":"main.ugc-video-detail.0.0",# 固定
    "lv":"0", 
    "mid":"0",
    "mobi_app":"android",
    "part":"1",
    "sid":"0",
    "spmid":"main.ugc-video-detail.0.0",
    "sub_type":"0",
    "type":"3"
}

aid和cid就是视频的唯一标识,我们可以去浏览器直接获取,而cid就需要我们手动获取了

我们在did那个位置一直点,就能找到下面这个页面

我这里就说一下意思是啥就好,大家可以一个个点进去

拿到 mac地址|蓝牙地址|设备总线|sn号,这个都不重要,只要hook一下,然后生成随机值就好

至此,全部破解完成,这里就给出一个源码给大家研究研究吧~

import random
import string
import base64
import time
import re
import json
import requests
import hashlib

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def base64_encrypt(data_string):
    data_bytes = bytearray(data_string.encode('utf-8'))
    data_bytes[0] = data_bytes[0] ^ (len(data_bytes) & 0xFF)
    for i in range(1, len(data_bytes)):
        data_bytes[i] = (data_bytes[i - 1] ^ data_bytes[i]) & 0xFF
    res = base64.encodebytes(bytes(data_bytes))
    return res.strip().strip(b"==").decode('utf-8')


def create_random_mac(sep=":"):
    """ 随机生成mac地址 """
    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 gen_sn():
    return "".join(random.sample("123456789" + string.ascii_lowercase, 10))


mac_string = create_random_mac(sep="")

did = base64_encrypt(f"{mac_string}|||")

header = {
    'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36'
}
res = requests.get("https://www.bilibili.com/video/BV1bg4y1D7aJ/?spm_id_from=333.337.search-card.all.click",
                   headers=header)

data_list = re.findall(r'var options = (.+);', res.text)

data_dict = json.loads(data_list[0])
aid = data_dict['aid']
cid = data_dict['cid']

# 1.明文参数
data_dict = {
    "aid": aid,
    "auto_play": "0",
    "build": "6240300",
    "cid": cid,
    "did": did,
    "epid": "",
    "from_spmid": "main.ugc-video-detail.0.0",
    "ftime": str(int(time.time() - random.randint(100, 5000))),
    "lv": "0",
    "mid": "0",
    "mobi_app": "android",
    "part": "1",
    "sid": "0",
    "spmid": "main.ugc-video-detail.0.0",
    "stime": str(int(time.time())),
    "sub_type": "0",
    "type": "3"
}

# 2.sign签名
v1 = "&".join([f"{key}={data_dict[key]}" for key in sorted(data_dict)])
salt = "9cafa6466a028bfb"
obj = hashlib.sha256()
obj.update(v1.encode('utf-8'))
obj.update(salt.encode('utf-8'))

sign_string = obj.hexdigest()
print(sign_string)

data_string = f"{v1}&sign={sign_string}"

# 3.AES加密

KEY = "fd6b639dbcff0c2a1b03b389ec763c4b"
IV = "77b07a672d57d64c"

aes = AES.new(
    key=KEY.encode('utf-8'),
    mode=AES.MODE_CBC,
    iv=IV.encode('utf-8')
)
bytes_data = pad(data_string.encode('utf-8'), 16)

result = [item for item in bytes_data]
print(result)

 六.下期预告

源码都给了,那就不总结了吧~ 下期还是和大家说b站,具体哪个接口你们猜猜吧~

  补充

如有需要学习资料和交流加我绿泡泡

这里插入一条广告(希望理解一下,养家糊口)!!!

有需要逆向需求或者是项目,课设等等,都能找我接单,麻烦大家了

公众号(后续会更新相关文章) 

 期待你的关注

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值