记得去年暑假,大概也是这会,我刚刚入门iOS安全方向的研究。那会老师给的第一个工作是,你能不能给我砸壳这几个ipa。然后简单学习了下大概知道了砸壳是个什么样的事情:
简而言之,苹果给分发的ipa进行了加密,导致我们从公开渠道(包括ipatool/Asspp/…/哪怕是越狱的iPhone)上获得的ipa是没法直接拖进ida进行分析的。但是又因为ipa肯定是要在设备上运行的,也就是说我们只需要把运行时解密好的程序dump下来就可以了。
然而这个做法会有一个缺陷——在旧版的手机上没法安装并dump新版的ipa,只有等到新版本的iPhone被越狱了才能dump(吗?
于是有了第二个方法:假装我们自己是dyld,手动加载可执行,并且让设备主动给我们解密。
mremap_encrypt
大概的系统调用就是这个mremap_encrypt,还有mmap之类的,先mmap映射可执行文件到内存(这里要用 PROT_READ | PROT_EXEC),然后mremap_encrypt去解密。于是我们发现了flexdecrypt(或者类似unfaird)但是大家发现它只能在macOS(11.3之前的)arm版上才能使用(可恶,为什么x86版不行,为什么Virtualization的虚拟机框架只给到了 12+)
那为什么iOS不行呢?众所周知iOS是一个权限管理非常非常非常严格的设备,如果权限不够什么都没法用。骗你的,权限够了还是有拿不到的东西。
我们只需要简单伪造一下entitlements,coretrust绕过了就好吧?
高兴的尝试了下发现mmap这里就eperm了。原来因为权限不够,iOS不让你做可执行代码映射。还需要在内核里改一下进程权限:另外这玩意似乎是放在ro区的,只有darksword搞不了,必须有ppl bypass才行。(哦好吧mmap在a15+设备上也是ppl管理的,而且这个ppl 权限还绕不过,不是bypass拿到physrw就行的)
int unfair_prepare_app_bundle_decryption(char *error, size_t error_size) {#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE static int prepared = 0; if (prepared) { return 0; } if (geteuid() != 0) { set_error(error, error_size, "root privileges required for jailbreak primitives"); return -1; } void *handle = open_libjailbreak(error, error_size); if (handle == NULL) { return -1; } jbclient_initialize_primitives_fn jbclient_initialize_primitives = (jbclient_initialize_primitives_fn)required_symbol(handle, "jbclient_initialize_primitives", error, error_size); jbclient_root_steal_ucred_fn jbclient_root_steal_ucred = (jbclient_root_steal_ucred_fn)required_symbol(handle, "jbclient_root_steal_ucred", error, error_size); jbclient_root_set_mac_label_fn jbclient_root_set_mac_label = (jbclient_root_set_mac_label_fn)required_symbol(handle, "jbclient_root_set_mac_label", error, error_size); proc_find_fn proc_find = (proc_find_fn)required_symbol(handle, "proc_find", error, error_size); proc_csflags_set_fn proc_csflags_set = (proc_csflags_set_fn)required_symbol(handle, "proc_csflags_set", error, error_size); if (jbclient_initialize_primitives == NULL || jbclient_root_steal_ucred == NULL || jbclient_root_set_mac_label == NULL || proc_find == NULL || proc_csflags_set == NULL) { return -1; } if (jbclient_initialize_primitives() != 0) { set_error(error, error_size, "jbclient_initialize_primitives failed"); return -1; } uint64_t original_ucred = 0; if (jbclient_root_steal_ucred(0, &original_ucred) != 0) { set_error(error, error_size, "jbclient_root_steal_ucred failed"); return -1; } uint64_t original_label = 0; if (jbclient_root_set_mac_label(1, UINT64_MAX, &original_label) != 0) { set_error(error, error_size, "jbclient_root_set_mac_label failed"); return -1; } uint64_t proc = proc_find(getpid()); if (proc == 0) { set_error(error, error_size, "proc_find(%d) failed", getpid()); return -1; } proc_csflags_set(proc, UNFAIR_CS_PLATFORM_BINARY); prepared = 1; return 0;#else (void)error; (void)error_size; return 0;#endif} 好然后这里做完了你终于能mmap了,但是为什么mremap_encrypt还是会阻止!
经过简单的尝试我们发现,放在一般目录的macho可以mmap 加载但是 mremap_encrypt会被拒绝,只有把内容放到/var/mobile 下才可以mremap_encrypt。
并且这里没有gatekeeper也不会做完整性校验什么的,那也就相当于只需要把下载的macho搭配上 SC_Info 还有里面的东西,一个macho上传解密一次就好了。
同时,用于解密的手机需要登录App Store随意下载一个软件并启动一次,这样才会更新IC_Info 里面关于这个账号的解密凭据。
于是我们就可以配上 ipatool 或者 AssppWeb 来做一套全自动下载砸壳服务了。稍微让codex写一写还能自动上传到telegram频道的bot。
https://github.com/lbr77/AssppJailbroken
Dive deeper?
有没有办法看到更底层呢?比如,这个ipa包里放的到底是什么凭据可以用来解密呢?以及,为什么一定要下载一个运行程序之后才能解密?
找到了这一篇文章:
其中SUPF文件和IPA一起分发,每个用户的IPA和SUPF文件都是一致的,其中SUPF文件中保存了加密Mach-O的密钥,但是密钥本身被另外的机制加密。而SINF文件则作为每个用户的DRM许可,记录了购买用户的标识符和姓名,以及解密SUPF需要的信息,因此在Sandbox策略下,App无法读取自身的SINF文件,以防止其被作为唯一ID追踪用户。
经过又一番研究,我们把整个运行前解密链的后半段逻辑猜出来:(因为代码用了ollvm混淆,并没有系统学过反编译混淆的处理方法所以这里我不献丑了)
- 内核收到mremap_encrypt,根据com.apple.fairplay这个标号转到 FairplayIOKit 内核 extension去处理
- 内核 extension 给用户态 fairplayd 发送 mig 请求,fairplayd 收到请求读取 sinf /supf,还有激活(MobileActivation,苹果的另一个安全保护逻辑)后下载的 icinfo (sidb, sidv)
- 而后fairplayd解析上面的几个不同凭据之后会把东西丢回到内核态的IOKit继续处理,使用 SEP 独立硬件 (以及硬件绑定的AP GID Key,也许不是)进行解密。获得sinf的解密结果,最后返回给fairplayd
- fairplayd再用软件aes解密supf拿到真实的aes key。再扔回到内核,最后内核解密macho。
另一个逻辑是关于MobileActivation:
- 在下载了一个app并且运行后,fairplayd 发现自己的icinfo 没办法解密!
- 去找激活相关框架,上报给苹果服务器自己的设备序列号/ecid/…一系列环境信息/账号信息。苹果验证后返回对应的icinfo,本地更新。
这两个逻辑配上苹果的从SEP开始往上的信任链,导致在解密过程中break,拿到某一环的内容从而一劳永逸地解密变得不现实。
The END?
最后,关于fairplay的研究最开始是想在unicorn里直接运行SEP的固件。。。于是被迫/主动了解了一些详细的知识,顺带往上多看了看,顺着顺着就看到了之前一直在研究的拆壳。