记一次穿山甲广告SDK的流量加密分析

发布于 2021-12-15  603 次阅读


本文章首发于微信公众号Th0r安全,如未授权禁止转载。

前言

穿山甲作为字节的两大广告投放平台之一,用户还是很多的,所以我正好在分析某个app时发现了host为api-access.pangolin-sdk-toutiao.com的广告流量,其数据是加密的,如下:

request body:
file

response body:
file

这一看我就来劲了啊,要不试试看逆向学学大厂的加密思维?

SDK 获取

所以既然是广告sdk,那么肯定有jar包能直接引用,所以就去穿山甲平台上注册了一下,然后在开发者平台里面找到了下载渠道,但是其提供的下载方式只有maven和直链两种,前者的仓库格式我不太熟,后者只提供了最新版的下载链接,和我们的目标并不一致,而且文件名还是hash的,没法手动改成旧版本的链接。

file

所以只能硬着头皮去看maven仓库了,打开https://artifact.bytedance.com/repository/pangle,发现有个com的子目录指向,点进去直接404了。

file

file

不过发现url并不是子目录展开,而是直接替换了当前目录,所以猜测可能是因为访问了/pangle而不是/pangle/造成的bug。

那么再访问https://artifact.bytedance.com/repository/pangle/去看看,发现目录结构能正常点击了,那么根据咱们的目标包名和版本找到了aar格式的下载链接https://artifact.bytedance.com/repository/pangle/com/pangle/cn/ads-sdk-pro/4.0.1.9/ads-sdk-pro-4.0.1.9.aar, 下载了解压得到如下一堆东西。

file

Request 加密

先去最醒目的jar里康康代码。

直接全局搜索关键词cypher,发现只有一处是符合咱们发送的数据包内容的,直接进去康康。

file

file

调用堆栈并不复杂,就是函数名脱敏之后比较呆逼,而且还有函数重载比较影响思路。

大致就是message来自于a(String str)这个函数,其结果又来自于"3"+a()+b()+a.a(str, b(), a(a(), 32)),所以只要一段一段看过去就行。

首先是作为版本标识的"3"放在开头,没啥好说的,a()+b()的话大概率是两个定长的字符串用来传参,方便服务端解析最后一个核心参数。

先看看a(),就是调用a(16),然后确保长度是32就算是生成成功了。

file

至于a(16)的话,就是生成指定长度的随机数据然后用c.a(data)去处理一下。

file

file

懒得看逻辑了,因为并不长所以直接python复现一下康康规律就行,试了一下应该就是个byte转hexstr。

file

所以结论就是a()生成了一个16字节的随机raw串并转成hexstr。

再去康康b(),实际上也就是生成了一个8字节的随机raw串并转成hexstr。

file

最后的str2,是a.a(str, b, a2)函数生成的,其中b就是上面b()生成的;a2就是上面a()生成好后再a(..., 32)处理一下,实际上就是a[16:32]+a[0:16]

file

然后就是a.a这个函数了,跟进去看一下发现是AES/CBC/PKCS5Padding的经典加密,biva2key

file

所以这回所有逻辑都拎清楚了,最终的加密后的数据字符串就是"3"+hexkey[16:32]+hexkey[0:16])+hexiv[0:16])+aes(data, hexkey, hexiv, "CBC", "PKCS5Padding")。(需要注意的是,这里是把key和iv的hex字符串形式直接当成密钥和偏移量使用,而不是其raw值)

手动测试一下,成功解密。

file

Response 解密

由于上面并未发现接收数据包的相关逻辑,于是猜测接收解密的代码在其他文件中,所以一个一个文件看过去,发现assets下还有一个压缩包,里面有classes.dex文件,那就打开康康代码。

file

发现三处用于获取cypher字段的代码,猜测是用来解密的。

file

file

file

看了几个发现调用的都是同一处解密代码,那基本上就不会有多种逻辑了,直接跟。

file

乍一看好像和发送加密时的一样啊,但是不知道为啥数据格式和发送不一样,是全base64的,没有hexstr。

尝试直接按加密的方式解密,还真成功了...

file


The End