作者:FloatingGuy 转载请注明出处:https://floatingguy.github.io/


环境配置

详情参考:iOS 越狱的Tweak开发

新版的已经是内置CydiaSubstrate.framework,不是网上其它教程中说的需要运行bootstrap.sh脚本或者是从手机上拷贝等方式.

Theos 使用方法

Theos 当前有12种模板,这里拿书中的 iOSREProject tweak 工程的例子做演示。

  • 创建项目
    nic.pl

  • 模板类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    iOS_Tweak $ 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

初级阶段常用的类型是:2, 10, 11,

  • 项目类型:
    Choose a Template (required): 11

  • 工程名称:
    Project Name (required): iOSREProject

  • deb 包名
    Package Name [com.yourcompany.iosreproject]: com.iosre.iosreproject

  • tweak 作者:
    Author/Maintainer Name [xx]: xx

  • tweak 作用的对象(指定要 hook的目标APP 的 bundle identifier)
    [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.apple.springboard

  • tweak 安装完以后要重启的应用
    [iphone/tweak] List of applications to terminate upon installation (space-separated, ‘-‘ for none) [SpringBoard]: SpringBoard

Instantiating iphone/tweak in iosreproject/…
Done.

文件结构

当前创建了一个 tweak 项目,在工程目录下只有4个文件:

Makefile
control
iOSREProject.plist
Tweak.xm

Makefile

这是一个编译脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#### 额外的编译参数
# 目标的 ip
THEOS_DEVICE_IP = iosIP
# 处理器架构
ARCHS = armv7 arm64
# SDK 版本
# TARGET = iphone: BASE SDK 编译使用的 SDK 版本: Deployment target 发布的机型系统版本
# TARGET = iphone:latest:8.0
TARGET = iphone:8.1:8.0
# 必须导入的标准编译脚本
include $(THEOS)/makefiles/common.mk
# Project name
TWEAK_NAME = iOSREProject
# Source files
iOSREProject_FILES = Tweak.xm
# 根据选择的模板类型 导入编译脚本
include $(THEOS_MAKE_PATH)/tweak.mk
# 导入 framework
iOSREProject_FRAMEWORKS = UIKit CoreTelephony CoreAudio
# 导入 privete framework
iOSREProject_PRIVATE_FRAMEWORKS = AppSupport ChatKit IMCore BaseBoard
# 安装以后, 自动执行的命令
after-install::
install.exec "killall -9 SpringBoard"

注意:private framework 中导入的包可能存在兼容性问题。比如 BaseBoard 这个 private framework只存在 8.0及以上版本的 SDK 里。这种情况在 ios7中无法正常运行,通过弱链接(makefile weak linking)或者 dlopen/dlsym/dlclose 系列函数调用 private framework。

control

control 中的信息会显示在 Cydia 中, 且 control 文件中内容可以修改。

1
2
3
4
5
6
7
8
9
10
11
12
Package: com.iosre.iosreproject #deb 包名
Name: iOSREProject
# 当前ios 版本必须 高于8.0, 且安装 CydiaSubstarte
Depends: mobilesubstrate, firmware (>=8.0)
Version: 0.0.1
Architecture: iphoneos-arm # 目标设备的 架构
Description: An awesome MobileSubstrate tweak!
Maintainer: xy
Author: xx
Section: Tweaks #模板类型

更多字段在 debian 的官网。

iOSREProject.plist

此 plist 文件的作用和 app 开发时的 plist 文件作用相似, 记录配置信息 描述了 tweak 的作用范围。

Filter 包含 3类数组

  • Bundles
    • 指定 tweak 起作用的APP \< “bundle identifier” >
  • Executables
    • 指定作用的 可执行文件 \<文件名>
  • Classes
    • 指定 作用的 class \<类名>

注意: 当Filter 下包含不同的 Array 时需要添加一个 “Mode : Any”, 当只有一个 Array 时不需要。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
Filter = {
Mode = Any;
Executables = (
callservicesd,
imagent,
mediaserverd,
);
Classes = (
NSString,
SBIconModel,
);
Bundles = (
"com.apple.springboard",
"com.apple.AddressBook",
);
};
}

校验 plist 文件格式:
plutil -lint xx/xx/iOSREProject.plist

不熟悉 xml 语法,使用 xcode 编辑.

Tweak.xm

Tweak.xm 后缀 xm 代码文件使用的编程语言。
x 源文件支持 Logos 和 C语法
xm 源文件支持Logos 和 C/ C++ 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// hook 住 SpringBoard 类里的 _menuButtonDown 函数
%hook SpringBoard
- (void) _menuButtonDown:(id)down
{
NSLog(@"pressed home button");
%log((NSString *)@"iOSRE", (NSString *)@"Debug");
%orig;
}
%new
- (void)namespaceNewMethod
{
NSLog(@"add a new method to class SpringBoard")
}
%end
%hook SBLockScreenDateViewController
- (void)setCustomSubtitleText:(id)arg1 withColor:(id)arg2
{
// 在锁屏界面显示 "iOS 8 App Reverse Engineering"
%orig(@"iOS 8 App Reverse Engineering", arg2);
}
%end
%group iOS7Hook
%hook iOS7Class
- (id)iOS7Method
{
...
}
%end //hook
%end //group
%group iOS8Hook
%hook iOS8Class
- (id)iOS8Method
{
...
}
%end //hook
%end //group
%ctor
{
%init() //初始化 _ungrouped %group
// %init(iOS7Hook) // 初始化 iOS7Hook %group
// %init(iOS8Hook) // 初始化 iOS8Hook %group
}

Logos 函数使用 % 开头。
这里用户定义了2个 group(iOS7Hook 和 iOS8Hook), 并且显示定义了 %ctor 负责初始化要使用的 group

编译 + 打包 + 安装

1
2
3
make // 没有任何文件创建
make package
make package install // 提前在 Makefile 中配置目标 ip

安装是通过 ssh 命令实现,要求越狱的 iOS 安装 OpenSSH, 需要在 makefile 中设置 THEOS_DEVICE_IP
还可以配置 公钥登录, 具体步骤略。

锁屏界面文字替换

总结

上面使用大量篇幅介绍开发 tweak 的流程略显啰嗦,其实 tweak 开发的本质就是 Cydia Substrate 的 hook。
hook 的目标进程、可执行文件、类:通过 .plist 文件控制
具体的 hook 代码:定义在 xm 文件中,可以使用 Object-C/C/C++ 开发
hook 实现:通过 Logos 语法封装。

更多关于 tweak 的高级玩法和 其框架的限制,还要在未来的实践中去摸索

逆向tweak

将deb 包解压会有2个重要的压缩文件:

  • control.tar.gz 安装控制文件
  • data.tar.lzma 二进制程序(动态库,可执行文件等)
    最后那个后缀是压缩类型,可能是lzma, gz,xz等。

解压上面的压缩包以后得到 control文件 和 data 目录。

control 文件

1
2
3
4
5
6
7
8
9
10
Package: com.iosre.iosreproject
Name: iOSREProject
Depends: mobilesubstrate
Architecture: iphoneos-arm
Description: An awesome MobileSubstrate tweak!
Maintainer: xxx
Author: xxx
Section: Tweaks
Version: 0.0.1-4+debug
Installed-Size: 136

data 目录

我逆向了某个 tweak 解压data.tar.gz 发现Library 下有3个文件夹,而我写的ioreproject只有MobileSubstrate一个文件夹。

后来发现 PreferenceBundles 下面是一个设置里的一个插件(我们先不关注这个)。
直接分析 动态库 /data/Library/MobileSubstrate/DynamicLibraries/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
__ZL55_logos_method$_ungrouped$SpringBoard$namespaceNewMethodP11SpringBoardP13objc_selector: // _logos_method$_ungrouped$SpringBoard$namespaceNewMethod(SpringBoard*, objc_selector*)
00007d70 push {r7, lr}
00007d72 mov r7, sp
00007d74 sub sp, #0x8
00007d76 movw r2, #0x2fa
00007d7a movt r2, #0x0 ; 0x2fa
00007d7e add r2, pc ; @"add a new method to class SpringBoard"
00007d80 str r0, [sp, #0x8 + var_4]
00007d82 str r1, [sp, #0x8 + var_0]
00007d84 mov r0, r2
00007d86 blx imp___picsymbolstub4__NSLog
00007d8a add sp, #0x8
00007d8c pop {r7, pc}
; endp
*/
%new
- (void)namespaceNewMethod
{
NSLog(@"add a new method to class SpringBoard");
}
%end

采用标准的arm 汇编, 和android so 一样。

Logos 语法

指令集

  • %log 在%hook 内部使用。
  • %orig 执行被 hook 的函数的原始代码
  • %group 便于代码管理及按条件初始化分组。不属于某个自定义group 的 hook , 默认归类到%group _ungrouped
    group 必须要配置 %init 使用才能生效。
  • %init 只有调用了%init(group name) 才能使对应的%group 起作用
  • %ctor tweak 的构造函数, 完成初始化工作; 如果不定义,theos 会自动生成一个%ctor
  • %c 动态获取一个类的定义
  • %new 给一个现有的 class,添加新函数
指令 作用域
%hook %group
%log %hook
%orig %hook
%init %hook , %ctor
%c %hook , %ctor

Logos 语言的本质是theos对cydia Substrate提供的函数的宏封装。

Cydia Substrate hook iOS

Cydia Substrate由三部分组成, 需要在越狱手机上通过 Cydia 安装。

  • MobileHooker
  • MobileLoader
  • Safe mode

MobileHooker

此模块专门用来实现 hook, 下面3个函数就来执行 hook操作。

1
2
3
4
5
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,主要区别在于hook 的语言。

  • 针对 C/C++ 函数, 使用 MSHookFunction(...), 使用 inline hook 的原理在函数头部添加汇编指令跳转。
  • 针对 Object-C 函数, 使用 MSHookMessageEx(...), 使用 OC 语言特性。

MSHookFunction(...)MSHookMessageEx(...) 使用案例, 熟悉 hook 的 api 方便逆向 (不展示 hook C++ private method 的代码)

MSHookFunction(...)

1
2
3
4
5
6
7
8
9
10
11
MSHook(void, CFShow, CFTypeRef obj) { // our replacement of CFShow().
printf("Calling original CFShow(%p)...", obj);
_CFShow(obj); // calls the original CFShow.
printf(" done.\n");
}
...
// hook CFShow to our own implementation.
MSHookFunction(CFShow, MSHake(CFShow));
// From now on any call to CFShow will pass through our CFShow replacement first.
...
CFShow(CFSTR("test"));

MSHookMessageEx(...)

1
2
3
4
5
6
7
8
9
10
static IMP original_UIView_setFrame_;
void replaced_UIView_setFrame_(UIView* self, SEL _cmd, CGRect frame) { // Note the implicit self and _cmd parameters are needed explicitly here.
CGRect originalFrame = self.frame;
NSLog("Changing frame of %p from %@ to %@", self, NSStringFromCGRect(originalFrame), NSStringFromCGRect(frame));
original_UIView_setFrame_(self, _cmd, frame); // Remember to pass self and _cmd.
}
...
MSHookMessageEx([UIView class], @selector(setFrame:), (IMP)replaced_UIView_setFrame_, (IMP *)&original_UIView_setFrame_);
...
myView.frame = CGRectMake(0, 0, 100, 100);

实际上 CydiaSubstarte 提供的 api 远不止上面的3个,还有很多功能的 api,比如:MSFindSymbole(..)、MSGetImageByName(…)等等,在官方文档中均有介绍。

这三个都是用来进行hook操作的,也就是我们在非越狱开发中常说的swizzle! cydia Substrate还提供了MobileLoader:“钩子”需要在运行时被加载,靠的就是MobileLoader的功劳。MobileLoader会在适当的时机加载/Library/MobileSubstrate/DynamicLibraries/目录下的动态库(.dylib,这是tweak的最终产品)

MobileLoader

此模块用来加载手机中的 tweak,这些 tweak 最终是以 dylib 库的形式保存在/Library/MobileSubstrate/DynamicLibraries/目录下。

需要注意的是 MobileLoader 的作用范围并不是全局 hook。 还记得我们在上面介绍的 plist 文件吗,其中只定义了Filter 过滤器,用来过滤要 hook 的目标进程、目标可执行文件、以及目标类。
那么 MobileLoader 就会根据 plist 中过滤器指定的作用范围,有选择的在不同进程里通过 dlopen 加载对应的 dylib。

注意:从 ios9 开始必须定义 plist 文件。没有相应的plist的Dylib将不被加载。 要复制以前没有过滤器plist的效果,导致将dylib加载到所有进程中,请将Filter设置为com.apple.Security。

Safe mode

为了防止编写的 tweak 不规范导致整个进程崩溃,所以 添加了 Safe Mode,它可以捕获 一下6种信号,然后进入安全模式。

  • SIGABRT
  • SIGILL
  • SIGBUS
  • SIGSEGV
  • SIGSYS

其他技术

检查是否加壳

otool -l WeChat.app/WeChat | grep -B 2 crypt

1代表加密了,0代表被解密了:

1
2
3
4
5
6
7
8
9
10
11
cmd LC_ENCRYPTION_INFO
cmdsize 20
cryptoff 16384
cryptsize 16384
cryptid 0
--
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 16384
cryptid 0

Bugs

lzma

1
'dpkg-deb: error: obsolete compression type 'lzma'; use xz instead'

解决方案

1
2
3
修改opt/theos/makefiles/package/deb.mk
第六行:THEOSPLATFORM_DPKG_DEB_COMPRESSION ?= lzma
把最后lzma 改成 xz 就可以了

dpkg 降级

1
2
3
4
5
dpkg: error processing archive /tmp/_theos_install.deb (--install):
subprocess dpkg-deb --fsys-tarfile returned error exit status 2
Errors were encountered while processing:
/tmp/_theos_install.deb
make: *** [internal-install] Error 1

解决方案

1
2
3
brew remove dpkg
HOMEBREW_NO_AUTO_UPDATE=1 brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/7a4dabfc1a2acd9f01a1670fde4f0094c4fb6ffa/Formula/dpkg.rb
brew pin dpkg