在某特上关注了一点乱七八糟的东西,然后就看到了这么一款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下载了:
- 解密后的图片资源
- 解密后的m3u8文件
apk文件下载地址: ZnUyLmxpdmUv
最后于 1天前 被obaby编辑 ,原因: