0%

【iOS逆向】使用Theos编写CydiaSubstrate插件

常用的逆向手段有

  1. 通过Reveal查看App的视图和ViewController
  2. 通过cycript动态调试正在运行的App
  3. 通过class-dump导出脱壳后的可执行文件的头文件
  4. 通过IDAHopper反编译脱壳后的可执行文件

通过逆向确定了实现逻辑,可以通过Theos的tweak编写hook插件

安装Theos

1. 下载

1
sudo git clone --recursive https://github.com/theos/theos.git ~/theos

2. 添加环境变量

编辑~/.bash_profile

1
2
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH

打开终端,这时候可以使用命令了nic.pl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ nic.pl
NIC 2.0 - New Instance Creator
------------------------------
[1.] iphone/activator_event
[2.] iphone/application_modern
[3.] iphone/application_swift
[4.] iphone/flipswitch_switch
[5.] iphone/framework
[6.] iphone/library
[7.] iphone/preference_bundle_modern
[8.] iphone/tool
[9.] iphone/tool_swift
[10.] iphone/tweak
[11.] iphone/xpc_service
Choose a Template (required):

3. 创建tweak项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 选择 [10.] iphone/tweak
Choose a Template (required): 10
# 项目名
Project Name (required): mytweak
# 唯一标识bundleId(自己定)
Package Name [com.yourcompany.mytweak]: com.bomo.mytweak
# 作者
Author/Maintainer Name [bomo]: bomo
# 需要hook的App包名
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.bomo.demo
# 安装完成后需要重启的App(貌似没用,可以生成后手动改)
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]:
Instantiating iphone/tweak in mytweak/...
Done.

得到4个文件

1
2
3
4
control
Makefile
mytweak.plist
Tweak.x

Tweak.x就是源码文件,编写tweak代码

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
%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

Logos语法其实是CydiaSubstruct框架提供的一组宏定义。便于开发者使用宏进行HOOK操作。语法简单,功能强大且稳定。

Logos语法分为三大类

  • Top level
  • Block level
  • Function level

Top level

这个TopLevel指令不放在BlockLevel中。
%config全局配置
%hookf:用于hook符号(C/C++方法)
%ctor会在动态库(dylib)被加载的时候调用,用于初始化
%dtor会在程序结束的时候调用,通常用于回收资源

Block level

这一类型的指令会开辟一个代码块,以%end结束

%group

%init配合使用,%group用于给代码块分组,%init用于让代码块生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%group iOS8
%hook IOS8ClassName
// TODO: your code here
%end
%end

%group iOS9
%hook IOS9ClassName
// TODO: your code here
%end
%end

%ctor {
if (kCFCoreFoundationVersionNumber > 1200) {
%init(iOS9);
} else {
%init(iOS8);
}
}

%hook

用于hook类方法,%new用于声明新方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%hook SBApplicationController

// hook实例方法
-(NSInteger)method:(NSString *)name {
// 调用原来方法
NSInteger result = %orig;
// 修改返回值
return result + 1;
}

// 新增类方法
%new
+ (NSInteger)someNewMethod {
return 1 + 1;
}

%end

%subclass

用于新增类,新增方法都要加上%new

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
%subclass MyNewObject : NSObject

// 声明属性
%property (nonatomic, copy) id someValue2;

// 构造方法
- (id)init {
self = %orig;
[self setSomeValue:@"value"];
return self;
}

// someValue和setSomeValue:方法等价于声明属性
// `@property (nonatomic, retain) id someValue;`
%new
- (id)someValue {
return objc_getAssociatedObject(self, @selector(someValue));
}

%new
- (void)setSomeValue:(id)value {
objc_setAssociatedObject(self, @selector(someValue), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

%end

使用新类MyNewObject

1
2
MyNewObject *myObject = [[%c(MyNewObject) alloc] init];
NSLog(@"myObject: %@", [myObject someValue]);

Function level

这一块的指令就放在方法中。
%init:如上,与%group配合使用
%class:废弃不用
%c(className):生成一个Class对象,如:%c(NSObject),相当于NSObject.class
%orig:调用方法原来的实现,包括参数
%log:打印方法和参数

编译

编译配置

Makefile配置编译选项和设备信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装完成后重启SB
after-install::
install.exec "killall -9 SpringBoard"

# 如果是RELEASE,设置DEBUG=0
DEBUG = 0

# 越狱iPhone的ip地址和端口(也可以配置到 ~/.bash_profile 上)
THEOS_DEVICE_IP = 192.168.1.2
THEOS_DEVICE_PORT = 22

# 指定支持的处理器架构
ARCHS = armv7 arm64

# 指定需要的SDK版本iphone:Base SDK:Deployment Target
TARGET = iphone:latest:9.0 //最新的SDK,程序发布在iOS9.0以上

# 导入框架,多个框架时用空格隔开
mytweak_FRAMEWORKS = UIKit
mytweak_PRIVATE_FRAMEWORKS = AppSupport

# 链接libsqlite3.0.dylib、libz.dylib和dylib1.o
mytweak_LDFLAGS = -lz –lsqlite3.0 –dylib1.o

安装到手机

1
2
3
4
5
6
# 编译(make package包含了make,这个也可以省略)
make
# 打包(release)
make packages debug=0
# 安装到手机上(需要设置环境变量THEOS_DEVICE_IP和THEOS_DEVICE_PORT)
make install

合并起来

1
make packages debug=0 && make install

包会被安装到/Library/MobileSubstrate/DynamicLibraries/目录下

1
2
/Library/MobileSubstrate/DynamicLibraries/xxx.plist(存放要hook App的bundleId)
/Library/MobileSubstrate/DynamicLibraries/xxx.dylib

资源处理

有时候我们有一些资源需要添加到插件中,例如图片
在tweak中使用到的资源,可以放到工程的layout/Library/PreferenceLoader/Preferences/xxx下面,在代码中通过绝对路径读取,工程中的layout路径相当于手机的根路径,该路径下的文件会被安装到手机对应的路径下

1
2
3
4
5
6
- Makefile
- control
- tweakxxx.plist
- layout/Library/PreferenceLoader/Preferences
|- mytweak
|- icon.png

上面icon.png资源会被安装到手机的/Library/PreferenceLoader/Preferences/mytweak/icon.png路径下

多文件

插件代码多的时候,可能会有多个源文件(Person.m, Tweak1.mx, Tweak2.mx),如下

1
2
3
4
5
6
7
8
9
- Makefile
- control
- tweakxxx.plist
- src
|- Tweak1.mx
|- Tewak2.mx
|- Model
|- Person.h
|- Person.m

需要在Makefile里面配置需要编译的文件,使用空格隔开

1
tweakwechat_FILES = src/Tweak1.xm src/Tweak2.xm src/Model/Person.m

如果文件多,可以使用通配符*

1
tweakwechat_FILES = src/*.xm src/Model/*.m

Tweak1.mx中引用其他头文件的时候需要使用相对路径,如下

1
#import "Model/Person.h"

Theos-Tweak原理

  1. make编译代码为动态库dylib
  2. make package将 dylib 和资源打包成 deb
  3. make install将 deb 发送到手机上,并通过 cydia 安装 deb
  4. 插件会安装到/Library/MobileSubstrate/DynamicLibraries
  5. 启动App,Cydia Substrate会根据已装插件的plist里面配置的bundleId与App的bundleId一致,就会自动注入对应的dylib
  6. dylib会根据编写的代码自动hook对应的类和方法

卸载插件只需要删除/Library/MobileSubstrate/DynamicLibraries里面对应的xxx.dylibxxx.plist即可,如果有包含资源的话,还需要把对应的资源删掉

未脱壳的App也可以注入dylib
在内存中修改逻辑,不修改原来的App

引用