声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系作者删除。
最近开始学习iOS的逆向,正好拿来这个应用来学习思路。整体应用加密不难,适合入门学习。
设备
工具
逆向目标

image.png (73.68 KB, 下载次数: 3)
下载附件
2025-8-10 20:23 上传
开始分析
使用 ida 打开脱壳后的App主程序,等待分析完成。使用 shift + f12 进入字符串搜索界面,直接搜索 phone 字符串。

image-1.png (158.25 KB, 下载次数: 1)
下载附件
1
2025-8-10 20:22 上传
有该字符串的信息,但是并不是想要的结果(可以自己试试),只能另寻他法。frida启动!
手机号位数是11位固定的,多次切换发现加密后的字符串长度是固定的32位,有点像md5。废话不多说,直接实战吧。
这里直接使用内存漫游的方式进行定位了,使用memcpy的方式。打印的信息可能太多,直接限制32位长度的才可以输出。
Interceptor.attach(Module.findExportByName(null, 'memcpy'), {
onEnter: function (args) {
var dest = args[0];
var src = args[1];
var size = args[2].toInt32();
var srcData = Memory.readByteArray(src, size);
if (srcData == null) {
return;
}
var stringData = Memory.readCString(src);
if (stringData && stringData.length == 32 ) {
// 打印memcpy调用的信息
console.log('memcpy called:');
console.log(' Destination: ' + dest);
console.log(' Source: ' + src);
console.log(' Size: ' + size);
console.log(' Source Data (as string): ' + stringData);
// 打印当前的堆栈信息,帮助调试
console.log('------------------------------------------------');
var backtrace = Thread.backtrace(this.context, Backtracer.FUZZY)
.map(DebugSymbol.fromAddress)
backtrace.forEach(function (symbol) {
// 遍历并打印每个堆栈符号
console.log(' ' + symbol);
});
}
}
});
进入到主界面开始注入,并将日志保存

image-2.png (127.98 KB, 下载次数: 1)
下载附件
2
2025-8-10 20:22 上传
frida -UF -l frida.js -o output.log
点击获取验证码,在Reqable里找到发包的接口,发现手机号加密成SzdXSjMvMjB4S21NYzZmcXZFWExJQT09

image-3.png (20.72 KB, 下载次数: 2)
下载附件
3
2025-8-10 20:22 上传
进入刚刚运行的日志,进行搜索

image-4.png (220.36 KB, 下载次数: 2)
下载附件
4
2025-8-10 20:22 上传
发现无法定位到相对应的位置,只能换一种思路了。在ida中functions里搜索phone,发现NSString下有encodePhoneNum方法

image-5.png (164.35 KB, 下载次数: 2)
下载附件
5
2025-8-10 20:22 上传
双击进去看看,发现内部调用了des的加密。(起始这里应该能看出来密钥已经出来了,防止滥用,打码处理。为了学习,我们接着往下走。)

image-6.png (131.51 KB, 下载次数: 2)
下载附件
6
2025-8-10 20:22 上传
使用frida 开始hook,并运行。
const NSString = ObjC.classes.NSString;
Interceptor.attach(NSString["- encodePhoneNum"].implementation, {
onEnter(args) {
console.log("输入值", new ObjC.Object(args[0]))
},
onLeave() {
console.log("返回值", new ObjC.Object(this.context.x0));
}
});
发现确实走了这里,这里就是加密生成的地方,知道了是des,现在要继续分析它的运算模式是ECB还是CBC。

image-7.png (36.25 KB, 下载次数: 2)
下载附件
7
2025-8-10 20:23 上传
继续在ida中继续分析,进入desEncryptWithKey方法。

image-8.png (120.08 KB, 下载次数: 1)
下载附件
8
2025-8-10 20:23 上传
发现继续调用了desEncryptWithDataKey方法。

image-9.png (120.74 KB, 下载次数: 2)
下载附件
9
2025-8-10 20:23 上传
进入desEncryptOrDecrypt:data:dataKey:mode:,发现调用了CCCrypt方法。

image-10.png (45.5 KB, 下载次数: 2)
下载附件
10
2025-8-10 20:23 上传
发现是一个C函数

image-11.png (83.96 KB, 下载次数: 1)
下载附件
11
2025-8-10 20:23 上传
询问了AI,给出了如下解释:
CCCrypt 是 iOS/macOS 系统提供的一个 加密/解密函数,来自 CommonCrypto 库,用来做对称加密(AES、DES、3DES 等)和解密。
CCCrypt代码参考
CCCryptorStatus CCCrypt(
CCOperation op, // 操作类型:加密/解密
CCAlgorithm alg, // 加密算法(如 AES)
CCOptions options, // 加密选项(如填充模式、加密模式)
const void *key, // 密钥
size_t keyLength, // 密钥长度
const void *iv, // 初始化向量(IV)
const void *dataIn, // 输入数据(明文/密文)
size_t dataInLength, // 输入数据长度
void *dataOut, // 输出数据缓冲区(密文/明文)
size_t dataOutAvailable, // 输出缓冲区大小
size_t *dataOutMoved // 实际输出数据长度
);
接着分析原调用代码
objc_retainAutoreleasedReturnValue(
-[NSString desEncryptOrDecrypt:data:dataKey:mode:](
self,
"desEncryptOrDecrypt:data:dataKey:mode:",
0, // a3
v5,
v4,
3)); // a6
// id __cdecl -[NSString desEncryptOrDecrypt:data:dataKey:mode:](
// NSString *self,
// SEL a2,
// unsigned int a3,
// id a4,
// id a5,
// int a6)
CCCrypt(
a3, // desEncryptOrDecrypt 传入是0 0是加密
1u, // 1 是DES
a6, // desEncryptOrDecrypt 传入是3 3 CFB加密模式
v14,
8u,
0,
objc_msgSend(v15, "bytes"),
(size_t)objc_msgSend(v15, "length"),
dataOut,
(size_t)dataOutAvailable,
&v18)
)
总体发现是通过des加密的方式得到,有一点需要注意的是,des加密得到的base64字符串后,又使用了base64字符串加密。

image-12.png (100.35 KB, 下载次数: 2)
下载附件
12
2025-8-10 20:23 上传

image-13.png (107.41 KB, 下载次数: 2)
下载附件
13
2025-8-10 20:23 上传
结尾
至此,分析完毕。整个流程不是很难,适合入门学习。知道结果之后,可以换一种思路使用frida直接hookbase64EncodedString这个方法是不是直接定位到。
CCCrypt说明
函数定义
CCCryptorStatus CCCrypt(
CCOperation op, /* kCCEncrypt, etc. */
CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */
CCOptions options, /* kCCOptionPKCS7Padding, etc. */
const void *key,
size_t keyLength,
const void *iv, /* optional initialization vector */
const void *dataIn, /* optional per op and alg */
size_t dataInLength,
void *dataOut, /* data RETURNED here */
size_t dataOutAvailable,
size_t *dataOutMoved)
CCOperation op:
加密:kCCEncrypt = 0, 解密:kCCDecrypt = 1
CCAlgorithm alg:
使用的算法标准
kCCAlgorithmAES128=0,
kCCAlgorithmAES =0,
kCCAlgorithmDES =1,
kCCAlgorithm3DES =2,
kCCAlgorithmCAST,
kCCAlgorithmRC4,
kCCAlgorithmRC2,
kCCAlgorithmBlowfish
CCOptions options:
kCCModeECB = 1,
kCCModeCBC = 2,
kCCModeCFB = 3,
kCCModeCTR = 4,
kCCModeF8 = 5,
kCCModeLRW = 6,
kCCModeOFB = 7,
kCCModeXTS = 8,
kCCModeRC4 = 9,
kCCModeCFB8 = 10,