本文章首发于微信公众号Th0r安全,如未授权禁止转载。
前言
穿山甲作为字节的两大广告投放平台之一,用户还是很多的,所以我正好在分析某个app时发现了host为api-access.pangolin-sdk-toutiao.com
的广告流量,其数据是加密的,如下:
request body:
response body:
这一看我就来劲了啊,要不试试看逆向学学大厂的加密思维?
SDK 获取
所以既然是广告sdk,那么肯定有jar包能直接引用,所以就去穿山甲平台上注册了一下,然后在开发者平台里面找到了下载渠道,但是其提供的下载方式只有maven和直链两种,前者的仓库格式我不太熟,后者只提供了最新版的下载链接,和我们的目标并不一致,而且文件名还是hash的,没法手动改成旧版本的链接。
所以只能硬着头皮去看maven仓库了,打开https://artifact.bytedance.com/repository/pangle
,发现有个com
的子目录指向,点进去直接404了。
不过发现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
, 下载了解压得到如下一堆东西。
Request 加密
先去最醒目的jar里康康代码。
直接全局搜索关键词cypher
,发现只有一处是符合咱们发送的数据包内容的,直接进去康康。
调用堆栈并不复杂,就是函数名脱敏之后比较呆逼,而且还有函数重载比较影响思路。
大致就是message
来自于a(String str)
这个函数,其结果又来自于"3"+a()+b()+a.a(str, b(), a(a(), 32))
,所以只要一段一段看过去就行。
首先是作为版本标识的"3"
放在开头,没啥好说的,a()+b()
的话大概率是两个定长的字符串用来传参,方便服务端解析最后一个核心参数。
先看看a()
,就是调用a(16)
,然后确保长度是32
就算是生成成功了。
至于a(16)
的话,就是生成指定长度的随机数据然后用c.a(data)
去处理一下。
懒得看逻辑了,因为并不长所以直接python复现一下康康规律就行,试了一下应该就是个byte转hexstr。
所以结论就是a()
生成了一个16字节的随机raw串并转成hexstr。
再去康康b()
,实际上也就是生成了一个8字节的随机raw串并转成hexstr。
最后的str2,是a.a(str, b, a2)
函数生成的,其中b
就是上面b()
生成的;a2
就是上面a()
生成好后再a(..., 32)
处理一下,实际上就是a[16:32]+a[0:16]
。
然后就是a.a
这个函数了,跟进去看一下发现是AES/CBC/PKCS5Padding
的经典加密,b
当iv
,a2
当key
。
所以这回所有逻辑都拎清楚了,最终的加密后的数据字符串就是"3"+hexkey[16:32]+hexkey[0:16])+hexiv[0:16])+aes(data, hexkey, hexiv, "CBC", "PKCS5Padding")
。(需要注意的是,这里是把key和iv的hex字符串形式直接当成密钥和偏移量使用,而不是其raw值)
手动测试一下,成功解密。
Response 解密
由于上面并未发现接收数据包的相关逻辑,于是猜测接收解密的代码在其他文件中,所以一个一个文件看过去,发现assets
下还有一个压缩包,里面有classes.dex
文件,那就打开康康代码。
发现三处用于获取cypher
字段的代码,猜测是用来解密的。
看了几个发现调用的都是同一处解密代码,那基本上就不会有多种逻辑了,直接跟。
乍一看好像和发送加密时的一样啊,但是不知道为啥数据格式和发送不一样,是全base64的,没有hexstr。
尝试直接按加密的方式解密,还真成功了...