某加密到牙齿的APP数据加密分析
在某特上关注了一点乱七八糟的东西,然后就看到了这么一款app。无聊业余时间的时候爬取了一些福利app的数据,于是就想顺便看下这个东西的数据是否也可以爬取。
图片解析
习惯性的打开HttpCanary抓包,目前一切正常。
数据都能获取到,既然要爬数据,肯定是要能够看到图片,这个是最起码的。 图片链接如下: https://ssimg.bdxxo.cn/tv_adult/avid5c33013611e90.jpg?k=0bf5566b1e365d4f0cf78f566269e769&t=1593571053
看到后面的k和t,忽然觉得这个东西可能没这么简单,应该是服务器进行访问校验了,先不管这个,直接访问下看看。
这个,尼玛,厉害了。带着key和token访问直接返回了个黑窗口(如果时间超过了链接中的t参数表示的时间,那么直接就403了)。
把图片下载下来,拉入010,果然是加密处理了。
没有找到图片文件的文件头,所以浏览器或者图片查看器也就没有办法解析这个图片。
如何解析图片,那就要从apk入手进行分析了。最终在package net.idik.lib.cipher.so;下面找到了可疑的key和iv:
public static final String dbImgKey() {
return CipherCore.get("29993fb387b37c932b56fd54b130e0c6");
}
public static final String decodeImgIv() {
return CipherCore.get("f3d9434408e52778164db2214e3a0a22");
}
通过交叉引用,可以定位到图片解密代码位于com.ilulutv.fulao2.other.g.b:
public static byte[] b(byte[] arg3, byte[] arg4, String arg5) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
Cipher v0 = Cipher.getInstance("AES/CBC/PKCS5Padding");
v0.init(2, new SecretKeySpec(arg3, "AES"), new IvParameterSpec(arg4));
return v0.doFinal(Base64.decode(arg5, 2));
}
配置jeb调试器,如果不修改ro.debuggable 直接附加进程,会出现下面的错误信息:
确定之后就直接失败了:
修改安卓的ro.debuggable属性可以通过magisk 或者mprop([https://bbs.pediy.com/thread-215311.htm]),当然也有其他的办法,修改boot.img等等。不幸的是,我在夜神模拟器上安装magisk之后,卡在了检查更新上,没有办法继续安装。知道原因的还望不吝赐教。
另外一个办法,就是通过mprop修改,下载之后,直接运行对应的bat文件即可。
需要注意的是,运行完脚本之后需要重新打开apk,否则依旧无法进行附加。
附加之后,向下滚动页面加载内容。此时断点就断下来了。
单步执行到00000014 invoke-direct SecretKeySpec-><init>([B, String)V, v1, p0, v2 这一行就可以看到具体的key和iv的值了。
默认的jeb的局部变量类型全部为int,可以根据代码来修改变量类型,这里两个参数的类型都是B[,修改之后就可以看到具体的数值了, 如下:
比较蛋疼的一点是,jeb直接复制的变量的值是下面的格式:
array@7671 (type=[B)
[-78(FFFFFFFFFFFFFFB2h), -13(FFFFFFFFFFFFFFF3h), -124(FFFFFFFFFFFFFF84h), 40(28h), 102(66h), -7, 88(58h), 61(3Dh), 30(1Eh), -50(FFFFFFFFFFFFFFCEh), 97(61h), -60(FFFFFFFFFFFFFFC4h), -32(FFFFFFFFFFFFFFE0h), 85(55h), -62(FFFFFFFFFFFFFFC2h), 85(55h)]
鉴于net.idik.lib.cipher.so的目录下的key比较多,并且key包含不可打印字符,直接复制数值也比较蛋疼。
此时frida hook就派上用场了:
var base64 = Java.use('android.util.Base64');
var aes = Java.use('com.ilulutv.fulao2.other.g.b');
// 图片加密处理
aes.b.overload("[B", "[B", "java.lang.String").implementation = function(k, iv, source_string){
send("Image_key:"+k);
send(base64.encodeToString(k, 0))
send("Image_iv:"+iv);
send(base64.encodeToString(iv, 0))
return this.b(k, iv, source_string);
};
由于该函数的key和为是一个byte数据,所以直接通过send函数发送。接收到的数据是个Image_iv:[object Object] 无法正常显示,所以上面的代码对数据进行了base64编码之后发送。
实际接收到的数据为:
[*] ===========================override image a begin ===========================
[*] Image_key:[object Object]
[*] svOEKGb5WD0ezmHE4FXCVQ==
[*] Image_iv:[object Object]
[*] 4B7eYzHTevzHvgVZfWVNIg==
[*] ===========================override image a end ===========================
有了这两个数据就可以去解密图片内容了:
def aes_decrypt_raw(key, data, ivs):
encodebytes = data
cipher = AES.new(key, AES.MODE_CBC, ivs)
text_decrypted = cipher.decrypt(encodebytes)
unpad = lambda s: s[0:-s[-1]]
text_decrypted = unpad(text_decrypted)
return text_decrypted
def decode_image():
f = open(r"H:\PyCharmProjects\frida_test\avid5c33013611e90.jpg", 'rb') # 二进制读
b = f.read()
f.close()
# array@7687 (type=[B)
image_key_base64 = bytes('svOEKGb5WD0ezmHE4FXCVQ==', encoding='utf8') # 图片解密key
image_key = base64.decodebytes(image_key_base64)
print(image_key)
iv = base64.decodebytes(bytes('4B7eYzHTevzHvgVZfWVNIg==', encoding='utf8')) # 图片解密iv
de = aes_decrypt_raw(image_key, b, iv)
f = open(r"H:\PyCharmProjects\frida_test\avid5c33013611e90_decode.jpg", 'wb')
f.write(de)
f.close()
解密之后的图片内容:
鉴于图片内容比较暴力,这里就不展示了,感兴趣的自己去解析即可。到这里图片的内容算是处理完成了。
数据接口分析
图片可以查看之后,主要的目标是要爬取数据,那么数据来源就很关键。通过接口格式猜测,请求视频列表的接口应该是https://api-al.vipmxmx.cn/v1/videos/menu/0?payload=D%2FPrh8wy4ODFaRYJGqhokg%3D%3D.9VP71aDIZgmZFc6X3l%2BfPoETfpXd4Jt%2BTN49ks4edK8vgtl1XHAvEPzA9EC7mTBjU59pMvWwASxSl9nUQA%2BpzTqjNk0hzAO%2FTMZ6fBkTwtJ4S11%2F4RABwCQVs%2Flk5VuDxcF6DUYuV7XnKO%2FI25woZXbONYp47i%2F1h5OGcW3I91LfJ4G8c0HZI7kli5RLbgn2Rvqt6Jk897dHkmnj4n2tbhoS3nC%2Bp3hxauGMGH2%2Byl1kah6ZGKL%2FarjRwBKR8%2Bbv7XCApO%2BWMrjwxMdJBZjxnV6obnCF5KqYGtauUC5ZN31AjG%2F7ilKr7PGYqu2b%2FrSqpvPXWBizmuw9JF1e%2BnC41vj6bOBXx4swAHsBFFK64C46byIxosDNHN4i5dofoaQH
请求接口比较简洁,应该是把所有的参数都放到了payload下面
返回的数据比较复杂,头部包含了大量的信息:
并且返回的数据进行了加密:
要想通过接口访问数据,那么就要解析请求数据,解密返回的数据。
通过参数的payload最终可以定位到以下代码:
private void d() { // 页面请求函数
String v0_3;
try {
this.j.put("path", this.o.substring(1));
this.j.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000L));
String v0_1 = new Gson().toJson(this.j);
byte[] v1 = Base64.decode(CipherClient.apiEncryptParamsKey(), 0);
// 下面一行jeb给解析成了一个数组,实际在代码中的是个随机函数。
byte[] v2 = new byte[]{-73, 0x3F, 110, -34, 0xE1, -56, -7, -4, 88, 0x8E, 101, 38, -92, 21, -61, 17};
// 下面是aes加密,如果要知道加密的key和iv只需要hook b.c函数即可。
String v0_2 = b.c(v1, v2, v0_1);
int v1_1 = this.l;
if(v1_1 == 81002) {
label_39:
// b.a base64编码。v2为加密的iv, 通过base64编码iv之后 使用.将iv和加密后的请求数据链接。
v0_3 = b.a(v2) + "." + v0_2;
}
else {
if(this.l == 81004) {
goto label_39;
}
// b.g 为urlencoder函数
v0_3 = b.g(b.a(v2) + "." + v0_2);
}
this.j.clear();
this.j.put("payload", v0_3);
}
catch(NoSuchAlgorithmException v0) {
v0.printStackTrace();
}
}
通过frida hook 对b.c函数进行hook:
// 接口加密处理
aes.c.overload("[B", "[B", "java.lang.String").implementation = function(k, iv, source_string){
send("===========================override c begin ===========================")
send("key:"+k);
send(base64.encodeToString(k, 0))
send("iv:"+iv);
send(base64.encodeToString(iv, 0))
send("source_string:");
send(source_string);
var res = this.c(k, iv, source_string);
send('result:');
send(res)
send("===========================override c end ===========================")
return res;
};
所以如果要进行接口加密,那么只需要知道加密的key即可,以为通过上面的代码分析可以知道,iv是一个随机函数生成的长度为16的数组。如果要想模拟的真实一点可以自己写一个随机函数,当然也可以直接使用jeb解析出来的数组去请求也是ok的。 捕获到的数据如下;
[*] ===========================override c begin ===========================
[*] key:[object Object]
[*] euZN1Gg3JIwWOEWhmE7C4l5dSSRU34fyuPMXjtuoqVs=
[*] iv:[object Object]
[*] D/Prh8wy4ODFaRYJGqhokg==
[*] source_string:
[*] {"timestamp":"1593572575","order":"time","video_type":"long","type":"uncover","page":"6","token":"eyJ1c2VyX2lkIjoyMTg4MjU2NCwibGFzdGxvZ2luIjoxNTkzMzI3NzQyfQ.c39375da9af6cf24aae0349c4f0b5641.9b0e72bc9a1eea26114dc955d730603e4c11f865d43ea9595d9fd29c","path":"v1/videos/menu/0"}
[*] result:
[*] 9VP71aDIZgmZFc6X3l+fPoETfpXd4Jt+TN49ks4edK8vgtl1XHAvEPzA9EC7mTBjU59pMvWwASxSl9nUQA+pzTqjNk0hzAO/TMZ6fBkTwtJ4S11/4RABwCQVs/lk5VuDxcF6DUYuV7XnKO/I25woZXbONYp47i/1h5OGcW3I91LfJ4G8c0HZI7kli5RLbgn2Rvqt6Jk897dHkmnj4n2tbhoS3nC+p3hxauGMGH2+yl1kah6ZGKL/arjRwBKR8+bv7XCApO+WMrjwxMdJBZjxnV6obnCF5KqYGtauUC5ZN31AjG/7ilKr7PGYqu2b/rSqpvPXWBizmuw9JF1e+nC41vj6bOBXx4swAHsBFFK64C46byIxosDNHN4i5dofoaQH
[*] ===========================override c end ===========================
将euZN1Gg3JIwWOEWhmE7C4l5dSSRU34fyuPMXjtuoqVs= base64 decode之后即可获得加密用的key。
有了这些数据,那么就可以发送请求了, 测试代码如下:
def new_video_get_test():
request_key = base64.decodebytes(bytes('euZN1Gg3JIwWOEWhmE7C4l5dSSRU34fyuPMXjtuoqVs=', encoding='utf8'))
# request_iv = b'\x49\x09\x3E\x49\x6D\x29\x50\xBB\xF1\x67\x9C\x5D\x52\x77\xBF\x4E'
request_iv = base64.decodebytes(bytes('HTpKwS4MVfB2pktFSGRzvw==', encoding='utf8'))
ss = '{"timestamp":"' + str(int(
time.time())) + '","order":"time","video_type":"long","type":"uncover","page":"1","token":"eyJ1c2VyX2lkIjoyMTg4MjU2NCwibGFzdGxvZ2luIjoxNTkzMzI3NzQyfQ.c39375da9af6cf24aae0349c4f0b5641.9b0e72bc9a1eea26114dc955d730603e4c11f865d43ea9595d9fd29c","path":"v1/videos/menu/0"}'
ds = AES_Encrypt_raw(request_key, ss, request_iv)
print(ds)
payload = 'HTpKwS4MVfB2pktFSGRzvw==.' + ds
base_url = 'https://api-tc.bjsongmoxuan.cn/v1/videos/menu/0?payload=' + urllib.parse.quote(payload)
print(base_url)
resp = requests.get(base_url)
print(resp.text)
此时虽然已经能够发送请求了,但是返回的数据是加密的,如果要想获取直接可用的数据,那么就需要对返回的数据进行解密。
通过层层分析,可以定位到响应数据的解密函数为:
public static String a(String arg2, String arg3, String arg4) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException { // 请求解密函数
String v2 = b.e(arg2); // MD5加密
return b.a(new IvParameterSpec(arg3.getBytes(StandardCharsets.UTF_8)), new SecretKeySpec(v2.getBytes(StandardCharsets.UTF_8), "AES"), arg4);
}
同样对对该函数进行hook:
// 接口解密处理
aes.a.overload( "java.lang.String", "java.lang.String", "java.lang.String").implementation = function(k, iv, source_string){
send("===========================override a begin ===========================")
send("key:"+k);
//send(base64.encodeToString(k, 0))
send("iv:"+iv);
//send(base64.encodeToString(iv, 0))
send("source_string:");
send(source_string);
var new_key = this.e(k);
send("new key:" + new_key);
var res = this.a(k, iv, source_string);
send('result:');
send(res)
send("===========================override a end ===========================")
return res;
};
获取数据:
[*] ===========================override a begin ===========================
[*] key:fe34dd6bbd3020c2fb69abe73b5b973c
[*] iv:ce9337500ee76035
[*] source_string:
[*] Ev+s/klIcLdo5nodmhTvnxoUfD6wcyWiWBDBRgyS6sApaptgy/95gTgcmuyE1nGJzbFIj2aeJ2K5SjKQvD+soqLny5frYjbkvVm2IjrYkJfgeNRsA9zYrXSZ......1Qei+PMKE5mIzPWcLUfHVLNl6zQgbcu5oMLMs+qnKlgFRMjDCnAsL1PRXtkjHZ6XjDmimZIjMc125RbYNtF5EfPpsSvCue38a+pye4GuNNR8ssAJDx8NM9tSj1WmvZYaBh6LeC+3f7X5niBm18aNinI44Qu0wtwuWMSsLPqwsADLFPdOvvG3lFkuwOjuCCIEQ+LDGV7z2CBQlBO1NfYvueGyK/Ifm2eDEGoQ42BURtu2JiEeTV0SSQVRJt+Nk9OmDdrrtAlmJv/BHBaVjWez/zsF
[*] result:
[*] {"status":{"code":200,"message":"success"},"response":{"videos":[{"video_id":"62754","video_title":"清纯模特丽丽第二弹! 多姿势不间断各种大战表情超诱人","actor":"素人","thumb":"\/tv_adult\/avid5aa739d33cb71.jpg?k=3c31a0b2eb0c7f27f2bba952492dd7f4&t=1593573476","cover":"\/tv_adult\/avid5aa739d33cb71.jpg?k=3c31a0b2eb0c7f27f2bba952492dd7f4&t=1593573476","upload_date":1593356402,"release_date":-1,"video_duration":3660,"main_tag":[],"second_tag":["無"],"video_like":false},{"video_id":"112902","video_title":" 姑娘胯下含三次,胯界妹妹的中出日记! (FC2-PPV-1387608)","actor":"素人","thumb":"\/tv_adult\/avid5ed2ca1c2f19.jpg?k=232d13e7da07b264a567fc64c6a52f64&t=1593573476","cover":"\/tv_adult\/avid5ed2ca1c2f19.jpg?k=232d13e7da07b264a567fc64c6a52f64&t=1593573476","upload_date":1593354601,"release_date":-1,"video_duration":2940,"main_tag":[],"second_tag":["無"],"video_like":false},,"upload_date":1593325802,"release_date":1490371200,"video_duration":1020,"main_tag":[],"second_tag":["無"],"video_like":false}],"total_results":20435,"page":6}}
[*] ===========================override a end ===========================
多次请求就会发现,key是固定的。但是iv却是变的。跟踪iv的数据来源,最终可以定位到
private void b(r arg5) {
try {
String v0_1 = b.a(arg5);
if(v0_1 != null && !v0_1.isEmpty() && v0_1.getBytes(StandardCharsets.UTF_8) != null) {
super.a(b.a(CipherClient.decodeKey(), v0_1, ((String)arg5.a())), this.m);
return;
}
super.a(((String)arg5.a()), this.m);
}
catch(Exception v0) {
Crashlytics.logException(new Exception(v0 + " blank " + arg5.c().toString() + " blank " + b.a(arg5)));
super.a(905, String.valueOf(this.k));
}
}
// b.a函数
public static String a(r arg2) {
s v2 = arg2.c();
if(v2.a(CipherClient.headerIsEncryptKey()) != null) {
return v2.a(CipherClient.headerIsEncryptKey()).equals(CipherClient.headerIsEncryptValue()) ? b.e(v2.a(CipherClient.headerKey())).substring(8, 24) : null;
}
return "";
}
// b.e 函数md5:
public static String e(String arg6) {
try {
MessageDigest v0 = MessageDigest.getInstance("MD5");
v0.update(arg6.getBytes());
byte[] v6_1 = v0.digest();
StringBuilder v0_1 = new StringBuilder();
int v2;
for(v2 = 0; v2 < v6_1.length; ++v2) {
String v3;
for(v3 = Integer.toHexString(v6_1[v2] & 0xFF); v3.length() < 2; v3 = "0" + v3) {
}
v0_1.append(v3);
}
return v0_1.toString();
}
catch(NoSuchAlgorithmException v6) {
v6.printStackTrace();
return "";
}
}
为了简便期间,把上面的函数全部hook掉:
// 接口解密处理
aes.a.overload( "java.lang.String", "java.lang.String", "java.lang.String").implementation = function(k, iv, source_string){
send("===========================override a begin ===========================")
send("key:"+k);
//send(base64.encodeToString(k, 0))
send("iv:"+iv);
//send(base64.encodeToString(iv, 0))
send("source_string:");
send(source_string);
var new_key = this.e(k);
send("new key:" + new_key);
var res = this.a(k, iv, source_string);
send('result:');
send(res)
send("===========================override a end ===========================")
return res;
};
aes.a.overload("[B", "[B", "java.lang.String").implementation = function(k, iv, source_string){
send("===========================override a bytes begin ===========================")
send("key:"+k);
send(base64.encodeToString(k, 0))
send("iv:"+iv);
send(base64.encodeToString(iv, 0))
send("source_string:");
send(source_string);
var res = this.a(k, iv, source_string);
send('result:');
send(res)
send("===========================override a bytes end ===========================")
return res;
};
// md5函数
aes.e.overload("java.lang.String").implementation = function(source_string){
send("===========================override e begin ===========================")
send("source_string:");
send(source_string);
var res = this.e(source_string);
send('result:');
send(res)
send("===========================override e end ===========================")
return res;
};
aes.a.overload("i.r").implementation = function(source_string){
send("===========================override ia begin ===========================")
send("source_string:");
send(source_string.toString());
var res = this.a(source_string);
send('result:');
send(res)
send("===========================override ia end ===========================")
return res;
};
var iv_class = Java.use('e.s');
iv_class.a.overload("java.lang.String").implementation = function(arg2){
send("===========================iv test ===========================")
send("source_string:");
send(arg2);
var res = this.a(arg2);
send('result:');
send(res)
send("===========================iv test ===========================")
return res;
};
最终捕获到的数据如下:
[*] ===========================iv test ===========================
[*] ===========================override ia begin ===========================
[*] source_string:
[*] Response{protocol=h2, code=200, message=, url=https://api-al.vipmxmx.cn/v1/videos/menu/0?payload=D%2FPrh8wy4ODFaRYJGqhokg%3D%3D.9VP71aDIZgmZFc6X3l%2BfPoETfpXd4Jt%2BTN49ks4edK8vgtl1XHAvEPzA9EC7mTBjU59pMvWwASxSl9nUQA%2BpzTqjNk0hzAO%2FTMZ6fBkTwtJ4S11%2F4RABwCQVs%2Flk5VuDxcF6DUYuV7XnKO%2FI25woZXbONYp47i%2F1h5OGcW3I91LfJ4G8c0HZI7kli5RLbgn2Rvqt6Jk897dHkmnj4n2tbhoS3nC%2Bp3hxauGMGH2%2Byl1kah6ZGKL%2FarjRwBKR8%2Bbv7XCApO%2BWMrjwxMdJBZjxnV6obnCF5KqYGtauUC5ZN31AjG%2F7ilKr7PGYqu2b%2FrSqpvPXWBizmuw9JF1e%2BnC41vj6bOBXx4swAHsBFFK64C46byIxosDNHN4i5dofoaQH}
[*] ===========================iv test ===========================
[*] source_string:
[*] X-App-Name
[*] result:
[*] app
[*] ===========================iv test ===========================
[*] ===========================iv test ===========================
[*] source_string:
[*] X-App-Name
[*] result:
[*] app
[*] ===========================iv test ===========================
[*] ===========================iv test ===========================
[*] source_string:
[*] X-VTag
[*] result:
[*] 1115682708
[*] ===========================iv test ===========================
[*] ===========================override e begin ===========================
[*] source_string:
[*] 1115682708
[*] result:
[*] d16ec86cce9337500ee76035d220d5a9
[*] ===========================override e end ===========================
[*] result:
[*] ce9337500ee76035
[*] ===========================override ia end ===========================
[*] ===========================override a begin ===========================
[*] key:fe34dd6bbd3020c2fb69abe73b5b973c
[*] iv:ce9337500ee76035
通过关联可以找到,iv的数据来源为X-VTag字段。所以请求之后从response header中取出X-VTag就可以解密数据了。
解密代码:
response_headers = resp.headers
vtag = response_headers.get('x-vtag')
print(vtag)
i = md5(vtag)
print(i)
iv = i[8:24]
print(iv)
new_key = md5('fe34dd6bbd3020c2fb69abe73b5b973c')
dds = AES_Decrypt(new_key.encode('utf8'),
resp.text,
iv.encode('utf8'))
print(dds)
到这里接口的解密基本就完成了,可以获取app的视频基础信息了。
最后一米
虽然现在视频列表数据已经有了,但是在视频信息中并没有播放地址。所以最后的工作就是获取视频的播放地址。继续抓包可以看到视频地址信息为:https://api.bdxxo.cn/v1/video/info/67432?payload=hAnCu1kQHy0hCrdZo4swYQ%3D%3D.BrHBQxJsA%2BevWaHMbZYNjOj6B7kDZk98IbSJ94j2EhhuMqcY9Rkv37MRgcYIzprPpxX0VJKcAc4sGAIG%2FtgQ3ZW%2FsnDAC%2FdUtA7Y2AfafmRsjxAhzazlbpOo6AXlh0WD91CaE7D%2FymW129p%2Fx5xMJc8NWvaRBGmQSQLIsle0hdipXQKeOKXN3RBbVLv143p7wOLcabVOhYK22AMBucZl0dCYo7Nz1%2Bv2UH8AlaiMIkwwa6JPnZW8CQayhJrrEXU91phsb%2Bam8zNr9CIvSfTxgUaI%2BOXryyt%2BEsmyBMm2CMBUUD52Q95HBrw0rSgRQSxFurjKQtgckxQqVyshwDnK%2F884URbCKU9WouvTjpEnHdc%3D
使用上面分析的数据,对于请求进行解密, 并且模拟请求:
def video_get_detail_test(video_id):
request_key = base64.decodebytes(bytes('euZN1Gg3JIwWOEWhmE7C4l5dSSRU34fyuPMXjtuoqVs=', encoding='utf8'))
request_iv = b'\x49\x09\x3E\x49\x6D\x29\x50\xBB\xF1\x67\x9C\x5D\x52\x77\xBF\x4E'
request_iv = base64.decodebytes(bytes('HTpKwS4MVfB2pktFSGRzvw==', encoding='utf8'))
# {"an_stream":"https://tv-as.00ph.cn","timestamp":"1593568332","an_quality":"240","token":"eyJ1c2VyX2lkIjoyMTg4MjU2NCwibGFzdGxvZ2luIjoxNTkzMzI3NzQyfQ.c39375da9af6cf24aae0349c4f0b5641.9b0e72bc9a1eea26114dc955d730603e4c11f865d43ea9595d9fd29c","path":"v1/video/info/65696"}
ss = '{"an_stream":"https://tv-as.00ph.cn","timestamp":"' + str(int(
time.time())) + '","an_quality":"240","token":"eyJ1c2VyX2lkIjoyMTg4MjU2NCwibGFzdGxvZ2luIjoxNTkzMzI3NzQyfQ.c39375da9af6cf24aae0349c4f0b5641.9b0e72bc9a1eea26114dc955d730603e4c11f865d43ea9595d9fd29c","path":"v1/video/info/' + video_id + '"}'
ds = AES_Encrypt_raw(request_key, ss, request_iv)
print(ds)
payload = 'HTpKwS4MVfB2pktFSGRzvw==.' + ds
base_url = 'https://api.bdxxo.cn/v1/video/info/' + video_id + '?payload=' + urllib.parse.quote(payload)
print(base_url)
resp = requests.get(base_url)
print(resp.text)
# 1216557403 x-vtag
response_headers = resp.headers
vtag = response_headers.get('x-vtag')
print(vtag)
i = md5(vtag)
print(i)
iv = i[8:24]
print(iv)
new_key = md5('fe34dd6bbd3020c2fb69abe73b5b973c')
dds = AES_Decrypt(new_key.encode('utf8'),
resp.text,
iv.encode('utf8'))
print(dds)
返回数据信息:
{
"status":{
"code":200,
"message":"success"
},
"response":{
"video_id":"64852",
"video_title":"秀人网嫩模龙泽美曦宾馆与土豪援交!被玩到尖叫 汁液狂流",
"actor":[
"素人"],
"video_urls":{
"240":"https:\/\/tv-as.00ph.cn\/media\/240\/64852.m3u8?expire=1593570576&hash=4f11da5357d1b89828a952984fef1177",
"480":"https:\/\/tv-as.00ph.cn\/media\/240\/64852.m3u8?expire=1593570576&hash=4f11da5357d1b89828a952984fef1177"
},
"cover_url":"\/tv_adult\/avid5ba324d210218.jpg?k=166aea03d3630b419d8f23ec3078202a&t=1593569676",
"cover":"\/tv_adult\/avid5ba324d210218.jpg?k=166aea03d3630b419d8f23ec3078202a&t=1593569676",
"thumb":"\/tv_adult\/avid5ba324d210218.jpg?k=166aea03d3630b419d8f23ec3078202a&t=1593569676",
"upload_date":1593351002,
"release_date":0,
"video_duration":1260,
"video_like":false,
"video_publisher":"",
"video_number":"avid5ba324d210218",
"video_category":[
"小視頻"],
"video_tags":[
"小视频",],
"video_description":"",
"status":2,
"open_date":1593351002
}
}
到这里全部的数据接本就都有了,不过还有最后一点需要处理那就是返回的m3u8文件也是加密的,需要进行解密。解密方式与其他请求的解密方式一致。
不仅如此,返回的播放列表的地址也是带有效期参数。
对于播放地址,请求之后进行解密就可以看到m3u8文件的全部内容了:
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/key.php",IV=0xB3DFF72D36D4408E5B4CDF180B9EE03B
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-0.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-1.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-2.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-3.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-4.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-5.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-6.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-7.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-8.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-9.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-10.ts
#EXTINF:6.000000,
https://stream.00ph.cn/tv_adult/avid5ba324d210218/240/ts/avid5ba324d210218-11.ts
解密后的m3u8文件就可以扔给ffmpeg下载了:
总结
- apk整体的数据加密做的比较全,如果要获取数据需要对全部的数据进行分析拆分。否则无法正常展示相关信息
- 所有的数据都存在有效期,过了有效期之后将无法访问,于是要爬取这个app的数据需要本地存储部分数据,
- 可能还有其他的数据需要解密,这个感兴趣的自己去处理吧。这里就不写了,处理方式可以参考上面的方法。
- 分析了一些app和网站,这个算是数据加密做的比较彻底的,基本服务器返回的数据全部进行加密了,没有任何明文的内容。安全意识不错。
apk文件下载地址: ZnUyLmxpdmUv
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2020-7-1 13:53
被obaby编辑
,原因: