Chomper 是一个基于 Unicorn 的 iOS 程序模拟执行框架,主要用于协助分析复杂、非标准的加密算法。
项目内置兼容了一组 iOS 14.4 的系统库,包含 libsystem_kernel.dylib、libsystem_platform.dylib、libsystem_c.dylib、CoreFoundation、Foundation 等绝大多数系统基础依赖,基本满足一般的加密、设备信息收集场景。
这些系统库均使用 DyldExtractor 从 dyld_shared_cache 中提取。
程序模拟执行过程中经常会涉及到一些文件操作,这些操作又可能依赖实际的文件或者系统目录结构,因此需要在宿主系统上有一个目录作为程序的“虚拟文件系统”,提供可供程序访问的目录空间,程序的所有文件操作也会被限制在该目录下。
rootfs 仓库中已经将系统库以及一些必要系统文件&目录整合好,只需要克隆下来并将路径指定给 Chomper 类即可。
另外,框架对于 executable 和 Info.plist 有着一些特殊逻辑。通常在 iOS 设备上,executable 和 Info.plist 都存在于 Container 目录下。但框架通过 load_module 加载 executable 时,该文件可能并不一定存在于 rootfs 中的 Container 目录下,为了方便起见,程序对 Container 目录下 executable 和 Info.plist 的操作会被转发到实际的 executable 所在位置。所以,只需将 Info.plist 和 executable 放在同一目录下,该 Info.plist 就可以被程序正常访问到。
框架本身没有对寄存器相关操作做二次封装,但可以通过暴露的 Unicorn 对象来访问寄存器
参考仓库 examples
模拟框架环境因为和真机环境有差别,可能因为程序未初始化完成或者执行了某些框架不支持的函数等原因导致崩溃。
典型的崩溃 Exception 为下面所示:
其中有一个地址,是程序发生错误的地址。接下来有两种情况:一是该地址显示为系统模块的一个偏移或者一个单纯未识别为任何模块的地址,二是显示为目标程序的一个偏移。
对于第一种情况,说明是程序在执行某个系统函数的内部发生的错误,这时我们就要先确认目标程序最终执行了哪个系统函数。
在日志中,往前几行,可以找到一条是程序崩溃时调用栈的日志:
我们找到其中非系统模块的那一层调用地址,就可以确定目标程序最终调用的系统函数。这里举个例子可能是 +[CLLocationManager locationServicesEnabled]。那么如何解决呢,我们需要判断该函数的执行是否真的会影响到程序的其他流程,很多情况下是不会的,那么我们只需要简单 Hook 掉该函数就能让程序继续执行,如果不行,可以报告到仓库,复杂情况需要具体分析。
对于第二种情况,就是崩溃在目标程序自身的逻辑中,可能是某个程序内部的变量、类未初始化好(当然也有可能是某个系统函数应该返回一个地址,结果返回 null,导致的错误),这就需要检测程序最后的那一段代码逻辑,是否正在操作某个变量以及该变量的值是否符合预期,然后想办法让该变量的值正确地被设置上。
可以对整个模块进行 Trace,然后通过自定义 Trace 回调函数的方式,在回调中把不符合预期的地址过滤掉。
框架中的日志都使用 Python 中的 logging 标准库进行输出,可以很方便地在外层设置日志写入文件。
框架并未实现所有系统调用的处理,因此有时候可能因为触发了未支持的系统调用而中止。
对于这种情况,你可以报告到仓库,我会处理。如果你想自己兼容,你可以选择 Hook 上层系统函数来规避,也可以注册相关系统调用的处理程序。
对于程序的 __mod_init_func 节中的初始化函数,框架在 load_module 内部会自动执行它们,但如果执行失败,只会输出一条警告日志,不会中止程序。
某些情况下,如果需要禁止 __mod_init_func 自动执行,可以指定 load_module 的 exec_init_array 参数为 False:
emu = Chomper(
...
rootfs_path=".../rootfs/ios",
)
emu = Chomper(
...
rootfs_path=".../rootfs/ios",
)
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
emu = Chomper(
arch=ARCH_ARM64,
os_type=OS_IOS,
rootfs_path=".../rootfs/ios",
)
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
emu = Chomper(
arch=ARCH_ARM64,
os_type=OS_IOS,
rootfs_path=".../rootfs/ios",
)
str_ptr = emu.create_string("chomper")
bytes_ptr = emu.create_bytes(b"chomper")
buffer_ptr = emu.create_buffer(64)
emu.free(buffer_ptr)
str_ptr = emu.create_string("chomper")
bytes_ptr = emu.create_bytes(b"chomper")
buffer_ptr = emu.create_buffer(64)
emu.free(buffer_ptr)
emu.write_u8(buffer_ptr, 1)
emu.write_s32(buffer_ptr, -1)
emu.write_u64(buffer_ptr, 1)
emu.write_pointer((buffer_ptr, 1)
emu.write_float((buffer_ptr, 1.0)
emu.write_double((buffer_ptr, 1.0)
emu.write_bystes(buffer_ptr, b"chomper")
emu.write_string(buffer_ptr, "chomper")
print(emu.read_u64(buffer_ptr))
print(emu.read_string(buffer_ptr))
emu.write_u8(buffer_ptr, 1)
emu.write_s32(buffer_ptr, -1)
emu.write_u64(buffer_ptr, 1)
emu.write_pointer((buffer_ptr, 1)
emu.write_float((buffer_ptr, 1.0)
emu.write_double((buffer_ptr, 1.0)
emu.write_bystes(buffer_ptr, b"chomper")
emu.write_string(buffer_ptr, "chomper")
print(emu.read_u64(buffer_ptr))
print(emu.read_string(buffer_ptr))
result = emu.call_symbol("_strlen", str_ptr)
print(result)
emu.call_address(0x1000)
fmt_ptr = emu.create_string("hello %s")
str_ptr = emu.create_string("chomper")
emu.call_symbol("_printf", fmt_ptr, va_list=(str_ptr,))
result = emu.call_symbol("_strlen", str_ptr)
print(result)
emu.call_address(0x1000)
fmt_ptr = emu.create_string("hello %s")
str_ptr = emu.create_string("chomper")
emu.call_symbol("_printf", fmt_ptr, va_list=(str_ptr,))
module = emu.load_module("CSMBP-AppStore-Package")
print(module.base + 0x1000)
for symbol in module.symbols:
print(f"symbol: name={symbol.name}, address={hex(symbol.address)}")
module = emu.load_module("CSMBP-AppStore-Package")
print(module.base + 0x1000)
for symbol in module.symbols:
print(f"symbol: name={symbol.name}, address={hex(symbol.address)}")
def hook_access(uc, address, size, user_data):
emu = user_data["emu"]
return 0
emu.add_interceptor("_access", hook_access)
def hook_stat(uc, address, size, user_data):
emu = user_data["emu"]
emu.logger.info("[Hook] stat called")
a1 = emu.get_arg(0)
a2 = emu.get_arg(1)
def hook_stat_return(uc, address, size, user_data):
emu = user_data["emu"]
emu.logger.info("[Hook] stat returned")
retval = emu.get_retval()
emu.add_hook("_stat", hook_stat, return_callback=hook_stat_return)
def hook_access(uc, address, size, user_data):
emu = user_data["emu"]
return 0
emu.add_interceptor("_access", hook_access)
def hook_stat(uc, address, size, user_data):
emu = user_data["emu"]
emu.logger.info("[Hook] stat called")
a1 = emu.get_arg(0)
a2 = emu.get_arg(1)
def hook_stat_return(uc, address, size, user_data):
emu = user_data["emu"]
emu.logger.info("[Hook] stat returned")
retval = emu.get_retval()
emu.add_hook("_stat", hook_stat, return_callback=hook_stat_return)
from chomper.const import HOOK_MEM_READ, HOOK_MEM_WRITE
def on_mem_read(uc, access, address, size, value, user_data):
emu = user_data["emu"]
emu.logger.info(f"[READ] {hex(address)} ({size} bytes), value = {hex(value)}")
def on_mem_write(uc, access, address, size, value, user_data):
emu = user_data["emu"]
emu.logger.info(f"[WRITE] {hex(address)} ({size} bytes), value = {hex(value)}")
emu.add_mem_hook(HOOK_MEM_READ, on_mem_read)
emu.add_mem_hook(HOOK_MEM_WRITE, on_mem_write)
from chomper.const import HOOK_MEM_READ, HOOK_MEM_WRITE
def on_mem_read(uc, access, address, size, value, user_data):
emu = user_data["emu"]
emu.logger.info(f"[READ] {hex(address)} ({size} bytes), value = {hex(value)}")
def on_mem_write(uc, access, address, size, value, user_data):
emu = user_data["emu"]
emu.logger.info(f"[WRITE] {hex(address)} ({size} bytes), value = {hex(value)}")
emu.add_mem_hook(HOOK_MEM_READ, on_mem_read)
emu.add_mem_hook(HOOK_MEM_WRITE, on_mem_write)
result = emu.debug_symbol(address)
print(result)
reuslt = emu.backtrace()
print(result)
emu.log_backtrace()
result = emu.debug_symbol(address)
print(result)
reuslt = emu.backtrace()
print(result)
emu.log_backtrace()
from unicorn import arm64_const
emu.uc.reg_write(arm64_const.UC_ARM64_REG_X0, 1)
print(emu.uc.reg_read(arm64_const.UC_ARM64_REG_X0))
from unicorn import arm64_const
emu.uc.reg_write(arm64_const.UC_ARM64_REG_X0, 1)
print(emu.uc.reg_read(arm64_const.UC_ARM64_REG_X0))
from chomper.objc import ObjcRuntime
objc = ObjcRuntime(emu)
from chomper.objc import ObjcRuntime
objc = ObjcRuntime(emu)
ns_string_class = objc.find_class("NSString")
ns_string_class.get_class_method("stringWithUTF8String:")
ns_string_class.get_class_method_list()
ns_string_class.get_instance_method("UTF8String")
ns_string_class.get_method_list()
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!