Theos是一个越狱开发工具包,由iOS越狱界知名人士Dustin Howett(@DHowett)开发并分享到GitHub上。Theos与其他越狱开发工具相比,最大的特点就是简单:下载安装简单、Logos语法简单、编译发布简单,可以让使用者把精力都放在开发工作上去。
值得一提的是,越狱开发中常用的另一工具iOSOpenDev是整合在Xcode里的,熟悉Xcode的朋友可能会对它更感兴趣。但逆向工程接触底层知识较多,很多东西无法自动化,因此推荐使用整合度并不算高的Theos,当你手动完成一个又一个练习时,对逆向工程的理解一定会更深。
这里插播一个关于DHowett的小段子:DHowett的全名叫Dustin L.Howett,他是个很有个性的少年,出生在美国宾夕法尼亚州的郊区,从小痴迷电脑。大学读了不到一年,觉得老师讲得没意思,就不愿意好好听了,自然也就跟不上。更重要的是,他和一个姑娘展开了疯狂的异地恋,于是就干脆辍学,搬到了那个姑娘的所在地加州,并求职进了Saurik的公司SaurikIT。DHowett的早期作品CyDelete以Cy开头,而这种命名方式是Saurik御用的,说明DHowett的作品得到了Saurik的认可,也足见DHowett与Saurik关系之好。但遗憾的是,在Dustin辍学后,他和女朋友之间开始出现问题,最后分道扬镳了。之后Dustin离开了SaurikIT,进入了另一家创业公司DailyBooth,但这家公司经营不善,没多久就倒闭了,他就又回家待业了。过了没多久,Dustin爱上了另一个女孩,所以他又为了这个姑娘搬回旧金山,并且在当地一家不错的公司Airbnb找到了一份新工作。在我眼里,Dustin敢想敢干、敢爱敢恨、敢作敢当,真是“让我们红尘作伴活得潇潇洒洒”,可以说是一个风一般的男子,令人十分崇拜。
1.安装Xcode与Command Line Tools
一般来说,iOS开发者都会安装Xcode,其中附带了Command Line Tools。如果还没有安装Xcode,请到Mac AppStore免费下载。如果安装了多个Xcode,需要使用xcode-select命令指定一个活动Xcode,即Theos默认使用的Xcode。假设安装了3个Xcode,并将它们分别命名为Xcode1.app、Xcode2.app和Xcode3.app,若要指定Xcode3为活动Xcode,则运行如下命令:
snakeninnys-MacBook:~ snakeninny$ sudo xcode-select -s /Applications/Xcode3.app/Contents/Developer
2.下载Theos
从GitHub上下载Theos,操作如下:
snakeninnysiMac:~ snakeninny$ export THEOS=/opt/theos snakeninnysiMac:~ snakeninny$ sudo git clone git://github.com/DHowett/theos.git $THEOS Password: Cloning into '/opt/theos'... remote: Counting objects: 4116, done. remote: Total 4116 (delta 0), reused 0 (delta 0) Receiving objects: 100% (4116/4116), 913.55 KiB | 15.00 KiB/s, done. Resolving deltas: 100% (2063/2063), done. Checking connectivity... done
3.配置ldid
ldid是专门用来签名iOS可执行文件的工具,用以在越狱iOS中取代Xcode自带的codesign。从http://joedj.net/ldid 下载ldid,把它放在“/opt/theos/bin/”下,然后用以下命令赋予它可执行权限:
snakeninnysiMac:~ snakeninny$ sudo chmod 777 /opt/theos/bin/ldid
4.配置CydiaSubstrate
首先运行Theos的自动化配置脚本,操作如下:
snakeninnysiMac:~ snakeninny$ sudo /opt/theos/bin/bootstrap.sh substrate Password: Bootstrapping CydiaSubstrate... Compiling iPhoneOS CydiaSubstrate stub... default target? failed, what? Compiling native CydiaSubstrate stub... Generating substrate.h header...
此处会遇到Theos的一个bug,它无法自动生成一个有效的libsubstrate.dylib文件,需要手动操作。解决方法很简单:首先在Cydia中搜索安装“CydiaSubstrate”(如图3-2所示)。
图3-2 CydiaSubstrate
然后用iFunBox或scp等方式将iOS上的“/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate”拷贝到OSX中,将其重命名为libsubstrate.dylib后放到“/opt/theos/lib/libsubstrate.dylib”中,替换掉无效的文件即可。
5.配置dpkg-deb
deb是越狱开发安装包的标准格式,dpkg-deb是一个用于操作deb文件的工具,有了这个工具,Theos才能正确地把工程打包成为deb文件。
从https://raw.githubusercontent.com/DHowett/dm.pl/master/dm.pl 下载dm.pl,将其重命名为dpkg-deb后,放到“/opt/theos/bin/”目录下,然后用以下命令赋予其可执行权限:
snakeninnysiMac:~ snakeninny$ sudo chmod 777 /opt/theos/bin/dpkg-deb
6.配置Theos NIC templates
Theos NIC templates内置了5种Theos工程类型的模板,方便创建多样的Theos工程。除此以外,还可以从https://github.com/DHowett/theos-nic-templates/archive/master.zip 获取额外的5种模板,下载后将解压得到的5个.tar文件复制到“/opt/theos/templates/iphone/”下即可。
1.创建工程
1)更改工作目录至常用的iOS工程目录(如笔者的是“/Users/snakeninny/Code/”),然后输入“/opt/theos/bin/nic.pl”,启动NIC(New Instance Creator),如下:
snakeninnysiMac:Code snakeninny$ /opt/theos/bin/nic.pl NIC 2.0 - New Instance Creator ------------------------------ [1.] iphone/application [2.] iphone/cydget [3.] iphone/framework [4.] iphone/library [5.] iphone/notification_center_widget [6.] iphone/preference_bundle [7.] iphone/sbsettingstoggle [8.] iphone/tool [9.] iphone/tweak [10.] iphone/xpc_service
可以看到,这里共有10种模板可供选择,其中1、4、6、8、9是Theos的自带模板,2、3、5、7、10是上一小节下载的。在逆向工程初级阶段,所开发程序的主要类型是tweak,其他模板的用法可以来http://bbs.iosre.com 讨论交流。
2)选择“9”,即创建一个tweak工程,命令如下:
Choose a Template (required): 9
3)输入tweak的工程名称,命令如下:
Project Name (required): iOSREProject
4)输入deb包的名字(类似于bundle identifier),命令如下:
Package Name [com.yourcompany.iosreproject]: com.iosre.iosreproject
5)输入tweak作者的名字,命令如下:
Author/Maintainer Name [snakeninny]: snakeninny
6)输入“MobileSubstrate Bundle filter”,也就是tweak作用对象的bundle identifier,命令如下:
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.apple.springboard
7)输入tweak安装完成后需要重启的应用,以进程名表示,如下:
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: SpringBoard Instantiating iphone/tweak in iosreproject/... Done.
简单的7步完成之后,一个名为iosreproject的文件夹就在当前目录生成了,该文件夹里就是刚创建的tweak工程。
2.定制工程文件
用Theos创建tweak工程非常方便,但简洁的工程框架下目前还是些粗糙的内容,需要进一步加工相关的文件。先来看看刚刚生成的工程目录,如下:
snakeninnysiMac:iosreproject snakeninny$ ls -l total 40 -rw-r--r-- 1 snakeninny staff 184 Dec 3 09:05 Makefile -rw-r--r-- 1 snakeninny staff 1045 Dec 3 09:05 Tweak.xm -rw-r--r-- 1 snakeninny staff 223 Dec 3 09:05 control -rw-r--r-- 1 snakeninny staff 57 Dec 3 09:05 iOSREProject.plist lrwxr-xr-x 1 snakeninny staff 11 Dec 3 09:05 theos -> /opt/theos
除去一个指向Theos目录的符号链接外,只有4个文件,从工程复杂度来说完全不会吓跑初学者,反而会让我们跃跃欲试,Theos的产品体验做得很好。
古语云:“一粒米中藏世界,半边锅内煮乾坤”。4根顶梁柱就足以撑起tweak的毛坯房,但漂亮的tweak离不开我们的精装修,这4个文件的内容可是大有玄机!
(1)Makefile
Makefile文件指定工程用到的文件、框架、库等信息,将整个过程自动化。iOSREProject的Makefile内容如下:
include theos/makefiles/common.mk TWEAK_NAME = iOSREProject iOSREProject_FILES = Tweak.xm include $(THEOS_MAKE_PATH)/tweak.mk after-install:: install.exec "killall -9 SpringBoard"
下面来逐行解读。
include theos/makefiles/common.mk
固定写法,不要更改。
TWEAK_NAME = iOSREProject
tweak的名字,即用Theos创建工程时指定的“Project Name”,跟control文件中的“Name”字段对应,不要更改。
iOSREProject_FILES = Tweak.xm
tweak包含的源文件(不包括头文件),多个文件间以空格分隔,如:
iOSREProject_FILES = Tweak.xm Hook.xm New.x ObjC.m ObjC++.mm
可以按需更改。
include $(THEOS_MAKE_PATH)/tweak.mk
根据不同的Theos工程类型,通过include命令指定不同的.mk文件;在逆向工程初级阶段,我们开发的一般是Application、Tweak和Tool三种类型的程序,它们对应的.mk文件分别是application.mk、tweak.mk和tool.mk,可以按需更改。
after-install:: install.exec "killall -9 SpringBoard"
读者应该从字面意思就能对这两行的作用猜个八九不离十——在tweak安装之后杀掉SpringBoard进程,好让CydiaSubstrate在进程启动时加载对应的dylib。
是不是非常简单?Makefile里的默认内容确实非常简单,但有点简单过头了。如何指定SDK版本?怎么导入framework?lib文件在哪里链接?作为iOS开发者的你一定会提出这些问题。别急别急,面包会有的,牛奶也会有的。
·指定处理器架构
ARCHS = armv7 arm64
上面的语句在表示不同的处理器架构时,其间以空格分隔。值得注意的是,采用arm64架构的App不兼容armv7/armv7s架构,必须适配arm64架构的dylib。在绝大多数情况下,这里固定填写“arm7 arm64”就行了。
·指定SDK版本
TARGET = iphone:Base SDK:Deployment Target
比如:
TARGET = iphone:8.1:8.0
上面的语句即指定采用8.1版本的SDK,且发布对象为iOS 8.0及以上版本。也可以把“Base SDK”设置为“latest”,指定以Xcode附带的最新版本SDK编译,如:
TARGET = iphone:latest:8.0
·导入framework
iOSREProject_FRAMEWORKS = framework name
例如:
iOSREProject_FRAMEWORKS = UIKit CoreTelephony CoreAudio
上面的语句所展示的功能没什么多说的,但既然是tweak开发,很多朋友关注的应该是如何导入private framework吧?很简单,用下面的语句即可:
iOSREProject_PRIVATE_FRAMEWORKS = private framework name
例如:
iOSREProject_PRIVATE_FRAMEWORKS = AppSupport ChatKit IMCore
虽然只是多了个“PRIVATE”,但有一点要注意:private framework是AppStore开发所不允许使用的,它的内容在每个iOS版本之间可能发生变化,在导入之前,一定要确定导入的private framework确实存在。举一个例子,如果你的tweak打算兼容iOS 7和iOS 8两个版本,那么Makefile可写成如下内容:
ARCHS = armv7 arm64 TARGET = iphone:latest:7.0 include theos/makefiles/common.mk TWEAK_NAME = iOSREProject iOSREProject_FILES = Tweak.xm iOSREProject_PRIVATE_FRAMEWORK = BaseBoard include $(THEOS_MAKE_PATH)/tweak.mk after-install:: install.exec "killall -9 SpringBoard"
上面的语句可以成功编译和链接,并不会报错。但是,因为BaseBoard这个private framework只存在于8.0及以上版本的SDK里,在iOS 7里是没有的,所以这个tweak在iOS 7中会因找不到framework而无法正常工作。这种情况可以通过弱链接(谷歌搜索“makefile weak linking”)或dlopen()、dlsym()和dlclose()系列函数动态调用private framework来解决。
·链接Mach-O对象(Mach-O object)
iOSREProject_LDFLAGS = -lx
Theos采用GNU Linker来链接Mach-O对象,包括.dylib、.a和.o。在Terminal中输入“man ld”,定位到“-lx”部分,它是这么写的:
“-lx This option tells the linker to search for libx.dylib or libx.a in the library search path.If string x is of the form y.o,then that file is searched for in the same places,but without prepending`lib’or appending`.a’or`.dylib’to the filename.”
大致意思是说,-lx代表链接libx.a或libx.dylib,即给“x”加上“lib”的前缀,以及“.a”或“.dylib”的后缀;如果x是“y.o”的形式,则直接链接y.o,不加任何前缀或后缀。由图3-3可知,iOS支持链接的Mach-O对象全是以“libx.dylib”和“y.o”形式命名的,完全兼容GNU Linker。
图3-3 链接Mach-O对象
这样,链接Mach-O对象就很方便了。例如,要链接libsqlite3.0.dylib、libz.dylib和dylib1.o,像下面这么写就可以了:
iOSREProject_LDFLAGS = -lz –lsqlite3.0 –dylib1.o
稍后还有一个字段需要介绍,但一般来说,Makefile中定义了以上字段就已经完全够用了;更详细的Makefile介绍,可以参阅http://www.gnu.org/software/make/manual/html_node/Makefiles.html 。
(2)Tweak.xm
用Theos创建tweak工程,默认生成的源文件是Tweak.xm。“xm”中的“x”代表这个文件支持Logos语法,如果后缀名是单独一个“x”,说明源文件支持Logos和C语法;如果后缀名是“xm”,说明源文件支持Logos和C/C++语法,与“m”和“mm”的区别类似。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. %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 */
这就是最基本的Logos语法,包含%hook、%log、%orig这3个预处理指令,它们的作用如下。
·%hook
指定需要hook的class,必须以%end结尾,如下:
%hook SpringBoard - (void)_menuButtonDown:(id)down { NSLog(@"You've pressed home button."); %orig; // call the original _menuButtonDown: } %end
这段代码的意思是钩住(hook)SpringBoard类里的_menuButtonDown:函数,先将一句话写入syslog,再执行函数的原始操作。
·%log
该指令在%hook内部使用,将函数的类名、参数等信息写入syslog,可以以%log([(<type>)<expr>,...])的格式追加其他打印信息,如下:
%hook SpringBoard - (void)_menuButtonDown:(id)down { %log((NSString *)@"iOSRE", (NSString *)@"Debug"); %orig; // call the original _menuButtonDown: } %end
打印结果如下:
Dec 3 10:57:44 FunMaker-5 SpringBoard[786]: -[<SpringBoard: 0x150eb800> _menuBu-ttonDown:+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Timestamp: 75607608282 Total Latency: 20266 us SenderID: 0x0000000100000190 BuiltIn: 1 AttributeDataLength: 16 AttributeData: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ValueType: Absolute EventType: Keyboard UsagePage: 12 Usage: 64 Down: 1 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ]: iOSRE, Debug
·%orig
该指令在%hook内部使用,执行被钩住(hook)的函数的原始代码,如下:
%hook SpringBoard - (void)_menuButtonDown:(id)down { NSLog(@"You've pressed home button."); %orig; // call the original _menuButtonDown: } %end
如果去掉%orig,那么原始函数不会得到执行,例如:
%hook SpringBoard - (void)_menuButtonDown:(id)down { NSLog(@"You've pressed home button but it's not functioning."); } %end
还可以利用%orig更改原始函数的参数,例如:
%hook SBLockScreenDateViewController - (void)setCustomSubtitleText:(id)arg1 withColor:(id)arg2 { %orig(@"iOS 8 App Reverse Engineering", arg2); } %end
这样一来,锁屏界面原本显示日期的地方就变成了如图3-4所示的样子。
图3-4 更改锁屏界面
除了%hook、%log、%orig以外,Logos常用的预处理指令还有%group、%init、%ctor、%new、%c,下面继续逐一介绍。
·%group
该指令用于将%hook分组,便于代码管理及按条件初始化分组(含义稍后有详细解释),必须以%end结尾;一个%group可以包含多个%hook,所有不属于某个自定义group的%hook会被隐式归类到%group_ungrouped中。%group的用法如下:
%group iOS7Hook %hook iOS7Class - (id)iOS7Method { id result = %orig; NSLog(@"This class & method only exist in iOS 7."); return result; } %end %end // iOS7Hook %group iOS8Hook %hook iOS8Class - (id)iOS8Method { id result = %orig; NSLog(@"This class & method only exist in iOS 8."); return result; } %end %end // iOS8Hook %hook SpringBoard -(void)powerDown { %orig; } %end
这段代码的意思是在%group iOS7Hook中钩住iOS7Class的iOS7Method,在%group iOS8Hook中钩住iOS8Class的iOS8Method函数,然后在%group_ungrouped中钩住SpringBoard类的powerDown函数。
需要注意的是,%group必须配合下面的%init使用才能生效。
·%init
该指令用于初始化某个%group,必须在%hook或%ctor内调用;如果带参数,则初始化指定的group,如果不带参数,则初始化_ungrouped,如下:
#ifndef kCFCoreFoundationVersionNumber_iOS_8_0 #define kCFCoreFoundationVersionNumber_iOS_8_0 1140.10 #endif %hook SpringBoard - (void)applicationDidFinishLaunching:(id)application { %orig; %init; // Equals to %init(_ungrouped) if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0 && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS7Hook); if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) init(iOS8Hook); } %end
只有调用了%init,对应的%group才能起作用,切记切记!
·%ctor
tweak的constructor,完成初始化工作;如果不显式定义,Theos会自动生成一个%ctor,并在其中调用%init(_ungrouped)。因此,
%hook SpringBoard - (void)reboot { NSLog(@"If rebooting doesn't work then I'm screwed."); %orig; } %end
可以成功生效,因为Theos隐式定义了如下内容:
%ctor { %init(_ungrouped); }
而
%hook SpringBoard - (void)reboot { NSLog(@"If rebooting doesn't work then I'm screwed."); %orig; } %end %ctor { // Need to call %init explicitly! }
里的%hook无法生效,因为这里显式定义了%ctor,却没有显式调用%init,%group(_ungrouped)不起作用。%ctor一般可以用来初始化%group,以及进行MSHookFunction等操作,如下:
#ifndef kCFCoreFoundationVersionNumber_iOS_8_0 #define kCFCoreFoundationVersionNumber_iOS_8_0 1140.10 #endif %ctor { %init; if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0 && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS7Hook); if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS8Hook); MSHookFunction((void *)&AudioServicesPlaySystemSound, (void *)&replaced_AudioServicesPlaySystemSound, (void **)&original_AudioServicesPlaySystemSound); }
注意,%ctor不需要以%end结尾。
·%new
在%hook内部使用,给一个现有class添加新函数,功能与class_addMethod相同。它的用法如下:
%hook SpringBoard %new - (void)namespaceNewMethod { NSLog(@"We've added a new method to SpringBoard."); } %end
有的朋友可能会问,Objective-C的category语法也可以给现有class添加新函数,为什么还需要%new呢?其实原因就在于category与class_addMethod的区别,前者是静态的,而后者是动态的。那么在这种情况下,静态还是动态,有什么关系呢?当然有关系,尤其是当class来自某个可执行文件的时候。举个例子,上面的代码给SpringBoard类添加了一个新方法,如果使用category,代码应该是下面这样:
@interface SpringBoard (iOSRE) - (void)namespaceNewMethod; @end @implementation SpringBoard (iOSRE) - (void)namespaceNewMethod { NSLog(@"We've added a new method to SpringBoard."); } @end
如果尝试编译上面的代码,会得到“error:cannot find interface declaration for‘SpringBoard’”的报错信息,即编译器找不到SpringBoard类的定义。可以构造一个SpringBoard的定义,骗过编译器,如下:
@interface SpringBoard : NSObject @end @interface SpringBoard (iOSRE) - (void)namespaceNewMethod; @end @implementation SpringBoard (iOSRE) - (void)namespaceNewMethod { NSLog(@"We've added a new method to SpringBoard."); } @end
重新编译,仍然会报错,如下:
Undefined symbols for architecture armv7: "_OBJC_CLASS_$_SpringBoard", referenced from: l_OBJC_$_CATEGORY_SpringBoard_$_iOSRE in Tweak.xm.b1748661.o ld: symbol(s) not found for architecture armv7 clang: error: linker command failed with exit code 1 (use -v to see invocation)
ld找不到“SpringBoard”的定义。一般来说,iOS程序员在碰到这个错误时的第一反应是:“是不是忘了导入哪个framework?”,但是转念一想,SpringBoard类是SpringBoard这个App里的一个类,而不是一个framework,要怎么导入?现在你是不是觉得%new非常可爱了呢?
·%c
该指令的作用等同于objc_getClass或NSClassFromString,即动态获取一个类的定义,在%hook或%ctor内使用。
Logos的预处理指令还有%subclass和%config,但笔者到现在也没有用过,感兴趣的读者可以移步http://iphonedevwiki.net/index.php/Logos 一探究竟,也可以来http://bbs.iosre.com 跟大家一起讨论。
(3)control
control文件记录了deb包管理系统所需的基本信息,会被打包进deb包里。iOSREProject里control文件的内容如下:
Package: com.iosre.iosreproject Name: iOSREProject Depends: mobilesubstrate Version: 0.0.1 Architecture: iphoneos-arm Description: An awesome MobileSubstrate tweak! Maintainer: snakeninny Author: snakeninny Section: Tweaks
其中:
·Package字段用于描述这个deb包的名字,采用的命名方式同bundle identifier类似,均为反向DNS格式,可以按需更改;
·Name字段用于描述这个工程的名字,可以按需更改;
·Depends字段用于描述这个deb包的“依赖”。“依赖”指的是这个程序运行的基本条件,可以填写固件版本或其他程序,如果当前iOS不满足“依赖”中定义的条件,则此tweak无法正常运行。如
Depends: mobilesubstrate, firmware (>= 8.0)
表示当前iOS版本必须在8.0以上,且必须安装CydiaSubstrate,才能正常运行这个tweak,可以按需更改。
·Version字段用于描述这个deb包的版本号,可以按需更改;
·Architecture字段用于描述deb包安装的目标设备架构,不要更改;
·Description字段是deb包的简单介绍,可以按需更改;
·Maintainter字段用于描述deb包的维护人,例如BigBoss源中所有deb包的维护人均为BigBoss,而非软件作者,可以按需更改;
·Author字段用于描述tweak的作者(注意与Maintainer的区别),可以按需更改;
·Section字段用于描述deb包所属的程序类别,不要更改。
control文件中可以自定义的字段还有很多,但上面这些信息就已经足够了。更全面的说明可以参阅debian的官方网站(http://www.debian.org/doc/debian-policy/ch-controlfields.html )或留意其他deb包里的control文件。值得注意的是,Theos在打包deb时会对control文件作进一步处理,上面的control文件在得到处理后内容变为:
Package: com.iosre.iosreproject Name: iOSREProject Depends: mobilesubstrate Architecture: iphoneos-arm Description: An awesome MobileSubstrate tweak! Maintainer: snakeninny Author: snakeninny Section: Tweaks Version: 0.0.1-1 Installed-Size: 104
这里Theos更改了Version字段,用以表示Theos的打包次数,方便管理;增加了一个Installed-Size字段,用以描述deb包安装后的估算大小,可能会与实际大小有偏差,但不要更改。
control文件中的很多信息直接体现在Cydia中,如图3-5所示,大家可以对比看看。
图3-5 control信息在Cydia中的体现
(4)iOSREProject.plist
这个plist文件的作用和App中的Info.plist类似,它记录了一些配置信息,描述了tweak的作用范围。我们可以用plutil,也可以用Xcode来编辑它。
iOSREProject.plist的最外层是一个dictionary,只有一个名为“Filter”的键,如图3-6所示。
图3-6 iOSREProject.plist
Filter下是一系列array,可以分为三类。
·Bundles,指定若干bundle为tweak的作用对象,如图3-7所示。
图3-7 Bundles
按照图3-7中的配置,tweak的作用对象是三个bundle,即SMSNinja、AddressBook.framework和SpringBoard。
·Classes,指定若干class为tweak的作用对象,如图3-8所示。
图3-8 Classes
按照图3-8的配置,tweak的作用对象是三个class,即NSString、SBAwayController和SBIconModel。
·Executables,指定若干可执行文件为tweak的作用对象,如图3-9所示。
按照图3-9中的配置,tweak的作用对象是三个可执行文件,即callservicesd、imagent和mediaserverd。
图3-9 Executables
这三类array可以混合使用,如图3-10所示。
图3-10 混合三类array
注意,当Filter下有不同类的array时,需要添加一个“Mode:Any”键值对。当Filter下的array只有一类时,不需要添加“Mode:Any”键值对。
3.编译+打包+安装
前面在完成了Theos的安装后,使用NIC创建了第一个tweak工程,还逐一解读了工程的组成文件,那么现在就剩下最后一步——编译了。完成这一步,一个tweak就算正式完成——我们可以把tweak安装到设备上,开始周而复始的“safe mode”之旅了,是不是很期待呢?
(1)编译
Theos采用“make”命令来编译Theos工程。在Theos工程目录下运行make命令,如下:
snakeninnysiMac:iosreproject snakeninny$ make Making all for tweak iOSREProject... Preprocessing Tweak.xm... Compiling Tweak.xm... Linking tweak iOSREProject... Stripping iOSREProject... Signing iOSREProject...
从输出的信息看,Theos完成了预处理、编译、签名等一系列动作,此时会发现当前目录下多了一个新的“obj”文件夹,如下:
snakeninnysiMac:iosreproject snakeninny$ ls -l total 32 -rw-r--r-- 1 snakeninny staff 262 Dec 3 09:20 Makefile -rw-r--r-- 1 snakeninny staff 0 Dec 3 11:28 Tweak.xm -rw-r--r-- 1 snakeninny staff 223 Dec 3 09:05 control -rw-r--r--@ 1 snakeninny staff 175 Dec 3 09:48 iOSREProject.plist drwxr-xr-x 5 snakeninny staff 170 Dec 3 11:28 obj lrwxr-xr-x 1 snakeninny staff 11 Dec 3 09:05 theos -> /opt/theos
里面有一个.dylib文件,如下:
snakeninnysiMac:iosreproject snakeninny$ ls -l ./obj total 272 -rw-r--r-- 1 snakeninny staff 33192 Dec 3 11:28 Tweak.xm.b1748661.o -rwxr-xr-x 1 snakeninny staff 98784 Dec 3 11:28 iOSREProject.dylib
它就是tweak的核心。
(2)打包
打包使用的“make package”命令来自于Theos本身,其实就是先执行“make”命令,然后再执行“dpkg-deb”命令,如下:
snakeninnysiMac:iosreproject snakeninny$ make package Making all for tweak iOSREProject... Preprocessing Tweak.xm... Compiling Tweak.xm... Linking tweak iOSREProject... Stripping iOSREProject... Signing iOSREProject... Making stage for tweak iOSREProject... dm.pl: building package `com.iosre.iosreproject' in `./com.iosre.iosreproject_0.0.1-7_iphoneos-arm.deb'.
上面生成了一个名为“com.iosre.iosreproject_0.0.1-7_iphoneos-arm.deb”的文件,这就是可以最终发布的安装包。
“make package”命令还有一个很重要的功能。在执行完“make package”之后,除了“obj”文件夹外,你会发现tweak工程目录下还生成了一个“_”文件夹,如下:
snakeninnysiMac:iosreproject snakeninny$ ls -l total 40 -rw-r--r-- 1 snakeninny staff 262 Dec 3 09:20 Makefile -rw-r--r-- 1 snakeninny staff 0 Dec 3 11:28 Tweak.xm drwxr-xr-x 4 snakeninny staff 136 Dec 3 11:35 _ -rw-r--r-- 1 snakeninny staff 2396 Dec 3 11:35 com.iosre.iosreproject_0.0.1-7 _iphoneos-arm.deb -rw-r--r-- 1 snakeninny staff 223 Dec 3 09:05 control -rw-r--r--@ 1 snakeninny staff 175 Dec 3 09:48 iOSREProject.plist drwxr-xr-x 5 snakeninny staff 170 Dec 3 11:35 obj lrwxr-xr-x 1 snakeninny staff 11 Dec 3 09:05 theos -> /opt/theos
这个文件夹是干什么的?打开它,可以看到2个文件夹,分别是“DEBIAN”和“Library”:
snakeninnysiMac:iosreproject snakeninny$ ls -l _ total 0 drwxr-xr-x 3 snakeninny staff 102 Dec 3 11:35 DEBIAN drwxr-xr-x 3 snakeninny staff 102 Dec 3 11:35 Library
其中“DEBIAN”里只有tweak工程里的control文件,Theos在编译过程中向control文件里稍稍增加了几个字段而已,如下:
snakeninnysiMac:iosreproject snakeninny$ ls -l _/DEBIAN total 8 -rw-r--r-- 1 snakeninny staff 245 Dec 3 11:35 control
“Library”的目录结构如图3-11所示。
图3-11 Library目录结构
对比生成deb的包内容:
snakeninnysiMac:iosreproject snakeninny$ dpkg -c com.iosre.iosreproject_0.0.1-7_iphoneos-arm.deb drwxr-xr-x snakeninny/staff 0 2014-12-03 11:35 ./ drwxr-xr-x snakeninny/staff 0 2014-12-03 11:35 ./Library/ drwxr-xr-x snakeninny/staff 0 2014-12-03 11:35 ./Library/MobileSubstrate/ drwxr-xr-x snakeninny/staff 0 2014-12-03 11:35 ./Library/MobileSubstrate/DynamicLibraries/ -rwxr-xr-x snakeninny/staff 98784 2014-12-03 11:35 ./Library/MobileSubstrate/DynamicLibraries/iOSREProject.dylib -rw-r--r-- snakeninny/staff 175 2014-12-03 11:35 ./Library/MobileSubstrate/DynamicLibraries/iOSREProject.plist
以及在Cydia中iOSREProject的文件系统,如图3-12所示。
图3-12 iOSREProject文件系统
可以看到,三者是完全相同的。到这里,你可能也猜到了,这个deb包其实就是由“DEBIAN”提供debian信息,“Library”提供实际文件的简单组合。事实上,还可以在工程目录下创建一个名为“layout”的文件夹,然后把工程打包成deb并安装到iOS中,此时“layout”中的所有文件会被解包到iOS文件系统的相同位置(这里的“layout”相当于iOS中的根目录“/”),这极大扩充了deb包的作用范围。下面用一个小示例佐以说明。
回到刚才的iOSREProject中,在Terminal中输入“make clean”及“rm*.deb”,将工程恢复到最初的状态,如下:
snakeninnysiMac:iosreproject snakeninny$ make clean rm -rf ./obj rm -rf "/Users/snakeninny/Code/iosreproject/_" snakeninnysiMac:iosreproject snakeninny$ rm *.deb snakeninnysiMac:iosreproject snakeninny$ ls -l total 32 -rw-r--r-- 1 snakeninny staff 262 Dec 3 09:20 Makefile -rw-r--r-- 1 snakeninny staff 0 Dec 3 11:28 Tweak.xm -rw-r--r-- 1 snakeninny staff 223 Dec 3 09:05 control -rw-r--r--@ 1 snakeninny staff 175 Dec 3 09:48 iOSREProject.plist lrwxr-xr-x 1 snakeninny staff 11 Dec 3 09:05 theos -> /opt/theos
然后生成一个空的“layout”目录,如下:
snakeninnysiMac:iosreproject snakeninny$ mkdir layout
并在“layout”下随便放一些空文件,如下:
snakeninnysiMac:iosreproject snakeninny$ touch ./layout/1.test snakeninnysiMac:iosreproject snakeninny$ mkdir ./layout/Developer snakeninnysiMac:iosreproject snakeninny$ touch ./layout/Developer/2.test snakeninnysiMac:iosreproject snakeninny$ mkdir -p ./layout/var/mobile/Library/Preferences snakeninnysiMac:iosreproject snakeninny$ touch ./layout/var/mobile/Library/Preferences/3.test
最后用“make package”打包,并将生成的deb文件拷贝到iOS中,用iFile安装。然后在Cydia中查看iOSREProject的文件系统,如图3-13所示。
图3-13 iOSREProject文件系统
除“DEBIAN”以外的所有文件都被解包到了iOS文件系统的相同位置,本来不存在的中间文件夹也被自动创建。deb包的玄机还有很多,这里也只是管中窥豹,更全面的介绍请移步http://www.debian.org/doc/debian-policy ,官方文档总是最好的学习资料。
(3)安装
最后,要把这个deb文件安装到iOS中去。安装的方法多种多样,这里介绍两种最具代表性的:图形界面安装法和命令行安装法。大多数人的第一直觉是图形界面一定比命令行简单,那好,咱们先介绍图形界面安装法。
·图形界面安装法
这个方法确实简单:通过iFunBox等软件把deb拖到iOS里去,然后用iFile安装它,最后重启iOS。虽然全过程都由图形界面操作,但人机交互太多,又要动电脑又要滑手机,一来二去非常繁琐,并不适用于tweak开发。
·命令行安装法
这个方法要用到简单的ssh命令,故而要求越狱的iOS安装了OpenSSH,如果对这部分知识不了解,请先快速浏览一遍第4章的“OpenSSH”部分。下面具体介绍安装法。
首先,需要在Makefile的最上一行加上本机IP地址,如下:
THEOS_DEVICE_IP = iOSIP ARCHS = armv7 arm64 TARGET = iphone:latest:8.0
然后调用“make package install”命令完成编译打包安装一条龙服务,如下:
snakeninnysiMac:iosreproject snakeninny$ make package install Making all for tweak iOSREProject... Preprocessing Tweak.xm... Compiling Tweak.xm... Linking tweak iOSREProject... Stripping iOSREProject... Signing iOSREProject... Making stage for tweak iOSREProject... dm.pl: building package `com.iosre.iosreproject:iphoneos-arm' in `./com.iosre.iosreproject_0.0.1-15_iphoneos-arm.deb' install.exec "cat > /tmp/_theos_install.deb; dpkg -i /tmp/_theos_install.deb && rm /tmp/_theos_install.deb" < "./com.iosre.iosreproject_0.0.1-15_iphoneos-arm.deb" root@iOSIP's password: Selecting previously deselected package com.iosre.iosreproject. (Reading database ... 2864 files and directories currently installed.) Unpacking com.iosre.iosreproject (from /tmp/_theos_install.deb) ... Setting up com.iosre.iosreproject (0.0.1-15) ... install.exec "killall -9 SpringBoard" root@iOSIP's password:
从以上信息可以看到,Theos在整个安装过程中要求我们输入两次root密码。虽然多次输入密码给人很安全的感觉,但实在是太麻烦了。好在通过设置iOS的authorized_keys可以省略SSH输密码的步骤,让“make package install”真正地从“一只多脚虫”变成“一条飞天龙”,具体步骤如下:
1)删除“/Users/snakeninny/.ssh/known_hosts”中iOSIP对应的条目。
假设iOS的IP地址是iOSIP。编辑“/Users/snakeninny/.ssh/known_hosts”,找到iOSIP所在的那一行,如下:
iOSIP ssh-rsa hXFscxBCVXgqXhwm4PUoUVBFWRrNeG6gVI3Ewm4dqwusoRcyCxZtm5bRiv4bXfkPjsRkWVVfrW3uT52Hhx4RqIuCOxtWE7tZqc1vVap4HIzUu3mwBuxog7WiFbsbbaJY4AagNZmX83Wmvf8li5aYMsuKeNagdJHzJNtjM3vtuskK4jKzBkNuj0M89TrV4iEmKtI4VEoEmHMYzWwMzExXbyX5NyEg5CRFmA46XeYCbcaY0L90GExXsWMMLA27tA1Vt1ndHrKNxZttgAw31J90UDnOGlMbWW4M7FEqRWQsWXxfGPk0W7AlA54vaDXllI5CD5nLAu4VkRjPIUBrdH5O1fqQ3qGkPayhsym3g0VZeYgU4JAMeFc3
完整删掉这一行。
2)生成authorized_keys。
在Terminal中执行如下命令:
snakeninnysiMac:~ snakeninny$ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/Users/snakeninny/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/snakeninny/.ssh/id_rsa. Your public key has been saved in /Users/snakeninny/.ssh/id_rsa.pub. …… snakeninnysiMac:~ snakeninny$ cp /Users/snakeninny/.ssh/id_rsa.pub ~/authorized_keys
就会在用户目录下生成authorized_keys。
3)配置iOS。
在Terminal中执行如下命令:
FunMaker-5:~ root# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/var/root/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /var/root/.ssh/id_rsa. Your public key has been saved in /var/root/.ssh/id_rsa.pub. …… FunMaker-5:~ root# logout Connection to iOSIP closed. snakeninnysiMac:iosreproject snakeninny$ scp ~/authorized_keys root@iOSIP:/var/root/.ssh The authenticity of host 'iOSIP (iOSIP)' can't be established. RSA key fingerprint is 75:98:9a:05:a3:27:2d:23:08:d3:ee:f4:d1:28:ba:1a. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added 'iOSIP' (RSA) to the list of known hosts. root@iOSIP's password: authorized_keys 100% 408 0.4KB/s 00:00
重新使用ssh命令进入iOS试试看,还需要输密码吗?此时,“make package install”真正变成了一次配置,一键安装,一劳永逸!
(4)清理
Theos提供了方便的工程清理命令“make clean”,其实际作用就是依次执行“rm-rf./obj”和“rm-rf"/Users/snakeninny/Code/iosre/_"”两个命令,从而删除“make”和“make package”命令生成的文件夹。也可以用“rm*.deb”,删除“make package”命令生成的所有deb文件。
前几节完整地介绍了Theos的安装和使用方法,虽然还没有涵盖Theos的所有功能,但对于逆向工程初学者来说已经完全够用了。讲了这么多内容却还没有涉及一行真实的代码,是不是有些意犹未尽啊?
接下来将以一个最简单的tweak为例来进行讲解。安装了该程序之后,每次重启SpringBoard都将会弹出一个UIAlertView。
1.用Theos新建tweak工程“iOSREGreetings”
新建iOSREGreetings工程的命令如下:
snakeninnysiMac:Code snakeninny$ /opt/theos/bin/nic.pl NIC 2.0 - New Instance Creator ------------------------------ [1.] iphone/application [2.] iphone/cydget [3.] iphone/framework [4.] iphone/library [5.] iphone/notification_center_widget [6.] iphone/preference_bundle [7.] iphone/sbsettingstoggle [8.] iphone/tool [9.] iphone/tweak [10.] iphone/xpc_service Choose a Template (required): 9 Project Name (required): iOSREGreetings Package Name [com.yourcompany.iosregreetings]: com.iosre.iosregreetings Author/Maintainer Name [snakeninny]: snakeninny [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.apple.springboard [iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: Instantiating iphone/tweak in iosregreetings/... Done.
2.编辑Tweak.xm
编辑后的Tweak.xm内容如下:
%hook SpringBoard - (void)applicationDidFinishLaunching:(id)application { %orig; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Come to http://bbs.iosre.com for more fun!" message:nil delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; } %end
3.编辑Makefile及control
编辑后的Makefile内容如下:
THEOS_DEVICE_IP = iOSIP ARCHS = armv7 arm64 TARGET = iphone:latest:8.0 include theos/makefiles/common.mk TWEAK_NAME = iOSREGreetings iOSREGreetings_FILES = Tweak.xm iOSREGreetings_FRAMEWORKS = UIKit include $(THEOS_MAKE_PATH)/tweak.mk after-install:: install.exec "killall -9 SpringBoard"
编辑后的control内容如下:
Package: com.iosre.iosregreetings Name: iOSREGreetings Depends: mobilesubstrate, firmware (>= 8.0) Version: 1.0 Architecture: iphoneos-arm Description: Greetings from iOSRE! Maintainer: snakeninny Author: snakeninny Section: Tweaks Homepage: http://bbs.iosre.com
以上代码非常简单,当SpringBoard的applicationDidFinishLaunching:函数得到调用时,代表SpringBoard的启动过程已经结束。钩住(hook)这个函数,调用%orig完成它的原始操作,然后弹出一个自定义的UIAlertView;这样一来,每次重启SpringBoard都会弹出一个对话框。你看懂了吗?
准备就绪,在Terminal中敲入“make package install”,待SpringBoard重启之后会看到如图3-14所示的结果,简单粗暴。
图3-14 第一个tweak
是的,仅仅是这样一些小小的改动,就已经可以改变App的行为了。此时,封闭的iOS已经向我们打开了大门……
因为有Theos这样的开发工具存在,修改闭源的iOS程序变得前所未有的方便。不过在前面也提到了,现在的App工程量越来越大,class-dump头文件也越来越多,要从浩如烟海的函数名中筛选出我们感兴趣的目标,比确定目标后编写代码还要难得多。面对成千上万行代码,如果没有其他工具辅助分析,逆向工程简直是一场噩梦,让人一筹莫展。那么接下来,就轮到这些辅助分析工具隆重登场了。