iOS 越狱的Tweak开发
iOS 越狱的Tweak开发
原文链接在我的博客 https://yohunl.com/ios-yue-yu-de-tweakkai-fa/ 上,如果有更新,以博客为准
iOS越狱开发中,各种破解补丁的统称为Tweak,通常意义上我们说的越狱开发,都是指开发一个Tweak.
基本上,tweak都依赖于一个名叫cydia Substrate (以前名字也叫mobile Substrate)的动态库,Mobile Substrate是Cydia的作者Jay Freeman (@saurik)的作品,也叫Cydia Substrate,它的主要功能是hook某个App,修改代码比如替换其中方法的实现,Cydia上的tweak都是基于Mobile Substrate实现的.
iOS的tweak开发可以有两种发布方式
- 只能在越狱设备上安装的打包成deb格式的安装包
- 直接使用开发者自己的证书/企业证书直接将补丁打包成ipa,这样不需要越狱也是可以安装的,只是这种非越狱的限制比较大,通常只是用来给某个app打个补丁或者类似的功能啥的
tweak是啥?
tweak的实质就是ios平台的动态库。IOS平台上有两种形势的动态库,dylib与framework。Framework这种开发者用的比较多,而dylib这种就相对比较少一点,比如libsqlite.dylib,libz.dylib等。而tweak用的正是dylib这种形势的动态库。我们可以在设备的/Library/MobileSubstrate/DynamicLibraries目录下查看手机上存在着的所有tweak。这个目录下除dylib外还存在着plist与bundle两种格式的文件,plist文件是用来标识该tweak的作用范围,而bundle是tweak所用到的资源文件
开发tweak最常用的theos环境安装
TheOS被设计为一个在基于Unix平台开发IOS程序的集成开发环境,它给我们准备好了一些代码模板、预置一些基本的Makefile脚本,这样我们开发一个tweak就会变得方便的多,这里我们不说简单的多,是因为其配置还是稍显繁琐的,并且,也不能像我们通常开发xcode工程那样方便的调试.
theos的开发者是大神DHowett,不过大从2015年开始,原作者不再更新,交给社区维护了,新的地址是https://github.com/theos/theos.
网上的多数教程都是基于原作者两年前的原始theos来配置环境的,原始的配置环境的方式相对来说比较繁琐.
这里,我们介绍最新的theos环境配置
最新环境的地址是 https://github.com/theos/theos
其实只要将git地址的内容下载下来,然后放到一个目录下就可以了,网上的教程都是放到/opt/theos
1.是将theos环境下载下来,theos是放在github上的,使用git命令来clone比较方便,虽然可以放在任何目录下,但是官方建议大家放在 /opt/下
打开 终端
输入 export THEOS=/opt/theos #这个是建立一个环境变量方便后面的操作(引用这个环境变量是用$THEOS)
这种建立环境变量的方式,只是在当前终端中起作用了,关闭终端后又得重新设置,为了避免每次都建立这个环境变量,我们可以建立一个永久的环境变量
编辑~/.profile文件,在其中添加export THEOS=/opt/theos/,这个环境变量就是永久的了.
ps:怎么查看定义了哪些环境变量呢? 终端中输入命令env!
git clone --recursive https://github.com/theos/theos.git $THEOS
这个目录和终端变量$THEOS只是方便我们操作,其实你也可以放在任何你想放置的目录.
新版的theos下载下来后,其内部已经内置了cydia framework和iOS的一系列私有的头文件等,不需要像以前版本那样,自己从手机上或者其它地方拷贝cydia的lib来了,其放置的目录是vendor/lib和vendor/include
,新版的已经是内置CydiaSubstrate.framework,不是网上其它教程中说的需要运行bootstrap.sh脚本或者是从手机上拷贝等方式.
备注:最新版的已经没有这个bootstrap.sh脚本文件了.2016.08.15,并且已经集成了最新的cydia Substrate,在目录Vendor/lib下
这里的cydia使用的是framework模式了
看到官方的git中有注释
[common] Move vendored includes and libraries to a vendor/ subdir.
旧版的中
首先运行Theos的自动化配置脚本:
~~ sudo /opt/theos/bin/bootstrap.sh substrate ~~
由于Theos存在一个bug,所以无法自动生成一个有效的libsubstrate.dylib文件,需要手动添加,需>> 要再Cydia中搜索安装CydiaSubstrate,并且拷贝到电脑中,重命名为libsubstrate.dylib后放 到/opt/theos/lib/中
2.配置用来签名的ldid,如果不安装,那么产生的deb文件就安装不到手机上,
用来专门签名iOS可执行文件的工具,用以在越狱iOS中取代Xcode自带的codesign.
安装这个ldid,推荐的方式是采用brew来安装-- brew install ldid
从http://joedj.net/ldid下载ldid,放到/opt/theos/bin/下,然后用命令chmod 777 /opt/theos/bin/ldid 来提升它的权限
看到另一篇文章(http://www.kanxue.com/bbs/showthread.php?p=1303343)说的可以在 git clone git://git.saurik.com/ldid.git 下载编译ldid
完成以上操作会在ldid目录下生产一个mac 可执行程序 ldid
3.配置dpkg-deb
新版的theos,其没有内置 dpkg-deb,需要你用brew来安装dpkg (brew install dpkg)
brew查看安装了哪些工具的命令是 brew list
如果你没安装,那么可能会收到如下警告
SZ-lingdaiping:FLEXLoader-master yohunl$ make package
==> Error: /Applications/Xcode.app/Contents/Developer/usr/bin/make package requires dpkg-deb.
make: *** [internal-package-check] Error 1
deb是越狱开发包的标准格式,dpkg-deb是个用于操作deb文件的工具,有了这个工具,Theos才能正确的把工程打包成deb文件,旧版的
从https://github.com/DHowett/dm.pl 下载dm.pl文件(其实新版的theos的bin目录下包含了这个文件了),将其重命名为dpkg-deb后,放到/opt/theos/bin/目录下,chmod 777 /opt/theos/bin/dpkg-deb 来提升它的权限,再拷贝到theos/bin下了!!
4.配置Theos NIC templates (可选)
目前最新版的已经内置了所有模板了
tweak的demo简介
下面我们来创建一个默认的demo来简单说明一下
打开一个终端,
$THEOS/bin/nic.pl
可以看到
NIC 2.0 - New Instance Creator
------------------------------
[1.] iphone/activator_event
[2.] iphone/application_modern
[3.] iphone/cydget
[4.] iphone/flipswitch_switch
[5.] iphone/framework
[6.] iphone/ios7_notification_center_widget
[7.] iphone/library
[8.] iphone/notification_center_widget
[9.] iphone/preference_bundle_modern
[10.] iphone/tool
[11.] iphone/tweak
[12.] iphone/xpc_service
Choose a Template (required):
通常我们建立的都是tweak,所以选11(可能你的不是11)
接下来,输入工程的名称
Project Name (required): yohunlTemp
再下来是输入package的名字,这里可以回车,采用默认值,这里的默认是是com.yourcompany.project Name
Package Name [com.yourcompany.flextemp]:
再下来是输入作者名,默认值是你的电脑的用户名
Author/Maintainer Name [yohunl]:
再下来,是输入tweak可以作用的对象的bundle identifier
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]:
这里要说明一下了,这里的com.apple.springboard是iOS的桌面app,如果我们的tweak是想作用于所有的app呢?那么这里应该填 com.apple.UIKit,这一步填入的内容是对应于建立后的一个名字为 工程名.plist的配置文件,这个文件的内容大概如以下这样
{ Filter = { Bundles = ( "com.apple.springboard" ); }; }
当然了,这里可以更进一步的控制,具体可以去网上搜索
接下来,是输入安装完成后,需要重启的应用
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]:
建立后的工程目录如下
共有4个文件,其中
control文件中
Package: com.yourcompany.flextemp
Name: flexTemp
Depends: mobilesubstrate
Version: 0.0.1
Architecture: iphoneos-arm
Description: An awesome MobileSubstrate tweak!
Maintainer: yohunl
Author: yohunl
Section: Tweaks
是工程的一些常用的配置
flexTemp.plist文件,就是我们建立工程中输入的[iphone/tweak] MobileSubstrate Bundle filter
{ Filter = { Bundles = ( "com.apple.springboard" ); }; }
makefile文件
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = flexTemp
flexTemp_FILES = Tweak.xm
include $(THEOS_MAKE_PATH)/tweak.mk
after-install::
install.exec "killall -9 SpringBoard"
这里有$(THEOS),这个变量,这也是我们上面用export建立的,如果你没建立,新版的你要自己修改这里了
include $(THEOS)/makefiles/common.mk
这个都要添加的,因为很多关键的变量等,都在common.mk中,不包含这个,很多东西都用不了.例如编译过程中很多的变量的定义都在其中
THEOS_MAKE_PATH := $(THEOS)/makefiles
THEOS_BIN_PATH := $(THEOS)/bin
THEOS_LIBRARY_PATH := $(THEOS)/lib
THEOS_INCLUDE_PATH := $(THEOS)/include
THEOS_MODULE_PATH := $(THEOS)/mod
flexTemp_FILES = Tweak.xm 是我们要包含的需要被编译的文件,格式就是 工程名_FILES = 要编译的文件名
Tweak.xm文件
%hook ClassName
// Hooking a class method
+ (id)sharedInstance {
return %orig;
}
// Hooking an instance method with an argument.
- (void)messageName:(int)argument {
%log; // Write a message about this call, including its class, name and arguments, to the system log.
%orig; // Call through to the original function with its original arguments.
%orig(nil); // Call through to the original function with a custom argument.
// If you use %orig(), you MUST supply all arguments (except for self and _cmd, the automatically generated ones.)
}
// Hooking an instance method with no arguments.
- (id)noArguments {
%log;
id awesome = %orig;
[awesome doSomethingElse];
return awesome;
}
// Always make sure you clean up after yourself; Not doing so could have grave consequences!
%end
*/
这个文件中,就是我们tweak的核心代码,其中的%hook,%orig,%log等都是theos对cydia Substrate提供的函数的宏封装,cydia Substrate提供的方法的介绍可以参考Cydia_Substrate
cydia Substrate提供了三方最重要的方法
IMP MSHookMessage(Class class, SEL selector, IMP replacement, const char* prefix); > // prefix should be NULL.
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP *result);
void MSHookFunction(void* function, void* replacement, void** p_original);
这三个都是用来进行hook操作的,也就是我们在非越狱开发中常说的swizzle!
cydia Substrate还提供了MobileLoader:“钩子”需要在运行时被加载,靠的就是MobileLoader的功劳。MobileLoader会在适当的时机加载/Library/MobileSubstrate/DynamicLibraries/目录下的动态库(.dylib,这是tweak的最终产品)
MobileLoader能够在运行时候加载dylib的核心是利用
** attribute((constructor))的方法会在main函数执行之前执行!!!!!!**
...
// The attribute forces this function to be called on load.
__attribute__((constructor))
static void initialize() {
NSLog(@"MyExt: Loaded");
MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
}
Tweak.xm的文件名后缀x代表这个文件支持Logos语法,如果只有一个x代表源文件支持Logos和C语法;如果是xm,说明源文件支持Logos和C/C++语法。其中的%hook,%orig,%log等都是Logos语法支持的内容,详细语法说明请参考http://iphonedevwiki.net/index.php/Logos (不要被这个语法吓着了,Logos作为Theos开发组件的一部分,通过一组特殊的预处理指令,可以让编写函数钩子(hook)代码变得非常简单和清晰,Logos是随着Theos发布的,你能够在用Theos创建的项目中直接使用Logos的语法,如果不是Theos创建的工程,则使用不了哦)
到此为止,我们的demo已经建立起来了
如何编译和安装呢?
命令 make package就是编译deb安装包的
执行后,目录下会多出来几个文件和文件夹
2016-08-01_10-25-53.jpg
其中packages文件夹下保留的是每一次编译成功产生的deb安装包文件
还有个隐藏的目录 .theos
其内容如下
2016-08-01_10-50-29.jpg
其中的_文件夹下面
DEBIAN文件夹下面是deb安装到手机上后的控制文件信息,这个文件就是我们建立工程时候生成的那个control文件
其他的目录和文件都是安装后对应到手机上的真实文件,在这里显示出来,是为了方便用开发者查看,安装后在手机系统中哪些目录生成了哪些文件
怎么安装deb包和卸载deb包?
上面我们生成了能够用于安装到手机上的deb安装包了,怎么安装到手机上呢?
大体上可以有两种方法
方法一:
图形方式,使用iTools等工具将这个deb包拷贝到手机中,利用iFile浏览到此文件,进行安装
方法二:
需要使用到openssh服务,确保你手机上已经安装了该服务(cydia中搜索安装)
要安装 OpenSSH 首先需要将设备越狱。越狱完成之后,就可以在 Cydia 中直接查找和安装 OpenSSH。安装完成之后就可以通过下面的步骤来将你的 Mac 连接到 iOS 设备。
- 首先得保证你的 iOS 设备和 Mac 在同一局域网的同一网段中。
- 打开终端,输入 ssh root@192.168.xxx.xxx
- 输入 iOS 设备密码,默认 alpine(强烈建议修改此默认密码,否则任何人都可以通过ssh连接到你手机上,然后获取你的信息)
- 等待连接,稍后,您就连接到您的iPhone、iPad上,可以执行 Unix 命令了。
- 还可以使用 Transmit 等软件来管理 iOS 设备的文件系统,非常方便。
在编译用的makefile文件最上面
添加
THEOS_DEVICE_IP = 你的手机的IP地址
然后使用 make package install命令,可以一次性完成编译,打包,安装一条龙服务,这个过程中可能需要要你两次输入ssh的root密码((一次是签名打包,一次是安装)).
这样还是稍显繁琐,每一次修改后,编译运行,都得输入两此手机的root密码,如果你连这两次都懒得输入,也是有办法的
brew安装openssh(brew install openssl)和 ssh-copy-id(brew install ssh-copy-id)
执行命令
ssh-keygen -t rsa -b 2048
按提示输入存放keygen存放的目录(最好是自己输入存放的目录的文件,而不是采用默认的,以防万一覆盖了其它的ssh)
再执行命令
ssh-copy-id root@<iP Address of your device>
然后,就可以不用密码安装了! 节约了两次密码的输入(一次是签名打包,一次是安装)
说完了安装,那么我们怎么卸载一个安装的deb包呢?
方式一: cydia可删除它,安装的deb包都在cydia的已安装列表中有显示
IMG_4714.PNG
方式二
如果手机上安装了dpkg(越狱手机上,一般都是安装了的,名字叫 Debian Package),那么将一个deb文件拷贝到手机里,就可以在手机中的终端(可以cydia安装MTerminal,MTerminal是一款越狱手机上的命令行终端环境)中执行dpkg -i com.daapps.FLEXInjected_0.0.1-1-7_iphoneos-arm.deb 来安装一个deb,(当然你也可以使用cydia来自动安装,或者pp助手等,也可以),安装完要重启springboard,使用命令 killall -HUP SpringBoard
卸载一个软件 dpkg -r com.daapps.FLEXInjected
**例如 dpkg -r com.yourcompany.testmywtweak **
(我们在theos环境打包出来的deb名字可能是com.yourcompany.testmywtweak_0.0.1-1_iphoneos-arm.deb等,但是最后在系统里的tweak的名字是com.yourcompany.testmywtweak )!
顺便提一句
若是只是想修改其它的deb包的某个文件,该怎么弄呢?
deb包的解读
,其实它就是一个压缩文件而已…你可以使用rar等解压缩工具解压缩,但是这样会丢失原有的文件的权限信息!
一个 deb 安装包由两部分组成,一个是安装控制/识别信息,另外一个就是实际的程序文件。
需要修改现有的deb包,那么第一步就是解包。
假设deb的文件名是abc.deb,解包命令为:
dpkg-deb -x abc.deb tmp #将abc.deb的程序文件解包到tmp文件夹
dpkg-deb -e abc.deb tmp/DEBIAN #将abc.deb的安装控制/识别信息解包到DEBIAN文件夹
注:安装控制/识别信息必须在当前程序文件文件夹中的DEBIAN文件夹中,必须大写。
进入DEBIAN目录,可以看到有一个control文件,无后缀名,这个文件就是用来记录deb的安装信息。
另外,postinst,preinst,prerm,postrm,extrainst_这些脚本文件,不是必须存在的,当安装包需要使用到脚本的时候才会用到的。脚本在后面的章节会详细讲到的,这一节我们先不管。
接下来介绍的是打包命令:
假设将需要打包的文件放在tmp文件夹中,DEBIAN文件夹也要在放在这个文件夹中,然后输入命令:
chmod -R 0755 tmp/DEBIAN #首先设置权限,如果没有包含脚本可以不需要设置权限
dpkg-deb -b tmp 1.deb #打包成一个叫做1.deb的包
如此这般,就完成了修改某个已存在的deb包了
OK,目前为止,我们已经介绍完了基础知识
下面让我们用一个稍微复杂一些的例子来演示一下 如何让Flex可以嵌入到所有的APP中
flex的动态嵌入
flex的简介
https://github.com/Flipboard/FLEX, FLEX是Flipboard开源的一款方便的应用内调试工具,开发者可在toolbar中查看和修改运行中的应用.
它提供了功能:
- 可以在层级中检测和调整视图,可查看每个对象上的属性和变量;
- 动态调整任何属性和变量;
- 动态调用实例和类方法;
- 通过扫描 heap访问任何活跃的对象;
- 在app的sandbox中查看文件系统;
- 探究应用中所有类和系统框架(公开的和私有的);
- 快速访问有用的对象(比如[UIApplication sharedApplication])、app委托以及关键窗口的根视图控制器等;
- 动态查看和调整 NSUserDefaults 值
- 显示所有的NSLog信息
- 显示所有的网络包等等
它是一个开源的框架集合,在我们自己的工程中,当然可以添加源码就可以用起来了,那么如果我们能够在其他的app中也嵌入它,那我们岂不是可以直接学习到其他app的UI布局等等,想想是不是就很激动呢?
官网截图
687474703a2f2f656e67696e656572696e672e666c6970626f6172642e636f6d2f6173736574732f666c65782f666c65782d726561646d652d726576657273652d312e706e67.png
看到没,可以用来分析系统的电话界面
额.....但是.....官网并没有告诉我们怎么做到这点,说是留给我们自己一个小练习....呵呵,这个练习可不小呀..
动态嵌入flex
首先,我们应该有一个可以选取所有的app的列表,通过这个列表,我们可以决定哪个应用可以嵌入flex.可喜的是,这个工作已经有人替我们完成了
APPlist 是个辅助获取已安装程序列表的插件,利用PreferenceLoader在设置中增加一个App列表,并可以供用户设置
preferenceloader是手机越狱必备的软件,如果少了preferenceloader,你的iphone.ipad.越狱后很多插件都会无法使用。preferenceloader是很多插件的依赖,例如非常出名的Activator需要它才能正常工作
https://github.com/DHowett/preferenceloader
说的再简单一点,就是我们可以利用preferenceloader和applist,很方便的在系统给的设置那里提供一个可以选择app的列表
IMG_4715.PNG
需要在设备的/Library/PreferenceLoader/Preferences下放置一个指定格式的plist文件和两个图标文件(图标文件是为了在系统的设置中显示)
怎么这个文件放在theos工程的哪里,最后安装后,才能在设备的/Library/PreferenceLoader/Preferences目录下呢?
Theos已经帮我们想到这点了,在Theos建立的工程的根目录下建立一个 layout文件夹,这个文件夹就相当于设备的根目录了!在编译生成的deb包中,会自动放到对应的文件夹
在工程的目录下创建 layout文件夹 这里的layout相当于iOS的文件系统的根目录
在其中放入一个控制系统设置项的plist文件FLEXInjected.plist
entry = {
bundle = AppList;
cell = PSLinkCell;
icon = "/Library/PreferenceLoader/Preferences/FLEXInjected.png";
isController = 1;
label = FLEXInjected;
ALSettingsPath = "/var/mobile/Library/Preferences/com.yourcompany.flexinjected.plist";
ALSettingsKeyPrefix = "FLEXInjectedEnabled-";
ALChangeNotification = "com.yourcompany.flexinjected.settingschanged";
ALSettingsDefaultValue = 0;
ALSectionDescriptors = (
{
title = "User Applications";
predicate = "(isSystemApplication = FALSE)";
"cell-class-name" = "ALSwitchCell";
"icon-size" = 29;
"suppress-hidden-apps" = 1;
},
{
title = "System Applications";
predicate = "(isSystemApplication = TRUE)";
"cell-class-name" = "ALSwitchCell";
"icon-size" = 29;
"suppress-hidden-apps" = 1;
"footer-title" = "© yohunl create for demo";
}
);
};
下载Flex的工程,编译产生FLEX.framework这个动态库,将其放入
2016-08-01_13-32-02.jpg
打开Tweak.xm文件
添加如下代码
/* How to Hook with Logos
Hooks are written with syntax similar to that of an Objective-C @implementation.
You don't need to #include <substrate.h>, it will be done automatically, as will
the generation of a class list and an automatic constructor.
*/
#include <dlfcn.h>
@interface MyDKFLEXLoader : NSObject
@end
@implementation MyDKFLEXLoader
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static MyDKFLEXLoader *_sharedInstance;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (void)show
{
// [[FLEXManager sharedManager] showExplorer];
Class FLEXManager = NSClassFromString(@"FLEXManager");
id sharedManager = [FLEXManager performSelector:@selector(sharedManager)];
[sharedManager performSelector:@selector(showExplorer)];
}
@end
%ctor {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.yourcompany.flexinjected.plist"] ;
NSString *libraryPath = @"/Library/Application Support/FLEXLoader/FLEX.framework/FLEX";
NSString *keyPath = [NSString stringWithFormat:@"FLEXInjectedEnabled-%@", [[NSBundle mainBundle] bundleIdentifier]];
NSLog(@"SSFLEXLoader before loaded %@,keyPath = %@,prefs = %@", libraryPath,keyPath,prefs);
if ([[prefs objectForKey:keyPath] boolValue]) {
if ([[NSFileManager defaultManager] fileExistsAtPath:libraryPath]){
void *haldel = dlopen([libraryPath UTF8String], RTLD_NOW);
if (haldel == NULL) {
char *error = dlerror();
NSLog(@"dlopen error: %s", error);
} else {
NSLog(@"dlopen load framework success.");
[[NSNotificationCenter defaultCenter] addObserver:[MyDKFLEXLoader sharedInstance]
selector:@selector(show)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
NSLog(@"SSFLEXLoader loaded %@", libraryPath);
} else {
NSLog(@"SSFLEXLoader file not exists %@", libraryPath);
}
}
else {
NSLog(@"SSFLEXLoader not enabled %@", libraryPath);
}
NSLog(@"SSFLEXLoader after loaded %@", libraryPath);
[pool drain];
}
简单的代码说明
NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.yourcompany.flexinjected.plist"] .这个文件是我们在上面的plist中定义的用来存放用户选择的app列表的.通过读取它我们就可以知道用户选中了哪个app.
然后使用dlopen动态的打开framework,注入到响应的app进程中去
详细的代码我已经放到github上去了,地址是 https://github.com/yohunl/FlexInjected
编译的命令是 make
打包成deb安装包的命令是 make package
编译,打包,安装一条龙的命令是 make package isntall,当然了,你需要先修改Makefile文件中的THEOS_DEVICE_IP = 10.0.44.136 为你自己越狱设备的ip地址
稍后,你的越狱设备将会重启,然后,就可以在设置那里看到了
IMG_4716.PNG
IMG_4718.PNG
选中 系统应用 计算器
然后,打开计算器应用(如果已经是打开的,需要先退出它,重新进,才能看到效果)
IMG_4719.PNG
自此,我们的第一部分就结束了,通过本部分,我们了解了theos的基本配置,还有flex的越狱注入.