0x00 前言
前段时间有这样一个需求,需要抓一下IOS端下appsflyer这个平台的数据,于是就帮忙看了一下。这里其实和APP没多大关系,需要接入appsflyer这个平台的APP就可以,然后将抓包获取的加密数据进行解密。
0x01 准备
给我的并不是一个ipa文件,而是一个压缩包文件。
Payload.zip其实就是ipa文件进行解压后再压缩而来的,但是修改文件名直接安装是不行的,需要重新签名。
这里将文件重命名为1.ipa,不进行签名,然后使用命令ideviceinstaller -i 1.ipa
进行安装,结果安装失败。
可以使用命令安装,也可以借助其他工具安装,如Xcode、爱思助手等。
签名的话也有多种方法,我这里使用IOS App Signer
进行签名。
签名后重新安装
1ideviceinstaller -i 1-sign.ipa
签名后安装成功,对比源文件与签名后文件的签名。
接下来就是进行抓包分析了。
0x02 分析
运行app并抓包,会发现有很多请求,但每次打开APP与appsflyer有关的请求一般有三个,可以直接过滤出来。
可以看到请求的内容是乱码,应该是采用了某种加密。接下来使用ida对文件进行分析。
一般都是进行关键字的搜索,然后慢慢定位找到加密相关的函数,例如这里搜索的关键字是iosevent
。
对比一下请求的URL,可能是第一个。
一直查找引用,直到发现-[AppsFlyerLib __validateAndLogInAppPurchase:price:currency:transactionId:additionalParameters:success:failure:]
进行了调用。
使用frida-trace
进行追踪
frida-trace -U -f cn.fuping.hhrx -m "-[AppsFlyerLib __validateAndLogInAppPurchase:price:currency:transactionId:additionalParameters:success:failure:]"
。
这里仍有三个appsflyer相关的请求,但是发现并没有对该方法进行调用,因此可能是找错了。
通过查看请求,可以看到Content-Type
是application/octet-stream
,所以也可去搜索application/octet-stream
。
搜索application/octet-stream
。
一直查找引用,最终定位在-[AppsFlyerHTTPClient sendEvent:completionHandler:]
方法中。
在其中也可以看到调用了与加密相关的内容-[AFSDKEvent encryptWithData:]
。
使用frida-trace
进行追踪
关键代码:
1{
2 onEnter(log, args, state) {
3 var arg2ObjC = ObjC.Object(args[2]);
4 var arg2 = Memory.readUtf8String(arg2ObjC.bytes(), arg2ObjC.length());
5 log(`-[AFSDKEvent encryptWithData:${arg2}]`);
6 },
7
8 onLeave(log, retval, state) {
9 log(`-[AFSDKEvent encryptWithData:->result->${ObjC.Object(retval)}]`);
10 }
11}
可以成功获取加密前的数据
加密后的结果也是一致的。接下来就是看加密是如何实现的,另外能否进行解密。
进入到-[AFSDKEvent encryptWithData:]
方法,看是如何进行加密的。
通过分析发现其调用了-[AFSDKEvent key]
获取key,这里key是固定的,为X3sgfYhYXWhDoD8DhW2aaJ
。然后调用+[AppsFlyerAES128Crypto encrypt:withObject:]
进行加密。继续跟进加密的方法。
1CCCrypt(0LL, 0LL, 1LL, v19, v20, v22, v24, v25, v27, v28, &v35)
这里看到了CCCrypt
函数的调用,可以先了解一下该函数,然后分析加密流程。
使用CCCrypt
进行加密时,需要引入CommonCrypto/CommonCryptor.h
框架。
其中CCCrypt
函数定义:
1CCCryptorStatus CCCrypt(
2 CCOperation op, /* kCCEncrypt, etc. */
3 CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */
4 CCOptions options, /* kCCOptionPKCS7Padding, etc. */
5 const void *key,
6 size_t keyLength,
7 const void *iv, /* optional initialization vector */
8 const void *dataIn, /* optional per op and alg */
9 size_t dataInLength,
10 void *dataOut, /* data RETURNED here */
11 size_t dataOutAvailable,
12 size_t *dataOutMoved)
一共有11个参数,参数简要说明如下:
参数 | 说明 | 备注 |
---|---|---|
CCOperation op | 加密(kCCEncrypt=0)解密(kCCDecrypt=1) | 这里是加密 |
CCAlgorithm alg | 加解密算法标准kCCAlgorithmAES128=0,kCCAlgorithmAES=0 | 这里为kCCAlgorithmAES128或者kCCAlgorithmAES加密 |
CCOptions options | 加密方式的选项 kCCOptionPKCS7Padding表示CBC kCCOptionECBMode表示ECB kCCOptionPKCS7Padding|kCCOptionECBMode表示ECB且PKCS7Padding填充 |
这里为CBC加密,用PKCS7Padding进行填充 |
const void *key | 加密密钥 | 固定值 |
size_t keyLength | 密钥长度 | |
const void *iv | iv 初始化向量,ECB 不需要指定 | 随机值 |
const void *dataIn | 加密的数据 | |
size_t dataInLength | 加密的数据长度 | |
void *dataOut | 缓冲区(地址),存放密文的 | |
size_t dataOutAvailable | 缓冲区的大小 | |
size_t *dataOutMoved | 加密结果大小 |
然后转过头来分析加密的流程。首先调用了+[AppsFlyerAES128Crypto randomDataOfLength:]
生成了一个16byte的随机字符作为iv。
接着调用+[AppsFlyerAES128Crypto AESKeyForPassword:salt:]
对key进行解密,解密后的的结果作为加密的key值,为84adf6ec41acb6cbeb349d0a7078f0d2
。
最后调用CCCrypt进行加密,加密方法是AES-CBC。
加密完成后将IV和8位00与加密数据进行拼接。所以可以直接根据请求的数据进行解密。
例如上面的请求包共分为三部分,第一部分为加密后的内容,第二部分为IV,长度为16,这里是b820980ed08844945179a96721bbbcd3
,第三部分为0,长度为8。
然后进行解密:
成功解密数据,到这里任务就完成了。
其实在搜索的时候也可以搜索其他关键字,例如buildnumber
。
被圈中的两个字符串与URL进行对比,相似度很高。可以进一步去分析查找引用进行分析。
另外这里的加密用到了CCCrypt
函数,所以也可以直接对其进行追踪,例如
1frida-trace -U -f xxxx -i CCCrypt
然后修改libcommonCrypto.dylib
下的CCCrypt.js
文件,主要代码:
1{
2
3 onEnter: function (log, args, state) {
4 log('CCCrypt(' +
5 'op=' + args[0] +
6 ', alg=' + args[1] +
7 ', options=' + args[2] +
8 ', key=' + args[3] +
9 ', keyLength=' + args[4] +
10 ', iv=' + args[5] +
11 ', dataIn=' + args[6] +
12 ', dataInLength=' + args[7] +
13 ', dataOut=' + args[8] +
14 ', dataOutAvailable=' + args[9] +
15 ', dataOutMoved=' + args[10] +
16 ')');
17 //保存参数
18 this.operation = args[0]
19 this.CCAlgorithm = args[1]
20 this.CCOptions = args[2]
21 this.keyBytes = args[3]
22 this.keyLength = args[4]
23 this.ivBuffer = args[5]
24 this.inBuffer = args[6]
25 this.inLength = args[7]
26 this.outBuffer = args[8]
27 this.outLength = args[9]
28 this.outCountPtr = args[10]
29 //this.operation == 0 代表是加密
30 if (this.operation == 0) {
31 //打印加密前的原文
32 console.log("In buffer:")
33 console.log(hexdump(ptr(this.inBuffer), {
34 length: this.inLength.toInt32(),
35 header: true,
36 ansi: true
37 }))
38 //打印密钥
39 console.log("Key: ")
40 console.log(hexdump(ptr(this.keyBytes), {
41 length: this.keyLength.toInt32(),
42 header: true,
43 ansi: true
44 }))
45 //打印 IV
46 console.log("IV: ")
47 console.log(hexdump(ptr(this.ivBuffer), {
48 length: this.keyLength.toInt32(),
49 header: true,
50 ansi: true
51 }))
52 }
53 },
54
55 onLeave: function (log, retval, state) {
56 }
57}
效果如下:
也是可以进行加密数据的解析。
0x03 总结
本文主要涉及部分内容,一是ios APP签名,另外就是加密函数CCCrypt
。该函数是IOS加密中常用的函数,在分析的时候可以直接使用frida-trace -U -f xxxx -i CCCrypt
来查看加密是否调用了该函数,然后进行数据解密。
原文始发于微信公众号(猎户攻防实验室):IOS逆向之某APP解密