android tsclib.so,续 某哩某哩APP之m3u8解密分析之跳过so文件 从APP日志入手(AES/CBC模式)...

前排提示!!!!!

论坛禁止留联系方式!!

禁止求成品,也没有成品!

写在前面的话

前段时间已经研究过这个平台网页和APP的AES加解密方式了。

最近发现这个平台的APP的加解密方式更新了,所以又继续研究了下...

本贴用到的工具

1.反编译:jadx+NP管理器+Androidkiller(mt管理器的日志注入或Androidkiller的log/toast均可或别的工具均可)[jadx看java,NP管理器用来打印字符串APP,Androidkiller可以实时查看日志内容]

2.抓包:fiddler+安卓模拟器(这里用的是逍遥)

3.加解密网站(由于可能会...所以这里就不贴了)

抓包

1.打开APP抓包部分接口

设置好fd和模拟器的代{过}{滤}理端口之后,打开APP查看网络请求

55fd2b2273b5a8b4531f72773c469d6e.gif

1.0 fd.png (30.63 KB, 下载次数: 1)

2021-2-23 11:56 上传

接口

请求方式

提交内容

数据类型

(猜测)用途

host_XXXX.txt

GET

密文(暂不知道是什么加密)

可能是返回能正常访问的主机群

/v1/initial

GET

明文

新版本检测+首页弹窗+视频分类

/v1/register/token

POST

device_id/platform/key/universal_id/lang

明文

注册新用户,返回token和uid

/v1/user/info

POST

token/lang/download_amount

明文

查询并返回用户信息

/v1/firstpurchase

GET

明文

买VIP悬浮广告

2.随意点一个视频查看fd的数据

55fd2b2273b5a8b4531f72773c469d6e.gif

1.1 m3u8密文.png (128.34 KB, 下载次数: 0)

2021-2-23 12:10 上传

由图看出:

之前的m3u8处理方法

现在的m3u8处理方法

链接加密,m3u8明文

链接明文拼接,m3u8密文

(PS:图中能看到http://localhost:1500/video?mode=的请求,其实关闭模拟器的fd代{过}{滤}理之后,用浏览器打开这个网址.看到的就是解密后的m3u8的内容,不过这个m3u8文件是需要处理后才能用下载器下载的)

55fd2b2273b5a8b4531f72773c469d6e.gif

1.2 需要处理的本地m3u8.png (16.92 KB, 下载次数: 0)

2021-2-23 12:16 上传

(再PS:这个m3u8的headers中还有和网页版中的X-VTag参数)

55fd2b2273b5a8b4531f72773c469d6e.gif

1.3 vtag.png (15.81 KB, 下载次数: 1)

2021-2-23 13:08 上传

那我们就来用工具解密下m3u8

反编译app解密m3u8

把app拉入jadx

搜"X-VTag"

用jadx搜"X-VTag",发现只有一个a.b.i.a.h.a,点进去看看代码

55fd2b2273b5a8b4531f72773c469d6e.gif

1.4 X-VTag.png (15.14 KB, 下载次数: 0)

2021-2-23 12:23 上传

以下是a.b.i.a.h.a截取的部分代码

String a5 = tVar.a("X-VTag");/* ①.a5就是X-VTag的值,我测试的视频X-VTag是825497755 */

if (a5 == null || (i = a.b.f.o.f.i(a5)) == null) {/* ④.i=a.b.f.o.f.i,那么a.b.f.o.f.i是什么操作呢?我们用jadx跳到声明 */

str = null;/* ②.如果a5或i是null的那str=null */

} else {

str = i.substring(8, 24);/* ③.否则str= 从i的第9位取到第24位,i的长度为16 */

i.a((Object) str, "(this as java.lang.Strin…ing(startIndex, endIndex)");

}

h0 h0Var2 = a2.g;

w c = h0Var2 != null ? h0Var2.c() : null;

if (!(str == null || c == null)) {

h0 a6 = h0.a(c, a.b.j.v3.a.a(CipherClient.decodeKey(), str, a3));

g0.a aVar2 = new g0.a(a2);

aVar2.g = a6;

g0 a7 = aVar2.a();

i.a((Object) a7, "response.newBuilder().body(body).build()");

return a7;

}

a.b.f.o.f.i函数

public static final String i(String str) {

if (str != null) {

try {

MessageDigest instance = MessageDigest.getInstance(AESEncryptor.HASH_ALGORITHM);/* AESEncryptor.HASH_ALGORITHM跳转后是MD5 */

byte[] bytes = str.getBytes(z.z.a.f8176a);

i.a((Object) bytes, "(this as java.lang.String).getBytes(charset)");

instance.update(bytes);

byte[] digest = instance.digest();

StringBuilder sb = new StringBuilder();

for (byte b : digest) {

String hexString = Integer.toHexString(b & 255);

while (hexString.length() < 2) {

hexString = '0' + hexString;

}

sb.append(hexString);

}

String sb2 = sb.toString();

i.a((Object) sb2, "hexString.toString()");

return sb2;

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

return "";

}

} else {

i.a("$this$toMd5");

throw null;

}

}

看这个操作可以结案了,a.b.f.o.f.i函数就是把str参数md5处理了下

所以可知案例中的部分参数

变量

解析

a5

headers中的X-VTag的值

825497755

i

md5(a5)

9807a3e5370512bab61bee56200ee1d6

str

i.substring(8, 24)

370512bab61bee56

我们继续往下看a.b.i.a.h.a

h0 a6 = h0.a(c, a.b.j.v3.a.a(CipherClient.decodeKey(), str, a3));

先跳转看这个CipherClient.decodeKey()函数

public static final String decodeKey() {

String str = CipherCore.get("aa01bdd83d0f12833ddaea2f2af22865");

return str;

}

我们接着看CipherCore.get函数,跳转就跳到了net.idik.lib.cipher.so.CipherCore类中

package net.idik.lib.cipher.so;

public final class CipherCore {

static {

System.loadLibrary("cipher-lib");

init();

}

public CipherCore() throws IllegalAccessException {

throw new IllegalAccessException();

}

public static String get(String str) {

return getString(str);

}

public static native String getString(String str);

public static native void init();

}

我去,这样一看CipherCore.get的方法在cipher-lib.so中??

55fd2b2273b5a8b4531f72773c469d6e.gif

111.png (47.01 KB, 下载次数: 1)

2021-2-23 14:18 上传

这伪代码我也看不懂呀....不过不用怕.这个函数有返回值咱就不怕.

我们尝试用NP管理器的NPPrintFuncSrc打印出CipherClient.decodeKey()和其他函数的值(PS:也可以用Androidkiller的toast或者log,别的方法也均可)

smali代码是这样的:

.method public static final decodeKey()Ljava/lang/String;

.registers 1

const-string v0, "解密-decodekey"

invoke-static {v0}, Lnp/NPLogEncryptString;->NPPrintFuncSrc(Ljava/lang/String;)V  #打印"解密-decodekey"

const-string v0, "aa01bdd83d0f12833ddaea2f2af22865"

.line 1

invoke-static {v0}, Lnet/idik/lib/cipher/so/CipherCore;->get(Ljava/lang/String;)Ljava/lang/String;

move-result-object v0

invoke-static {v0}, Lnp/NPLogEncryptString;->NPPrintFuncSrc(Ljava/lang/String;)V  #打印decodeKey返回的字符串

return-object v0

.end method

java代码是这样的:

/* 以上省略大部分代码 */

public static final String decodeKey() {

NPLogEncryptString.NPPrintFuncSrc("解密-decodekey");

String str = CipherCore.get("aa01bdd83d0f12833ddaea2f2af22865");

NPLogEncryptString.NPPrintFuncSrc(str);

return str;

}

/* 以下也省略大部分代码 */

在Androidkiller中显示是这样的

55fd2b2273b5a8b4531f72773c469d6e.gif

1.5 decodekey.png (20.28 KB, 下载次数: 0)

2021-2-23 12:47 上传

按类似的方法成功获取到部分常量如下:

函数

常量

获取到的结果

apiHashKey

d708e111b5db90af74ef84ff4d5e647b

666wInteriscommingyoUshouldNotpassmotherfuckeR=

ccToken

810d903a88254b27c643e0bc471d406a

decodeKey

aa01bdd83d0f12833ddaea2f2af22865

6e561ccd4aade2fed458d4da61e76770

encodeType

e82b6153b4ea2340333e2254c3553d03

everIv

c5b287eb7ee64e90ed015bac470f4b6b

BakinsodaIgotbakinsoda

everKey

0d7cb519eb483a597549f7f466b189bc

iaMiNloveWithtHecoCo

hostIv

6cf9e96524081ac264dc31982d0be319

5e8bf1f958f56644

hostKey

51cdba173d412fdecec3e78572cde731

f332ae8214fcbb0d98f8626f123459b4

imageDecodeIv

9e1add49a87568f90c43e418e7370287

E01EDE6331D37AFCC7BE05597D654D22

imageDecodeKey

a8ae2831cbea74111bc5116ba81ec191

B2F3842866F9583D1ECE61C4E055C255

registerKey

951eeb6b19b70177fd25706aa620edcf

我们再继续回去看a.b.i.a.h.a

h0 a6 = h0.a(c, a.b.j.v3.a.a('6e561ccd4aade2fed458d4da61e76770', '370512bab61bee56', a3)); /* decodeKey()的值为6e561ccd4aade2fed458d4da61e76770,这个a3我猜测是密文 */

那么a.b.j.v3.a.a是啥呢,我们继续跳声明

a.b.j.v3.a.a函数

public static String a(String str, String str2, String str3) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {

return a(new IvParameterSpec(str2.getBytes("UTF-8")), new SecretKeySpec(a(str).getBytes("UTF-8"), "AES"), str3);

}/* str=6e561ccd4aade2fed458d4da61e76770,str2就是a.b.i.a.h.a中的str=370512bab61bee56 */

又return a(IvParameterSpec, SecretKeySpec(a(str).getBytes("UTF-8"), "AES"), str3) /str3=a3猜测应该是密文,加密方式是AES/

又出现两个未知数,我们一个一个的解

解析a(str)

跳转声明发现这个a函数(a.b.j.v3.a)和a.b.f.o.f.i的函数是一致的,均为md5算法,那么a(str)=key=md5('6e561ccd4aade2fed458d4da61e76770')=ae52f7ffd2dd66ba5743bb180188b991

解析return a(x,x,x)

public static String a(IvParameterSpec ivParameterSpec, SecretKeySpec secretKeySpec, String str) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

Cipher instance = Cipher.getInstance(AESEncryptor.AES_MODE);/* AESEncryptor.AES_MODE的值跳转后可得是加解密方法为"AES/CBC/PKCS5Padding" */

instance.init(2, secretKeySpec, ivParameterSpec);

return new String(instance.doFinal(Base64.decode(str, 2)));

}

套入a.b.i.a.h.a中,那么本案例中的iv就是'9807a3e5370512bab61bee56200ee1d6'.substring(8, 24)=370512bab61bee56

尝试用在线aes解密和下载

成功解密m3u8

55fd2b2273b5a8b4531f72773c469d6e.gif

1.6 成功解密m3u8.png (53.88 KB, 下载次数: 0)

2021-2-23 13:12 上传

我们新建一个txt文档,把解密后的明文粘贴进去,保存关闭后,文件重命名为1.m3u8,拖入下载器,成功下载

55fd2b2273b5a8b4531f72773c469d6e.gif

下载器.png (29.22 KB, 下载次数: 0)

2021-2-23 20:55 上传

最后结案

m3u8相关

貌似算法和网页版的几乎一样?

切记获取的时候要协议头要带上version/platform/time/userid/hash   不然会拒绝访问的..

关于hash的加密算法在a.b.a.v.f中,用到了CipherClient.apiHashKey()

和一开始不加密的那种m3u8格式一样,但是现在m3u8是加密的需要解密:

m3u8地址:#域名#/media/#分辨率#/#视频id#.m3u8?token=#token#

加解密模式

key

iv

AES/CBC/PKCS5Padding

固定值:ae52f7ffd2dd66ba5743bb180188b991

md5(#X-VTag#).substring(8, 24)

PS:我用易语言的e2ee先Base64解码密文即可解密出明文..在线解密也可以解出的

请求头中hash值的算法

发现在请求m3u8密文的时候会经常提示拒绝访问access denied

看一下请求头中的参数

参数

来源

version

2.3.1

app版本

platform

Android

安卓平台

time

1614062660

当前十位时间戳

userid

AiwrHWxoYtGd

可能是随机值

hash

625698fd47f50c2c026b9cfe543d56c5

待探索

如果随便改的话也会提示拒绝访问access denied,所以我们去jadx中搜以下这个hash。

用jadx搜"hash"

55fd2b2273b5a8b4531f72773c469d6e.gif

hash.png (18.23 KB, 下载次数: 0)

2021-2-23 20:38 上传

所以i=hash的值,往上翻,找到i的赋值

55fd2b2273b5a8b4531f72773c469d6e.gif

i.png (29.99 KB, 下载次数: 0)

2021-2-23 20:52 上传

String i = a.b.f.o.f.i(a2 + value + valueOf + str + str2);

之前我们已经得出a.b.f.o.f.i是md5的操作。

依次看a2 / value / valueOf / str / str2

a2 = version的值 = APP版本号

value = platform的值 = 平台Android

valueOf = time的值 = 时间戳

str = userId的值 = 从"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"取随机字符

str2 = CipherClient.apiHashKey() = ‘666wInteriscommingyoUshouldNotpassmotherfuckeR=’

所以i = md5(app版本 + 平台Android + 时间戳 + 随机userId + ‘666wInteriscommingyoUshouldNotpassmotherfuckeR=’)

上述案例中 i = md5(2.3.1Android1614062660AiwrHWxoYtGd666wInteriscommingyoUshouldNotpassmotherfuckeR=) = 625698fd47f50c2c026b9cfe543d56c5

hash算法结案

host相关

由于我们以及得到了hostIv和hostkey,所以弯回去看看host_XXXX.txt,在线解密结果:

55fd2b2273b5a8b4531f72773c469d6e.gif

1.7 host.png (37.4 KB, 下载次数: 0)

2021-2-23 13:22 上传

关于倍速播放

正常情况下倍速播放只能是vip才有的功能,那么我们可以通过固定/v1/user/info接口中返回的Expiry的值即可免费使用该功能

.method public final getExpiry()J

.registers 3

.line 1

const-wide v0,0x5af3107a3ff6L #0x5af3107a3ff6L

return-wide v0

.end method

55fd2b2273b5a8b4531f72773c469d6e.gif

1.8 倍速.png (39.59 KB, 下载次数: 1)

2021-2-23 13:29 上传

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值