IOS基础
IOS 逆向基础
- ios/linux 常用命令,目录权限,文件权限 ssh scp 拷贝文件
- ios常见目录
- /Applications 所有系统app
- /Developer Xcode 开发调试相关的文件和工具
- /Library 系统支持库
- /Libaray/MobileSubstrate/DynamicLibraries 存放Cydia插件
- /System 存放系统组件和框架
- /System/Library/Frameworks Foundation.framework
- /System/Library/PrivateFrameworks
- /User 实际指向 /var/mobile 存放用户数据 (短信照片录音…)
- /User/Media
- /var/containers/Bundle/Application/xxx/xxx.app app store的app目录
- /var/mobile/Containers/Data/Application/xxx/xxx.app app沙盒数据目录
- /var/mobile/Documents/CrackerXI CrackerXI dump出来的ipa或者是Binary目录
IOS 目录
IMG
- ipa包常见组成结构
DuImageMapJsonFile.json
homeLayout.json
routerPath.json
SaveMoneyLayout.json
tabLayout.json
- MachO可以是多架构的二进制文件,称之为「通用二进制文件」
IPA文件结构
非越狱 只能安装ipa (zip压缩包,等价于安卓的apk)
越狱设备除了ipa,还可以安装deb
xxx.ipa → Payload
→ xxx.app ( 主逻辑 )
→ info.plist 应用的配置文件,定义了应用的基本信息,如版本、权限和图标
→ CFBundleIdentifier 包名
→ CFBundleExecutable 可执行文件名
→ _CodeSignature 签名文件| 签名信息
→
Base.lproj
, en.lproj
, zh-Hans.lproj
, 等:这些目录包含应用的本地化文件,以支持不同语言的界面
→
*.bundle
文件夹(如 ACAssetPicker.bundle
, AlipaySDK.bundle
等)包含第三方或自定义的资源和功能代码,通常用于特定功能或界面组件
→
Assets.car
: 资源文件夹,包含应用图形资源(图像、颜色等)→
Frameworks
: 包含应用依赖的第三方框架→
PlugIns
: 插件目录,可能包含扩展功能→
SC_Info
文件夹 通常包含StreamCapture信息,为应用中的音视频流控制提供支持→
各种 .json 文件
这些通常是应用的配置文件,定义了界面布局、路径等信息 例如:ipa中库
// 使用lipo -info 可以查看MachO文件包含的架构 $ lipo -info MachO文件 // 使用lipo –thin 拆分某种架构 $ lipo MachO文件 –thin 架构 –output 输出文件路径 // 使用lipo -create 合并多种架构 $ lipo -create MachO1 MachO2 -output 输出文件路径
通用二进制文件是苹果公司提出的一种程序代码。能同时适用多种架构的二进制文件 a. 同一个程序包中同时为多种架构提供最理想的性能 b. 因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大 c. 但是由于两种架构有共通的非执行资源,所以并不会达到单一版本的两倍之多 d. 而且由于执行中只调用一部分代码,运行起来也不需要额外的内存
maco文件结构
‣
- ios开发基础
- 基础的视图创建 类似java开发安卓中的 new View 的感觉
- 页面的跳转
- 插件
- LLDB ->
- …
手动布局 Frame-based Layout
通过手动设置视图的 frame
属性来确定视图的位置和大小
@interface ViewController () @property (nonatomic, strong) UILabel *label; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)]; self.label.text = @"Hello, World!"; self.label.backgroundColor = [UIColor lightGrayColor]; [self.view addSubview:self.label]; } @end
使用 Auto Layout
NSLayoutConstraint
定义视图之间的约束 和安卓的ConstraintLayout
差不多
@interface ViewController () @property (nonatomic, strong) UILabel *label; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.label = [[UILabel alloc] init]; self.label.text = @"Hello, World!"; self.label.backgroundColor = [UIColor lightGrayColor]; self.label.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.label]; [NSLayoutConstraint activateConstraints:@[ [self.label.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], [self.label.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], [self.label.widthAnchor constraintEqualToConstant:200], [self.label.heightAnchor constraintEqualToConstant:50] ]]; } @end
使用 UIStackView
和安卓的LinearLayout
类似
@interface ViewController () @property (nonatomic, strong) UIStackView *stackView; @property (nonatomic, strong) UILabel *label1; @property (nonatomic, strong) UILabel *label2; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.label1 = [[UILabel alloc] init]; self.label1.text = @"Label 1"; self.label1.backgroundColor = [UIColor lightGrayColor]; self.label2 = [[UILabel alloc] init]; self.label2.text = @"Label 2"; self.label2.backgroundColor = [UIColor lightGrayColor]; self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[self.label1, self.label2]]; self.stackView.axis = UILayoutConstraintAxisVertical; self.stackView.alignment = UIStackViewAlignmentCenter; self.stackView.spacing = 10; self.stackView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.stackView]; [NSLayoutConstraint activateConstraints:@[ [self.stackView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], [self.stackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor] ]]; } @end
使用代码定义的 Auto Layout (Visual Format Language)
通过视觉格式语言(VFL)定义视图之间的约束
@interface ViewController () @property (nonatomic, strong) UILabel *label; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.label = [[UILabel alloc] init]; self.label.text = @"Hello, World!"; self.label.backgroundColor = [UIColor lightGrayColor]; self.label.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.label]; NSDictionary *views = @{@"label": self.label}; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[label]-20-|" options:0 metrics:nil views:views]]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[label(50)]" options:0 metrics:nil views:views]]; } @end
presentViewController
@implementation ViewController UIViewController *viewController = vcMap[btn.crackTit]; if (viewController) { // pushViewController调用 // [self.navigationController pushViewController:viewController animated:YES]; viewController.modalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:viewController animated:YES completion:nil]; } else { // Log an error if the viewController is not found in vcMap NSLog(@"Error: No view controller found for key %@", btn.crackTit); } @end
开发基础
- iOS 框架
- CoreGraphics:
- 用于绘图和图像处理的底层框架。处理路径、颜色、渐变、图形上下文等
- CoreData:
- 一个用于管理应用数据模型的框架,提供对象图管理和持久化存储功能
- CoreAnimation:
- 提供强大的动画引擎,用于创建平滑的动画效果,是
UIKit
动画的基础 - CoreLocation:
- 用于处理地理位置、罗盘、地理围栏等相关功能
- AVFoundation:
- 提供音视频处理的功能,包括音频播放、视频录制、流媒体、处理等
- CoreMotion:
- 用于访问设备的加速度计、陀螺仪等运动传感器数据
- MapKit:
- 提供嵌入地图、标记位置、绘制路线等地图相关功能
- ARKit:
- 用于开发增强现实 (AR) 应用,支持在真实世界中叠加虚拟内容
- StoreKit:
- 用于处理应用内购买和订阅的框架
- CloudKit:
- 提供与 iCloud 的数据同步和管理功能
- Metal:
- 一个低级图形 API,用于高效的 GPU 编程,适合开发高性能的图形应用和游戏
开发常用框架
- 语言基础
- 字符串
- “xxx” c语言的字符串常量
- @“xxx” oc字符串常量
- 数据类型
- c语言中的所有数据类型 (兼容c语言) char int float double .. / 数组 结构体 / 指针 / 结构体 / void / typedef
- OC独有类型
- BOOL [ YES , NO ]
- Boolean [ true , false ]
- nil → 差不多就是NULL
- class → 类
- id → 万能指针 类似 void*
- SEL → 方法选择器 ( OC语言动态特性的基础 )
- block → 代码段 类似于 c++ lambda 表达式
- 面向对象
^v
: 这表示返回值是一个指向返回值类型的指针,v
表示返回值类型为void
。16@0:8
: 这是参数的类型及其位置的编码:@
表示一个对象(id
类型)。0:
表示第一个参数的偏移量(self
)。8
表示第二个参数的偏移量(_cmd
)。v
: 返回值为void
。36@0:8
: 第一个参数是一个对象,偏移量0
是self
,偏移量8
是_cmd
。i16
: 第二个参数是一个int
,偏移量16
。@20
: 第三个参数是一个对象,偏移量20
。28
: 第四个参数是一个指向某种类型的指针,偏移量28
。- Block
- Static
- 不能用来修饰属性和方法
- 影响到变量的生命周期,不影响所用范围
- 局部静态变量赋值只有一次(和c/c++一样的)
- ……
objectc开发 !todo
// NSLog 兼容 prinf 用法,新增了一些 OC 的符号 比如 %@ NSString* str = @"obcstring"; char* str1 = "obcstring"; NSLog(@"sss %d | %@ | %s", 200, str, str1 );
面向对象基本封装
// // main.m // xxxoc // // Created by cywl on 2024/8/28. // #import <Foundation/Foundation.h> // 类申明 (类名首字母大写/类声明在实现前) @interface TTOBJ : NSObject // 类成员变量(属性/字段/field)的定义放在类申明中的 {} 中, 变量用_开始 { @public // 不指定的话默认就是protect int _age; NSString* _name; char* _discription; void* _tt; @private void* _tp; } // 实例化类的时候会给定默认值 // 其中又一个isa指向的是当前类 (是当前类 不是类指针/不是cpp中的this指针) /* (lldb) p *p (TTOBJ) { NSObject = { isa = TTOBJ } _age = 0 _name = nil _discription = 0x0000000000000000 _tt = 0x0000000000000000 _tp = 0x0000000000000000 } */ // - 实例方法 | ( 返回值类型 ) | 方法名 - (void*) getTpPtr ; // 带多个参数 + (int) addNum1:(int)num1 addNum2:(int)num2; // + 类方法 带一个参数 + (void) talkWith:(NSString*) who; // ↑ 小驼峰命名 @end // 类实现 @implementation TTOBJ - (void*) getTpPtr { return &_tp; } + (int) addNum1:(int)num1 addNum2:(int)num2{ return num1 + num2; } + (void) talkWith:(NSString*) who { NSLog(@"called talkWith %@", who); } @end int main(int argc, const char * argv[]) { @autoreleasepool { // 基本数据类型初始化为0,OC类型初始化为nil (虽然nil也等于NULL ...) if (nil == NULL){ NSLog(@"nil == NULL"); } TTOBJ* p = [TTOBJ new]; NSLog(@"p age -> %d", p->_age); NSLog(@"p _tt -> %p", (*p)._tt); // 和c结构体用法一样 NSLog(@"getTpPtr = %p", [p getTpPtr]); // 调用实例方法 NSLog(@"this ptr = %p", p); // OC的smalltalk的编程风格 [TTOBJ talkWith:@"tom"]; NSLog(@"ADD VALUE => %d", [TTOBJ addNum1:100 addNum2:355]); // 赋值后查看内存 p->_age = 20; p->_discription = "this is discription"; p->_name = @"Alice"; p->_tt = [p getTpPtr]; + stringWithUTF8String: char bytes[0x100] = {0}; [p->_name getCString:bytes maxLength:0x100 encoding:NSUTF8StringEncoding]; raise(SIGSTOP); NSLog(@"EXIT"); } return 0; }
构造函数
@interface TestClazz : NSObject { @public NSString* _name; int _age; } + (instancetype) TestClazz; + (instancetype) TestClassWithName :(NSString*)name :(int)age; - (void*) getTpPtr ; @end @implementation TestClazz + (instancetype) TestClazz{ return [self new]; } + (instancetype) TestClassWithName :(NSString*)name :(int)age { TestClazz* ins = [self new]; ins->_name = name; ins->_age = age; return ins; } - (void*) getTpPtr { // 1 __bridge: 只进行简单的类型转换,不影响对象的内存管理。 // 2 __bridge_retained: 将 Objective-C 对象的所有权转移到 Core Foundation,不增加引用计数。 // 3 __bridge_transfer: 将 Core Foundation 对象的所有权移交到 ARC(自动引用计数)。 void *ptr = (__bridge void *)self; return ptr; } @end NSLog(@"TestClazz -> %p", [[TestClazz new] getTpPtr]); [TestClazz TestClassWithName:@"xiaoming" :20];
上文code中 [ 其中的self ( 也就是isa ) 是不同的 ]
第一个局部变量信息是 调用 addNum1:addNum2: ( + 类方法)
第二个局部变量信息是 调用 getTpPtr ( - 成员方法 )
@interface TestClazz : NSObject - (void*) getTpPtr ; @end @implementation TestClazz - (void*) getTpPtr { // 1 __bridge: 只进行简单的类型转换,不影响对象的内存管理。 // 2 __bridge_retained: 将 Objective-C 对象的所有权转移到 Core Foundation,不增加引用计数。 // 3 __bridge_transfer: 将 Core Foundation 对象的所有权移交到 ARC(自动引用计数)。 void *ptr = (__bridge void *)self; return ptr; } @end // 然后main函数中添加下面log NSLog(@"TestClazz -> %p", [[TestClazz new] getTpPtr]);
上述的代码测试发现程序正常运行,也就是说函数和对象是完全分离的,从反汇编中的objc_sendmsg也可以看出来其实调用函数是去通过字符串去找对象中的方法,只要对象中有这个方法
单继承 …
这 .. 没啥好说的 了解继承语法即可
SEL
从下面这部分代码可以看出OC的动态性
可以通过一个类指针或者是实例指针 + 函数名字符串 即可调用函数
(用上这个SEL就有点像是那种JAVA反射的味道了)
[[MyClass alloc] init] === [MyClass new]
TestClazz* tc = [TestClazz TestClassWithName:@"xiaoming" :20]; SEL mySelector = @selector(getTpPtr); SEL mySelector1 = NSSelectorFromString(@"getTpPtr"); NSLog(@"Called from SEL %p", [tc performSelector:mySelector]); NSLog(@"Called from SEL1 %p", [tc performSelector:mySelector1]);
遍历方法
这里还有很多指的深究的 ! REF
method_getImplementation 返回了函数指针,同时也有对应的set方法
TypeEncoding
在 Objective-C 中,方法的类型编码(type encoding)提供了方法返回值和参数类型的详细信息。你提到的
^v16@0:8
这样的编码主要由以下几个部分构成:因此,整体来说,
^v16@0:8
表示这个方法返回一个指向 void
的指针,带有一个对象参数和一个选择器参数。对其他方法的解析类似,
v36@0:8i16@20*28
可以按如下方式解读:! 意味着我使用new NativeCallBack 调用它的set即可实现修改函数
- (void*) getTpPtr ; - (void) test :(int)a1 :(NSString*)a2 :(char*)a3 ; #import <objc/runtime.h> void listMethodsOfClass(Class class) { unsigned int methodCount = 0; // Method *methods = class_copyMethodList(class, &methodCount); -> 遍历实例方法 Method *methods = class_copyMethodList(object_getClass(class), &methodCount); // -> 遍历类方法 NSLog(@"Methods of class %@:", NSStringFromClass(class)); for (unsigned int i = 0; i < methodCount; i++) { SEL selector = method_getName(methods[i]); const char *typeEncoding = method_getTypeEncoding(methods[i]); void* method_ptr = method_getImplementation(methods[i]); // method_setImplementation NSLog(@"%p -> %s: %s", method_ptr, sel_getName(selector), typeEncoding); } free(methods); } // 输出 Methods of class TTOBJ: 0x1000039fc -> getTpPtr: ^v16@0:8 0x100003aa4 -> test:::: v36@0:8i16@20*28 0x100003b00 -> .cxx_destruct: v16@0:8
定义和调用 Block
// 定义一个 Block 类型 typedef void (^SimpleBlock)(void); // 创建 Block 实例 SimpleBlock block = ^{ NSLog(@"Hello, Block!"); }; // 调用 Block block();
带参数和返回值的 Block
// 定义一个带参数和返回值的 Block 类型 typedef int (^MathBlock)(int, int); // 创建 Block 实例 MathBlock addBlock = ^int(int a, int b) { return a + b; }; // 调用 Block int result = addBlock(5, 3); NSLog(@"Result: %d", result); // Output: Result: 8
作为方法参数传递 Block
- (void)performOperationWithCompletion:(void (^)(NSString *result))completion { // 模拟一些操作 NSString *result = @"Operation completed!"; if (completion) { completion(result); } } // 使用 Block 作为参数传递 [self performOperationWithCompletion:^(NSString *result) { NSLog(@"%@", result); }];
捕获和内存管理
__weak typeof(self) weakSelf = self; self.completionBlock = ^{ [weakSelf doSomething]; };
异步操作与线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行一些耗时操作 dispatch_async(dispatch_get_main_queue(), ^{ // 更新 UI NSLog(@"Updated UI on main thread"); }); });
swift开发 !todo
c/c++ !todo
- 其他
/Library/LaunchDaemons/
目录存储的是 系统级的守护进程 配置文件。这个目录下的每个.plist
文件描述了一个守护进程的配置,通常用于服务或系统进程的启动(如 SSH、数据库、Web 服务等)。这些文件会在系统启动时被launchd
加载,并启动相应的服务或进程launchctl list | grep frida
用来管理守护进程otool -l debugserver
查询可执行文件信息codesign --display --verbose=4 debugserver
/codesign -dvvv debugserver
查询签名信息- 调试任意app
使用 ldid导出一个权限很多的可执行程序的签名文件,重新签名debugserver
同时还需要加上
get-task-allow、task_for_pid-allow
https://bbs.kanxue.com/thread-275803.htm - 遇到换新设备的时候
xcode会提示 ‘ could-not-locate-device-support-files-with-xcode-version ’
scp 到
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/1
code
ldid -e /usr/bin/debugserver > debugserver.entitlenments ldid -e /System/Library/CoreServices/SpringBoard.app/SpringBoard > SpringBoard.entitlements vim fix ↓ <key>task_for_pid-allow</key> <true/> <key>get-task-allow</key> <true/> cp /usr/bin/debugserver . ldid -SSpringBoard.entitlements debugserver mv /usr/bin/debugserver /usr/bin/debugserverBK mv debugserver /usr/bin/debugserver // 挂起调试app debugserver -x backboard 0.0.0.0:1111 /var/containers/Bundle/Application/B7977737-5057-4D7D-99EA-9054690015C0/KLDLZ.app/KLDLZ
‣
‣
逆向工具
- Frida / Objection
- Cycript 是一个 iOS 的调试和动态分析工具,提供类似于 JavaScript 的语法,可以与运行中的应用程序进行交互。它允许你动态地修改 iOS 应用的运行状态、读取对象的属性、调用方法等
cycript -p <AppName>
- GDB / LLDB
lldb -p <process_id> lldb-server p --server --listen *:1239 debugserver -x backboard 0.0.0.0:1111 /var/containers/Bundle/Application/60E15A29-3650-49D8-8432-88A4045DD351/KLDLZ.app/KLDLZ
cywltekiiPhone:~ root# find /var/containers/Bundle/Application/ -name KLDLZ
/var/containers/Bundle/Application/DE74B4EE-AC01-463A-9C41-B828AC4DD334/KLDLZ.app/KLDLZ
Clutch /Dumpdecrypted 砸壳
- Theos 是一个非常强大的工具集,用于在 越狱 设备上开发 iOS 应用和工具。它支持通过命令行创建、编译、构建和调试 iOS 应用程序
- Ldid 是一个用于在 iOS 设备上签名二进制文件的工具
ldid -S <binary_file>
MacOS环境
- 软件的安装和卸载
- .dmg 打开拖放 关闭
- .pkg 和windows差不多的安装指引
- 环境变量
- linux的模样
在用户的家目录下同样的 [ ’配方‘ →
.zshrc
]
- 基础工具
- 安装 brew | macos上的包管理工具 | ‣
- clasdump的使用‣
- Mach-O文件结构
- Brew install Go2Shell 方便我们从仿达打开命令行
- Brew install iTern2 换一个SHELL终端
- 安装爱思助手 ‣
- 安装 JDK
brew info openjdk@11
| 安装 pythonbrew install python@3.12
- 安装
issh
更方便的操作 IOS设备 - 使用cydia安装 filza (iphone上的文件管理器类似mt文件管理器)
- 安装配置AltStore ( 跟着教程走就是了 )
- frida 安装 ( 如果默认环境是python2 会出问题 )
(为了便于遍历python最好还是用上 pyenv 或者是 miniconda)
所以这里我们使用pyenv创建一个3.10的环境后再安装
pyenv install 3.10.14 && pyenv shell 3.10.14 && pip install frida-tools
当然也可以选择手机插在mac上,win or linux 使用 frida -H 来指定远程设备了… 得先手动启动一个server并指定端口→./usr/sbin/frida-server -l 0.0.0.0:2222
- …
- 关于远程frida-server需要这样使用 (主要是解决前面遇到的问题,避开使用macos来运行frida客户端)
- frida -H 192.168.xxx.xxx -F 这种方式来attach最前端的app
- 手动去启动一下frida-server ,守护进程创建的server只能 -UF 连接,没有开端口
逆向流程
- 越狱
- 对应关系
- Checkra1n
- Checkra1n 只做越狱优盘
- Checkrain UsB启动
- "Options"->"Allow untested i0s/iPadOs/tvOs versions’
- 进入DFU模式 (适用于iPhone 6s或者跟早机型、iPad、iPod Touch) 1. 开机状态下接入数据线 2. 按下"HOME"键不松手在按下"开机键"不松手(直到屏幕熄灭) 3. 继续按4s后先松开"开机键"直到提示"DFU模式"后松开"HOME"键
- "Start"-> 成功后提示 “All Done”
详细流程
设备 | 越狱版本 | 刷机版本 |
iPhone 12 | 14.1~14.3 | 16.3.1~16.4 |
iPhone 11 | 13.0 ~14.3 | 16.3.1~ 16.4 |
iPhone X | 11.1 ~14.6 | 16.3.1 ~ 16.4 |
iPhone 8 | 11.0~14.4 | 16.3.1~ 16.4 |
iPhone 7 | 10.0.1-14.6 | 15.7.3~15.7.3 |
iPhone 6 | 8.0~ 12.5.5 | 12.5.7 |
iPhone 5 | 6.0-10.3.4 | 10.3.3 ~ 10.3.4 |
iPhone 4 | 5.0~7.1.2 | 5.0~7.1.2 |
支持系统 IOS 12.3 ~ 14.7.1 支持设备 iPhone 5s ~ iPhone X ( A7 ~ A11 )
- 安装app / 以及关于提取ipa包
- 系统应用和Cydia安装的应用,就在 /Applications/ 目录下
- App Store下载的应用在 /private/var/containers/Bundle/Application/ 目录下 ipa是一个zip压缩包,ipa -> Payload -> xxx.app -> xxx
- 关于去定位 IPA 包的位置 ( 未砸壳前的可执行文件 )
- 提取IOS包还可以用些三方网站 比如 ‣
- 可能用到的越狱插件 ( repo :
https://apt.cydia.ren
/https://repo.byteage.com
) - substitute : 和 cydia substrate 差不多的hook框架,优势在于不会和frida冲突
- appsync : 使得没有签名的ipa包依旧可以安装在手机上
- AppSync Unified 安装不签名的ipa包
- AFC2 : 爱思助手图形化查看完整根目录 ( 安不安装它ssh都能完整访问目录 )
- CrackerXi : 简单易用的砸壳工具 源地址 http://cydia.iphonecake.com 或者 http://apt.cydiami.com
- openssh 和 openssl 方便链接ios设备 ( 可以配合
issh
使用 ) 如果连接不上还可以考虑 (usbmuxd
+iproxy
) [ brew install usbmuxd & iproxy 2222 22 & ssh -p 2222 root@localhost ] - XcodeRootDebug 让xcode可以调试所有的app [ README ]
- 命令行工具
- 列出应用程序
-l, --list-apps
:列出设备上的应用程序 可以通过附加选项进一步过滤结果:-o list_user
:仅列出用户安装的应用程序(默认)-o list_system
:仅列出系统应用程序-o list_all
:列出所有类型的应用程序-o xml
:以 XML plist 格式输出完整信息- 安装应用程序
-i, --install ARCHIVE
:从指定的包文件(ARCHIVE)安装应用程序,ARCHIVE 可以是 .ipa 文件或 .ipcc 文件- 卸载应用程序
-U, --uninstall APPID
:卸载指定的应用程序- 升级应用程序
-g, --upgrade ARCHIVE
:从指定的包文件升级应用程序- 应用程序归档管理
-L, --list-archives
:列出已归档的应用程序,可以通过o xml
选项以 XML plist 格式输出完整信息。-a, --archive APPID
:归档指定的应用程序,可以附加多个选项:-o uninstall
:归档后卸载应用程序-o app_only
:仅归档应用程序数据-o docs_only
:仅归档文档(用户数据)-o copy=PATH
:归档完成后将归档副本复制到指定目录 PATH-o remove
:在copy=PATH
选项使用时有效,表示复制后删除归档- 恢复和移除应用程序归档
-r, --restore APPID
:恢复指定的归档应用程序-R, --remove-archive APPID
:移除指定的应用程序归档- 其他选项
-u, --udid UDID
:通过 UDID 指定目标设备-n, --network
:连接到网络设备-w, --notify-wait
:等待应用程序安装/卸载通知后再报告操作成功-o, --options
:传递额外选项给指定命令-d, --debug
:启用通信调试-v, --version
:打印版本信息-h, --help
:打印帮助信息- 一键实现
- 手动签名
ideviceinstaller 在 macOS 上管理 iOS 设备上的应用程序的工具
HELP | brew install ideviceinstaller
Usage: ideviceinstaller OPTIONS Manage apps on iOS devices. OPTIONS: -u, --udid UDID Target specific device by UDID. -n, --network Connect to network device. -l, --list-apps List apps, possible options: -o list_user - list user apps only (this is the default) -o list_system - list system apps only -o list_all - list all types of apps -o xml - print full output as xml plist -i, --install ARCHIVE Install app from package file specified by ARCHIVE. ARCHIVE can also be a .ipcc file for carrier bundles. -U, --uninstall APPID Uninstall app specified by APPID. -g, --upgrade ARCHIVE Upgrade app from package file specified by ARCHIVE. -L, --list-archives List archived applications, possible options: -o xml - print full output as xml plist -a, --archive APPID Archive app specified by APPID, possible options: -o uninstall - uninstall the package after making an archive -o app_only - archive application data only -o docs_only - archive documents (user data) only -o copy=PATH - copy the app archive to directory PATH when done -o remove - only valid when copy=PATH is used: remove after copy -r, --restore APPID Restore archived app specified by APPID -R, --remove-archive APPID Remove app archive specified by APPID -o, --options Pass additional options to the specified command. -w, --notify-wait Wait for app installed/uninstalled notification to before reporting success of operation -h, --help prints usage information -d, --debug enable communication debugging -v, --version print version information
ideviceinfo 获取设备的基本信息
lzy@cywldeMac-mini ~/theos_proj/tt ideviceinfo ActivationState: FactoryActivated BasebandActivationTicketVersion: V2 BasebandCertId: 2315222105 BasebandChipID: 241889 BasebandKeyHashInformation: AKeyStatus: 2 SKeyHash: u+/tcCwvaQ+1Y9t40I4yegCEmB28mALlaROhaIVGBWo= SKeyStatus: 0 BasebandMasterKeyHash: 8CB15EE4C8002199070D9500BB8FB183B02713A5CA2A6B92DB5E75CE15536182 BasebandRegionSKU: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== BasebandSerialNumber: JcxFig== BasebandStatus: BBInfoAvailable BasebandVersion: 5.70.01 BluetoothAddress: 88:19:08:a6:d9:02 BoardId: 2 BrickState: false BuildVersion: 17H35 CPUArchitecture: arm64 CarrierBundleInfoArray[0]: CertID: 2315222105 ChipID: 32789 ChipSerialNo: JcxFig== DeviceClass: iPhone DeviceColor: 2 DeviceName: iPhone DieID: 2858945059807278 EthernetAddress: 88:19:08:a6:d9:03 FirmwareVersion: iBoot-5540.140.12 FusingStatus: 3 HardwareModel: D20AP HardwarePlatform: t8015 HasSiDP: true HostAttached: true InternationalMobileEquipmentIdentity: 354892090977622 MLBSerialNumber: FG38162053WJ0Y1A4 MobileEquipmentIdentifier: 35489209097762 MobileSubscriberCountryCode: MobileSubscriberNetworkCode: ModelNumber: MQ742 NonVolatileRAM: IONVRAM-SYNCNOW-PROPERTY: SU9OVlJBTS1TWU5DTk9XLVBST1BFUlRZ auto-boot: dHJ1ZQ== backlight-level: MTUxMw== boot-args: com.apple.System.tz0-size: MHgxN0NDMDAw oblit-begins: T2JsaXRUeXBlOiBPYmxpdGVyYXRlRGF0YVBhcnRpdGlvbi4gUmVhc29uOiBtaW5hZXJhc2VyIGVyYXNlIGRldmljZSBjb21tYW5k obliteration: aGFuZGxlX21lc3NhZ2U6IE9ibGl0ZXJhdGlvbiBDb21wbGV0ZQo= usbcfwflasherResult: RXJyb3IgRG9tYWluPU5TQ29jb2FFcnJvckRvbWFpbiBDb2RlPS01MzY4NzAyMTIgIihudWxsKSIgVXNlckluZm89e0Vycm9yIENvZGU9RmFpbGVkIHRvIGluaXRpYWxpemUgQXN0cmlzfQ== PartitionType: GUID_partition_scheme PasswordProtected: false PkHash: if98WU1oTt+jcaZlvUC9AOG/rlbJxZpIN+cg7Rv1bCY= ProductName: iPhone OS ProductType: iPhone10,1 ProductVersion: 13.7 ProductionSOC: true ProtocolVersion: 2 ProximitySensorCalibration: f3oABTIAHgBBKXlBFa3IQQAAyEJgS0lBuTufQQAAFkNgS0lBuTufQQAAFkOCt6dClCocwdJadkRJTHdEzFt2RJ3vdER9AqtBlmmxQejoy0H4pNNBAAAgQkyf0UK4iOZBAABIQudnkUNT1ZBDVjjuRW1pTkPlQIogAh4IDmsggkWpWHdDfcKRRQIAAAAAAAA2 RegionInfo: LL/A SIMStatus: kCTSIMSupportSIMStatusNotInserted SIMTrayStatus: kCTSIMSupportSIMTrayAbsent SerialNumber: C8PWKEGNJC6F SoftwareBehavior: EQAAAAAAAAAAAAAAAAAAAA== SoftwareBundleVersion: SupportedDeviceFamilies[1]: 0: 1 TelephonyCapability: true TimeIntervalSince1970: 1725867002.036151 TimeZone: Asia/Shanghai TimeZoneOffsetFromUTC: 28800.000000 TrustedHostAttached: true UniqueChipID: 2858945059807278 UniqueDeviceID: 07832190233cf389dfd356c1ab41fc881634026c UseRaptorCerts: true Uses24HourClock: false WiFiAddress: 88:19:08:a6:d9:01 WirelessBoardSerialNumber: F00FC1B234 kCTPostponementInfoPRLName: 0 kCTPostponementInfoServiceProvisioningState: false kCTPostponementStatus: kCTPostponementStatusReady
usbmuxd 本地ssh的连接方式(有线连接稳定性和速度)
otool MachOView 的命令行替代工具 (仅macos)
HELP
lzy@cywldeMac-mini ~/Downloads/i4ToolsDownloads/SignIpa/Payload/unc0ver.app otool Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ... -f print the fat headers -a print the archive header -h print the mach header -l print the load commands -L print shared libraries used -D print shared library id name -t print the text section (disassemble with -v) -x print all text sections (disassemble with -v) -p <routine name> start dissassemble from routine name -s <segname> <sectname> print contents of section -d print the data section -o print the Objective-C segment -r print the relocation entries -S print the table of contents of a library (obsolete) -T print the table of contents of a dynamic shared library (obsolete) -M print the module table of a dynamic shared library (obsolete) -R print the reference table of a dynamic shared library (obsolete) -I print the indirect symbol table -H print the two-level hints table (obsolete) -G print the data in code table -v print verbosely (symbolically) when possible -V print disassembled operands symbolically -c print argument strings of a core file -X print no leading addresses or headers -m don't use archive(member) syntax -B force Thumb disassembly (ARM objects only) -q use llvm's disassembler (the default) -Q use otool(1)'s disassembler -mcpu=arg use `arg' as the cpu for disassembly -j print opcode bytes -P print the info plist section as strings -C print linker optimization hints --version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
查看是否被加壳 otool -l unc0ver | grep crypt
lzy@cywldeMac-mini ~/Downloads/i4ToolsDownloads/SignIpa/Payload/unc0ver.app otool -l unc0ver | grep crypt cryptoff 16384 cryptsize 19251200 cryptid 0
签名工具
‣
查询
security find-identity -v -p codesigning
签名
codesign -f -s 'iPhone Developer: xxx zhuo (MQF8D9PK85)' xxx.app
查看
codesign -vv -d xxx.app
验证
codesign --verify xxx.app
- 砸壳 关于砸壳这一块 实测 CrackerXI 和 frida-ios-dump 版本的效果还不错
frida-ios-dump [ windows上一些常用命令不支持,避免遇到坑,建议还得在linux/macos上运行 ]
windows上操作需要修改一下python代码
主要就是py中关于用到的chmod之类的unix命令得注释掉,还有就是ssh的端口及其地址
( 电脑的conda环境有问题还没测试,等mini到了用它测试 )
这里需要去 下载一个frida的server安装一下,和安卓差不多的
版本和电脑端的server版本一致即可 | frida_16.4.10_iphoneos-arm.deb
CrackerXI 界面化的操作, 简单易用唯一的麻烦点就是得手动拷贝一下(不过也可以写个脚本来完成 !todo) dump出来以后使用scp cp到电脑上即可
dumpdecrypted
好像也不是很好用
‣
bfinject
六年前的工具了 算了算了 …
Clutch ( 静态工具 )
测试了两个ipa都不行 可能也是太老了 pass …
- 签名
- ios-deploy <- brew install npm && npm install ios-deploy -g
- ‣
- 爱思助手
- 静态分析
- class-dump 导出头文件
- 查看二进制文件信息
- MachOView
- 图像化界面的MachO文件解析
- jtool2
- rabin2
- 字符串信息
- strings
- nm
- otools
- rabin2
- 恢复符号表
- restore-symbol
- 分析代码逻辑
- IDA
- Hopper
class-dump -H xxx.app -o /Users/lzy/Desktop/apps/temp
lzy@cywldeMac-mini ~/Desktop/xia0LLDB/issh master /Users/lzy/Desktop/class-dump class-dump 3.5 (64 bit) Usage: class-dump [options] <mach-o-file> where options are: -a show instance variable offsets -A show implementation addresses --arch <arch> choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64) -C <regex> only display classes matching regular expression -f <str> find string in method name -H generate header files in current directory, or directory specified with -o -I sort classes, categories, and protocols by inheritance (overrides -s) -o <dir> output directory used for -H -r recursively expand frameworks and fixed VM shared libraries -s sort classes and categories by name -S sort methods by name -t suppress header in output, for testing --list-arches list the arches in the file, then exit --sdk-ios specify iOS SDK version (will look in /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk --sdk-mac specify Mac OS X version (will look in /Developer/SDKs/MacOSX<version>.sdk --sdk-root specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)
- 动态调试 ( 需要苹果设备 )
- debugserver+lldb
- Xcode + MonkeyDev / iosopendev
- Frida
- 界面元素
- Cycript
- Reveal
- Tweak插件开发
- CydiaSubstrate Theos / Logos
- fishhook
- CaptainHook
关于frida在OC上的使用
- OC类获取
ObjC.classes.ClassName
ObjC.classes[”ClassName”]
- OC类方法获取(拿到函数地址)
- 类似于java中 OC 也有自己的 ”toString“
- OC对象中还有一些常用属性
+ 类方法 - 实例方法
# 使用 [] 的时候前面的 + / - 可写可不写 [iOS Device::小红书 ]-> ptr(ObjC.classes.NSURL["URLWithString:"].implementation) "0x1be547308" [iOS Device::小红书 ]-> ptr(ObjC.classes.NSURL["+ fileURLWithPath:isDirectory:"].implementation) "0x1be5593f4" # 逆向中看到的 + 类方法 、 - 是实例方法 [Apple iPhone::小红书 ]-> ObjC.classes.__NSTimeZone.$methods [ "+ allocWithZone:", "+ initialize", "+ automaticallyNotifiesObserversForKey:", "+ supportsSecureCoding", "+ calendarTimeZone", "+ _intents_decodeWithJSONDecoder:codableDescription:from:", "+ _ui_canonicalTimeZoneNameForTimeZoneName:", "+ timeZoneForAddress:", "+ vs_isTimeZoneSet", "+ timeZoneForSecondsFromGMT:", ...... "- CAMLType", "- CA_prepareRenderValue", "- CA_copyNumericValue:", "- CA_archivingValueForKey:", "- CA_roundToIntegerFromValue:", "- mf_objectWithHighest:", "- forwardInvocation:", "- forwardingTargetForSelector:", "- mutableCopy", "- finalize" ]
ObjC.classes.ClassName.[’MethodName’]
获取函数地址 ObjC.classes.ClassName.[’MethodName’].implementation
使用Interceptor的时候args中 假设开发时候看到的是4个参数,实际汇编中应该是六个参数 前两个分别为类指针和方法,后续四个为开发时候的四个参数 类指针是一个OC对象,方法是一个char*指针 [ __NSCFSTRING ]
然后对于值为OC对象的话,可以使用ObjC.Object(ptr(xxx)) 包装这个地址
封装成OC对象以下两个好处 ↓
!todo 后续我在使用lldb去调试一下OC,估计也就是上面的这种输出效果
.$className / .$ownMethods / .$methods
- 关于函数栈 [ 参考官网文档:Thread.backtrace ] 其实就是安卓逆向中用到的native堆栈打印 …
console.log('called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n');
- OC方法的主动调用
- (
NSData
*)dataUsingEncoding:(NSStringEncoding
)encoding;
# 获取NSString类 [iOS Device::小红书 ]-> var ss = ObjC.classes.NSString # 类方法的调用(不需要类实例的 / 类似于静态方法) [iOS Device::小红书 ]-> var sv = (ss.stringWithFormat_("123abc")) # 实例方法调用 [iOS Device::小红书 ]-> console.log(sv.dataUsingEncoding_(4)) {length = 6, bytes = 0x313233616263} # 可以看出来实例方法调用其实是封装的 objc_msgSend [iOS Device::小红书 ]-> console.log(sv.dataUsingEncoding_(4).length) function () { return objc_msgSend(this, sel); } # 实例方法调用后值获取 [iOS Device::小红书 ]-> console.log(sv.dataUsingEncoding_(4).length()) 6 [iOS Device::小红书 ]-> console.log(sv.dataUsingEncoding_(4).bytes().readCString()) 123abc
- OC复杂参数构造
- NSData
- NSMutableArray
- NSMutebleDictionary
[iOS Device::小红书 ]-> var m = Memory.alloc(0x20) [iOS Device::小红书 ]-> m.writeByteArray([0x11,0x22,0x33,0x44]) "0x122f3e690" [iOS Device::小红书 ]-> console.log(hexdump(m, {length:0x20})) 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 122f3e690 11 22 33 44 00 00 00 00 00 00 00 00 00 00 00 00 ."3D............ 122f3e6a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [iOS Device::小红书 ]-> var s = ObjC.classes.NSData.dataWithBytes_length_(m,0x20) [iOS Device::小红书 ]-> console.log(s) {length = 32, bytes = 0x11223344 00000000 00000000 00000000 ... 00000000 00000000 } # 发生了内存拷贝 ↓ [iOS Device::小红书 ]-> console.log(s.bytes()) 0x283ff16c0
[iOS Device::小红书 ]-> var s = ObjC.classes.NSMutableArray.array() [iOS Device::小红书 ]-> console.log(s) ( ) [iOS Device::小红书 ]-> s.addObject_("987654ppp") [iOS Device::小红书 ]-> s.addObject_(123) [iOS Device::小红书 ]-> s.addObject_("123abc") [iOS Device::小红书 ]-> console.log(s) ( 987654ppp, 123, 123abc ) [iOS Device::小红书 ]-> console.log(s.objectAtIndex_(0)) 987654ppp [iOS Device::小红书 ]-> console.log(s.objectAtIndex_(0).$className) NSTaggedPointerString [iOS Device::小红书 ]-> console.log(s.objectAtIndex_(1).$className) __NSCFNumber [iOS Device::小红书 ]-> console.log(s.count()) 3 # 遍历 // - (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block; var arr = ObjC.classes.NSMutableArray.array() arr.addObject_("123") arr.addObject_(124) var handler = new ObjC.Block({ retType:"void", argTypes:["object", "int", "int"], implementation:(a,b,c)=>{ console.warn(a, b, c) var obj = new ObjC.Object(a) console.log(`class:${obj.$class} | className:${obj.$className}| superClass:${obj.$superClass}`) console.log(`ivars:${obj.$ivars} | moduleName:${obj.$moduleName} | super:${obj.$super} | protocols:${obj.$protocols}`) } }) arr.enumerateObjectsUsingBlock_(handler)
[iOS Device::小红书 ]-> var dic = ObjC.classes.NSMutableDictionary.dictionary() [iOS Device::小红书 ]-> dic { "handle": "0x2836de960" } [iOS Device::小红书 ]-> dic.$className "__NSDictionaryM" [iOS Device::小红书 ]-> dic.setObject_forKey_("value","key") [iOS Device::小红书 ]-> dic.setObject_forKey_("lzy","name") [iOS Device::小红书 ]-> dic.setObject_forKey_("aaa","name") [iOS Device::小红书 ]-> dic.toString() "{\n key = value;\n name = aaa;\n}" [iOS Device::小红书 ]-> dic.objectForKey_("key").toString() "value" [iOS Device::小红书 ]-> dic.objectForKey_("name").toString() "aaa" [iOS Device::小红书 ]-> var enu = dic.keyEnumerator() [iOS Device::小红书 ]-> console.log(enu) <__NSFastEnumerationEnumerator: 0x11ea28450>
- Hook OC方法和替换OC方法 ! hook方法的时候同时挂上lldb会循环报错 无法忽略? 实测使用替换 impl 的不会出现这种问题的 (所以还是比较建议使用替换,而不是hook)
# 关于Hook函数 Interceptor.detachAll() var old_func = ObjC.classes.NSURL.URLWithString_.implementation # 不推荐上面这种写法 获取地址不是很稳定 建议还是用下面这种 # ptr(ObjC.classes.NSURL["+ URLWithString:"].implementation) Interceptor.attach(old_func, { onEnter(args) { console.warn("called URLWithString:") console.log(`${new ObjC.Object(args[0])}`) console.log(`${ObjC.selectorAsString(args[1])}`) console.log(`${new ObjC.Object(args[2])}`) }, }) # 关于替换函数 var method = ObjC.classes.NSURL.URLWithString_ var old_impl = method.implementation // + (instancetype)URLWithString:(NSString *)URLString; method.implementation = ObjC.implement(method, function (clazz, selector, URLString) { console.warn(`called URLWithString: ${new ObjC.Object(URLString)}`) return old_impl(clazz, selector, URLString) }) # 恢复 method.implementation = old_impl
补充部分
[Remote::JailbreakCheck ]-> DebugSymbol.fromAddress(ptr(ObjC.classes["UIApplication"]["- canOpenURL:"])) { "address": "0x1ffab6da8", "column": 0, "fileName": "", "lineNumber": 0, "moduleName": "UIKitCore", "name": "_OBJC_CLASS_RO_$_UIKeyCommandDiscoverabilityHUDVisualStyle" } [Remote::JailbreakCheck ]-> DebugSymbol.fromAddress(ptr(ObjC.classes["UIApplication"]["- canOpenURL:"].implementation)) { "address": "0x1c2332fbc", "column": 0, "fileName": "", "lineNumber": 0, "moduleName": "UIKitCore", "name": "-[UIApplication canOpenURL:]" }
- 实例方法和类方法在frida中的调用表现形式
! 类方法 ! # + (instancetype)URLWithString:(NSString *)URLString; # + (instancetype)URLWithString:(NSString *)URLString relativeToURL:(NSURL *)baseURL; [iOS Device::小红书 ]-> console.log(ObjC.classes.NSURL.URLWithString_) function (a1) { return retType.fromNative.call(this, objc_msgSend(this, sel, argTypes[0].toNative.call(this, a1))); } # - (BOOL)isEqualToURL:(NSURL *)url; [iOS Device::小红书 ]-> console.log(s.isEqualToURL_) function (a1) { return retType.fromNative.call(this, objc_msgSend(this, sel, argTypes[0].toNative.call(this, a1))); } ! 实例方法 ! [iOS Device::小红书 ]-> console.log(s.path) function () { return retType.fromNative.call(this, objc_msgSend(this, sel)); }
由上可见:其实都是不管类方法还是实例方法都是统一使用 objc_msgSend 来进行调用的
下面手动去实现一下调用
[iOS Device::小红书 ]-> DebugSymbol.fromAddress(Module.findExportByName(null,"objc_msgSend")) { "address": "0x1bdfa3b20", "column": 0, "fileName": "", "lineNumber": 0, "moduleName": "libobjc.A.dylib", "name": "objc_msgSend" } [iOS Device::小红书 ]-> var class_NSURL = ObjC.classes.NSURL [iOS Device::小红书 ]-> ObjC.selector("URLWithString:") "0x1f97fde33" [iOS Device::小红书 ]-> var objc_msgSend = new NativeFunction(ptr(0x1bdfa3b20),'pointer', ['pointer','pointer', 'pointer']) [iOS Device::小红书 ]-> var ocstr = ObjC.classes.NSString.stringWithFormat_("www.baidu.com") [iOS Device::小红书 ]-> console.log(ocstr) www.baidu.com [iOS Device::小红书 ]-> var ret = objc_msgSend(class_NSURL, ObjC.selector("URLWithString:"),ocstr) [iOS Device::小红书 ]-> console.log(ret) 0x280a16030 [iOS Device::小红书 ]-> console.log(ret.$className) undefined [iOS Device::小红书 ]-> console.log(ret.toString()) 0x280a16030 [iOS Device::小红书 ]-> console.log(new ObjC.Object(ret).$className) NSURL [iOS Device::小红书 ]-> console.log(new ObjC.Object(ret).path()) www.baidu.com [iOS Device::小红书 ]-> console.log(new ObjC.Object(ret).toString()) www.baidu.com
- 多函数批量hook
# frida api | OC函数 new ApiResolver("objc") /** * + / - 可以使用 * 来替换 * URL 匹配类 * *URLString* 匹配方法 * 都支持正则表达式 */ .enumerateMatches("-[URL *URLString*]") .forEach(item => { console.log(`${item.address} | ${item.name}`) // 匹配到函数以后 这里就可以正常的使用 Interceptor进行hook ... }) ObjC.classes.NSURL.$methods.forEach(item => { var address = ptr(ObjC.classes.NSURL[item].implementation) console.log(`${address} -> ${item}`) // 同上 }) # frida api | C 函数 new ApiResolver("module") /** * exports / import * *!CC_* -> 也可以写成 libcommonCrypto.dylib!CC_MD5_Init * ! 前是moduleName / ! 后是FunctionName * 都支持正则表达式 */ .enumerateMatches("exports:*!CC_*") .forEach(item => { console.log(`${item.address} | ${item.name}`) // 匹配到函数以后 这里就可以正常的使用 Interceptor进行hook ... }) # frida-trace -m *URLString* ...
- 关键代码的定位
- NSURL +[NSURL URLWithString:]
- NSMutableString
- NSString +[NSString stringWithFormat:] -[NSString dataUsingUTF8Encoding] -[NSString dataUsingEncoding:] -[NSString uppercaseString] -[NSString lowercaseString] -[NSString UTF8String] -[NSString initWithData:usedEncoding:] -[NSString initWithData:encoding:]
- NSData -[NSData bytes] +[NSData dataWithBytes:length:] -[NSData base64EncodedStringWithOptions:] -[NSData base64EncodedDataWithOptions:] +[NSData dataWithContentsOfFile:]→ 定位从文件读取类容到NSData -[NSData initWithBase64EncodedString:options:] -[NSData initWithBase64Encoding:] -[NSData initWithBase64EncodedData:options:]
- UIApplication -[UIApplication openURL:] → ( 涉及到越狱检测 cydia:// )
- NSBundle -[NSBundle pathForResource:ofType:] → 定位资源获取
- …. 后续再去完善一些其他的加解密函数 一些证书库 找一些login / encrypt / request … 等等函数的打印
-[NSMutableString appendString:]
-[NSMutableString appendFormat:]
-[NSMutableDictionary setValue:forKey:]
- 关于函数命名的问题
关于前缀 _ 和 __
前缀 _:
通常用于表示私有方法或属性,表示该方法不应该在类的外部调用
关于类成员一般也是考虑使用 _ 开头
前缀 __:
通常表示更加强调私有或内部使用的方法
有时是框架或系统保留的方法,开发者一般不应直接使用
这种命名约定是实现细节的一部分,有助于区分公共 API 和内部实现细节
- 关于界面上定位
使用他来帮我们快速判断app是原生应用还是使用框架的app
部分app会使用到webview ( 判断以后在进行关键函数的hook避免无用功 )
- 其他
需要注意下开发的时候的命名方式 ( IDA看起来函数名也是这样 )
↑ 需要OC基础帮我们看懂IDA解析出来的函数命名风格
frida打印出来的堆栈信息在IDA中查看的时候需要添加一个偏移,反汇编中大量的使用到了开发中默认提供到的默认支持头文件
关于在IOS上使用objection比较常用的工具函数
- ios sslpinning disable
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios sslpinning disable (agent) Hooking common framework methods (agent) [746493] Found AFNetworking library. Hooking known pinning methods. (agent) Found NSURLSession based classes. Hooking known pinning methods. (agent) Hooking lower level SSL methods (agent) Hooking lower level TLS methods (agent) Hooking BoringSSL methods (agent) Registering job 746493. Type: ios-sslpinning-disable com.xingin.discover on (iPhone: 13.5.1) [usb] # (agent) [746493] Called SSL_CTX_set_custom_verify(), setting custom callback. (agent) [746493] Called SSL_CTX_set_custom_verify(), setting custom callback. (agent) [746493] Called custom SSL context verify callback, returning SSL_VERIFY_NONE. (agent) [746493] Called SSL_get_psk_identity(), returning "fakePSKidentity". (agent) [746493] Called custom SSL context verify callback, returning SSL_VERIFY_NONE. (agent) [746493] Called SSL_get_psk_identity(), returning "fakePSKidentity". (agent) [746493] Called SSL_CTX_set_custom_verify(), setting custom callback. (agent) [746493] Called custom SSL context verify callback, returning SSL_VERIFY_NONE. (agent) [746493] Called SSL_get_psk_identity(), returning "fakePSKidentity".
- ios ui dump [ 帮我们快速定位界面 ] REF_IMPL
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios ui dump <XYWindow: 0x125d75530; baseClass = UIWindow; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x2810dc810>; layer = <UIWindowLayer: 0x281dc4880>> | <UITransitionView: 0x129de5130; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281dc32e0>> | | <UIDropShadowView: 0x129dd04a0; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281dc6f00>> | | | <UILayoutContainerView: 0x125d50e40; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x2810ddf50>; layer = <CALayer: 0x281dc1ba0>> | | | | <UINavigationTransitionView: 0x125d73bd0; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281dc1cc0>> | | | | | <UIViewControllerWrapperView: 0x12b906010; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281d657a0>> | | | | | | <UIView: 0x129dad4d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281da13a0>> | | | | | | | <UIView: 0x129da0f90; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281da1d20>> | | | | | | | | <UIView: 0x125d49be0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281da6e80>> | | | | | | | | <UIImageView: 0x125d519a0; frame = (112.5 130; 150 78); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x281dbfcc0>> | | | | | | | | <UIButton: 0x125dcf310; frame = (326 30; 33 32); opaque = NO; layer = <CALayer: 0x281dbf940>> | | | | | | | | | <UIButtonLabel: 0x125dcf5e0; frame = (0 6; 33 20); text = '帮助'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283f007d0>> | | | | | | | | | | <_UILabelContentLayer: 0x281db2b20> (layer) | | | | | | | | <XYPHDynamicWelcomeView: 0x12b92a9d0; frame = (0 208; 375 444); layer = <CALayer: 0x281db5e20>> | | | | | | | | | <UIButton: 0x12b927d30; frame = (48 233; 279 44); clipsToBounds = YES; opaque = NO; tintColor = <UIDynamicProviderColor: 0x281db0b00; provider = <__NSMallocBlock__: 0x281097b10>>; layer = <CALayer: 0x281db5e40>> | | | | | | | | | | <UIImageView: 0x12b92b2f0; frame = (92.5 12; 20 20); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x281db6060>> | | | | | | | | | | <UIButtonLabel: 0x129dcc9e0; frame = (114.5 13.5; 72 17); text = '手机号登录'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283f32440>> | | | | | | | | | | | <_UILabelContentLayer: 0x281db6180> (layer) | | | | | | | | | <UIButton: 0x12b92b4c0; frame = (0 0; 0 0); clipsToBounds = YES; hidden = YES; opaque = NO; layer = <CALayer: 0x281db6160>> | | | | | | | | | | <UIImageView: 0x12b92be10; frame = (-1 0; 0 0); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x281db6460>> | | | | | | | | | | <UIButtonLabel: 0x12b92b790; frame = (1 -8.5; 0 17); text = '微信登录'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283f32760>> | | | | | | | | | | | <_UILabelContentLayer: 0x281db6420> (layer) | | | | | | | | | <XYAuthFlowKit.AuthLoginTermsView: 0x129df8b20; frame = (37 395; 301 49); layer = <CALayer: 0x281d96960>> | | | | | | | | | | <TTTAttributedLabel: 0x12b92ceb0; baseClass = UILabel; frame = (23 9; 278 40); text = '我已阅读并同意《用户协议》《隐私政策》《儿童/青少...'; gestureRecognizers = <NSArray: 0x28109cc00>; layer = <_UILabelLayer: 0x283f32ad0>> | | | | | | | | | | | <_UILabelContentLayer: 0x281db8c60> (layer) | | | | | | | | | | <UIButton: 0x12b9051f0; frame = (0 0; 30 30); opaque = NO; tintColor = <UIDynamicProviderColor: 0x281db0b00; provider = <__NSMallocBlock__: 0x281097b10>>; layer = <CALayer: 0x281d96a20>> | | | | | | | | | | | <UIImageView: 0x126b259d0; frame = (8 8; 14 14); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x281db8d20>> | | | | | | | | | <UIButton: 0x12b92a630; frame = (143.5 297; 88 20); opaque = NO; layer = <CALayer: 0x281d96f40>> | | | | | | | | | | <UIButtonLabel: 0x126b26470; frame = (0 2.5; 88 15); text = '其他登录方式'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283f10050>> | | | | | | | | | | | <_UILabelContentLayer: 0x281db8120> (layer)
- ios hooking watch class NSURL [ 快速的hook整个类 ]
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios hooking watch class NSURL (agent) Watching method: + URLQueryParameters: (agent) Watching method: + URLQueryParametersWithoutDoubleDencode: (agent) Watching method: + pm_URLWithStringAutoEncoding: (agent) Watching method: + iOS17FixHook (agent) Watching method: + xy_URLWithString_iOS17Fix: (agent) Watching method: + acSwizzleNSURL (agent) Watching method: + cameraURLWithSource: (agent) Watching method: + coverOutputURLWithIdentifier: (agent) Watching method: + intermediateURLWithIdentifier: (agent) Watching method: + photoDirectoryURL (agent) Watching method: + photoOutputURLWithIdentifier: (agent) Watching method: + saveToAlbumVideoURLWithIdentifier: ......
- ios hooking list class_methods NSString [ 查看类方法 ]
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios hooking list class_methods NSString + ch_stringWithCHTransactionType: + ch_stringWithCHRecentCallVerificationStatus: + stringWithUTF8Data: + sinaCountWord: + stringContainsEmoji: + stringContainsEmojiForTenKeys: + matchString:toRegexString: + processWithString:regularExpression: + stringWithCountDownNSDate: + stringWithMilTimestamp:formatType: + stringWithUnkownObject: + countStringWithCount: + countStringWithCount:numberExponent: + countStringWithoutDecimalPointWithCount: + avaliableUsernameUnicodes
- ios info binary
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios info binary Name Type Encrypted PIE ARC Canary Stack Exec RootSafe -------------------------- ------- --------- ----- ----- ------ ---------- -------- discover execute True True True True False False A dylib False False True False False False KasaSDK dylib False False True True False False TXFFmpeg dylib False False False True False False TXSoundTouch dylib False False False True False False Tquic dylib False False True True False False libswift_Concurrency.dylib dylib False False False True False False
- ios monitor crypto [ 监控加密库 ]
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios monitor crypto (agent) Registering job 137978. Type: ios-crypto-monitor com.xingin.discover on (iPhone: 13.5.1) [usb] # (agent) [137978] [SecRandomCopyBytes] ( rnd : 0 count : 8 bytes : 6c2e54248b8dd37b ) (agent) [137978] [SecRandomCopyBytes] ( rnd : 0 count : 8 bytes : 043622e2307589e7 ) (agent) [137978] [SecRandomCopyBytes] ( rnd : 0 count : 8 bytes : 2dffcff996952f52 ) (agent) [137978] [SecRandomCopyBytes] ( rnd : 0 count : 8 bytes : 5960569edcf864d7 ) (agent) [137978] [SecRandomCopyBytes] ( rnd : 0 count : 8 bytes : 6762782a159001de
- ios pasteboard monitor [ 监控剪切板 ]
com.xingin.discover on (iPhone: 13.5.1) [usb] # ios pasteboard monitor (agent) [pasteboard-monitor] Data: 225 6555 5
- memory list exports libobjc.A.dylib
查看导出函数
com.xingin.discover on (iPhone: 13.5.1) [usb] # memory list exports libobjc.A.dylib Save the output by adding `--json exports.json` to this command Type Name Address -------- -------------------------------------------- ----------- function NXCompareHashTables 0x1bdfa49bc function NXCompareMapTables 0x1bdfa5320 function NXCopyHashTable 0x1bdfa4b10 function NXCountHashTable 0x1bdfa4b00 function NXCountMapTable 0x1bdfa5524 function NXCreateHashTable 0x1bdfa4238 function NXCreateHashTableFromZone 0x1bdfa4260 function NXCreateMapTable 0x1bdfa522c function NXCreateMapTableFromZone 0x1bdfa5068 function NXEmptyHashTable 0x1bdfa4930 function NXFreeHashTable 0x1bdfa4858 function NXFreeMapTable 0x1bdfa526c function NXHashGet 0x1bdfa43ec function NXHashInsert 0x1bdfa44bc function NXHashInsertIfAbsent 0x1bdfa4b9c function NXHashMember 0x1bdfa4a38 function NXHashRemove 0x1bdfa4d30 function NXInitHashState 0x1bdfa4b08 function NXInitMapState 0x1bdfa54c4 function NXMapGet 0x1bdfa552c function NXMapInsert 0x1bdfa5558 function NXMapMember 0x1bdfa5520 function NXMapRemove 0x1bdfa57c0 function NXNextHashState 0x1bdfa4764 function NXNextMapState 0x1bdfa54d0 function NXNoEffectFree 0x1bdfa4144 function NXPtrHash 0x1bdfa4130 function NXPtrIsEqual 0x1bdfa4138 variable NXPtrPrototype 0x1ff253040 variable NXPtrStructKeyPrototype 0x1ff253080 variable NXPtrValueMapPrototype 0x1ff2530c0 function NXReallyFree 0x1bdfa421c function NXResetHashTable 0x1bdfa4998 function NXResetMapTable 0x1bdfa529c function NXStrHash 0x1bdfa4148 function NXStrIsEqual 0x1bdfa419c variable NXStrPrototype 0x1ff253060 variable NXStrStructKeyPrototype 0x1ff2530a0 variable NXStrValueMapPrototype 0x1ff2530e0 variable OBJC_CLASS_$_NSObject 0x20b2ce218 variable OBJC_CLASS_$_Object 0x20b2ce100 variable OBJC_CLASS_$_Protocol 0x20b2ce1c8 variable OBJC_EHTYPE_id 0x1ff252fc0 variable OBJC_IVAR_$_NSObject.isa 0x20822c25c variable OBJC_IVAR_$_Object.isa 0x20822c258 variable OBJC_METACLASS_$_NSObject 0x20b2ce1f0 variable OBJC_METACLASS_$_Object 0x20b2ce128 variable OBJC_METACLASS_$_Protocol 0x20b2ce1a0 function __objc_personality_v0 0x1bdfa7ae0 function _class_getIvarMemoryManagement 0x1bdfa65e0 function _class_isFutureClass 0x1bdfafc98 function _objcInit 0x1bdfbbd64 function _objc_addWillInitializeClassFunc 0x1bdfa927c function _objc_atfork_child 0x1bdfbfe94 function _objc_atfork_parent 0x1bdfbfbec function _objc_atfork_prepare 0x1bdfbf958 function _objc_autoreleasePoolPop 0x1bdfc416c function _objc_autoreleasePoolPrint 0x1bdfc4170 function _objc_autoreleasePoolPush 0x1bdfc4168 function _objc_deallocOnMainThreadHelper 0x1bdfc4258 variable _objc_empty_cache 0x1bdfc69f0 variable _objc_empty_vtable 0x0 function _objc_flush_caches 0x1bdfafcb0 function _objc_getClassForTag 0x1bdfbae78 function _objc_getFreedObjectClass 0x1bdfbad2c function _objc_init 0x1bdfbffac function _objc_msgForward 0x1bdfa4020 function _objc_realizeClassFromSwift 0x1bdfaef58 function _objc_registerTaggedPointerClass 0x1bdfbad34 function _objc_rootAlloc 0x1bdfc15ec function _objc_rootAllocWithZone 0x1bdfba8dc function _objc_rootAutorelease 0x1bdfc1a78 function _objc_rootDealloc 0x1bdfc1708 function _objc_rootFinalize 0x1bdfc3d6c function _objc_rootHash 0x1bdfc3d88 function _objc_rootInit 0x1bdfc3d80 function _objc_rootIsDeallocating 0x1bdfc3a14 function _objc_rootRelease 0x1bdfc1e58 function _objc_rootReleaseWasZero 0x1bdfc3a58 function _objc_rootRetain 0x1bdfc28e0 function _objc_rootRetainCount 0x1bdfc1740 function _objc_rootTryRetain 0x1bdfc25e0 function _objc_rootZone 0x1bdfc3d84 function _objc_setBadAllocHandler 0x1bdfc2940 function _objc_setClassCopyFixupHandler 0x1bdfa99a0 function _protocol_getMethodTypeEncoding 0x1bdfb31e8 function class_addIvar 0x1bdfb8e34 function class_addMethod 0x1bdfb86d8 function class_addMethodsBulk 0x1bdfb8904 function class_addProperty 0x1bdfb92fc function class_addProtocol 0x1bdfb91c4 function class_conformsToProtocol 0x1bdfb840c function class_copyImpCache 0x1bdfb4c60 function class_copyIvarList 0x1bdfb57f8 function class_copyMethodList 0x1bdfb5630 function class_copyPropertyList 0x1bdfb5940 function class_copyProtocolList 0x1bdfb5c40 function class_createInstance 0x1bdfba864 function class_createInstances 0x1bdfba964 function class_getClassMethod 0x1bdfa6dc0 function class_getClassVariable 0x1bdfa6de4 function class_getImageName 0x1bdfbbe10 function class_getInstanceMethod 0x1bdfb6cb8 function class_getInstanceSize 0x1bdfa6f2c function class_getInstanceVariable 0x1bdfa6c0c function class_getIvarLayout 0x1bdfb7e58 function class_getMethodImplementation 0x1bdfa65a4 function class_getName 0x1bdfb6b38 function class_getProperty 0x1bdfb75a0 function class_getSuperclass 0x1bdfa6f10 function class_getVersion 0x1bdfb6b64 function class_getWeakIvarLayout 0x1bdfb7e80 function class_isMetaClass 0x1bdfa6f1c function class_lookupMethod 0x1bdfa6e94 function class_replaceMethod 0x1bdfb87f0 function class_replaceMethodsBulk 0x1bdfb8d18 function class_replaceProperty 0x1bdfb9548 function class_respondsToMethod 0x1bdfa6e04 function class_respondsToSelector 0x1bdfa6e88 function class_setIvarLayout 0x1bdfb7ea8 function class_setSuperclass 0x1bdfbae94 function class_setVersion 0x1bdfb6b98 function class_setWeakIvarLayout 0x1bdfb80a8 function gdb_class_getClass 0x1bdfb7830 variable gdb_objc_realized_classes 0x20b2cec30 function gdb_object_getClass 0x1bdfb785c function imp_getBlock 0x1bdfc0970 function imp_implementationWithBlock 0x1bdfc0668 function imp_removeBlock 0x1bdfc0a74 function instrumentObjcMessageSends 0x1bdfa6f0c function ivar_getName 0x1bdfb25e0 function ivar_getOffset 0x1bdfb25d0 function ivar_getTypeEncoding 0x1bdfb25ec function method_copyArgumentType 0x1bdfa71a8 function method_copyReturnType 0x1bdfa7098 function method_exchangeImplementations 0x1bdfb245c function method_getArgumentType 0x1bdfa70fc function method_getDescription 0x1bdfb207c function method_getImplementation 0x1bdfb22a8 function method_getName 0x1bdfb233c function method_getNumberOfArguments 0x1bdfa6f5c function method_getReturnType 0x1bdfa6ffc function method_getTypeEncoding 0x1bdfb2358 function method_invoke 0x1bdfa40a0 function method_setImplementation 0x1bdfb2378 variable objc_absolute_packed_isa_class_mask 0xffffffff8 function objc_addLoadImageFunc 0x1bdfbdff0 function objc_alloc 0x1bdfc3afc function objc_allocWithZone 0x1bdfc3b24 function objc_alloc_init 0x1bdfc3b50 function objc_allocateClassPair 0x1bdfb9fe4 function objc_allocateProtocol 0x1bdfb3c44 function objc_autorelease 0x1bdfc29e0 function objc_autoreleasePoolPop 0x1bdfc3e64 function objc_autoreleasePoolPush 0x1bdfc3d8c function objc_autoreleaseReturnValue 0x1bdfc4180 function objc_begin_catch 0x1bdfa7d84 variable objc_class_abi_version 0x1bdfcaa98 function objc_clear_deallocating 0x1bdfc3a34 function objc_constructInstance 0x1bdfba810 function objc_copyClassList 0x1bdfb4b84 function objc_copyClassNamesForImage 0x1bdfb68b4 function objc_copyClassNamesForImageHeader 0x1bdfb6a00 function objc_copyCppObjectAtomic 0x1bdfbd810 function objc_copyImageNames 0x1bdfb5f84 function objc_copyProtocolList 0x1bdfb4d90 function objc_copyRealizedClassList 0x1bdfb4980 function objc_copyStruct 0x1bdfbd5fc function objc_copyWeak 0x1bdfc362c variable objc_debug_autoreleasepoolpage_child_offset 0x1bdfcaa8c variable objc_debug_autoreleasepoolpage_depth_offset 0x1bdfcaa90 variable objc_debug_autoreleasepoolpage_hiwat_offset 0x1bdfcaa94 variable objc_debug_autoreleasepoolpage_magic_offset 0x1bdfcaa7c variable objc_debug_autoreleasepoolpage_next_offset 0x1bdfcaa80 variable objc_debug_autoreleasepoolpage_parent_offset 0x1bdfcaa88 variable objc_debug_autoreleasepoolpage_thread_offset 0x1bdfcaa84 function objc_debug_class_getNameRaw 0x1bdfb6b50 variable objc_debug_class_rw_data_mask 0x1bdfcaa28 variable objc_debug_indexed_isa_index_mask 0x1bdfcaa40 variable objc_debug_indexed_isa_index_shift 0x1bdfcaa48 variable objc_debug_indexed_isa_magic_mask 0x1bdfcaa30 variable objc_debug_indexed_isa_magic_value 0x1bdfcaa38 variable objc_debug_isa_class_mask 0x1bdfcaa50 variable objc_debug_isa_magic_mask 0x1bdfcaa58 variable objc_debug_isa_magic_value 0x1bdfcaa60 variable objc_debug_realized_class_generation_count 0x20822c348 variable objc_debug_swift_stable_abi_bit 0x1bdfcaa68 variable objc_debug_taggedpointer_classes 0x20b2ce300 variable objc_debug_taggedpointer_ext_classes 0x20b2ce380 variable objc_debug_taggedpointer_ext_mask 0x20822c2a8 variable objc_debug_taggedpointer_ext_payload_lshift 0x20822c2c0 variable objc_debug_taggedpointer_ext_payload_rshift 0x20822c2c4 variable objc_debug_taggedpointer_ext_slot_mask 0x20822c2b8 variable objc_debug_taggedpointer_ext_slot_shift 0x20822c2b0 variable objc_debug_taggedpointer_mask 0x20822c288 variable objc_debug_taggedpointer_obfuscator 0x20b2cec38 variable objc_debug_taggedpointer_payload_lshift 0x20822c2a0 variable objc_debug_taggedpointer_payload_rshift 0x20822c2a4 variable objc_debug_taggedpointer_slot_mask 0x20822c298 variable objc_debug_taggedpointer_slot_shift 0x20822c290 function objc_destroyWeak 0x1bdfc32d8 function objc_destructInstance 0x1bdfbac6c function objc_disposeClassPair 0x1bdfba55c function objc_duplicateClass 0x1bdfb95c0 variable objc_ehtype_vtable 0x1ff252fd8 function objc_end_catch 0x1bdfa7dcc function objc_enumerationMutation 0x1bdfbbe4c function objc_exception_rethrow 0x1bdfa7d58 function objc_exception_throw 0x1bdfa7b94 function objc_getAssociatedObject 0x1bdfbbe8c function objc_getClass 0x1bdfbafe0 function objc_getClassList 0x1bdfb4680 function objc_getFutureClass 0x1bdfaf87c function objc_getMetaClass 0x1bdfbb040 function objc_getProperty 0x1bdfbd100 function objc_getProtocol 0x1bdfb5558 function objc_getRequiredClass 0x1bdfbafec variable objc_indexed_classes 0x20822c338 variable objc_indexed_classes_count 0x20822c340 function objc_initWeak 0x1bdfc2ef0 function objc_initWeakOrNil 0x1bdfc30e4 function objc_initializeClassPair 0x1bdfb9b04 function objc_loadClassref 0x1bdfaa1b8 function objc_loadWeak 0x1bdfc3604 function objc_loadWeakRetained 0x1bdfc33d0 function objc_lookUpClass 0x1bdfbb034 function objc_moveWeak 0x1bdfc3664 function objc_msgLookup 0x1bdfa3c00 function objc_msgLookupSuper2 0x1bdfa3de0 function objc_msgSend 0x1bdfa3b20 function objc_msgSendSuper 0x1bdfa3ce0 function objc_msgSendSuper2 0x1bdfa3d60 function objc_msgSendSuper2_debug 0x1bdfa4080 function objc_msgSend_debug 0x1bdfa4060 function objc_msgSend_noarg 0x1bdfa4040 function objc_opt_class 0x1bdfc3c10 function objc_opt_isKindOfClass 0x1bdfc3c78 function objc_opt_new 0x1bdfc3b90 function objc_opt_respondsToSelector 0x1bdfc3cfc function objc_opt_self 0x1bdfc3be8 function objc_readClassPair 0x1bdfba3f0 function objc_registerClassPair 0x1bdfba14c function objc_registerProtocol 0x1bdfb3db8 function objc_release 0x1bdfc2a90 function objc_removeAssociatedObjects 0x1bdfbc5cc function objc_retain 0x1bdfc2970 function objc_retainAutorelease 0x1bdfc4244 function objc_retainAutoreleaseReturnValue 0x1bdfc41ac function objc_retainAutoreleasedReturnValue 0x1bdfc41dc function objc_retainBlock 0x1bdfc294c function objc_retain_autorelease 0x1bdfc2958 function objc_retainedObject 0x1bdfc4264 function objc_setAssociatedObject 0x1bdfbc5bc function objc_setEnumerationMutationHandler 0x1bdfbbe80 function objc_setExceptionMatcher 0x1bdfa7ab4 function objc_setExceptionPreprocessor 0x1bdfa7a9c function objc_setForwardHandler 0x1bdfbbd68 function objc_setHook_getClass 0x1bdfb9550 function objc_setHook_getImageName 0x1bdfbbd74 function objc_setHook_setAssociatedObject 0x1bdfbc01c function objc_setProperty 0x1bdfbd23c function objc_setProperty_atomic 0x1bdfbd374 function objc_setProperty_atomic_copy 0x1bdfbd4c8 function objc_setProperty_nonatomic 0x1bdfbd464 function objc_setProperty_nonatomic_copy 0x1bdfbd5a4 function objc_setUncaughtExceptionHandler 0x1bdfa7ac8 function objc_should_deallocate 0x1bdfc2950 function objc_storeStrong 0x1bdfc2a30 function objc_storeWeak 0x1bdfc2b28 function objc_storeWeakOrNil 0x1bdfc2d0c function objc_sync_enter 0x1bdfbc90c function objc_sync_exit 0x1bdfbcd44 function objc_sync_try_enter 0x1bdfbccf8 function objc_terminate 0x1bdfa7df8 function objc_unretainedObject 0x1bdfc4268 function objc_unretainedPointer 0x1bdfc426c function objc_unsafeClaimAutoreleasedReturnValue 0x1bdfc41f8 function object_copy 0x1bdfbaa38 function object_dispose 0x1bdfbad0c function object_getClass 0x1bdfa6304 function object_getClassName 0x1bdfa64c0 function object_getIndexedIvars 0x1bdfa9ab0 function object_getInstanceVariable 0x1bdfa6b98 function object_getIvar 0x1bdfa69a8 function object_getMethodImplementation 0x1bdfa6524 function object_isClass 0x1bdfa649c function object_setClass 0x1bdfa635c function object_setInstanceVariable 0x1bdfa6a10 function object_setInstanceVariableWithStrongDefault 0x1bdfa6ad4 function object_setIvar 0x1bdfa6870 function object_setIvarWithStrongDefault 0x1bdfa690c function property_copyAttributeList 0x1bdfb2608 function property_copyAttributeValue 0x1bdfb27a0 function property_getAttributes 0x1bdfb2600 function property_getName 0x1bdfb25f8 function protocol_addMethodDescription 0x1bdfb4180 function protocol_addProperty 0x1bdfb4420 function protocol_addProtocol 0x1bdfb3ff8 function protocol_conformsToProtocol 0x1bdfb3348 function protocol_copyMethodDescriptionList 0x1bdfb3564 function protocol_copyPropertyList 0x1bdfb3a88 function protocol_copyPropertyList2 0x1bdfb3938 function protocol_copyProtocolList 0x1bdfb3a94 function protocol_getMethodDescription 0x1bdfb3304 function protocol_getName 0x1bdfb32f0 function protocol_getProperty 0x1bdfb3710 function protocol_isEqual 0x1bdfb3508 function sel_getName 0x1bdfbc790 function sel_getUid 0x1bdfbc8f0 function sel_isEqual 0x1bdfbc8fc function sel_isMapped 0x1bdfbc7a4 function sel_registerName 0x1bdfbc8e4
- memory list modules --json /tmp/md memory list modules [ 查看模块 ]
- job list / job kill <jobID> [ 查看/删除现在的任务 ]
iOS OC方法 hook 原理
- 基于oc的动态性 主要分为三种方式hook函数 ↓
method_exchangeImplementations
class_replaceMethod
method_setImplementation & class_getMethodImplementation | class_getMethodImplementation
class_getMethodImplementation 得到的就是函数汇编地址的开始位置
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface INJ : NSString - (BOOL)canBeConvertedToEncodingNew:(NSStringEncoding)encoding; - (BOOL)canBeConvertedToEncodingNew1:(NSStringEncoding)encoding; @end @implementation INJ - (BOOL)canBeConvertedToEncodingNew:(NSStringEncoding)encoding{ char ss[0x50]; [self.className getCString:ss]; NSLog(@"Called New Funciton 0 %s", ss); return NO; } - (BOOL)canBeConvertedToEncodingNew1:(NSStringEncoding)encoding{ char ss[0x50]; [self.className getCString:ss]; NSLog(@"Called New Funciton 1 %s", ss); return NO; } @end Method oldM = class_getInstanceMethod(objc_getClass("NSString"), @selector(canBeConvertedToEncoding:)); Method newM = class_getInstanceMethod(objc_getClass("INJ"), @selector(canBeConvertedToEncodingNew:)); method_exchangeImplementations(oldM, newM);
class_replaceMethod(objc_getClass("NSString"), @selector(canBeConvertedToEncoding:), method_getImplementation(newM), method_getTypeEncoding(oldM));
method_setImplementation(newM, class_getMethodImplementation(objc_getClass("INJ"), @selector(canBeConvertedToEncodingNew1:))); NSLog(@"canConv2 %d", [s canBeConvertedToEncoding:NSUTF8StringEncoding]);
- 基于汇编指令的 INLINE HOOK
参考项目 DOBBY | 和在安卓上Hook一致
检测 / TRACE
越狱检测
- 常见的越狱检测方案
- !todo
GP 1 1. REF ‣ & 2. REF A0CrackMe
使用 stat 检查常用的目录
首先检查 stat 是不是被 plthook
噶了,然后在使用它做其他目录检查
static char *JbPaths[] = { "/Applications/Cydia.app", "/usr/sbin/sshd", "/bin/bash", "/etc/apt", "/Library/MobileSubstrate", "/User/Applications/"}; BOOL isStatNotSystemLib() { if(TARGET_IPHONE_SIMULATOR)return NO; int ret ; Dl_info dylib_info; int (*func_stat)(const char *, struct stat *) = stat; if ((ret = dladdr(func_stat, &dylib_info))) { NSString *fName = [NSString stringWithUTF8String: dylib_info.dli_fname]; // 这里其实可以直接使用c函数来比较 所以噶isEqualToString可能用处不大 // 甚至说可能跟安卓一样都是自己实现的strcmp ... if(![fName isEqualToString:@"/usr/lib/system/libsystem_kernel.dylib"]){ return YES; } } for (int i = 0;i < sizeof(JbPaths) / sizeof(char *);i++) { struct stat stat_info; if (0 == stat(JbPaths[i], &stat_info)) { return YES; } } return NO; }
应对措施 hook stat 和 hook dladdr
int stat(const char *, struct stat *)
| 还有fstat文件相关
( 是一个在 Unix/Linux 系统中用于获取文件状态信息的系统调用 ) → 成功返回0 | 失败返回 -1
const JbPaths = [ "/Applications/Cydia.app", "/usr/sbin/sshd", "/bin/bash", "/etc/apt", "/Library/MobileSubstrate", "/User/Applications/" ] const checkIfContainJbPaths = (str: string): boolean => { return JbPaths.some(item => str.includes(item)) } export const hook_stat = () => { // int stat(const char *, struct stat *) // 成功返回 0 失败返回 -1 // #define __DARWIN_STRUCT_STAT64 { \ // dev_t st_dev; /* [XSI] ID of device containing file */ \ 设备编号 // mode_t st_mode; /* [XSI] Mode of file (see below) */ \ 文件类型及权限 // nlink_t st_nlink; /* [XSI] Number of hard links */ \ 硬链接数量 // __darwin_ino64_t st_ino; /* [XSI] File serial number */ \ inode 编号 // uid_t st_uid; /* [XSI] User ID of the file */ \ 拥有者的用户 ID // gid_t st_gid; /* [XSI] Group ID of the file */ \ 拥有者的组 ID // dev_t st_rdev; /* [XSI] Device ID */ \ // __DARWIN_STRUCT_STAT64_TIMES \ // off_t st_size; /* [XSI] file size, in bytes */ \ 文件大小(以字节为单位) // blkcnt_t st_blocks; /* [XSI] blocks allocated for file */ \ // blksize_t st_blksize; /* [XSI] optimal blocksize for I/O */ \ // __uint32_t st_flags; /* user defined flags for file */ \ // __uint32_t st_gen; /* file generation number */ \ // __int32_t st_lspare; /* RESERVED: DO NOT USE! */ \ // __int64_t st_qspare[2]; /* RESERVED: DO NOT USE! */ \ // } const addr = Module.findExportByName("libsystem_kernel.dylib", "stat")! Interceptor.attach(addr, { onEnter(args) { this.disp = `stat ( '${args[0].readCString()}', ${args[1]} )` }, onLeave(retval) { if (checkIfContainJbPaths(this.disp)) { loge(`${retval} <= ${this.disp}`) retval.replace(ptr(-1)) } else { logd(`${retval} <= ${this.disp}`) } } }) }
检查是否被调试
主要涉及的 sysctl
函数 | 注意这里在 dyld
中有同名符号
// hook isDebugged // 校验当前进程是否为调试模式,hook sysctl方法可以绕过 // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). // Thanks to https://developer.apple.com/library/archive/qa/qa1361/_index.html // sysctl 是一个用于查询和设置内核参数的系统调用。它主要用于获取或更改系统信息,例如 CPU 类型、内存大小,以及其他系统级别的配置 // int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, const void *newp, size_t newlen); // 成功返回 0 失败返回 -1 BOOL isDebugged() { int junk; int mib[4]; struct kinfo_proc info; size_t size; info.kp_proc.p_flag = 0; // 指向存储查询结果的缓冲区的指针,如果只是想设置系统参数,设置为NULL mib[0] = CTL_KERN; // 整数数组,用于指定要查询或设置的系统参数 0x4 mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); assert(junk == 0); return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); }
下面是对应的 frida hook impl
// Process.enumerateModules() // .forEach(module => { // module.enumerateSymbols() // .forEach(symbol => { // if (symbol.name.includes("sysctl") && !symbol.address.isNull()) { // logd(`${symbol.address} <= ${symbol.name}`) // } // }) // }) const hook_isDebugged = () => { logd("hook_isDebugged") let addr_sysctl = Module.findExportByName("libsystem_c.dylib", "sysctl")! // addr_sysctl = DebugSymbol.fromName("sysctl").address <- 这里被坑了 重名符号 返回的是最后一个 // DebugSymbol.fromName("sysctl") // { // "address": "0x101214e30", // "column": 0, // "fileName": "", // "lineNumber": 0, // "moduleName": "dyld", // "name": "sysctl" // } Interceptor.attach(addr_sysctl, { onEnter(args) { // junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); // int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, const void *newp, size_t newlen); this.disp = `sysctl ( name=${args[0]}, namelen=${args[1]}, oldp=${args[2]}, oldlenp=${args[3]}, newp=${args[4]}, newlen${args[5]} )` let tmp :string = '' for (let i = 0; i < args[1].toInt32(); i++) { // int* ++ // if (i == 0) { // tmp += `${CTLT_YPE[args[0].readInt()].toString()} | ` // } else { tmp += `${[args[0].add(i).readInt()].toString()} | ` // } } this.disp1 = tmp.substring(0, tmp.length - 3) this.info = args[2] }, onLeave(retval) { logd(`${retval} <= ${this.disp}`) logz(`\t${this.disp1}`) // struct kinfo_proc { // struct extern_proc kp_proc; /* proc structure */ // struct eproc { // struct proc *e_paddr; /* address of proc */ // struct session *e_sess; /* session pointer */ // struct _pcred e_pcred; /* process credentials */ // 由上可见就是第一个结构体 // struct extern_proc { // union { // struct { // struct proc *__p_forw; /* Doubly-linked run/sleep queue. */ // struct proc *__p_back; // } p_st1; // struct timeval __p_starttime; /* process start time */ // } p_un; // #define p_forw p_un.p_st1.__p_forw // #define p_back p_un.p_st1.__p_back // #define p_starttime p_un.__p_starttime // struct vmspace *p_vmspace; /* Address space. */ // struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */ // int p_flag; /* P_* flags. */ // (lldb) p/a &(info.kp_proc) // (extern_proc *) 0x000000016ef08ef0 // (lldb) p/a &(info.kp_proc.p_flag) // (int *) 0x000000016ef08f10 // ofset 0x20 = pointersize * 4 // p/x info.kp_proc.p_flag => (int) 0x04004804 // p/x ~0x800 & 0x04004804 // #define P_TRACED 0x00000800 /* Debugged process being traced */ let p_flag_addr = ptr(this.info).add(Process.pointerSize * 4) logd(`${p_flag_addr} => ${p_flag_addr.readPointer()}`) if (p_flag_addr.readInt() & 0x00000800) { loge(`! bypass isDebug`) p_flag_addr.writePointer(p_flag_addr.readPointer().and(~0x00000800)) } } }) }
检查是否被注入 [ objc_copyImageNames
]
Substrate
这种东西不应该存在非越狱的设备中
sDylibSet = [NSSet setWithObjects: @"/usr/lib/CepheiUI.framework/CepheiUI", @"/usr/lib/libsubstitute.dylib", @"/usr/lib/substitute-inserter.dylib", @"/usr/lib/substitute-loader.dylib", @"/usr/lib/substrate/SubstrateLoader.dylib", @"/usr/lib/substrate/SubstrateInserter.dylib", @"/Library/MobileSubstrate/MobileSubstrate.dylib", @"/Library/MobileSubstrate/DynamicLibraries/0Shadow.dylib", nil]; BOOL isInjectedWithDynamicLibrary() { unsigned int outCount = 0; const char **images = objc_copyImageNames(&outCount); for (int i = 0; i < outCount; i++) { printf("%s\n", images[i]); } int i=0; while(true){ // hook _dyld_get_image_name方法可以绕过 const char *name = _dyld_get_image_name(i++); if(name==NULL){ break; } if (name != NULL) { NSString *libName = [NSString stringWithUTF8String:name]; if ([sDylibSet containsObject:libName]) { return YES; } } } return NO; }
对应的frida bypass脚本如下
// _dyld_get_image_name const hook_get_image_name = () => { logd("hook dyld_get_image_name") // extern const char* _dyld_get_image_name(uint32_t image_index) // __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0); const addr = Module.findExportByName("libdyld.dylib", "_dyld_get_image_name")! Interceptor.attach(addr, { onEnter(args) { this.disp = `dyld_get_image_name ( index=${args[0]} | ${args[0].toInt32()} )` }, onLeave(retval) { const ret_str = retval.readCString()! const disp_str = `${this.disp} => ${ret_str}` if (ret_str.includes("substitute") || ret_str.includes("substrate") || ret_str.includes("CepheiUI") ) { loge(disp_str) const new_ret = ret_str .replace("substitute", "---") .replace("substrate", "---") .replace("CepheiUI", "---") retval.replace(Memory.allocUtf8String(new_ret)) return } logd(disp_str) } }) }
检查 canOpenURL cydia 这种字段/app 也是非越狱设备不应该存在的
if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.avl.com"]]) { return YES; } if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]) { return YES; }
frida hook 脚本
const hook_canOpenURL = () => { logd("hook canOpenURL") const old_Method_canOpenURL = ObjC.classes["UIApplication"]["- canOpenURL:"] const old_impl = old_Method_canOpenURL.implementation old_Method_canOpenURL.implementation = ObjC.implement(old_Method_canOpenURL, function (clazz, selector, URLString) { // [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.avl.com"]] const disp = `canOpenURL ( ${new ObjC.Object(clazz)}, ${ObjC.selectorAsString(selector)}, ${new ObjC.Object(URLString)} )` as string let retval = old_impl(clazz, selector, URLString) as number if (disp.includes("cydia")) { loge(`${retval} <= ${disp}`) retval = 0 } else { if (!SLOG) logd(`${retval} <= ${disp}`) } return retval }) }
检查 canOpen [ 涉及 NSFileManager
和 fopen
]
检测一些越狱后的关键文件
BOOL fileExist(NSString* path) { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDirectory = NO; if([fileManager fileExistsAtPath:path isDirectory:&isDirectory]){ return YES; } return NO; } BOOL directoryExist(NSString* path) { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDirectory = YES; if([fileManager fileExistsAtPath:path isDirectory:&isDirectory]){ return YES; } return NO; } BOOL canOpen(NSString* path) { FILE *file = fopen([path UTF8String], "r"); if(file==nil){ return fileExist(path) || directoryExist(path); } fclose(file); return YES; } BOOL JCheckKuyt() { NSArray* checks = [[NSArray alloc] initWithObjects:@"/Application/Cydia.app", @"/Library/MobileSubstrate/MobileSubstrate.dylib", @"/bin/bash", @"/usr/sbin/sshd", @"/etc/apt", @"/usr/bin/ssh", @"/private/var/lib/apt", @"/private/var/lib/cydia", @"/private/var/tmp/cydia.log", @"/Applications/WinterBoard.app", @"/var/lib/cydia", @"/private/etc/dpkg/origins/debian", @"/bin.sh", @"/private/etc/apt", @"/etc/ssh/sshd_config", @"/private/etc/ssh/sshd_config", @"/Applications/SBSetttings.app", @"/private/var/mobileLibrary/SBSettingsThemes/", @"/private/var/stash", @"/usr/libexec/sftp-server", @"/usr/libexec/cydia/", @"/usr/sbin/frida-server", @"/usr/bin/cycript", @"/usr/local/bin/cycript", @"/usr/lib/libcycript.dylib", @"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", @"/System/Library/LaunchDaemons/com.ikey.bbot.plist", @"/Applications/FakeCarrier.app", @"/Library/MobileSubstrate/DynamicLibraries/Veency.plist", @"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", @"/usr/libexec/ssh-keysign", @"/usr/libexec/sftp-server", @"/Applications/blackra1n.app", @"/Applications/IntelliScreen.app", @"/Applications/Snoop-itConfig.app" @"/var/lib/dpkg/info", nil]; //Check installed app for(NSString* check in checks) { if(canOpen(check)) { return YES; } } }
检查环境变量 [ getenv
]
BOOL dyldEnvironmentVariables () { if(TARGET_IPHONE_SIMULATOR)return NO; return !(NULL == getenv("DYLD_INSERT_LIBRARIES")); }
frida hook 代码
const hook_getenv = ()=>{ logd("hook getenv") // getenv // char *getenv(const char *); Interceptor.attach(Module.findExportByName("libsystem_c.dylib", "getenv")!, { onEnter(args) { this.envName = args[0].readCString() }, onLeave(retval) { const disp = `getenv ( ${this.envName} ) | ret: '${retval.readCString()}'` if (this.envName != null && this.envName == "DYLD_INSERT_LIBRARIES") { loge(`${disp}`) retval.replace(NULL) } else if (!SLOG) { logd(`${disp}`) } } }) }
检查 ptrace
#define PT_DENY_ATTACH 31 typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data); void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW); ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace"); ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0); dlclose(handle);
frida hook 脚本
const hook_ptrace = ()=>{ logd("hook ptrace") // void mRiYXNnZnZmZGF2Ym() { // // No Need to encode these strings, because they will be directly compiled, they are not going to be present in the 'DATA' segment of the binary. // __asm ( // "mov r0, #31\n" // set #define PT_DENY_ATTACH (31) to r0 // "mov r1, #0\n" // clear r1 // "mov r2, #0\n" // clear r2 // "mov r3, #0\n" // clear r3 // "mov ip, #26\n" // set the instruction pointer to syscal 26 // "svc #0x80\n" // SVC (formerly SWI) generates a supervisor call. Supervisor calls are normally used to request privileged operations or access to system resources from an operating system // ); // } // ptrace(PT_DENY_ATTACH, 0, 0, 0); // ↑ 还有可能是使用系统调用实现 这里不做处理 trace指令查看svc调用 不在这里实现 ↑ // int ptrace(int request, pid_t pid, caddr_t addr, int data); // #define PT_DENY_ATTACH 31 const PT_DENY_ATTACH = 31 const addr = Module.findExportByName("libsystem_kernel.dylib", "ptrace")! const srcCall = new NativeFunction(addr, "int", ["int", "int", "pointer", "int"]) Interceptor.replace(Module.findExportByName("libsystem_kernel.dylib", "ptrace")!, new NativeCallback((request, pid, addr, data)=>{ loge(`called ptrace( ${request}, ${pid}, ${addr}, ${data} )`) if (request == PT_DENY_ATTACH) return 0 return srcCall(request, pid, addr, data) }, "int", ["int", "int", "pointer", "int"])) }
监控 NSClassFromString
const hook_NSClassFromString = () =>{ logd("hook NSClassFromString") // 从类名获取类 类似java的Class.forName const checkArray = [ "HBPreferences", // 用于以越狱环境下从偏好设置读取配置项 ] // FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName); Interceptor.attach(Module.findExportByName("Foundation","NSClassFromString")!, { onEnter(args) { // Foundation -> -[NSString(NSStringOtherEncodings) UTF8String] this.className = new ObjC.Object(args[0]).UTF8String() // this.className = new ObjC.Object(args[0]).toString() }, onLeave(retval) { if (checkArray.includes(this.className)) { loge(`${retval} <= NSClassFromString( '${this.className}' )`) } else { if (!SLOG) logd(`${retval} <= ${this.className}`) } } }) }
…
GP 2
关于 cydia
中关于越狱检测的实现
GP 3
关于 objection
中关于越狱检测的实现
系统加密算法
隐藏越狱
Dopamine-roothide
roothide • Updated Dec 2, 2024
越狱插件开发(THEOS)
主要还是需要借助frida或者是lldb等等其他的工具分析得到关键函数以后
插件开发只是将想法封装的一个表现形式 fridagadet 的替代方案
简单试用
- REFS
- iOS越狱-Theos的配置与「插件」的简单开发
- theos docs ← 更多更详细的命令需要使用到的时候就去参考官方文档
首先是安装 Theos 环境
在mac系统上一句话的事情 (当然需要先配置好xcode ios开发环境,避免后续奇怪问题)
bash -c "$(curl -fsSL
https://raw.githubusercontent.com/theos/theos/master/bin/install-theos
)"
如果上命令正常执行完成会在用户家目录下看到一个
theos
文件夹需要添加一个环境变量方便后续命令调用
echo "PATH=$PATH:$THEOS/bin" >> ~/.bashrc
在linux上还需要做一些其他的配置 [详见]
[ windows同理,win用的是linux子系统,就还是使用的linux ]
创建工程
$THEOS/bin/
nic.pl
/nic.pl
( 由于前面添加了环境变量,这样写都可以 )
lzy@cywldeMac-mini ~/theos_proj/tt nic.pl NIC 2.0 - New Instance Creator ------------------------------ [1.] iphone/activator_event [2.] iphone/activator_listener [3.] iphone/application [4.] iphone/application_swift [5.] iphone/application_swiftui [6.] iphone/control_center_module-11up [7.] iphone/cydget [8.] iphone/flipswitch_switch [9.] iphone/framework [10.] iphone/library [11.] iphone/notification_center_widget [12.] iphone/notification_center_widget-7up [13.] iphone/null [14.] iphone/preference_bundle [15.] iphone/preference_bundle_swift [16.] iphone/theme [17.] iphone/tool [18.] iphone/tool_swift [19.] iphone/tweak [20.] iphone/tweak_swift [21.] iphone/tweak_with_simple_preferences [22.] iphone/xpc_service [23.] iphone/xpc_service_modern Choose a Template (required): 19 Project Name (required): test Package Name [com.yourcompany.test]: Author/Maintainer Name [cywl]: // 需要对那个包名生效 [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.xxx.xxx // 是否需要重启 SpringBoard [iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: - Instantiating iphone/tweak in test/... Adding 'test' as an aggregate subproject in Theos makefile 'Makefile'. Done.
编写HOOK代码
- 当前目录情况
lzy@cywldeMac-mini ~/theos_proj/tt/test ls -lh total 24 -rw-r--r-- 1 lzy staff 176B Sep 6 11:53 Makefile -rw-r--r-- 1 lzy staff 1.0K Sep 6 11:53 Tweak.x -rw-r--r-- 1 lzy staff 47B Sep 6 11:53 test.plist
- Tweak.x:源码文件 | 即为我们需要编写hook代码的位置
x
后缀 → 使用Objective-C编写xm
后缀 → 使用Objective-C++编写m
后缀 → 使用C编写mm
后缀 → 使用C++编写
编译运行
- make 可以通过修改mk文件来指定编译成品包含的架构
指定为arm64 →
ARCHS:=arm64
lzy@cywldeMac-mini ~/theos_proj/tt/test make ==> Notice: Build may be slow as Theos isn’t using all available CPU cores on this computer. Consider upgrading GNU Make: https://theos.dev/docs/parallel-building ==> Warning: Building for iOS 7.0, but the current toolchain can’t produce arm64e binaries for iOS earlier than 14.0. More information: https://theos.dev/docs/arm64e-deployment > Making all for tweak test… ==> Preprocessing Tweak.x… ==> Compiling Tweak.x (armv7)… ==> Linking tweak test (armv7)… ld: warning: -multiply_defined is obsolete ==> Generating debug symbols for test… warning: no debug symbols in executable (-arch armv7) ==> Preprocessing Tweak.x… ==> Compiling Tweak.x (arm64)… ==> Linking tweak test (arm64)… ld: warning: -multiply_defined is obsolete ==> Generating debug symbols for test… warning: no debug symbols in executable (-arch arm64) ==> Preprocessing Tweak.x… ==> Compiling Tweak.x (arm64e)… ==> Linking tweak test (arm64e)… ld: warning: -multiply_defined is obsolete ==> Generating debug symbols for test… warning: no debug symbols in executable (-arch arm64e) ==> Merging tweak test… ==> Signing test…
- make package 得到一个deb包 scp到手机上安装即可在Cydia中查看到,同时也会针对指定的包名生效
- make install
这样会比较方便的直接编译安装一步到位
需要先指定设备的ssh链接信息,可以在命令行中先执行或者是直接写在mk文件中
export THEOS_DEVICE_IP=localhost ? 192.168.xxx.xxx export THEOS_DEVICE_PORT=22 ? others
- update-theos 更新
脱离xcode编译可执行文件
帮助我们去理解xcode到底帮我们做了什么 是如何编译链接的
- 涉及到objc 最简编译命令
#include <stdio.h> int main(int argc, const char * argv[]) { printf("Hello, World!"); return 0; } clang -fobjc-arc hello.m -o hello
- 稍微多一点东西 (
Foundation
框架 )
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Hello, World!"); } return 0; } clang -fobjc-arc -framework Foundation hello.m -o hello
obj = obj.$super
然后这里再使用非常用的hook框架dobby来举例
- 首先是编译dobby
- macos版没啥好说 (当前平台编译当前平台库) cmake -B build -S . && cmake —build build
- 跨平台编译 ( IOS )
方式一 (直接使用他提供的现成脚本来编译)
python3 scripts/platform_builder.py --platform=iphoneos --arch=all
方式二 (手动命令指定工具链)
# 获取 SDK_PATH 即 OSX_SYSROOT ~ xcrun --sdk iphoneos --show-sdk-path /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.5.sdk # 最短命令 cmake -B build_IOS -S . -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=9.3 -DCMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.5.sdk # 可选命令行参数 -DCMAKE_SYSTEM_PROCESSOR=arm64 -DCMAKE_SYSTEM_NAME=iOS 如果不使用cmake,直接命令行 clang++ -target arm64-apple-ios13.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk -framework Foundation -arch arm64 -ob b.mm 然后就是scp到远程ios设备上了,和linux不同的是ios的可执行程序还需要签名 ldid -e /usr/bin/debugserver > dig ldid -Sdig b ./b // 到此为止才可以正常执行 区分到底是那个平台的可执行文件 platform 2 ios / platform 1 macos ➜ otool -l a | grep platform -A10 platform 1 minos 14.0 sdk 14.0 ntools 1 tool 3 version 1115.7.3 Load command 12 cmd LC_SOURCE_VERSION cmdsize 16 version 0.0 Load command 13 ➜ otool -l b | grep platform -A10 platform 2 minos 13.0 sdk 18.0 ntools 1 tool 3 version 1115.7.3 Load command 11 cmd LC_SOURCE_VERSION cmdsize 16 version 0.0 Load command 12
- 使用xcode引入dobby库
- 在 Build Phases 中 Link Binary With Libraries 中添加依赖库
- 还需要修改一下 Build Settings 中的 Other Linker Flags 添加
-lc++
dobby是c++写的,如果不添加链接属性会导致找不到c++的一些符号,比如 Undefined symbol: operator delete(void*)
IMGS
协议相关 ( 后续… )
- 代理与代理检测
- 密码学基础 工具
- 编解码 Hex / Base64
- 摘要算法 MD5 / SHA / Hmac
- 对称加密算法 DES / AES
- 非对称加密算法 RSA
上述都是标准算法,这里在IOS逆向中可能更加关注点在于 IOS系统帮我们实现的部分
即IOS开发中用到的头文件,以及IOS默认提供给我们的系统链接库 CC_MD5 / CCCrypt
进阶: 使用其他语言来复现 ( js/c/cpp )
!todo 后续使用xcode开发工具写一些demo来实现它并逆向它然后再归纳总结
- 关键代码的定位
- 关键系统库 NSURL / NSString / Base64 …
- frida-trace ( 先触发了在具体改js 打印具体信息 )
- Objection hook OC 方法
frida-trace -U -f com.kldlz.ios.sqhd -i fopen -i getenv -m "
[
canOpenURL:]" -m "
[
writeToFile:atomically:encoding:error:]" -m "
[
fileExistsAtPath*]" -m "
[
countByEnumeratingWithState:objects:count:]" -m "
[
isEqualToString
]" -i NSClassFromString -i objc_getClass
软件源
‣ ← 越狱检测
越狱插件 ‣
软件源
- 雷锋源:apt.abcydia.com
- Frida:build.frida.re
- Ai - 小苹果™ :https://repo.cydiabc.top/
- Alice 源™ :https://apt.coolstar.xyz/
- Sileo 中文源™ :https://apt.nuosike.cn/
- 按键精灵 : http://apt.mobileanjian.com
- Zebra:
https://getzbra.com/repo/
- BigBoss:
http://apt.thebigboss.org/repofiles/cydia/
- Bingner/Elucubratus:
https://apt.bingner.com/
- Limneos Repo:
https://limneos.net/repo/
MERONA Repo:https://repo.co.kr/
- Chariz:
https://repo.chariz.io/
- Havoc:
https://havoc.app/
- jjolano:
https://ios.jjolano.me/
- opa334's Repo:
https://opa334.github.io/
- ichitaso repository:
http://cydia.ichitaso.com/
- 蜗牛源:
https://repo.snailovet.com/
- AutoTouch:
https://repo.autotouch.net/
- Packix:
https://repo.packix.com/
- Ginsu Tweaks:
https://repo.ginsu.dev/
- Liam-apt:
https://liam.page/apt/
/https://liam.page/apt-beta/
- Liam - oldabi:
https://liam.page/oldabi
- Ivano Bilenchi:
https://ib-soft.net/repo/
- subdiox's Repo:
https://subdiox.com/cydia/
- Cydiakk:
https://apt.cydiakk.com/
- CokePokes:
https://www.ios-repo-updates.com/repository/cokepokes/
- Fouadraheb:
https://apt.fouadraheb.com/
- 电话助手作者源:
https://apt.htv123.com/
- 老牌猫源:
https://apt.25mao.com/
- Netskao:
https://repo.initnil.com/
- Procursus:
https://apt.procursus.us/
- Acreson:
https://repo.acreson.cn/
- Lenglengyu:
https://lenglengyu.com/
- SOPPPra:
https://sopppra.mooo.com/
- PoomSmart:
https://poomsmart.github.io/repo/
- alias20:
https://alias20.gitlab.io/apt/
- c1d3r:
https://c1d3r.com/repo/
巨魔商店 ‣
支持的版本在14.0以上
协议相关 ( 后续… )bee 蜜蜂源
- 代理与代理检测
- 密码学基础 工具
- 编解码 Hex / Base64
- 摘要算法 MD5 / SHA / Hmac
- 对称加密算法 DES / AES
- 非对称加密算法 RSA
上述都是标准算法,这里在IOS逆向中可能更加关注点在于 IOS系统帮我们实现的部分
即IOS开发中用到的头文件,以及IOS默认提供给我们的系统链接库 CC_MD5 / CCCrypt
进阶: 使用其他语言来复现 ( js/c/cpp )
!todo 后续使用xcode开发工具写一些demo来实现它并逆向它然后再归纳总结
- 关键代码的定位
- 关键系统库 NSURL / NSString / Base64 …
- frida-trace ( 先触发了在具体改js 打印具体信息 )
- Objection hook OC 方法
- 越狱插件 (类比安卓xp框架模块)
- theos
- Tweak hook / 原理
- logos 语法
- iOS本地化存储的数据保存在沙盒 ‣
- Documents:iTunes会备份该目录。一般用来存储需要持久化的数据。
- Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除。一般存储体积大、不需要备份的非重要数据。
- Library/Preference:iTunes会备份该目录,可以用来存储一些偏好设置。
- tmp:iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。
debugserver 0.0.0.0:1111 -waitfor com.kldlz.ios.sqhd
debugserver的使用
- spawn模式
debugserver -x backboard 0.0.0.0:1111 /var/containers/Bundle/Application/DE74B4EE-AC01-463A-9C41-B828AC4DD334/KLDLZ.app/KLDLZ
- attach模式
debugserver 0.0.0.0:1111 -a ${pid}
然后就是主机端启动lldb(m系列的芯片是arm架构的,但是由于也有罗塞塔转译所以也能运行x86版本的lldb,当然用x86版本的是不太行的,注意环境变量里面的lldb [ which lldb & file path/lldb ] )
iproxy 1111 1111
lldb
process connect connect://localhost:1111
cycript
官方手册 → ‣
- 安装 使用apt 或者是 cydia商店里面安装即可
- ssh登录到手机后,cycript -p xxx 进入交互页面
常用命令
cy# UIApplication.sharedApplication
#"<Calculator.CalcApplication: 0x106407240>"
cy# [[NSBundle mainBundle] bundlePath]
@"/private/var/containers/Bundle/Application/983EBD41-937F-43F7-948B-7DC514304959/Calculator.app"
cy# NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)
@["/var/mobile/Containers/Data/Application/52AE6E1B-CFBA-4E94-BA50-CF2D2B4BAB24/Library"]
cy# UIWindow.keyWindow()
#"<SBCoverSheetWindow: 0x12432bb90; CoverSheet-0x12432bb90-13; baseClass = UIWindow; frame = (0 0; 375 667); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x2829ce550>; layer = <UIWindowLayer: 0x2827b0c60>>"
cy# UIApp.keyWindow.rootViewController
#"<SBHomeScreenViewController: 0x102dd9d90>"
cy# choose(UIViewController)
[#"<Calculator.CalculatorController: 0x10640c4e0>",#"<Calculator.DisplayViewController: 0x10640c9a0>",#"<Calculator.KeypadViewController: 0x10640ce50>",#"<UIApplicationRotationFollowingController: 0x104c08530>",#"<_UIAlertControllerTextFieldViewController: 0x106305400>",#"<UIAlertController: 0x10700a600>"]
cy# NSHomeDirectory()
@"/var/mobile/Containers/Data/Application/52AE6E1B-CFBA-4E94-BA50-CF2D2B4BAB24"
[[UIAlertView alloc] initWithTitle:@"hello word!" message:nil delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil]
#"<UIAlertView: 0x10630aed0; frame = (0 0; 0 0); layer = <CALayer: 0x282eec5e0>>”
cy# [NSProcessInfo processInfo]
#"<NSProcessInfo: 0x282198690>"
cy# [[NSProcessInfo processInfo] environment ]
@{"TMPDIR":"/private/var/mobile/Containers/Data/Application/52AE6E1B-CFBA-4E94-BA50-CF2D2B4BAB24/tmp/","XPC_FLAGS":"0x0","SHELL":"/bin/sh","HOME":"/private/var/mobile/Containers/Data/Application/52AE6E1B-CFBA-4E94-BA50-CF2D2B4BAB24","_MSSafeMode":"0","XPC_SERVICE_NAME":"UIKitApplication:com.apple.calculator[6821][rb-legacy]","PATH":"/usr/bin:/bin:/usr/sbin:/sbin","LOGNAME":"mobile","CFFIXED_USER_HOME":"/private/var/mobile/Containers/Data/Application/52AE6E1B-CFBA-4E94-BA50-CF2D2B4BAB24","USER":"mobile","__CF_USER_TEXT_ENCODING":"0x1F5:0:0"}
- #+地址 把这个地址看作为类
- UIApp.keyWindow.recursiveDescription().toString() 打印层级
- 关于
mjcript
的封装函数
文件位置
/usr/lib/cycript0.9
功能 | 命令 |
获取App的bundleId | MJAppId |
获取App可执行文件路径 | MJAppPath |
获取keyWindow | MJKeyWin() |
获取根控制器 | MJRootVc() |
获取最前面显示的控制器 | MJFrontVc() |
递归打印UIViewController view的层级结构 | MJVcSubviews(MJFrontVc()) |
打印所有对象方法 | MJInstanceMethods(MJFrontVc()) 或 MJInstanceMethods(#0x15f2d3600) |
打印所有对象方法名称 | MJInstanceMethodNames(MJFrontVc()) |
打印所有类方法 | MJClassMethods(MJFrontVc()) |
打印所有类方法名称 | MJClassMethodNames(MJFrontVc()) |
打印所有成员变量 | MJIvars(MJFrontVc()) |
打印所有成员变量名称 | MJIvarNames(MJFrontVc()) |
CG函数封装 | MJPointMake(x,y)MJSizeMake(w,h)MJRectMake(x,y,w,h) |