命令行工具
命令行工具的本质也是Mach-o文件,是可执行文件类型。跟APP之间的差别就在于它没有界面,只能通过命令行调用。如果我们想创建iPhone上的命令行工具可以通过创建iOS项目来配置签名、架构等信息,如果想开发macOS上的命令行工具可以通过创建Mac项目进行配置。
创建命令行项目
创建iPhone命令行工具跟创建iOS项目一样。需要完成的步骤如下:
1. 创建一个新的iOS项目,选择 Release 版本,配置证书和描述文件
2. 去掉`main`函数中的`UIApplicationMain(argc, argv, nil, appDelegateClassName)`代码,删除多余的类和图片资源,保留`info.plist`文件
3. 选择`command + b`进行编译,编译成功后在`Product -> Show Build Folder in Finder -> Products -> Release-iphoneos`文件中看到编译好的程序
4. 右击`显示包内容`,选择对应的可执行文件,通过 iFunbox 将可以执行文件放到手机的`usr/bin`目录
7. 登录手机,通过`chmod +x /usr/bin/xxx`配置可执行权限,这样我们就能在登录手机后直接使用相关指令2
3
4
5
开发macOS的命令行工具和手机的步骤是一样的,只不过是在创建项目的时候选择Mac项目进行创建。main函数中的代码如下:
// argv 是命令行使用的参数
int main(int argc, char * argv[]) {
@autoreleasepool {
// 这里写入想做的事情
}
// 这里直接return 0 就不会显示界面了
return 0;
}2
3
4
5
6
7
8
读取Mach-O文件的格式
看文件是什么格式可以通过读取头部的前4个字节来进行区分,比如Mach-O的文件格式有很多种,胖二进制文件、arm64、arm_v7格式。我们可以通过xnu源码进行查看文件格式。整个代码如下:
#import <UIKit/UIKit.h>
#import <mach-o/fat.h>
#import <mach-o/loader.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString *apppath = @"/private/var/containers/Bundle/Application/9CE279BE-3ACA-48B5-BEDA-DF4E326EEC0E/XinGaiNian1.app/XinGaiNian1";
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:apppath];
int length = sizeof(uint32_t);
// 读取最前面的4个字节(magic number,魔数,用来标识文件类型)
NSData *magicData = [handle readDataOfLength:length];
// 魔数,用来标识文件类型
uint32_t magicNunber;
[magicData getBytes:&magicNunber length:length];
if (magicNunber == FAT_CIGAM || magicNunber == FAT_MAGIC) {
printf("FAT文件\n");
}else if (magicNunber == MH_CIGAM || magicNunber == MH_MAGIC) {
printf("非64位架构文件\n");
}if (magicNunber == MH_CIGAM_64 || magicNunber == MH_MAGIC_64) {
printf("64位架构文件\n");
}else{
printf("读取失败\n");
}
printf("magicNunber = 0x%x",magicNunber);
[handle closeFile];
}
return 0;
}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
命令行工具参数
参数是通过 main 函数中的argv[]参数进行传递的,argv[0]是当前程序的可执行文件路径。Xcode可以通过在Edit Scheme -> Arguments -> Arguments Passed on Launch中添加参数进行测试。
* argc: 参数的格式
* argv[]: 存放参数的数据
* argv[0]: 当前可执行文件路径2
3
如果我们想在命令行后添加参数,可以通过以下的代码进行解析:
int main(int argc, char * argv[]) {
@autoreleasepool {
if (argc == 1) {
printf("-l 查看MachO信息\n");
return 0;
}
if (strcmp(argv[1], "-l") != 0){
printf("打印-l相关信息");
return 0;
}
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
命令行工具权限
在iOS中每个应用都有自己的沙盒,如果想读取别的应用Mach-O文件,需要给与一定的权限,我们可以通过签名给工具添加权限。签名有两种方式codesigin和ldid。这里主要是ldid的用法。
* `ldid -e MobileSafari` : 导出权限,例如`ldid -e xxx > xxx.entitlements`,一个`>`表示覆盖,两个`>>`表示追加
* `ldid -S cat` : 查看权限
* `ldid -Stfp.xml gdb` : 将权限导入到可执行文件中2
3
我们怎么知道要签什么权限呢?可以通过别人的应用作为参考进行查看。比如之前的Clutch脱壳指令,或者手机桌面程序SpringBoard,我们可以导出它的权限文件,然后将权限通过ldid签到我们的指令中。
makefile
在李明杰老师的课程中,他先创建Makefile文件,通过Xcode中TARGETS -> Build Phses -> Run Script运行make脚本,达到每次编译项目之后可以直接通过脚本中的make指令,创建对应的指令工具并对其进行签名,我们也可以研究一下怎么通过Makefile文件达到一些自动化的东西。
# Config
ARCH = arm64
IOS_VERSION = 8.0
EXECUTABLE_NAME = MJAppTools
# Dirs
RELEASE_DIR = Release
PROJECT_DIR = MJAppTools
EXECUTABLE_FILE = $(RELEASE_DIR)/$(EXECUTABLE_NAME)
# Header Files
HEADER_DIR1 = $(PROJECT_DIR)
HEADER_DIR2 = $(PROJECT_DIR)/SystemHeaders
HEADER_DIR3 = $(PROJECT_DIR)/Extensions
HEADER_DIR4 = $(PROJECT_DIR)/Models
HEADER_DIR5 = $(PROJECT_DIR)/Tools
# Source Files
SOURCE_FILES = $(HEADER_DIR1)/*.m
SOURCE_FILES += $(HEADER_DIR3)/*.m
SOURCE_FILES += $(HEADER_DIR4)/*.m
SOURCE_FILES += $(HEADER_DIR5)/*.m
# Entitlements
ENTITLEMENT_FILE = $(RELEASE_DIR)/MJAppTools.entitlements
codesign: compile
@codesign -fs- --entitlements $(ENTITLEMENT_FILE) $(EXECUTABLE_FILE)
compile:
@xcrun -sdk iphoneos \
clang -arch $(ARCH) \
-mios-version-min=$(IOS_VERSION) \
-fobjc-arc \
-framework Foundation \
-framework UIKit \
-framework MobileCoreServices \
-Os \
-I $(HEADER_DIR1) \
-I $(HEADER_DIR2) \
-I $(HEADER_DIR3) \
-I $(HEADER_DIR4) \
-I $(HEADER_DIR5) \
$(SOURCE_FILES) \
-o $(EXECUTABLE_FILE)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
相关阅读: