1.前言
之前在百度发现某网站aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tLw== android有个版本不需要vip可以使用观看高画质,然后想看看是谁家高人破解的,也没有源代码,就自己分析了
2.逆向工具
frida jadx
3.逆向记录
3.1 旧版抓包
通过抓包发现接口playurl.v1.PlayURL/PlayView返回数据是二进制,然后二进制查看一下又
有明显的视频流,很奇怪就hook 解码了,直接hook okhttp的构建请求函数,然后查看调用就发现app.playurl.v1.PlayURLMoss.executePlayView()方法,返回结构体很蒙圈,然后问下ai说是grpc的, 百度了一下服务器是rpc结构体,然后github上刚好找到proto文件,
3.2 新版抓包
通过上面方法找到接口app.playerunite.v1.Player/PlayViewUnite 也找到proto文件(后面也会逆向出文件了)
3.3 分析逻辑
playurl.v1.PlayURL/PlayView多次测试发现这个接口不会验证vip信息直接返回高画质视频流
直接上逻辑代码吧 playViewRequest PlayViewReply proto编译的 (备注:我需要c#做机器人然后c#grpc把我弄炸了直接模拟http吧)
private static async Task<Dictionary<long, string>?> RequestPlayView(long aid, long cid)
{
var playViewUnitedata = new Dictionary<long, string>();
var playViewRequest = new PlayViewReq
{
Aid = aid,
Cid = cid,
Qn = 125,
Fnver = 0,
Fnval = 4048,
Download = 2,
ForceHost = 0,
Fourk = false,
Spmid = "main.ugc-video-detail.0.0",
FromSpmid = "tm.recommend.0.0",
TeenagersMode = 0,
PreferCodecType = CodeType.Code265,
Business = Business.Unknown,
VoiceBalance = 1
};
var requestData = playViewRequest.ToByteArray();
var requestLength = BitConverter.GetBytes(requestData.Length);
Array.Reverse(requestLength);
var grpcPayload = new byte[1].Concat(requestLength).Concat(requestData).ToArray();
var request = new HttpRequestMessage(HttpMethod.Post,
"https://app.bilibili.com/bilibili.app.playurl.v1.PlayURL/PlayView");
AddGrpcHeaders(request);
request.Content = new StreamContent(new MemoryStream(grpcPayload));
request.Content.Headers.Add("Content-Type", "application/grpc");
var res = await Client.SendAsync(request);
try
{
var resdata = await res.Content.ReadAsByteArrayAsync();
var viewReply = PlayViewReply.Parser.ParseFrom(resdata.AsSpan(5));
foreach (var video in viewReply.VideoInfo.StreamList)
{
Console.WriteLine(video.StreamInfo.Quality);
if (video.DashVideo != null)
playViewUnitedata.TryAdd(video.StreamInfo.Quality, video.DashVideo.BaseUrl);
}
if (playViewUnitedata.Count <= 0) return null;
playViewUnitedata.TryAdd(0, viewReply.VideoInfo.DashAudio[0].BaseUrl);
return playViewUnitedata;
}
catch (Exception e)
{
Console.WriteLine("解析失败");
return null;
}
}
4 机器人很麻烦 有没有更简单的
有的有的, 直接hook 写xp模块
代码很乱我就用frida的了
Java.perform(function () {
var PlayerMoss = Java.use("com.bapis.bilibili.app.playerunite.v1.PlayerMoss");
var PlayViewReq = Java.use("com.bapis.bilibili.app.playurl.v1.PlayViewReq");
var PlayURLMoss = Java.use("com.bapis.bilibili.app.playurl.v1.PlayURLMoss");
var CodeType = Java.use("com.bapis.bilibili.app.playurl.v1.CodeType");
var PlayersharedStream = Java.use("com.bapis.bilibili.playershared.Stream");
var PlayersharedStreamInfo = Java.use("com.bapis.bilibili.playershared.StreamInfo");
var PlayersharedDashVideo = Java.use("com.bapis.bilibili.playershared.DashVideo");
var PlayersharedDashItem = Java.use("com.bapis.bilibili.playershared.DashItem");
PlayerMoss.executePlayViewUnite.implementation = function (req) {
var vod = req.vod_.value;
var aid = vod.aid_.value;
var cid = vod.cid_.value;
var builder = PlayViewReq.newBuilder();
builder.setAid(aid);
builder.setCid(cid);
builder.setQn(125);
builder.setFnver(0);
builder.setFnval(4048);
builder.setDownload(0);
builder.setForceHost(0);
builder.setSpmid("main.ugc-video-detail.0.0");
builder.setFromSpmid("tm.recommend.0.0");
builder.setTeenagersMode(0);
builder.setVoiceBalance(0);
var codec = CodeType.valueOf("CODE265");
builder.setPreferCodecType(codec);
var playViewReq = builder.build();
var moss = PlayURLMoss.$new("IGNORED", 443, null, 4, null);
var PlayViewreply = moss.executePlayView(playViewReq).getVideoInfo();
var result = this.executePlayViewUnite(req);
result.getVodInfo().clearStreamList();
var playurllistsize = PlayViewreply.getStreamListCount();
for (var i = 0; i < playurllistsize; i++) {
var oldStream = PlayViewreply.getStreamList(i);
var oldInfo = oldStream.getStreamInfo();
var oldDash = oldStream.getDashVideo();
// ===== Stream Builder(基于原始 stream)=====
var newstr = PlayersharedStream.newBuilder();
// ===== StreamInfo Builder =====
var newinfo = PlayersharedStreamInfo.newBuilder();
newinfo.setAttribute(0);
newinfo.setQuality(oldInfo.getQuality());
newinfo.setFormat(oldInfo.getFormat());
newinfo.setDescription(oldInfo.getDescription());
newinfo.setErrCodeValue(0);
newinfo.setNeedVip(false);
newinfo.setNeedLogin(false);
newinfo.setIntact(true);
newinfo.setNoRexcode(oldInfo.getNoRexcode());
newinfo.setNewDescription(oldInfo.getNewDescription());
newinfo.setDisplayDesc(oldInfo.getDisplayDesc());
newinfo.setSuperscript(oldInfo.getSuperscript());
newinfo.setVipFree(false);
newinfo.setSubtitle(oldInfo.getSubtitle());
newinfo.setSupportDrm(oldInfo.getSupportDrm());
// ===== DashVideo Builder =====
var newDashVideo = PlayersharedDashVideo.newBuilder();
newDashVideo.setBaseUrl(oldDash.getBaseUrl());
newDashVideo.setBandwidth(oldDash.getBandwidth());
newDashVideo.setCodecid(oldDash.getCodecid());
newDashVideo.setMd5(oldDash.getMd5());
newDashVideo.setSize(oldDash.getSize());
newDashVideo.setAudioId(oldDash.getAudioId());
newDashVideo.setNoRexcode(oldDash.getNoRexcode());
newDashVideo.setFrameRate(oldDash.getFrameRate());
newDashVideo.setWidth(oldDash.getWidth());
newDashVideo.setHeight(oldDash.getHeight());
newDashVideo.setWidevinePssh(oldDash.getWidevinePssh());
// ===== 写回 Stream =====
newstr.setStreamInfo(newinfo.build());
newstr.setDashVideo(newDashVideo.build());
result.getVodInfo().addStreamList(newstr.build());
}
// ===== DashAudio(DashItem)=====
for (var i = 0; i < PlayViewreply.getDashAudioCount(); i++) {
var oldDash = PlayViewreply.getDashAudio(i);
var newDash = PlayersharedDashItem.newBuilder();
newDash.setId(oldDash.getId());
newDash.setBaseUrl(oldDash.getBaseUrl());
newDash.setBandwidth(oldDash.getBandwidth());
newDash.setCodecid(oldDash.getCodecid());
newDash.setMd5(oldDash.getMd5());
newDash.setSize(oldDash.getSize());
newDash.setFrameRate(oldDash.getFrameRate());
newDash.setWidevinePssh(oldDash.getWidevinePssh());
result.getVodInfo().addDashAudio(newDash.build());
}
console.log(result.getVodInfo());
return result;
};
});