首页
社区
课程
招聘
[原创] IOS Chomper 修炼笔记
发表于: 2025-8-22 16:27 4010

[原创] IOS Chomper 修炼笔记

2025-8-22 16:27
4010

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 字符串的值

NSStringUTF8String() 方法是用于将 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 获取类方法的信息。

NSStringUTF8String() 方法是用于将 NSString 转换为 C 语言风格的 UTF-8 编码 char* 字符串。

NSData 是不可变的二进制数据容器,其在 OC 代码中的创建方式如下

logging.basicConfigfilename 进行设置

首先初始化模拟器时指定一个回调函数给参数 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
    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
        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 (下面这个密文省略了很大一部分)
        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
    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
        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 (下面这个密文省略了很大一部分)
        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
download_sample_file(binary_path)
base_path = get_base_path()
binary_path = "examples/binaries/ios/com.ceair.b2m/ceair_iOS_branch"
 
# Download sample file
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)
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():
# 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:
    """Call function with the symbol name."""
    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:
    """Call function with the symbol name."""
    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];
# title 打印标题
# dict 是一个 ObjcObject 对象
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")
# title 打印标题
# dict 是一个 ObjcObject 对象
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")
# 获取 ObjC 的类名
print(vendor_identifier.class_name)
ui_device = objc.msg_send("UIDevice", "currentDevice")
vendor_identifier = objc.msg_send(ui_device, "identifierForVendor")
# 获取 ObjC 的类名
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()
    # 格式化为 YYYYMMDDHHMM
    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()
    # 格式化为 YYYYMMDDHHMM
    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天前 被αβγδεξπ编辑 ,原因:
收藏
免费 53
支持
分享
最新回复 (44)
雪    币: 8637
活跃值: (6392)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2025-8-22 19:28
0
雪    币: 2432
活跃值: (4693)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
666
2025-8-22 20:57
0
雪    币: 45
活跃值: (1301)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
确实资料较少,研究下
2025-8-22 22:27
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2025-8-23 07:59
0
雪    币: 40
活跃值: (466)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2025-8-23 08:53
0
雪    币: 204
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2025-8-23 09:20
0
雪    币: 230
活跃值: (1531)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
1
2025-8-24 09:08
0
雪    币: 309
活跃值: (1331)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark
2025-8-24 10:33
0
雪    币: 6118
活跃值: (5895)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
66
2025-8-24 15:18
0
雪    币: 104
活跃值: (7159)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
tql
2025-8-25 09:19
0
雪    币: 4198
活跃值: (3920)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
12
不错
2025-8-25 10:09
0
雪    币: 384
活跃值: (1316)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
2025-8-25 11:08
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
6P
2025-8-25 14:05
0
雪    币: 1060
活跃值: (1701)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
15
sh4w
向您学习 ੭ ˙ᗜ˙ ੭
2025-8-25 15:16
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
666
2025-8-27 22:55
0
雪    币: 260
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
11
2025-9-3 23:14
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
666
2025-9-4 20:28
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
6666
2025-9-4 22:29
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
666666666
2025-9-7 23:02
0
雪    币: 31
活跃值: (620)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
研究下,资料较少
2025-9-10 16:45
0
雪    币: 9616
活跃值: (3940)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
22
1
2025-9-15 15:00
0
雪    币: 179
活跃值: (161)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
66
2025-9-16 12:18
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
6666
2025-9-16 19:21
0
雪    币: 1
活跃值: (176)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
55
2025-9-18 20:52
0
游客
登录 | 注册 方可回帖
返回