最近朋友找我帮忙解决一个软件BUG,软件名为AXJ,无法在13上正常使用,兴趣所致,于是简单分析了一下,这里分享给大家,本文内容仅作技术研究,切勿滥用。那么先去官网看看这是个什么东东:
软件介绍
国内最新支持iOS全系系统的一键新机、全息备份、位置伪装、ASO辅助工具
手机一键新机,轻松修改设备参数,功能定时更新,多重保障软件稳定运行,为用户提供更好体验
为iPhone/iPad提供反越狱检测,修改系统序列号、型号、系统版本、运营商、地理位置、MAC、UUID、IMEI、IDFA、SSID应用全息备等一系列强大功能
AXJ爱新机与AWZ爱伪装和ALS爱立思的区别
1、最高支持iOS13系统
2、新机参数中有更多的机型和iOS版本号选择
3、修复部分机型点【随机生成】闪退的问题
4、备份记录可彻底删除释放空间
5、增强伪装,修复已知BUG
从上面可以看到,AWZ-ALS-AXJ,该产品一直在改名字,AXJ主要是增加了iOS13的支持。但是这句话是有歧义的,可能性有三种(大部分人会以为是第三种吧,不过个人觉得是第一种):
- 软件可以模拟iOS13系统
- 可以在iOS13上运行该软件
- 两者都有
分析
文件分析
包名: AXJ
文件名: YOY YOY.dylib
配置文件路径: /var/mobile/Library/Preferences/
com.app1e.mobile.ifyoybackup.plist 所有备份设备参数数据
com.app1e.mobile.ifyoylocation.plist 当前位置参数
com.app1e.mobile.ifyoycommon.plist 当前设备参数
重现BUG
- 找一台13.x的机器(我这里13.3)
- 使用AXJ做一键新机
- 使用检测工具检测(这里我用的是我自己开发的checktweak)
检测结果(这里就不贴图了,本人比较懒):
- 设备参数,包括IDFA/IDFV,并未做修改,所以还是本机的参数
- 沙盒文件,文件备份正常
我们知道,AWZ/ALS/AXJ这类软件,其一键新机的行为,其实也就是“备份恢复沙盒+生成设备参数配置文件”,(注意keychain操作也是模拟为文件操作并持久化的)。而dylib负责真正的参数修改,那么新机参数不生效只可能是在dylib中出现的问题。要么配置文件生成错误,要么dylib初始化时某个环节出现BUG,下面就是一个个来尝试了:
解密配置文件
YOY对配置文件的加密处理也是简单的AES256算法,其解密逻辑大致可写成:
NSData* axx_decrypt_plist_data(NSData* enc_data) {
NSData* dec_data = nil;
// get key
const char* ckey = "www.iphonefree.org";
unsigned char md[CC_SHA256_DIGEST_LENGTH];
memset(md, 0, CC_SHA256_DIGEST_LENGTH);
CC_SHA256((const char*)ckey, (CC_LONG)strlen(ckey), md);
NSData* dkey = [NSData dataWithBytes:md length:CC_SHA256_DIGEST_LENGTH];
NSString* key = dkey.description;
key = [key stringByReplacingOccurrencesOfString:@" " withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"<" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@">" withString:@""];
if (key.length > CC_SHA256_DIGEST_LENGTH) {
key = [key substringToIndex:CC_SHA256_DIGEST_LENGTH];
}
NSString* iv = @"a859663e80d6f810";
// decrypt
size_t outlen = enc_data.length + kCCBlockSizeAES128;
size_t outlena = 0;
void* buf = malloc(outlen);
CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void*)key.UTF8String, CC_SHA256_DIGEST_LENGTH,
iv.UTF8String, enc_data.bytes, enc_data.length, buf, outlen, &outlena);
dec_data = [NSData dataWithBytes:buf length:outlena];
free(buf);
return dec_data;
}
这段代码可以去解密所有com.app1e.mobile.if*.plist配置文件,通用于AWZ/ALS/AXJ,使用该逻辑,尝试解密yoycommon,可以得到正确的结果,可见配置文件是没问题的,那么只能跟踪dylib:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>announcementID</key>
<integer>0</integer>
<key>authInfo</key>
<dict>
<key>billing_type</key>
<integer>0</integer>
<key>device_id</key>
<integer>0</integer>
<key>expiry_date</key>
<string>20200517144451000</string>
<key>product_id</key>
<integer>0</integer>
<key>status</key>
<integer>1</integer>
</dict>
<key>deviceEnv</key>
<dict>
<key>BSSID</key>
<string>2e:3a:f7:ec:c4:11</string>
<key>DeviceToken</key>
<string>25a5b8e146cce2d9891dad20a716a0521d403a3385ec59f398ecc8a22c3e6853</string>
<key>IDFA</key>
。。。。。
<key>systemVersion</key>
<string>12.4.6</string>
</dict>
。。。
</dict>
</plist>
跟踪dylib
那么这就需要lldb调试了或者frida跟踪了,这两种方式均可。YOY.dylib的执行逻辑大致如下:
- dylib加载,执行init段逻辑(InitFunc_0)
- 解密yoycommon,验证授权信息,并设置各个设备参数字段(DFjDNNaDUkxIHaldHcHyXTusbnMNGiyuCfeEZHjk)
- hook关键系统函数以及针对某些App的特殊类成员函数,以实现新机,屏蔽反越狱,屏蔽VPN等功能
- 其他操作(如修改顶部状态栏,包括运营商名,wifi信号等图标。。。这样看上去更逼真一些)
初步跟踪发现DFjDNNaDUkxIHaldHcHyXTusbnMNGiyuCfeEZHjk返回false,那么问题就出在这里,继续看该函数逻辑:
- 使用AES解密配置文件(dEmQNZGrXlrEATVyjWdskCoYFzFKtlLpuKYpUrvg)
- 比较expiry_date段查看是否授权过期
二次跟踪发现AES解密居然失败了,那么这时候为了方便,直接自己编译axx_decrypt_plist_data来跑一遍,问题就出现在sha256这一步!正常执行逻辑如下:
- (1)CC_SHA256,得到32字节的内存buffer,其值为'81 d4 91 7d 10 c6 47 fa ...'
- (2)调用description得到'<81d4917d 10c647fa f4f08402 6d64e3cb b31cd471 90a8f68e cad3f9c5 fc796927>'
- (3)去掉'<' '>' ' ',并截取前32字节,得到最终值'81d4917d10c647faf4f084026d64e3cb'
然而在iOS13上(不确定从具体哪个版本开始的,笔者使用的是13.3),由于NSData的description函数实现发生变化,这第二步得到的结果却是
{length = 32, bytes = 0x81d4917d 10c647fa f4f08402 6d64e3cb ... cad3f9c5 fc796927 }
,那么后面解密出错也难免了。
那么这里有个疑点,加解密都是一致的,如果description实现变化,那么加密出来的文件也应该有问题,checktweak也不应该解密成功啊?于是继续跟踪AXJ本身。
这件事神奇就在于,AXJ这种越狱App以及系统App 和 AppStore下载的普通App,他们的description实现不一样,越狱App中description的实现仍然和早期版本一致。
因此加密的文件没问题,而解密出问题。这件事情也告诉我们写代码要严谨,不要依赖于不确定的实现,像使用description这种习惯,其实是网上抄的代码,下面给出的这段解密代码完美解决了AXJ配置文件加解密问题,修复之后,一切正常!
修复后的Objective-C代码
NSData* decrypt_plist_data(NSData* enc_data) {
NSData* dec_data = nil;
// get key
const char* ckey = "www.iphonefree.org";
unsigned char md[CC_SHA256_DIGEST_LENGTH];
memset(md, 0, CC_SHA256_DIGEST_LENGTH);
CC_SHA256((const char*)ckey, (CC_LONG)strlen(ckey), md);
const int dkeylen = CC_SHA256_DIGEST_LENGTH * 2 + 1;
char cdkey[dkeylen];
memset(cdkey, 0, dkeylen);
const char num2hex[] = "0123456789abcdef";
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
cdkey[i * 2 + 1] = num2hex[md[i] & 0xf];
cdkey[i * 2] = num2hex[(md[i] >> 4) & 0xf];
}
NSString* key = @(cdkey);
if (key.length > 32) {
key = [key substringToIndex:32];
}
// key=81d4917d10c647faf4f084026d64e3cb
NSString* iv = @"a859663e80d6f810";
// decrypt
size_t outlen = enc_data.length + kCCBlockSizeAES128;
size_t outlena = 0;
void* buf = malloc(outlen);
CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, (const void*)key.UTF8String, CC_SHA256_DIGEST_LENGTH,
iv.UTF8String, enc_data.bytes, enc_data.length, buf, outlen, &outlena);
dec_data = [NSData dataWithBytes:buf length:outlena];
free(buf);
return dec_data;
}
总结
从以上分析过程可以知道,AXJ发布前应该没在iOS13上严谨测试;同时,严谨的编程习惯是十分重要的。
本文内容仅作技术研究,切勿做恶意用途。