Chomper 可以视为 iOS 环境下的 Unidbg 替代方案,采用 Python 语言开发。
笔者在寻找相关学习资料的过程中,发现 Chomper 的技术文档相对匮乏。因此我决定深入研究其源码,并整理出这套 《 IOS Chomper 修炼笔记 》,希望能与各位技术爱好者共同探讨和学习。

打开 Pycharm 将 项目导入,然后设置源代码根目录
右键点击 src 目录
右键点击 tests 目录
设置完以后找到 chomper/examples/objc.py 便可以点击运行直接执行了,也可以参考着修改下面的代码后再运行

bangbang.py 是 Chomper 提供的一个案例,源码如下(密文部分省略了很大一截)
一、配置全局日志设置,包括定义输出格式和日志级别,并获取日志处理器。
二、如果示例所需的二进制和 plist 文件不存在,则下载
三、初始化模拟器
四、创建 OC 运行时对象
五、加载二进制
六、寻找 OC类
七、确保 OC 对象能正确被释放
八、创建 NSString 对象
九、调用 OC 方法
注意:ObjcClass 的call_method 方法是对 ObjcType(ObjcClass 的父类) 的 msgSend 方法进行了一层封装
十、读取 NSString 字符串的值
NSString 的 UTF8String() 方法是用于将 NSString 转换为 C 语言风格的 UTF-8 编码 char* 字符串。
arch
mode
指定CPU执行模式,如 ARM 或 Thumb 模式
默认值: const.MODE_ARM
os_type
决定模拟哪种操作系统环境,包括系统调用、库函数等,如 IOS 或 安卓
默认值: const.OS_ANDROID
endian
rootfs_path
指定根文件系统路径,提供模拟环境所需的系统文件,如库文件、配置文件等。例如iOS模拟需要CoreFoundation、UIKit等框架。
默认值: None
enable_ui_kit
是否启用UIKit框架支持,允许模拟器加载和使用iOS的UIKit框架,用于UI相关功能
默认值: True
enable_objc
是否启用Objective-C运行时支持,允许模拟器执行Objective-C方法调用、处理对象等
默认值: True
enable_vfp
是否启用ARM的向量浮点扩展(Vector Floating Point),允许模拟器处理浮点运算指令
默认值: True
logger
自定义日志记录器,用于记录模拟过程中的日志信息
默认值: None
trace_inst
是否跟踪每条执行的指令,开启后会实时反汇编并输出每条执行的指令,但会显著降低模拟速度
默认值: False
trace_symbol_calls
是否跟踪所有符号调用,记录程序调用的所有外部函数
默认值: False
trace_inst_callback
自定义指令跟踪回调函数,允许用户提供自定义函数来处理指令执行事件
默认值: None
Chomper 类的 create_string 方法
首先看 Chomper 类的 create_string 方法,它负责动态内存分配,并将字符串写入新分配的内存空间,并返回指向该字符串的指针,源码如下:
Chomper 类的 call_symbol 方法
随后可以发现使用了 Chomper 类的 call_symbol 方法,调用了 OC 的 _objc_getClass 方法。
_objc_getClass 方法是 Objective-C 运行时中的一个重要函数,用于根据类名查找并返回对应的类对象。
call_symbol 方法的主要处理逻辑如下:
call_symbol 方法的源码如下所示
ObjcClass 构造方法
最后返回 ObjcClass 对象。
ObjcClass 是 Chomper 框架中表示 Objective-C 类的 Python 封装类,位于 chomper/src/chomper/objc/types.py 文件中。这个类提供了与 Objective-C 类交互的各种方法和属性,比如可以通过调用 call_method 去调用类方法,或者通过 get_class_method 获取类方法的信息。
NSString 的 UTF8String() 方法是用于将 NSString 转换为 C 语言风格的 UTF-8 编码 char* 字符串。
NSData 是不可变的二进制数据容器,其在 OC 代码中的创建方式如下
对 logging.basicConfig 的 filename 进行设置
首先初始化模拟器时指定一个回调函数给参数 trace_inst_callback
然后在模块加载的时候将参数 trace_inst 设置为 true
底层原理如下所示,
所以也可以使用 Chomper 的 add_inst_trace() 方法添加指定模块
trace 指定范围这个我没有看见提供的 API(也可能是没找到),所以笔者参考 Chomper 的 add_inst_trace 方法写了一个,代码如下:
如发现代码或思路存在问题,恳请指正。同时期待与各位大佬交流学习,共同进步。
在此十分感谢 sh4w 大佬分享的 Chomper 框架!具体可以参考文章: [分享]一个基于 Unicorn 实现的 iOS 程序模拟执行框架(Python)
$ git clone https://github.com/sledgeh4w/chomper ; cd chomper
$ pip install .
$ git clone https://github.com/sledgeh4w/chomper ; cd chomper
$ pip install .
import logging
import os
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
from chomper.objc import ObjcRuntime
from utils import get_base_path, download_sample_file
log_format = "%(levelname)s: %(message)s"
logging.basicConfig(
format=log_format,
level=logging.INFO,
)
logger = logging.getLogger()
def main():
base_path = get_base_path()
binary_path = "examples/binaries/ios/com.ceair.b2m/ceair_iOS_branch"
download_sample_file(binary_path)
emu = Chomper(
arch=ARCH_ARM64,
os_type=OS_IOS,
rootfs_path=os.path.join(base_path, "../../rootfs/ios"),
enable_ui_kit=True,
)
objc = ObjcRuntime(emu)
emu.load_module(os.path.join(base_path, "../..", binary_path))
bang_safe_sdk_class = objc.find_class("BangSafeSDK")
with objc.autorelease_pool():
encrypt_input = objc.create_ns_string('S{"osVersion":"14.2.1","os":"iOS","deviceModel":"iPhone","channelNo":"APPSTORE"}')
encrypt_result = bang_safe_sdk_class.call_method("checkcode:dataStyle:", encrypt_input, 2)
encrypt_result_str = emu.read_string(objc.msg_send(encrypt_result, "UTF8String"))
logger.info("Encrypt result: %s", encrypt_result_str)
decrypt_input = objc.create_ns_string("F5usBNAQFKA6GVTwxynosqc...MQhHYDNfa/VU2UGk6KzHdHpTfZVQzQICg==")
decrypt_result = bang_safe_sdk_class.call_method("decheckcode:", decrypt_input)
decrypt_result_str = emu.read_string(objc.msg_send(decrypt_result, "UTF8String"))
logger.info("Decrypt result: %s", decrypt_result_str)
if __name__ == "__main__":
main()
import logging
import os
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
from chomper.objc import ObjcRuntime
from utils import get_base_path, download_sample_file
log_format = "%(levelname)s: %(message)s"
logging.basicConfig(
format=log_format,
level=logging.INFO,
)
logger = logging.getLogger()
def main():
base_path = get_base_path()
binary_path = "examples/binaries/ios/com.ceair.b2m/ceair_iOS_branch"
download_sample_file(binary_path)
emu = Chomper(
arch=ARCH_ARM64,
os_type=OS_IOS,
rootfs_path=os.path.join(base_path, "../../rootfs/ios"),
enable_ui_kit=True,
)
objc = ObjcRuntime(emu)
emu.load_module(os.path.join(base_path, "../..", binary_path))
bang_safe_sdk_class = objc.find_class("BangSafeSDK")
with objc.autorelease_pool():
encrypt_input = objc.create_ns_string('S{"osVersion":"14.2.1","os":"iOS","deviceModel":"iPhone","channelNo":"APPSTORE"}')
encrypt_result = bang_safe_sdk_class.call_method("checkcode:dataStyle:", encrypt_input, 2)
encrypt_result_str = emu.read_string(objc.msg_send(encrypt_result, "UTF8String"))
logger.info("Encrypt result: %s", encrypt_result_str)
decrypt_input = objc.create_ns_string("F5usBNAQFKA6GVTwxynosqc...MQhHYDNfa/VU2UGk6KzHdHpTfZVQzQICg==")
decrypt_result = bang_safe_sdk_class.call_method("decheckcode:", decrypt_input)
decrypt_result_str = emu.read_string(objc.msg_send(decrypt_result, "UTF8String"))
logger.info("Decrypt result: %s", decrypt_result_str)
if __name__ == "__main__":
main()
log_format = "%(levelname)s: %(message)s"
logging.basicConfig(
format=log_format,
level=logging.INFO,
)
logger = logging.getLogger()
log_format = "%(levelname)s: %(message)s"
logging.basicConfig(
format=log_format,
level=logging.INFO,
)
logger = logging.getLogger()
base_path = get_base_path()
binary_path = "examples/binaries/ios/com.ceair.b2m/ceair_iOS_branch"
download_sample_file(binary_path)
base_path = get_base_path()
binary_path = "examples/binaries/ios/com.ceair.b2m/ceair_iOS_branch"
download_sample_file(binary_path)
emu = Chomper(
arch=ARCH_ARM64,
os_type=OS_IOS,
rootfs_path=os.path.join(base_path, "../../rootfs/ios"),
enable_ui_kit=True,
)
emu = Chomper(
arch=ARCH_ARM64,
os_type=OS_IOS,
rootfs_path=os.path.join(base_path, "../../rootfs/ios"),
enable_ui_kit=True,
)
objc = ObjcRuntime(emu)
emu.load_module(os.path.join(base_path, "../..", binary_path))
emu.load_module(os.path.join(base_path, "../..", binary_path))
bang_safe_sdk_class = objc.find_class("BangSafeSDK")
bang_safe_sdk_class = objc.find_class("BangSafeSDK")
with objc.autorelease_pool():
with objc.autorelease_pool():
encrypt_input = objc.create_ns_string('S{"osVersion":"14.2.1","os":"iOS","deviceModel":"iPhone","channelNo":"APPSTORE"}')
encrypt_input = objc.create_ns_string('S{"osVersion":"14.2.1","os":"iOS","deviceModel":"iPhone","channelNo":"APPSTORE"}')
encrypt_result = bang_safe_sdk_class.call_method("checkcode:dataStyle:", encrypt_input, 2)
encrypt_result = bang_safe_sdk_class.call_method("checkcode:dataStyle:", encrypt_input, 2)
encrypt_result_str = emu.read_string(objc.msg_send(encrypt_result, "UTF8String"))
encrypt_result_str = emu.read_string(objc.msg_send(encrypt_result, "UTF8String"))
def find_class(self, name: str) -> ObjcClass:
name_buf = self.emu.create_string(name)
try:
class_value = self.emu.call_symbol("_objc_getClass", name_buf)
if not class_value:
raise ValueError(f"ObjC class '{name}' not found")
return ObjcClass(self, class_value)
finally:
self.emu.free(name_buf)
def find_class(self, name: str) -> ObjcClass:
name_buf = self.emu.create_string(name)
try:
class_value = self.emu.call_symbol("_objc_getClass", name_buf)
if not class_value:
raise ValueError(f"ObjC class '{name}' not found")
return ObjcClass(self, class_value)
finally:
self.emu.free(name_buf)
def create_string(self, string: str) -> int:
address = self.memory_manager.alloc(len(string) + 1)
self.write_string(address, string)
return address
def create_string(self, string: str) -> int:
address = self.memory_manager.alloc(len(string) + 1)
self.write_string(address, string)
return address
def call_symbol(
self,
symbol_name: str,
*args: int,
va_list: Optional[Sequence[int]] = None,
) -> int:
self.logger.info(f'Call symbol "{symbol_name}"')
symbol = self.find_symbol(symbol_name)
address = symbol.address
return self._start_emulate(address, *args, va_list=va_list)
def call_symbol(
self,
symbol_name: str,
*args: int,
va_list: Optional[Sequence[int]] = None,
) -> int:
self.logger.info(f'Call symbol "{symbol_name}"')
symbol = self.find_symbol(symbol_name)
address = symbol.address
return self._start_emulate(address, *args, va_list=va_list)
encrypt_result = bang_safe_sdk_class.call_method("checkcode:dataStyle:", encrypt_input, 2)
encrypt_result = bang_safe_sdk_class.call_method("checkcode:dataStyle:", encrypt_input, 2)
print(emu.read_string(objc.msg_send(ns_str, "UTF8String")))
print(emu.read_string(objc.msg_send(ns_str, "UTF8String")))
headers = objc.create_ns_dictionary({
"sks": "2,1,0",
"Content-Type": "application/json;charset=UTF-8",
})
headers = objc.create_ns_dictionary({
"sks": "2,1,0",
"Content-Type": "application/json;charset=UTF-8",
})
data = objc.create_ns_data(b'{"code": 200,"data": "A3El6CecquxeBgFfJp6x_tKc1zEMQ==","status": 200}')
data = objc.create_ns_data(b'{"code": 200,"data": "A3El6CecquxeBgFfJp6x_tKc1zEMQ==","status": 200}')
// 空数据
NSData *emptyData = [NSData data];
// 从字符串创建
NSString *str = @"Hello";
NSData *strData = [str dataUsingEncoding:NSUTF8StringEncoding];
// 从文件创建
NSData *fileData = [NSData dataWithContentsOfFile:@"/path/to/file"];
// 从二进制数据创建
Byte bytes[] = {0x01, 0x02, 0x03};
NSData *bytesData = [NSData dataWithBytes:bytes length:3];
// 空数据
NSData *emptyData = [NSData data];
// 从字符串创建
NSString *str = @"Hello";
NSData *strData = [str dataUsingEncoding:NSUTF8StringEncoding];
// 从文件创建
NSData *fileData = [NSData dataWithContentsOfFile:@"/path/to/file"];
// 从二进制数据创建
Byte bytes[] = {0x01, 0x02, 0x03};
NSData *bytesData = [NSData dataWithBytes:bytes length:3];
def print_ns_dictionary(emu, objc, title, dict):
dict_desc = objc.msg_send(dict, "description")
dict_str = emu.read_string(objc.msg_send(dict_desc, "UTF8String"))
emu.logger.info(f"[ {title} ]\n{dict_str}\n")
print(f"[ {title} ]\n{dict_str}\n")
def print_ns_dictionary(emu, objc, title, dict):
dict_desc = objc.msg_send(dict, "description")
dict_str = emu.read_string(objc.msg_send(dict_desc, "UTF8String"))
emu.logger.info(f"[ {title} ]\n{dict_str}\n")
print(f"[ {title} ]\n{dict_str}\n")
ui_device = objc.msg_send("UIDevice", "currentDevice")
vendor_identifier = objc.msg_send(ui_device, "identifierForVendor")
print(vendor_identifier.class_name)
ui_device = objc.msg_send("UIDevice", "currentDevice")
vendor_identifier = objc.msg_send(ui_device, "identifierForVendor")
print(vendor_identifier.class_name)
def get_base_path() -> str:
return os.path.abspath(os.path.dirname(__file__))
def get_log_path(extra_tips="") -> str:
now = datetime.datetime.now()
formatted_time = now.strftime("%Y%m%d%H%M")
log_path = get_base_path() + "/log/" + formatted_time + extra_tips + ".log"
return log_path
log_format = "%(levelname)s: %(message)s"
logging.basicConfig(
format=log_format,
level=logging.INFO,
filename=get_log_path("***"),
)
logger = logging.getLogger()
def get_base_path() -> str:
return os.path.abspath(os.path.dirname(__file__))
def get_log_path(extra_tips="") -> str:
now = datetime.datetime.now()
formatted_time = now.strftime("%Y%m%d%H%M")
log_path = get_base_path() + "/log/" + formatted_time + extra_tips + ".log"
return log_path
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2天前
被αβγδεξπ编辑
,原因: