接着上次的写,这次跟一下响应数据的解密
看过这么多广告sdk,他们的流量数据,基本都是放在 JSONObject 中,所以直接从 JSONObject 中下手hook就稳稳的
其调用栈为如下:
根据上面的调用栈对 com.bytedance.sdk.openadsdk.core.q 的 g 函数 hook
其参数如下:
发现该函数的参数还是密文,说明密文就是在 g 函数内部进行解密的,其解密函数就是下面的 b 函数:
在 b 函数中先对密文进行切割,然后将切割的字符串传入 a.b 函数中
其中参数三还被函数a处理过
由上面可知 a.b 函数的三个参数依次为:
参数一为:密文的第50位到结尾
参数二为:密文第34位到49位
参数三为:密文第2位到33位,前16位与后16位对换
a.b 函数为如下:
函数具体解密逻辑为:
1 对参数一先用base64解密
2 由参数三生成密钥
3 设置解密模式为 "AES/CBC/PKCS5Padding"
4 初始化解密参数,写入参数三生成的密钥,并由参数二生成初始化向量
5 对参数一base64解密后的数据进行解密
到这里穿山甲流量的解密就完成了,直接copy代码写了一个解密的jar包
CSJDcrypt.java
package com.yooha;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileReader;
import java.io.IOException;
public class CSJDcrypt {
public static void main(String[] args) throws IOException {
String s = readfile(args[0]);
System.out.println(decrypt(s));
}
public static String readfile(String filename) throws IOException {
FileReader fileReader=new FileReader(filename);
int length=0;
char[] cfile = new char[0X1000 * 100];
int index = 0;
while((length=fileReader.read())!=-1) {
cfile[index++] = (char)length;
}
char[] newstr = new char[index + 1];
for(int i = 0; i < index; i++){
newstr[i] = cfile[i];
}
newstr[index] = 0;
fileReader.close();
return new String(newstr);
}
public static String a(String str, int i2) {
if (str == null || str.length() != i2) {
return null;
}
int i3 = i2 / 2;
return str.substring(i3, i2) + str.substring(0, i3);
}
public static String decrypt(String str) {
if (isEmpty(str) || str.length() < 49) {
return str;
}
String a = a(str.substring(1, 33), 32);
String substring = str.substring(33, 49);
return (substring == null || a == null) ? str: _decrypt(str.substring(49), substring, a);
}
public static String _decrypt(String str, String str2, String str3) {
if (isEmpty(str)) {
return null;
}
try {
byte[] decode = MyBase64.decode(str.getBytes());
System.out.println("str3 = " + str3);
SecretKeySpec secretKeySpec = new SecretKeySpec(str3.getBytes(), "AES");
System.out.println("str3 = " + secretKeySpec.getEncoded());
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
instance.init(2, secretKeySpec, new IvParameterSpec(str2.getBytes()));
String ret = new String(instance.doFinal(decode), "utf-8");
return ret;
} catch (Exception e2) {
System.out.println("_decrypt -> error = " + e2.getMessage());
return null;
}
}
public static boolean isEmpty(String str){
if (str.length() == 0){
return true;
}
return false;
}
}
MyBase64.java
这个是网上找的一个Base64,你们自己找个能跑起来就行
后来想想,之前有个沙箱当时就是用python调用jar包解密,搞的有点麻烦,沙箱越轻越好,还是直接用python解算了,然后了解了一下Crypto,挺好用的,写了个python的解密脚本
# -*- coding: utf-8 -*-
import base64
from Crypto.Cipher import AES
class AES_CIPHER(object):
#解密函数 ("AES/CBC/PKCS5Padding");
@classmethod
def _decrypt(cls, str1, str2, str3):
cryptor = AES.new(str3.encode("utf8"), AES.MODE_CBC, str2.encode("utf8"))
plain_text = cryptor.decrypt(base64.b64decode(str1))
unpad = lambda s: s[0:-ord(s[-1:])]
return unpad(plain_text)
@classmethod
def decrypt(cls, info):
if len(info) < 49:
return info
else:
return cls._decrypt(info[49:], info[33:49], info[1:33][16:32] + info[1:33][0:16]).decode('utf-8')
if __name__ == '__main__':
with open("flow.txt", "r", encoding='utf-8') as out:
info = out.readline()
print(AES_CIPHER.decrypt(info))
没错,用python写代码就是这么少。下面是解密内容
后面我写了一个广告sdk自渲染广告的监控工具,链接:https://github.com/YoooHaaa/renderAd
有兴趣可以去看看,工具拿来把js脚本改改,就能复用到其他业务上