小白获取电视直播源--2
今天分析这个对于我这个小白来说难度挺大的.我搞了好久,期间也有运气成分,才大致获取了它拿电视源的方法
他的源要解密好多次,就像玩密室逃脱一样...期间还有好多小陷阱
想想以前用OD手撕壳修复引用啥的,也没觉得有这次那么艰难啊...
但他也很简单,最终拿源如下是http的明文,你说作者包裹那么厚是干嘛呢....
现在开始分析
首先,他有壳,小白不会整,还是hook吧.
然后代{过}{滤}理抓包----有校验,不得行,不会绕,不代{过}{滤}理就不代{过}{滤}理呗, wireshark抓包也挺香
设置过滤条件,https,http,dns 出电视画面立即停止
然后过滤dns的包,再按流量排个序,大致就知道是哪个链接获取的直播源了(通讯量肯定大)
然后要通讯,总绕不过socket吧,直接hook socket.connect 找到和wireshark对应的IP域名(图是两次抓的,他域名会变,凑合着看吧)
点进去看堆栈,找到了b4.f这个方法 成功拿到源的下载地址.
但是下载下来是加密的,需要解密, hook aes试试
看大小,摘要,应该就是我们要的
打开看,有明文,有乱码,保存下来用16进制编辑器看看,我还大致分析了下他的格式,比如每个字符串前都会标明长度...然而没什么卵用....不过脑子里得记着这段乱码.
到底部看堆栈,看不到源码只能凭感觉 ,Y3.a.run像是个线程开始,然后调用X3.L.a方法
那继续hook,然后这里传递了2个参数类型,,都hook看看呗
hook出来一大堆,大致翻翻,翻出这个,比对下前面的乱码里的字符串,这应该就是操作文件后得到的内容了.
所以他部分是明文,slb字段是密文的
继续翻堆栈,发现初始堆栈都是Y3.a.run,但是X3.L的初始是U3.D.run
那hook G3.b
貌似发现了程序运行的起始位置(应该是吧)
我hook的是G3.b的初始化过程,
猜下他的过程,应该是初始化后,然后用线程去执行他.
那关键应该就是h3.g或者U3.a.N来控制线程,并执行不同的线
看下这个堆栈流程的下一个流程调用了哪些堆栈
省略中途一个看着没用的,找到了d3.e,在这里又调用了那段乱码,并且只有我看的那个台的
差不多要初始化了吧
然后额....线索断了...
没办法 瞎折腾了好久,期间 hook了文件操作得到了它动态加载的一个jar文件.
这个文件能用jadx反编译.
然后看hook的aes下面那条解密内容和堆栈,看见了这个乱码类,竟然在动态加载的jar里
吼吼 终于有源码看了,而且貌似关键的东西它放这个动态加载类里的,这里有一个不是那堆一坨abcd的东西,不分析下对不起作者
这个一看像是一个播放器的类, 查了一下java 的message,貌似是线程之间传递消息的,会不会前面消失的线索发这里来了?而且还是startup啥的
必须hook下
然后就发现了startcid这个方法,就是从刚刚那个d3.e.c过来的呀....还有cctv1的字样 呵呵....
把这串base64解码对比下,发现和原始的不一样,应该中途还有一次解密...
这里就不得不夸下ChatGPT,给它解密前后的代码让它分析,他竟然真找出算法来了...
std::vector<uint8_t> decodedBytes = Base64Decode(base64Input);
size_t byteCount = decodedBytes.size();
std::vector<uint8_t> resultBytes(byteCount);
for (size_t index = 0; index < byteCount; index++) {
int byteOffset = (byteCount + index) % 128;
int maskValue = (byteOffset >= 128) ? 128 : 0;// 如果 byteOffset >= 128 则掩码为 128,否则为 0
resultBytes = decodedBytes ^ maskValue;// 使用掩码值和当前字节异或
}
// 输出处理后的结果
for (auto byte : resultBytes) {
std::cout << std::hex << static_cast<int>(byte) << " ";
}
return 0;
看看下面解码的slb字段以及AES/CTR/NoPadding解密的内容,再用16进制编辑器看下那堆乱码,其实就是用0xa+length+content 写进去的N个需要aes解密的内容
接下来就是对这段源码进行分析,我不太看的懂java,丢进ChatGPT去让它读,分析的明明白白...
中间代码分析的过程就不说了(源码,好懂)
最终找到了这串代码
import android.net.Uri;
import android.util.Pair;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
/* loaded from: classes.dex */
public final class K extends b {
public static final String a = o0.a(f.a(97, 72, 82, 48, 99, 68, 111, 118, 76, 50, 100, 122, 98, 71, 74, 122, 90, 88, 74, 50, 76, 109, 108, 48, 100, 105, 53, 106, 98, 88, 90, 112, 90, 71, 86, 118, 76, 109, 78, 117, 76, 50, 108, 117, 90, 71, 86, 52, 76, 109, 48, 122, 100, 84, 103, 47, 89, 50, 104, 104, 98, 109, 53, 108, 98, 67, 49, 112, 90, 68, 48, 108, 99, 121, 90, 68, 98, 50, 53, 48, 90, 87, 53, 48, 97, 87, 81, 57, 74, 88, 77, 109, 98, 71, 108, 50, 90, 87, 49, 118, 90, 71, 85, 57, 74, 88, 77, 109, 99, 51, 82, 105, 83, 87, 81, 57, 78, 67, 86, 122));
public static final String b = o0.a("JnN0YXJ0dGltZT0lcyZlbmR0aW1lPSVz");
public static final String[] c = {f.a(121, 115, 116, 101, 110, 108, 105, 118, 101), f.a(98, 101, 115, 116, 122, 98), f.a(119, 97, 115, 117, 115, 121, 116), f.a(70, 105, 102, 97, 115, 116, 98, 76, 105, 118, 101), f.a(104, 110, 98, 98, 108, 105, 118, 101)};
public static final String[] d = {"J4YQcw", "J4Zb7g", "J4aOlA", "J4aVuw", "J4c5Lw", "J4d6tQ", "J4e86w", "J4iH8A", "J4iH8Q", "J4kYFg"};
public static final HashMap e = new HashMap(1);
@Override // p000.c
public final String a(String str) {
byte b2;
String format;
String[] split = str.split(I.a).split(I.b);
String str2 = split;
int parseInt = Integer.parseInt(str2.substring(0, 1));
if (parseInt >= 0) {
String[] strArr = c;
if (parseInt < strArr.length) {
String str3 = strArr;
String substring = str2.length() == 20 ? str2.substring(1) : str2.charAt(1) + String.format(Locale.getDefault(), "%09d", Integer.valueOf(Integer.parseInt(str2.substring(2, 3)))) + String.format(Locale.getDefault(), "%09d", Integer.valueOf(w0.c(str2.substring(3))));
if (split.length == 1) {
b2 = 1;
format = "";
} else {
b2 = 4;
long parseLong = Long.parseLong(split);
long a2 = q0.a();
long min = Math.min(parseLong, a2 - 120000);
Pair pair = new Pair(Long.valueOf(min), Long.valueOf(Math.min(a2 - 30000, 3600000 + min)));
format = String.format(b, q0.a("yyyyMMdd'T'HHmmss.SSS'Z'").format(new Date(((Long) pair.first).longValue())), q0.a("yyyyMMdd'T'HHmmss.SSS'Z'").format(new Date(((Long) pair.second).longValue())));
}
String format2 = String.format(a, str3, substring, Byte.valueOf(b2), format);
if (k0.a(format2, null, 300, 500)) {
return format2;
}
F a3 = H.a(new F(format2));
a3.b().setInstanceFollowRedirects(false);
String str4 = F.g;
try {
a3.e = E.a;
D d2 = a3.d;
if (d2 != null) {
try {
d2.close();
} catch (IOException e2) {
}
a3.d = null;
}
String headerField = a3.b().getHeaderField(str4);
String authority = Uri.parse(headerField).getAuthority();
if (authority == null) {
return null;
}
String str5 = authority.split(":");
e.put(F.f, str5);
for (String str6 : d) {
String replace = headerField.replace(str5, w0.a(str6));
HashMap hashMap = e;
if (k0.a(replace, hashMap, 300, 500)) {
return b.a(replace, hashMap);
}
}
return null;
} catch (IOException e3) {
throw new C(e3);
}
}
}
return null;
}
}
将string a还原下,其实就是
a = "http://gslbserv.itv.cmvideo.cn/index.m3u8?channel-id=%s&Contentid=%s&livemode=%s&stbId=4%s";
b = "&starttime=%ss&endtime=%s";
c是livemode的5个对应参数字典
d通过base64解码的4个字节是IP地址
slb解码出来的内容i://261HQ4
i代表用哪个类去执行,这里是K这个类.
2代表是channel-id是c
后面的得到contentID
最终得到链接
http://gslbserv.itv.cmvideo.cn/index.m3u8?channel-id=wasusyt&Contentid=6000000001000029752&livemode=1&stbId=4
继续 还有一系列小陷阱:
访问这个链接重定向到
http://cache.ott.wasulive.itv.cmvideo.cn:80/000000001000/6000000001000029752/index.m3u8?channel-id=wasusyt&Contentid=6000000001000029752&livemode=1&stbId=4&version=1.0&owaccmark=6000000001000029752&owchid=wasusyt&owsid=1477671728160118146&AuthInfo=PK0J3Tn6Yz04PXV3woR5G%2BZOF9YRCjgFxTMQsMRw6Hxi73CalIsyUCBTqakH8JJ3FaMlNZcUtmIsSDR4jFEC%2Fw%3D%3D
然后用d中的IP替换cache.ott.wasulive.itv.cmvideo.cn(这个域名解析出来的IP是1.1.1.1)
再将链接中的host改成cache.ott.wasulive.itv.cmvideo.cn
如果能拿到正确的文件,就开始播放
不能则循环到d中的下一个IP
至此 播放源获取完毕.....
这整个过程真够BT的.....而且最终拿源也要逐个去服务器上拿.还他娘的http明文,何必把代码包裹的这么好折腾人啊...
【代码包裹的这么好折腾人】,
这是破解和反破解的较真,
写了直直播app,然后他们就会开始收费,
但是,好多人不愿意给他们缴费,
于是诞生了一个又一个群,就是为了破解他们的链接,
最后的结局就是这样:代码包裹得一层又一层又一层的{:1_896:} 感谢分享🙏 现在看个电视真的很麻烦了 大佬好,继续虚心学习 这是真大佬啊,光看看都头晕 感谢大佬分享 感谢大佬分享,学习中 不可多得的精品! 只能仰望大佬了 跟着大佬学习了,感谢大佬的分享