我们以微信为例,在微信【发现】界面新增两行内容。
本文章仅供学习交流使用,禁止使用技术手段非法获益。
一、微信注入
1.1. 流程分析
ssh远程登录iPhone。
- 使用usbmuxd工具进行远程操作
运行微信,使用Reveal工具查看发现页面的层级结构,最顶层视图是
MMMainTableView
。
- 然后在终端使用cycript获取微信一些信息。通过一层层拦截发现,微信的发现页面使用的是
UITableView
,并且数据源是在FindFriendEntryViewController
中实现的。所以如果要添加新的数据到页面中,就需要重写控制器中tableView的数据源。
微信脱壳,找到可执行文件。
- 使用Clutch进行脱壳可能会失败,所以此处建议使用class-dumpdecrypted进行脱壳。
使用class-dump导出可执行文件的头文件。
在导出的头文件中找到找到
FindFriendEntryViewController
,并且查看tableView
实现的数据源方法有哪些。
使用theos中的
nic.pl
新建tweak项目。代码编写完成后,需要编辑打包安装(会自动重启SpringBoard)。
1 | make && make package && make install |
1.2. 编写代码
编写Tweak.xm
文件注意事项:
class-dump
导出的头文件里面的方法形参是可以自己编辑的,形参如果使用id
修饰的,在使用形参时不要使用点语法。- 如果需要引用应用本身代码的实现,需要使用
%orig
(theos提供的Logos语法)。 Tweak.xm
文件中默认是替换(%hook
)已有的方法,如果需要添加新的方法,需要在方法前面添加%new
。- 可以使用宏定义。
- 加载图片资源使用
imageWithContentsOfFile:
,因为后面的参数是一个绝对路径,所以把图片放到手机根路径或其他具体路径就可以。- 上面的操作其实不太方便,theos也考虑到这样的问题,所以如果要使用额外的资源文件,在tweak项目的根路径创建一个layout文件夹,把资源放入即可。当最后编译安装的时候,theos会自动把layout文件夹里面的内容放到手机根路径。
- 考虑到其他应用可能会有同名的资源,所以可以在layout文件夹中再创建一个文件夹(例:
layout/Library/PreferenceLoader/Preferences/DBWeChat
),把资源放到DBWeChat
。安装时theos会把layout里面的东西原封不动的放到手机根路径,如果有同名文件夹,会自动合并文件夹)。 - 使用:
[UIImage imageWithContentsOfFile:@"/Library/PreferenceLoader/Preferences/DBWeChat/skull.png"];
1 | #define DBDefaults [NSUserDefaults standardUserDefaults] |
以上仅仅是提供一种思路,如果有的应用是通过数据模型控制视图的,可以通过拦截数据模型进行处理。如果有些方法确实存在,但是编译的时候tweak报错(一般是self
调用时会报错),可以在tweak.xm最上面声明一个伪类,让伪类声明方法(仅仅是为了编译不报错)。
常用的Logos语法:
- %hook、%end
- hook一个类的开始和结束
- %log
- 打印⽅法调用详情
- 可以通过
Xcode -> Window -> Devices and Simulators
查看⽇志
- HBDebugLog
- 和NSLog类似
- %new
- 添加一个新的方法
- %c(className)
- 生成⼀个Class对象,比如%c(NSObject),类似于
NSStringFromClass()
、objc_getClass()
- 生成⼀个Class对象,比如%c(NSObject),类似于
- %orig
- 函数原来的代码逻辑
- %ctor
- 在加载动态库时调⽤
- %dtor
- 在程序退出时调⽤
- logify.pl
- 可以将一个头⽂件快速转换成已经包含打印信息的xm文件(方法比较多,并且需要打印信息时可能需要用到该工具)
logify.pl xx.h > xx.xm
- logify.pl生成的
xm
⽂件,有很多时候是编译不通过的,需要进行⼀些处理,最常见的有以下几种情况:- 删掉
__weak
- 删掉
inout
- 协议找不到时,删掉协议 或者 声明一下协议信息
@protocol XXTestDelegate;
- 类型转换错误时,替换
HBLogDebug(@" = 0x%x", (unsigned int)r);
为HBLogDebug(@" = 0x%@", r);
- 类名找不到时,声明一下类信息
@class XXPerson;
或者 替换类名为void
,⽐如将XXPerson *
替换为void *
- 删掉
- 如果有额外的资源文件(⽐如图⽚),放在tweak项目的layout⽂件夹中,对应着手机的根路径
/
二、Tweak原理
2.1. 编译
1 | make |
执行make
操作时,会编译Tweak代码为动态库(*.dylib
),生成的动态库在tweak项目根目录/.theos/obj/debug/***.dylib
。
2.2. 打包
1 | make package |
由于动态库无法直接安装到手机中使用,所以使用make package
可以把动态库生成deb
文件(生成的文件在tweak项目根目录/packages/***.deb
)。
如果不需要debug版本(debug版本安装包比较大,而且在Cydia中会有debug标识),需要添加debug=0
标识。
1 | make package debug=0 |
make package
已经包含了编译操作,所以可以省掉make
编译指令。
2.3. 安装
1 | make install |
越狱后的应用插件都是以deb
格式通过Cydia安装到手机中的,所以我们编写的代码最终也是打包成deb
,使用make install
传到手机中,让Cydia自动安装。
安装流程:
- ssh自动登录(根据环境变量
THEOS_DEVICE_IP
和THEOS_DEVICE_PORT
自动登录)。 - 把
deb
文件传到电脑映射的端口,并通过usbmuxd传到手机中。 - Cydia会自动把
deb
解压,把dylib
存放在/Library/Mobile/MobileSubstrate/DynamicLibraries/***.dylib
(还会有同名的plist文件,存放的内容是插件宿主的bundleId。其实就是创建tweak项目时的***.plist
文件)。 deb
是通过Cydia里面的Cydia Substrate插件进行管理的(这个插件也是Cydia作者开发的,安装Cydia时会自动安装),
2.4. 运行
当运行APP时,Cydia Substrate会到/Library/Mobile/MobileSubstrate/DynamicLibraries/
文件夹中查找*plist
是否包含APP的bundleId。
如果找到对应的插件,APP会去加载对应的同名动态库。并修改APP内存中的代码逻辑,去执⾏dylib中的函数代码(每次运行APP都会加载动态库)。
所以,theos的tweak不会对APP原来的可执行⽂件进行修改,仅仅是修改了内存中的代码逻辑。
三、疑问
- 未脱壳的APP是否⽀持tweak?
支持,因为tweak是在内存中实现的,并没有修改.app
包中的可执⾏⽂件。
- tweak效果是否永久性的?
取决于tweak中⽤到的APP代码是否被修改过。
- 如果一旦更新APP,tweak会不会失效?
取决于tweak中用到的APP代码是否被修改过。
- 未越狱的手机是否支持tweak?
不⽀持。
- 能不能对Swift/C函数进⾏tweak?
可以,⽅式跟OC不一样。
能不能对游戏项⽬进行tweak?
可以。但是游戏⼤多数是通过C++/C#编写的,而且类名、函数名会进行混淆操作。如果需要对多个文件进行拦截,在tweak项目
Makefile
文件的项目名_FILES
后面拼接新的文件名就行,使用空格隔开。如果需要自定义文件夹,就要填写文件的相对路径,在使用import
时也是使用的相对路径。
1 | // Makefile |