简介

爱伪装(AWZ)/爱立思(ALS)是一款iOS越狱系统上的改机工具,可以修改多种系统参数达到伪装设备型号及各种软硬件属性的目的,同时提供了防越狱检测机制,常用于iOS上的推广刷量,配合代理/VPN使用。 除了AWZ以外,该软件商还有类似工具ALS/IGG/LRN/NZT/AXJ等,功能大体一致。AWZ/ALS支持iOS 7/8/9/10/11/12的全息备份,一键新机功能。ALS是AWZ的升级版,增加了更新的设备型号和iOS版本。

ALS伪装哪些参数

IDFA
IDFV
用户名
系统版本
设备型号,固件版本
User-Agent
移动网络运营商信息
地理位置
uname / sysctl等参数
WIFI SSID BSSID
IMEI
序列号
MAC地址

ALS有哪些屏蔽刷机检测的手段

VPN隐藏
代理隐藏
WIFI隐藏
反越狱检测,越狱文件检测/模块检测/APP检测等

ALS的Cydia源

    als.ucydia.com      ALS     11.0.5
awz.ucydia.com AWZ 10.0.5
awz.itouchgo.com AWZ 10.0.5
apt.awzcn.com ALS 11.6.5
apt.awzcn.com AWZ 8.2.5
apt.awzcn.com AWZ 10.5.7
apt.abogeek.com ALS 11.6.5
apt.abogeek.com AWZ 8.2.5
apt.abogeek.com AWZ 10.5.7

分析要点

这里只做学习讨论改机原理及ALS自身反逆向机制。ALS安装后,有如下文件:

  • /Applications/ALS.app 主程序,用于生成改机参数
  • /Library/LaunchDaemons/dhpdaemon.plist 用于daemon方式执行DHPDaemon,用于帮助ALS实现一些隐藏操作
  • /usr/bin/DHPDaemon
  • /MobileSubstrate/DynamicLibraries/ALS.{dylib,plist},该tweak通过hook一些可以获取系统属性和app属

    性的C函数和ObjC函数实现的修改app参数

第一阶段

  • ALS属性2755防止加载tweak

  • 存在restrict段,防止被加载tweak

  • 存在syscall函数进行ptrace系统调用,存在ptrace函数,以及svc汇编指令实现的ptrace,防止调试器附加

  • 对于restrict段和汇编指令反调试的处理,解法就是patch文件然后重签名,但是要写一个通用的命令是比较困

    难的,慢慢收集吧,这里提供的方式如下:

    sed -i 's/RESTRICT/RXSTRICT/g' ALS

    sed -i 's/\x80\x52\x01\x10\x00\xd4/\x80\x52\x1f\x20\x03\xd5/g' ALS

  • 对于文件属性和函数级的反调试,方法不再赘述,写tweak即可

第二阶段

在第一阶段破解后,仍然是闪退的,此时反调试已经去除,所以可以加调试器看检测点

  • -[NSBundle executablePath]检测主进程文件是否被修改
  • _dyld_get_image_name检测tweak模块
  • sysctl检测进程的p_flag是否有调试器flag
  • isatty检测终端
  • ioctl(TIOCGWINSZ)检测终端
  • fopen检测主进程文件是否被修改(在DHPDaemon中)
  • posix_spawn检测/Application/ALS.app下是否存在超过3M的文件(补丁啊)

注意

在IFMagicMainVC的几个按钮handler函数中,存在大量检测代码,这里建议直接自己实现,例如:

void (*old_IFMagicMainVC_appListClick)(Class cls, SEL sel, void* click);
void new_IFMagicMainVC_appListClick(Class cls, SEL sel, void* click) {
UIStoryboard* board = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController* newcontrol = [board instantiateViewControllerWithIdentifier:@"IFApplicationSelectorVC"];
UIViewController* control = (UIViewController*)cls;
[[control navigationController] pushViewController:newcontrol animated:YES];
} void (*old_IFMagicMainVC_backupRecordClick)(Class cls, SEL sel, void* click);
void new_IFMagicMainVC_backupRecordClick(Class cls, SEL sel, void* click) {
UIStoryboard* board = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController* newcontrol = [board instantiateViewControllerWithIdentifier:@"IFMagicBackupVC"];
UIViewController* control = (UIViewController*)cls;
[[control navigationController] pushViewController:newcontrol animated:YES];
} void (*old_IFMagicMainVC_doGoMagicSetting)(Class cls, SEL sel, void* click);
void new_IFMagicMainVC_doGoMagicSetting(Class cls, SEL sel, void* click) {
UIStoryboard* board = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController* newcontrol = [board instantiateViewControllerWithIdentifier:@"IFMagicSettingVC"];
UIViewController* control = (UIViewController*)cls;
[[control navigationController] pushViewController:newcontrol animated:YES];
} void (*old_IFMagicMainVC_paramSettingClick)(Class cls, SEL sel, void* click);
void new_IFMagicMainVC_paramSettingClick(Class cls, SEL sel, void* click) {
UIStoryboard* board = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController* newcontrol = [board instantiateViewControllerWithIdentifier:@"IFMagicDeviceSettingVC"];
UIViewController* control = (UIViewController*)cls;
[[control navigationController] pushViewController:newcontrol animated:YES];
}

第三阶段

在第二阶段后,不闪退了,但是显示注册码过期

  • 解密栈字符串,ALS和DHPDaemon几乎全用的栈字符串混淆,蛮体力活的
  • 定位到注册的函数,一方面通过socket通信,用加密本机信息获取软件激活状态,另一方面通过cjson反序列化回写注册状态。
  • 定位get_json_value函数,该函数为c层cjson解析函数,用于从json数据的到key对应的value,该函数刚好位于socket网络通信,解密响应得到json数据后。其中必要重要的key有:ps,vs,hs,ts,as,aes
  • 还原ObjC函数调用关系
  • system函数会独立向服务器验证激活码,如果校验不过会删除backup文件,这样操作记录都没了,但是并没实际删除,可通过fopen检测绕过
  • 控制软件注册状态的主要字段是status和expiry_date,分别对应注册状态及过期时间字符串,其中status字段含义为:

    enum {

    STATE_LOCKED = 0, // 已锁定

    STATE_NORMAL = 1, // 已激活

    STATE_INACTIVE1 = 2, // 未激活

    STATE_OUTDATE = 3, // 激活码过期

    STATE_INACTIVE2 = 4, // 未激活

    STATE_LOGOFF = 5, // 已注销

    };
  • 对于栈字符串的处理,见:https://github.com/lich4/personal_script/blob/master/IDA_Script/

    parse_stack_string.py
  • 对于ObjC函数调用关系还原,见:https://github.com/lich4/personal_script/blob/master/IDA_Script/

    add_xref_for_macho.py
  • 使用网络请求方式更新注册状态的响应中,get_json_value获取的as键对应status,aes键对应于expiry_date

    另外一些字段用于激活码验证,如果不通过则结束进程,可以自行在newAppEnvClick函数中研究。
  • 使用cjson反序列化回写注册状态逻辑存在于文件/private/var/mobile/Library/Preferences/

    com.app1e.mobile.ifalscommon.plist,解密后仍然是json数据,要修改的字段如下:

    {

    "authInfo": {

    "status": @0,

    "expiry_date": @"21000101080000000"

    }

    }
  • mapapi.bundle模块存在一些干扰,在hook函数的时候要注意

第四阶段

a. 捕获按钮触发的功能函数

b. 分析ALS和DHPDaemon的notify通信,有些重要函数是ALS调用DHPDaemon执行

a. 捕获按钮触发,利用frida脚本,https://github.com/lich4/personal_script/blob/master/

Frida_script/utils.js,这里tranverse_view用于检测当前呈现的界面可以获取的元素,以及对应的响应

selector,如果找按钮的回调,又不想触发,可以用这个。另外更通用的得是trace_view函数,可以拦截到

所有界面消息以及响应selector,在执行点击等操作后可以得到更全的信息

b. 下面是一些分析结果:

清理safari逻辑在函数中-[IFMagicMainVC cleanSafariClick:]

    // 杀死进程
BKSTerminateApplicationForReasonAndReportWithDescription(__bridge CFStringRef)@"com.apple.mobilesafari", 5, 0, NULL);
NSFileManager* man = [NSFileManager defaultManager];
// 清理cookie
NSString cookiepath = @"/var/mobile/Library/Cookies";
if ([man fileExistsAtPath:cookiepath]) {
NSString* cmd = [NSString stringWithFormat:@"rm -rf %@/*", cookiepath];
system(cmd);
}
cookiepath = @"/private/var/root/Library/Cookies";
if ([man fileExistsAtPath:cookiepath]) {
NSString* cmd = [NSString stringWithFormat:@"rm -rf %@/*", cookiepath];
system(cmd);
}
// 获取safari的沙盒路径
NSString* safaricontainer = nil;
NSString* installplist = @"/var/mobile/Library/Caches/com.apple.mobile.installation.plist";
if ([man fileExistsAtPath:]) {
NSDictionary* plist = [NSDictionary dictionaryWithContentsOfFile:installplist];
id obj = plist[@"User"][@"com.apple.mobilesafari"];
if (obj == nil) {
obj = plist[@"System"][@"com.apple.mobilesafari"];
}
if (obj != nil) {
safaricontainer = obj[@"Container"];
}
} else {
Class* LSApplicationProxy = NSClassFromString(@"LSApplicationProxy");
id obj = [LSApplicationProxy performSelector:applicationProxyForIdentifier: withObject:@"com.apple.mobilesafari"]);
if (obj != nil && [obj respondsToSelector:@selector(dataContainerURL)]) {
safaricontainer = [[obj performSelector:@selector(dataContainerURL)] path];
}
}
// 清理library
NSString* libpath = [safaricontainer stringByAppendingPathComponent:@"Library"];
NSString* libcachepath = [libpath stringByAppendingPathComponent:@"Caches"];
if ([man fileExistsAtPath:libcachepath]) {
NSString* cmd = [NSString stringWithFormat:@"rm -rf %@/*", libcachepath];

清理keychain逻辑在函数中-[IFMagicMainVC cleanKeychainClick:]

    NSFileManager* man = [NSFileManager defaultManager];
if ([man fileExistsAtPath:@"/var/Keychains/keychain-2.db"]) {
system("cp /var/Keychains/keychain-2.db /tmp/");
void* ppDb = 0;
char cmd[256];
if (0 == sqlite3_open("/tmp/keychain-2.db", &ppDb)) {
strcpy(cmd, "DELETE FROM cert WHERE agrp<>'apple' and agrp not like '%apple%' and agrp <> 'ichat' and agrp <>'lockdown-identities'");
sqlite3_exec(ppDb, cmd, 0, 0, 0);
strcpy(cmd, "DELETE FROM keys WHERE agrp<>'apple' and agrp not like '%apple%' and agrp <> 'ichat' and agrp <>'lockdown-identities'");
sqlite3_exec(ppDb, cmd, 0, 0, 0);
strcpy(cmd, "DELETE FROM inet WHERE agrp<>'apple' and agrp not like '%apple%' and agrp <> 'ichat' and agrp <>'lockdown-identities'");
sqlite3_exec(ppDb, cmd, 0, 0, 0);
system("cp /tmp/keychain-2.* /var/Keychains/");
}

清理pasteboard逻辑在函数中-[IFMagicMainVC cleanPastboardClick:]

    UIPasteboard* pb = [UIPasteboard generalPasteboard];
if (pb != nil) {
NSArray* items = [pb items];
if (items != nil) {
[items removeAllObjects];
}
[pb setItems:items];
} NSFileManager* man = [NSFileManager defaultManager];
NSProcessInfo* proc = [NSProcessInfo processInfo];
BOOL isbe8 = FALSE;
NSOperatingSystemVersion ver;
ver.majorVersion = 8;
ver.minorVersion = 0;
ver.patchVersion = 0;
if ([proc respondsToSelector:@selector(isOperatingSystemAtLeastVersion:&ver)]) {
isbe8 = [proc isOperatingSystemAtLeastVersion:&ver];
}
NSString* pbplist = nil;
NSString* pbbundle = nil;
if ([man fileExistsAtPath:@"/System/Library/LaunchDaemons/com.apple.UIKit.pasteboardd.plist"]) {
pbplist = @"/System/Library/LaunchDaemons/com.apple.UIKit.pasteboardd.plist";
pbbundle = @"com.apple.UIKit.pasteboardd";
}
else if ([man fileExistsAtPath:@"/Library/LaunchDaemons/com.apple.UIKit.pasteboardd.plist"]) {
pbplist = @"/Library/LaunchDaemons/com.apple.UIKit.pasteboardd.plist";
pbbundle = @"com.apple.UIKit.pasteboardd";
}
else if ([man fileExistsAtPath:@"/System/Library/LaunchDaemons/com.apple.pasteboard.pasted.plist"]) {
pbplist = @"/System/Library/LaunchDaemons/com.apple.pasteboard.pasted.plist";
pbbundle = @"com.apple.pasteboard.pasted";
}
BOOL pbdbexist = [man fileExistsAtPath:@"/var/mobile/Library/Caches/com.apple.UIKit.pboard/pasteboardDB"];
NSString* pbcontainer = nil;
if ([man fileExistsAtPath:@"/var/mobile/Library/Caches/com.apple.UIKit.pboard"]) {
pbcontainer = @"/var/mobile/Library/Caches/com.apple.UIKit.pboard";
} else if ([man fileExistsAtPath:@"/var/mobile/Library/Caches/com.apple.Pasteboard"]) {
pbcontainer = @"/var/mobile/Library/Caches/com.apple.Pasteboard";
}
if (!isbe8 && [man fileExistsAtPath:pbplist]) {
system("launchctl unload -w");
}
if (pbcontainer != nil && [man fileExistsAtPath:pbcontainer]) {
NSString* cmd = [NSString stringWithFormat:@"rm -rf %@/*", pbcontainer];
system([cmd UTF8String]);
}
if (pbdbexist) {
NSString* cmd = [NSString stringWithFormat:@"cp %@ %@", @"/Applications/ALS.app/pb.dat", @"/var/mobile/Library/Caches/com.apple.UIKit.pboard/pasteboardDB"];
system([cmd UTF8String]);
}

思考

改机原理是什么

在iOS上目前所有流行的改机工具,本质上是利用substrate框架对某些用来获取设备和系统参数函数进行hook,从而欺骗App达到修改的目的,具体的如下:

  • 用作获取设备参数的函数,无论是C函数,还是Objective-C/Swift函数,可以使用hook框架来修改其返回值
  • 屏蔽VPN/HTTP代理检测
  • 屏蔽越狱检测

一键新机怎么实现的

在用户进行一键新机时,ALS有如下操作:

  • 生成设备参数并保存到文件
/private/var/mobile/Library/Preferences/
com.app1e.mobile.ifalscommon.plist 保存伪造设备参数数据
com.app1e.mobile.ifalslocation.plist 保存伪造位置数据
  • 将应用沙盒目录下的数据备份,同时为新环境创建沙盒目录结构

    备份的数据存放在/private/var/mobile/alsdata

  • 应用启动后,ALS.dylib会hook关键函数,并根据plist文件修改函数返回的数据

    在这一步,ALS还会根据情况清理keychain,同时做简单的反越狱检测

我该如何保护自己的App,防止被改机软件篡改设备信息

可以将一些重要检测使用汇编指令(SVC)代替函数调用完成,同时使用内存校验来检测汇编代码是否被篡改。这样ALS这种通用工具便无能为力,因为hook框架只能做到函数级别的hook。

ALS都修改了哪些参数

那么怎么确定ALS是否修改了参数,以及改了哪些参数呢?可以自行开发模块来进行检测,这里提供我开发好的checktweak工具(链接: https://pan.baidu.com/s/1vGB-rENpz0ift03QT0QQww 提取码: 4qm8),在下载安装过checktweak.deb之后,可以在任意App运行后,在App界面上,以三根手指长按屏幕即可出现弹窗。使用ALS之前,先进行一次检测,一键新机以后,再进行一次检测。

爱伪装(AWZ)/爱立思(ALS)改机改串一键新机原理分析-LMLPHP

checktweak可以用来对比使用ALS前和使用ALS后,App中检测到的参数变化;当然如果你不用ALS的话,也可以用来获取机器本身的参数了。此外该工具可以看到ALS已经设置好的参数和历史参数信息。

通过分析ALS.dylib可以发现其hook了如下函数(注意这里只做技术探讨,不要用于商业用途):

sysctl                                  修改设备名,设备型号,iOS版本等
sysctlbyname 修改设备名,设备型号,iOS版本等
uname 修改设备名,设备型号,iOS版本等
SCNetworkReachabilityGetFlags 修改网络类型,WIFI/2G/3G/4G
CNCopySupportedInterfaces 修改WIFI名和BSSID
CNCopyCurrentNetworkInfo 修改WIFI名和BSSID
IORegistryEntrySearchCFProperty 修改设备串号,IMEI
IORegistryEntryCreateCFProperty 修改设备串号,IMEI
_CTServerConnectionCopyMobileIdentity 修改IMEI
UIDevice 修改设备型号,iOS版本,设备名,IDFV
ASIdentifierManager 修改IDFA
CTCarrier 修改运营商信息,包括运营商名,MCC,MNC,ICC
CTTelephonyNetworkInfo 修改运营商信息,包括运营商名,MCC,MNC,ICC
NSProcessInfo 修改设备名,iOS版本
NSBundle 修改App版本号
CLLocationManager 修改位置参数,包括经纬度,海拔,速度等
MKUserLocation 修改位置参数,包括经纬度,海拔,速度等
ChromeViewController 修改位置参数,包括经纬度,海拔,速度等
MapsMainModeController 修改位置参数,包括经纬度,海拔,速度等
LocationShare 修改位置参数,包括经纬度,海拔,速度等
SAKCLLocationDelegateProxy 修改位置参数,包括经纬度,海拔,速度等
MOARegionalMonitoring 修改位置参数,包括经纬度,海拔,速度等
CLLocationManagerBlocks 修改位置参数,包括经纬度,海拔,速度等
QMLocationManager 修改位置参数,包括经纬度,海拔,速度等
TDLocationInfo 修改位置参数,包括经纬度,海拔,速度等
TaxiCancelOrderViewController 修改位置参数,包括经纬度,海拔,速度等
ONELocationStore 修改位置参数,包括经纬度,海拔,速度等
NSKVONotifying_ONELocationStore 修改位置参数,包括经纬度,海拔,速度等
NSKVONotifying_MomoLocationManager 修改位置参数,包括经纬度,海拔,速度等
AMapDiscoverManager 修改位置参数,包括经纬度,海拔,速度等
MMUDeviceObserver 修改位置参数,包括经纬度,海拔,速度等
MMUJSLocation 修改位置参数,包括经纬度,海拔,速度等
MomoLocationManager 修改位置参数,包括经纬度,海拔,速度等
IphoneGPSMan 修改位置参数,包括经纬度,海拔,速度等
YiXinLocationManager 修改位置参数,包括经纬度,海拔,速度等
CFNetworkCopySystemProxySettings 屏蔽代理检测
NEVPNConnection 屏蔽VPN检测
UIStatusBarIndicatorItemView 屏蔽VPN检测
AppsFlyerUtils 屏蔽越狱检测
CLSAnalyticsMetadataController 屏蔽越狱检测
WXOMTAEnv 屏蔽越狱检测
lstat 屏蔽越狱检测
stat 屏蔽越狱检测
access 屏蔽越狱检测
fopen 屏蔽越狱检测
NSFileManager 屏蔽越狱检测

关于位置参数ALS做了很多hook,用于对抗多种App,例如滴滴。如果细心对比的话,可以发现ALS的一个漏洞是未设置sysctlbyname的机器名,结果sysctl和sysctlbyname结果不一致,以下是个检测样本:

以下是一个检测报告的样本:

检测时间:2019-11-26 22:11:30
检测报告文件保存路径:/var/mobile/Containers/Data/Application/4930BF56-D0BE-43CE-80F1-406427F716C9/Documents/checkparam_2019-11-26 22:11:30.txt ----------------基本信息----------------
应用标识符:com.apple.mobilesafari
应用路径:/Applications/MobileSafari.app
数据路径:/var/mobile/Containers/Data/Application/4930BF56-D0BE-43CE-80F1-406427F716C9
IDFA:685385AB-DFBB-4784-B69D-BB43CDF51944
IDFV:99CF0686-7CF3-451E-8477-C26E94C87CEC ----------------设备信息----------------
UIDevice设备类型:iPhone
UIDevice设备类型(方式2):iPhone
。。。。 ----------------iTunes信息---------------- ----------------SIM卡信息----------------
SIM卡运营商:中国联通
SIM卡VOIP:允许 ----------------区域信息----------------
区域信息国家代码:US
区域信息活跃输入法0:en-US
。。。。 ----------------存储信息----------------
存储信息分区剩余空间(Byte):8758792192
存储信息分区空间(Byte):12040671232 ----------------音频信息----------------
音频信息输入延迟:0.5
音频信息缓冲区持续时间:0.5
音频信息输出延迟:0.5
音频信息输出0:Speaker
音频信息音量:0.5 ----------------网络信息----------------
网关en0.2:192.168.1.1
DNS服务器:2408:8888::8,2408:8899::8,192.168.1.1,
MAC地址ap1:020000000000
MAC地址en0:020000000000
MAC地址awdl0:020000000000
MAC地址en1:020000000000
UserAgent:Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14C92
。。。。 ----------------代理/VPN----------------
VPN:未开启
iPhone7:/var/mobile/Containers/Data/Application/4930BF56-D0BE-43CE-80F1-406427F7
05-08 15:03