前言
- 首先:原文链接
- 目的:iOS逆向开发中经常使用的Reveal、SSL Kill Switch 2等工具都是依附于设置而存在的,本文就是做一个这样的应用
- 环境:macOS10.14.6、Xcode11.3
- 适配:iOS11.0-iOS最新
新建
-
nic.pl
新建AppNIC 2.0 - New Instance Creator ------------------------------ ... [7.] iphone/preference_bundle_modern ... Choose a Template (required): 7 Project Name (required): Test Package Name [com.yourcompany.test]: com.test.www Author/Maintainer Name [drag]: xxoo [iphone/preference_bundle_modern] Class name prefix (three or more characters unique to this project) [XXX]: Instantiating iphone/preference_bundle_modern in test/... Done.
- 创建后,目录结构如下图
# drag @ dragdeMacBook-Pro in ~/Desktop/OCTest/test [18:01:00] $ tree . ├── Makefile ├── Resources │ ├── Info.plist │ └── Root.plist ├── XXXRootListController.h ├── XXXRootListController.m └── entry.plist 1 directory, 6 files
- 更改Makefile配置
- 目前支持的架构有armv7 armv7s arm64 arm64e等
- 添加
ARCHS = armv7 armv7s arm64
表示支持 armv7 armv7s arm64 三种处理器架构 - 对于最新的A12处理器(iPhoneX以后)的设备,需要添加arm64e,否则生成的dylib文件不能正常加载
- 本文只适配64位架构设备,设置如下:
ARCHS = arm64 arm64e
- 添加
- 指定目标规范:
TARGET = iPhone:BaseSDK:DeploymentTarget
-
BaseSDK
代表编译用的SDK版本 -
Deployment Target
是最低兼容的系统版本, - 本文初始设置
TARGET = iPhone:latest:11.0
-
- 目前支持的架构有armv7 armv7s arm64 arm64e等
编译
-
make
编译出现找不到私有库Preferences.framework的错误,Preferences.framework包含在PrivateFrameworks里面,本文采取的是下载整个sdk$ make > Making all for bundle Test… ==> Copying resource directories into the bundle wrapper… ==> Compiling XXXRootListController.m (arm64)… ==> Linking bundle Test (arm64)… ld: warning: directory not found for option '-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/PrivateFrameworks' ld: framework not found Preferences clang: error: linker command failed with exit code 1 (use -v to see invocation) make[3]: *** [/Users/drag/Desktop/OCTest/test/.theos/obj/debug/arm64/Test.bundle/Test] Error 1 make[2]: *** [/Users/drag/Desktop/OCTest/test/.theos/obj/debug/arm64/Test.bundle/Test] Error 2 make[1]: *** [internal-bundle-all_] Error 2 make: *** [Test.all.bundle.variables] Error 2
- 从
https://github.com/xybp888/iOS-SDKs
下载iPhoneOS13.0.sdk
放入theos/sdks。- 因为本人Xcode版本是11.3,所以下载iPhoneOS13.0.sdk,可以根据自己的Xcode版本选择合适的sdk
- 如果从github下载过慢,可以通过gitee中转一下。
- Makefile里面更改
TARGET = iPhone:13.0:11.0
- 下载的sdk版本为13.0,BaseSDK是填入13.0
- 适配版本11.0,DeploymentTarget填入11.0
-
make
编译通过$ make > Making all for bundle Test… ==> Copying resource directories into the bundle wrapper… ==> Compiling XXXRootListController.m (arm64)… ==> Linking bundle Test (arm64)… ==> Generating debug symbols for Test… ==> Compiling XXXRootListController.m (arm64e)… ==> Linking bundle Test (arm64e)… ==> Generating debug symbols for Test… ==> Merging bundle Test… ==> Signing Test…
打包
-
make package
打包报错,提示缺少control文件$ make package > Making all for bundle Test… ==> Copying resource directories into the bundle wrapper… make[2]: Nothing to be done for `internal-bundle-compile'. > Making stage for bundle Test… ==> Error: /Applications/Xcode.app/Contents/Developer/usr/bin/make package requires you to have a layout/ directory in the project root, containing the basic package structure, or a control file in the project root describing the package. make: *** [internal-package] Error 1
- 新建control文件,填入如下内容
Package: com.test.www Architecture: iphoneos-arm Version: 0.0.1
- 再次
make package
,成功,生成deb包$ make package > Making all for bundle Test… ==> Copying resource directories into the bundle wrapper… make[2]: Nothing to be done for `internal-bundle-compile'. > Making stage for bundle Test… dm.pl: building package `com.test.www:iphoneos-arm' in `./packages/com.test.www_-2+debug_iphoneos-arm.deb
安装
- 执行
make install
进行安装,安装成功后可以去设置里面查看有没有Test,- 如果没有发现,是因为没有安装PreferenceLoader,需要在Cydia中安装PreferenceLoader
- 如果发现了,那是因为其它插件对PreferenceLoader有依赖,安装插件的时候就顺带安装了
- PreferenceLoader
- 是一个开源的基础依赖包,越狱插件的系统设置菜单是由它提供的,最好在
iphone/preference_bundle_modern
开发前进行安装 - 进入系统设置时,PreferenceLoader会从/Library/PreferenceLoader/Preferences/目录下解析符合规则的plist文件,并生成相应的控件动态添加到系统设置中
- 是一个开源的基础依赖包,越狱插件的系统设置菜单是由它提供的,最好在
实战一:构造SSL Kill Switch 2样式的App
最终效果图
构造过程
- 打开上面创建的项目,找到entry.plist文件。entry.plist描述了设置菜单项看到的内容,更改后的内容如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>entry</key> <dict> <key>bundle</key> <string>Test</string> <key>cell</key> <string>PSLinkCell</string> <key>detail</key> <string>XXXRootListController</string> <key>icon</key> <string>test.png</string> <key>isController</key> <true/> <key>label</key> <string>Test</string> </dict> </dict> </plist>
- cell:cell类型,这里是PSLinkCell类型,表示是一个可以点击的控件,更多控件类型参考
- detail:XXXRootListController,表示点击后跳转到此控制器
- icon:该cell前面显示的图标,图标test.png需要存储在Resources文件夹下面。
- label:该cell前面显示的文字
- 当执行
make install
安装后,theos会做两件事情- 在/Library/PreferenceLoader/Preferences/文件夹下新建和项目同名的plist文件,比如Test.plist
- 把entry.plist内容拷贝一份存储到Test.plist,进入设置时会读取该plist文件,生成相应的cell
- Resources文件夹:存放项目需要的资源文件,比如图片,比如构建控制器需要的plist文件
- Root.plist描述XXXRootListController的视图结构,更改里面相应的值,可以更改控制里面看到的内容
- 目前项目只有一个控制器,如果我们需要定义另外的控制器CustomListController,那么就需要新建一个custom.plist文件描述此控制器的内容。
- test.png,设置菜单项看到的图片资源
- 打开XXXRootListController控制器后发现其内容是由Root.plist构建。
- (NSArray *)specifiers { if (!_specifiers) { _specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self]; } return _specifiers; }
- 更改
Resources/Root.plist
内容如下:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>items</key> <array> <dict> <key>cell</key> <string>PSGroupCell</string> <key>label</key> <string>禁用后可以绕过https验证。</string> <key>footerText</key> <string>温馨提示:违权必究</string> </dict> <dict> <key>cell</key> <string>PSSwitchCell</string> <key>defaults</key> <string>com.test.www</string> <key>key</key> <string>isDisCerVarify</string> <key>label</key> <string>禁用证书验证</string> <key>default</key> <false/> </dict> <dict> <key>cell</key> <string>PSEditTextCell</string> <key>defaults</key> <string>com.test.www</string> <key>key</key> <string>excludedIDs</string> <key>label</key> <string>排除BundleIDs:</string> </dict> </array> <key>title</key> <string>Test</string> </dict> </plist>
- defaults 用户操作控件得到的值存储到/var/mobile/Library/Preferences/com.test.www.plist文件中
- key 在defaults中用于关联该cell的键值
- default 表示存储在
com.test.www.plist
中的默认值
- 整个构建过程到这里就结束了,执行
make && make package && make install
后,重新打开设置App就会看到如上图所示的内容
与tweak相互
- 上面只是做了UI方面的操作,没有实际的功能是没有任何用的。要想发挥作用,必须与tweak进行交互。
- 新建tweak,BundleID填写
com.apple.UIKit
表示可以拦截任何App,也就是所有App启动都会走Tweak.x中的%ctor {}
初始化函数$ nic.pl NIC 2.0 - New Instance Creator ------------------------------ ... [9.] iphone/tool_swift [10.] iphone/tweak [11.] iphone/xpc_service ... Choose a Template (required): 10 Project Name (required): Tweak Package Name [com.yourcompany.tweak]: com.tweak.www Author/Maintainer Name [drag]: [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.apple.UIKit [iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: Instantiating iphone/tweak in tweak/... Done.
- Makefile文件增加以下内容
TARGET = iPhone:13.0:11.0 ARCHS = arm64 arm64e
- Tweak.x里面hook相应的方法来实现禁用证书的功能,核心代码里面调用shouldHookFromPreference()来判断某个App是否需要hook
static BOOL shouldHookFromPreference() { BOOL shouldHook = NO; NSMutableDictionary *plist = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.test.www.plist"]; if (!plist) { NSLog(@"Preference file not found."); return shouldHook; } shouldHook = [[plist objectForKey:@"isDisCerVarify"] boolValue]; // Root.plist里面填写的key // 查看过滤的bundleid NSString *excludedBundleIdsString = [plist objectForKey:@"excludedIDs"]; // Root.plist里面填写的key excludedBundleIdsString = [excludedBundleIdsString stringByReplacingOccurrencesOfString:@" " withString:@""]; NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; bundleId = [bundleId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSArray *excludedBundleIds = [excludedBundleIdsString componentsSeparatedByString:@","]; if ([excludedBundleIds containsObject:bundleId]) { NSLog(@"Not hooking excluded bundle: %@", bundleId); shouldHook = NO; } return shouldHook; }
- Test项目和Tweak项目目前是分离的,都会生成相应的deb文件。难道实让用户安装两个deb包,这个用户体验好像不太好吧。有没有更简单的方式,答案是有的。
- Tweak项目根目录新建layout文件夹
- 进入Test项目
.theos/_/
,找到Library
文件夹,拷贝到layout文件夹下面。 - Cydia里面卸载上面安装的Test App。进入Tweak项目,执行
make && make package && make install
进行安装,安装完成就可以在设置里面看到Test App的选项
- 现在只需要进入Tweak项目,把packages文件夹下的deb安装包发送给其他人就可以了,方便又快捷
实战二:构造Reveal样式的App
最终效果图
构造与交互
- 大部分和实战一是相同的,这里只写出不同的地方。
- 如上图所示,获取了应用列表,这个功能是AppList插件提供的,所以在开发前要在Cydia里面搜索安装AppList。
- 更改
Resources/Root.plist
内容如下:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>items</key> <array> <dict> <key>cell</key> <string>PSGroupCell</string> <key>label</key> <string>选择需要启用的App。</string> <key>footerText</key> <string>温馨提示:违权必究</string> </dict> <dict> <key>bundle</key> <string>AppList</string> <key>isController</key> <string>1</string> <key>cell</key> <string>PSLinkCell</string> <key>label</key> <string>App列表</string> <key>ALNavigationTitle</key> <string>选择需要注入的应用</string> <key>ALSettingsPath</key> <string>/var/mobile/Library/Preferences/com.test.www.plist</string> <key>ALSettingsKeyPrefix</key> <string>TestAppEnabled-</string> <key>ALSettingsDefaultValue</key> <string></string> <key>ALSectionDescriptors</key> <array> <dict> <key>title</key> <string>用户App列表</string> <key>predicate</key> <string>isSystemApplication = FALSE</string> <key>icon-size</key> <string>29</string> <key>suppress-hidden-apps</key> <string>1</string> <key>cell-class-name</key> <string>ALSwitchCell</string> </dict> <dict> <key>title</key> <string>系统App列表</string> <key>predicate</key> <string>isSystemApplication = TRUE</string> <key>icon-size</key> <string>29</string> <key>suppress-hidden-apps</key> <string>1</string> <key>cell-class-name</key> <string>ALSwitchCell</string> </dict> </array> </dict> </array> <key>title</key> <string>Test</string> </dict> </plist
- ALNavigationTitle 导航栏标题
- ALSettingsPath 存储文件的路径,文件用来存储自己设置的值
- ALSettingsKeyPrefix key的前缀信息
- ALSectionDescriptors 各个部分信息的描述,本例中分成两个部分:用户App列表和系统App列表
- 当点击App列表里面的开关时,会动态更改
com.test.www.plist
文件里面对应的键值。进行交互时,读取相应的键值,做出自己的逻辑判断即可。 - 与Tweak进行交互
%ctor { NSString *identifier = [[NSBundle mainBundle] bundleIdentifier]; NSMutableDictionary *plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.test.www.plist"]; // 是否在用户APP列表点击开启了 if ([[plistDict objectForKey:[@"TestAppEnabled-" stringByAppendingString:identifier]] boolValue]) { } }
扩展
某个cell的点击操作
- plist里面:cell为PSButtonCell表示可以点击,action表示执行的动作
<dict> <key>cell</key> <string>PSButtonCell</string> <key>label</key> <string>我也要点击</string> <key>icon</key> <string>cydia.png</string> <key>action</key> <string>btnClicked</string> </dict>
- 控制器里面直接写上相应的方法
- (void)btnClicked { NSLog(@"我被打印了"); }
自定义cell
- .h头文件里面导入PSSpecifier.h,继承PSTableCell
#import <UIKit/UIKit.h> #import <Preferences/PSSpecifier.h> @interface CustomCell: PSTableCell @end
- .m实现文件里面
#import "CustomCell.h" @implementation CustomCell -(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(id)identifier specifier:(PSSpecifier *)specifier { //init method self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier specifier:specifier]; //call the super init method if (self) { // specifier就是模型数据 } return self; } @end
- 控制器里面通过操作PSSpecifier来更新Cell界面的值。