簡介
愛偽裝(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之前,先進行一次檢測,一鍵新機以后,再進行一次檢測。
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