AWZ原理分析

简介

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

AWZ伪装哪些参数?

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

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

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

AWZ的Cydia源

    als.ucydia.com      ALS     11.0.5 
    awz.ucydia.com      AWZ     10.0.5 
    hbs.ucydia.com      HBS     2.0.1            
    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 

分析要点

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

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

第一阶段

  • AWZ属性2755防止加载tweak
  • 存在restrict段,防止被加载tweak
  • 存在syscall函数进行ptrace系统调用,存在ptrace函数,以及svc汇编指令实现的ptrace,防止调试器附加

  对于restrict段和汇编指令反调试的处理,解法就是patch文件然后重签名,但是要写一个通用的命令是比较困难的,慢慢收集吧,这里提供的方式如下:

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

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

第二阶段

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

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

  对于砸壳,因为已经破解所以方法很多,当然我还是推荐我的frIDA脱壳脚本https://github.com/lichao890427/personal_script/blob/master/Frida_script/ios_dump.js;其他函数检测方法不再赘述,写tweak即可

第三阶段

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

  • 解密栈字符串,AWZ和DHPDaemon几乎全用的栈字符串混淆,蛮体力活的
  • 定位到注册的函数,一方面通过socket通信,用加密本机信息获取软件激活状态,另一方面通过cjson反序列化回写注册状态。
  • 定位get_json_value函数,该函数为c层cjson解析函数,用于从json数据的到key对应的value,该函数刚好位于socket网络通信,解密响应得到json数据后。
  • 还原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/lichao890427/personal_script/blob/master/IDA_Script/parse_stack_string.py   

  对于ObjC函数调用关系还原,见:https://github.com/lichao890427/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.ifawzcommon.plist,解密后仍然是json数据,要修改的字段如下:

@{
    @"authInfo": {
        @"status": @0,
        @"expiry_date": @"21000101080000000"
    }
}

  最后一点,最大的干扰可能是mapapi.bundle做了一些特殊操作,懒得去分析了,简单绕过,循环写status和date就行了。最终破解的结果就如本文开头的图,无任何限制   

第四阶段

  • 捕获按钮触发的功能函数
  • 分析AWZ和DHPDaemon的notify通信,有些重要函数是AWZ调用DHPDaemon执行

  捕获按钮触发,利用frida脚本,https://github.com/lichao890427/personal_script/blob/master/Frida_script/utils.js,这里tranverse_view用于检测当前呈现的界面可以获取的元素,以及对应的响应selector,如果找按钮的回调,又不想触发,可以用这个。另外更通用的得是trace_view函数,可以拦截到所有界面消息以及响应selector,在执行点击等操作后可以得到更全的信息  

下面是一些分析结果:

清理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];
        system(cmd);
}
NSString* libsafaripath = [libpath stringByAppendingPathComponent:@"Safari"];
if ([man fileExistsAtPath:libsafaripath]) {
        NSString* cmd = [NSString stringWithFormat:@"rm -rf %@/History.*", libsafaripath];
        system(cmd);
        cmd = [NSString stringWithFormat:@"rm -rf %@/SuspendState.*", libsafaripath];
        system(cmd);
}

清理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/AWZ.app/pb.dat", @"/var/mobile/Library/Caches/com.apple.UIKit.pboard/pasteboardDB"];
        system([cmd UTF8String]);
        cmd = [NSString stringWithFormat:@"chown mobile:mobile %@", @"/var/mobile/Library/Caches/com.apple.UIKit.pboard/pasteboardDB"];
        system([cmd UTF8String]);

}
if (pbplist != nil && !isbe8 && [man fileExistsAtPath:pbplist]) {
        system("launchctl load -w");
}
if (isbe8 && pbbundle != nil) {
        NSString* cmd = [NSString stringWithFormat:@"launchctl kickstart -k system/%@", pbbundle];
        system([cmd UTF8String]);
}

  我自己也是在逆向中不断学习新的东西,在分析过程中见到了更多的反逆向技术,还有改机方式,深有感悟。而AWZ本身也缺失很多功能,一直在更新的不是功能而是反逆向能力。。。,如代理屏蔽,appid隐藏,流量伪造,mac伪造,磁盘容量,内存容量,没有修改语言,音量,亮度等参数,一应俱无。

THE END
喜欢就支持以下吧
点赞0
分享
评论 共1条
  • 管埋员

    昵称

  • 取消
    昵称
    • 管埋员
    • 小鱼0
      我也尝试过分析awz,但是看到混淆的字符串就蒙蔽了,看到你的分析过程真的是好佩服,现在很缺乏反反调试,反反hock的教程
      1年前回复