###本笔记是学习了(IOS应用逆向与安全)一书所得.非常不错的一本书.很值得看.
#ios9 ssh安装
cydia 搜索 openssh 安装即可
#ios10 ssh安装
ios 10 ~ 10.2 不用安装 openssh, yalu 内置了一个相对轻量级的 ssh 服务 dropdear, 首先在 cydia 搜索并安装 MTerminal 和 adv-cmds, 然后打开 MTerminal 输入 ps aux | grep dropdear
命令. 表示默认支持 usb 连接. 然后在 mac 上打开终端 输入命令 iproxy 2222 22
这是做端口转发 (iproxy需要安装),如果是要通过 wifi 连接要手机终端输入命令 /usr/local/bin/dropdear -F -R -p 22
.然后 mac 执行 ssh root@192.168.1.1
(192.168.1.1 是连接设备ip地址)
#iproxy 安装
在电脑终端输入 brew install libimobiledevice
安装后就可以使用 iproxy 2222 22
命令
设置免密码登录iphone设备 ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.31.98
每次输入命令进行端口转发比较麻烦,可以把命令写到开机启动项中. 创建文命令touch ~/Library/LaunchAgents/com.usbmux.iproxy.plist
然后输入内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.usbmux.iproxy</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/iproxy</string>
<string>2222</string>
<string>22</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
复制代码
然后在终端运行命令 launchctl load ~/Library/LaunchAgents/com.usbmux.iproxy.plist
如果觉得每次输入 ssh root@192.168.31.230 -p 2222
命令麻烦,可以通过给它指定一个别名进行 ssh
连接.在 mac 的 Home (家目录) 下 .ssh 文件夹中创建一个 config 文件 输入命令 touch ~/.ssh/config
输入命令 open ~/.ssh/config
打开文件.文件中写入内容
Host 5s #自定义设备名称
HostName localhost #通过 usb 端口映射, 写 localhost .
User root #以 root 用户登录
Port 2222 #指定端口号为映射的端口号 2222
复制代码
#ios系统目录结构
Applications :存放所有系统app和来自cydia的app,不包括app store下载的app
Developer :供开发者使用
Library :系统资源,用户设置,例如,logs 系统日志,Ringtones 系统自带铃声,LaunchDaemons 是启动 daemon 程序. 其中比较重要的目录是/Library/MobileSubstrate,里面存放了所有基于cydia substrate 的插件 (需要在cydia 安装 cydia substrate)
/system :系统的重要组成部分,
/system/Library/Carrier Bundles : 里面是运营商的一些设置
/system/Library/Frameworks 和 /system/Library/PrivateFrameworks :里面是系统中各种公开的和未公开的framework
/system/Library/CoreServices/SpringBoard.app 是桌面管理器,是用户和系统的直接交互的部分.
/system/Library/Perferencebundles :里面存放的是系统设置中的一些设置项.
/User :用户目录,实际指向/var/mobile/
/User/Media/ : 里面存放的是相册等
/User/Library/ : 里面存放的是短信,邮件等.
/bin/ : 存放用户级二进制文件,例如mv ls 等.
/dev/ : 设备文件,每个设备在/dev/目录下都有一个对应的文件(节点)
/etc/ : 存放系统脚本, hosts 配置, ssh 配置文件等,实际指向/private/etc/
/sbin/ : 存放系统级二进制文件,例如reboot,mouut等
/usr/ 用户工具和程序./usr/include/中存放标准c头文件./usr/lib/中存放库文件.
/var/ :一些经常改动的文件.包括keychains, 临时文件,从app store 下载的应用.
#文件权限
ls -l 查看文件命令 drwxr-xr-x 第1个字符, l 是符号链接文件, d 是文件夹 - 是普通文件
第2-4个字符表示文件所有者权限,例如 lrwxr-xr-x
表示文件所有者组拥有读 r 和写 w 和 执行 x 权限
第5-7字符便是文件所属组的权限,例如 lrwxr-xr-x
表示所属组拥有读 r 和 执行 x 权限, 但没有写 w 权限
第8-10字符表示其他人的权限,例如 lrwxr-xr-x
表示其他人拥有读 r 和执行 x 权限 但没有写 w 权限
root 表示文件所属用户, wheel 和 admin 表示文件所属组
r,w,x 中每一个都对应着二进制1或0,可以写成111,所以可以用4代表读取权限.用2 代表写权限,用1代表执行权限,当然,也可以组合使用,例如 r-x
代表读和执行权限,即 4 读 + 1执行 = 5
可以使用chmod
修改文件或者目录的权限,有两种模式,既符号模式和绝对模式,符号模式:表示设置所有者权限为删除和执行,组权限文渡河执行,其他人权限增加写和执行,示例如下
chmod u-x,g=rx,o+wx testfile
复制代码
绝对值模式:便是设置所有者权限为读,写和执行(r,w和x),组权限为读和执行r和x),其他人权限为读和执行(r和x),示例如下
chmod 755 testfile
复制代码
#cydia substrate
cydia substrate (以前叫做 MobileSubstrate)是一个框架,允许第三方开发者在越狱系统的方法里打一些运行时补丁和扩展一些方法,是开发越狱插件的基石.首先,在越狱设备上面安装 cydia substrate 里面包含三个主要模块,分别是 MobileHooker,MobileLoader和safe mode.
#MobileHooker
MobileHooker 用于替换系统和应用的方法.他提供MHookMessageEx来Hook OC 函数,提供MSHookFunction 来Hook C函数.
#MobileLoader
MobileLoader 用于将第三方动态库加载到运行的目标应用里面.MobileLoader首先通过环境变量 DYLD_INSERT_LIBEARIES 把它自己加载到目标应用里面,然后查找/Library/MobileSubstrate/DynamicLibraries/目录下面所有的plist文件,如果plist文件里面的配置信息符合当前运行应用,就会通过dlopen函数打开对应的dylib文件,plist文件中有一些过滤条件,只有满足条件时,第三方动态库才会加载,过滤条件如下:
CoreFoundationVersion
只有 CoreFoundationVersion 对的版本高于某个值时才会加载.
Bundles
只有应用的 Bundle id 在列表中时才会加载.
Classes
只有应用实现了某个铁定的OC类时才会加载.
Executables
只有应用的可执行文件的名字和该列表匹配时才会加载 例如注入SpringBoard 可以写成如下方式:
Filter = {
Bundles = (com.apple.springboare);
};
复制代码
基于安全考虑当plist文件不存在时,dylib不会注入所有进程.如果确定要这么做可以使用如下方式:
{
Filter = {
Bundles = (
"com.apple.Security",
);
};
}
复制代码
#Safe mode
如果插件加载导致SpringBoard崩溃,MobileSafety 会捕获这个异常并让设备进入安全模式. 在安全模式中,所有的第三方插件都不会加载.所以,当因为编写插件导致崩溃而进入安全模式后,可以找到最近安装的插件,然后将其卸载.
#越狱必备工具
adv-cmds
在手机上使用ps命令查看当前进程id及可执行文件的路径,如果出现-sh:ps:command not found
这样的错误,这是因为没有安装 adv-cmds. adv-cmds 是提供ps命令.
appsync
直接修改一个应用的结构文件会破坏应用本身的签名信息,所以,安装修改后的应用汇出现 Failed to Verify code signature of xxxx
这样的错误. 这时需要安装appsync,让系统不在验证应用的签名.在安装时要选择能够支持当前系统版本.
ifile
是手机的文件管理器,用于管理手机中的文件,修改文件的权限等.
scp
对于ios10 以后的版本,使用yalu越狱后就没有scp这个工具了,可以自己下载scp使用,电脑的pp助手或者其他工具 将scp传到/usr/bin/目录下,然后执行命令
cd /usr/bin/
ldid -S scp
chmod 777 scp
复制代码
或者,在cydia中搜索并安装rsync来替代scp
rsync -avze 'ssh -p 2222' root@localhost:/tmp/tmpfile ./
rsync -avze 'ssh -p 2222' ./utils.cy root@localhost:/var/root/
复制代码
#逆向工具详解
应用上传到app store后,苹果会对应用进行加密.在分析应用时需要先对应用进行解密.这就要用到 dumpdecrypted
, dumpdecrypted 是一个开源工具,它会注入可执行文件,动态的从内存中dump出解密后的内容. dumpdecrypted下载地址.或使用git命令 git clone https://github.com/stefanesser/dumpdecrypted.git (前提是你已经安装了git命令支持).下载完后 cd 到 dumpdecrypted 目录中 然后执行 make
命令,编译完成后在 dumpdecrypted 目录中生成一个 dumpdecrypted.dylib 文件.
打开终端 ssh 6s
连接到远程设备,然后输入命令 ps -e
查看当前进程.如果要解密的app不在列表中请先打开app在执行 ps -e .找到你要解密的app 例如:要解密 FanweApp
iphone-6s-plus:~ root# ps -e
这里省略几百行
/var/mobile/Containers/Bundle/Application/20456510-BF09-4C3D-AE2E-5BB6E51D4EB2/xxx.app/FanweApp
再次输入命令,注意这里的路径就是上面找到的
cat /var/mobile/Containers/Bundle/Application/20456510-BF09-4C3D-AE2E-5BB6E51D4EB2/xxx.app/Info.plist | grep CFBundleIdentifier -A 1
<key>CFBundleIdentifier</key>
<string>com.huig.xiaojiejie</string>
复制代码
然后新建个工程把下面代码放到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions 方法中
NSString *bundleID = @"com.huig.xxx"; // 注意这里就是上面刚刚找到的
NSURL *dataURL = [[NSClassFromString(@"LSApplicationProxy") performSelector:@selector(applicationProxyForIdentifier:) withObject:bundleID] performSelector:@selector(dataContainerURL)];
NSLog(@"%@",[dataURL.absoluteString stringByAppendingString:@"/Documents"]);
复制代码
然后选择目标设备运行.即可在xcode看到打印的路径 /var/mobile/Containers/Data/Application/73895B96-859A-4895-808E-F20A554657B1/Documents
然后把dumpdecrypted.dylib复制到这个目录系,在mac 终端执行命令 scp -p 2222 ./dumpdecrypted.dylib root@localhost:/var/mobile/Containers/Data/Application/73895B96-859A-4895-808E-F20A554657B1/Documents
现在就可以正式开始解密了, 输入命令
ssh 6sp 登录到终端设备
cd /var/mobile/Containers/Data/Application/73895B96-859A-4895-808E-F20A554657B1/Documents/
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/20456510-BF09-4C3D-AE2E-5BB6E51D4EB2/XiaoJieJie.app/FanweApp
复制代码
解密后会在当前目录生成一个FanweApp.decrypted 文件,赶紧把他复制到mac设备用吧.使用命令复制到mac中scp -p 2222 root@localhost:/var/mobile/Containers/Data/Application/73895B96-859A-4895-808E-F20A554657B1/Documents/FanweApp.decrypted ./
查看FanweApp.decrypted的加密标识,命令如下 otool -l FanweApp.decrypted | grep crypt
显示如下
MacBook-Pro:TargetApp huig$ otool -l FanweApp | grep crypt
FanweApp.decrypted (architecture armv7):
cryptoff 16384
cryptsize 28164096
cryptid 1
FanweApp.decrypted (architecture arm64):
cryptoff 16384
cryptsize 32243712
cryptid 0
复制代码
cryptid 为 0 标识没有加密
除了使用 dumpdecrypted 还可以用 Clutch 解密,具体使用如下 下载Clutch 用xcode打开 选择debug模式生成所有架构(build Active archiecture Only - NO),运行目标设备真机 然后 command + b 编译,把生成的Clutch上传到设备的/usr/bin/目录下: 如下
scp -P 2222 ./Clutch root@localhost:/usr/bin/
ssh 6sp
chmod +x /usr/bin/Clutch // 这里是给Clutch权限
复制代码
使用方法: 在设备终端上输入 Clutch -i
查看安装设备 Bundle id 然后 Clutch -b com.xxx.xxx
等待完成后会显示DOME: /private/var/mobile/Documents/Dumped/xxx.ipa
这就是解密出来的文件,复制到mac.
#class-dump使用
class-dump 主要用来获取解密后二进制文件中的头文件信息.也就是.h文件,命令./class-dump --arch arm64 二进制文件 -H -o ./要保存的文件夹名称
(注意:如果是armv7架构可能会报错,需要对class-dump进行修改) CDObjectiveC2Processor.m 文件中的 objc2Class.data = value & ~7; 改为 objc2Class.data = value & ~3;
#lldb 常用指令
查看偏移地址
im li -o -f
或im li -o -f
"app名称"
b 0x0000aaaa
(基地址+偏移的结果)
br a -a 0x0000aaaa
'基地址+偏移'
打印
po
是 print object 的简写 打印oc对象
po $x0
po [$x0 class]
显示类名
p 16
; 16 默认格式
p/x 16
; 0x10 十六进制显示
p/t
; 二进制显示
p/c
; 打印字符
p/s
打印以空终止的字符串(\0 结尾的字符串)
e int $a = 2
; 声明变量 a 初始值 2
p $a * 19
; a 乘以 19 并输出结果
e NSArray *$array = @[@"a",@"b",@"c"]
; 声明一个数组
p [$array count]
; 输出数组的 count
po [[$array objectAtIndex:0] uppercasrString]
; 输出数组下标 0 的值
po (char)[[$array objectAtIndex$a] characterAtIndex:0]
; 输出数组下标 $a 中 第0个字符. 必须指定字符的类型
p/d (char)[[$array objectAtIndex$a] characterAtIndex:0]
; 输出数组下标 $a 中 第0个字符的 ascii 码.
b main.m:17
; 直接在 main.m 文件的第17行下一个断点
breakpoint set -f "-[NSarray objectAtIndex:]"
; 给函数下断点
b isEven
; 在一个符号上下断点 (c 语言的函数) br s -F isEven
这样添加条件断点 breakpoint modify -c 'i == 99' 1
breakpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> p i
> DONE
复制代码
e char *$str = (char *)malloc(8)
; 声明一个变量 str 空间为 8 字节
e (void)strcpy($str, "huig")
; 给 str 赋值 huig
e $str[1] = 'o'
; 将 str 中位置1的内容修改为 o
x/1 $str
; 显示 4 个字节
x/1 '$str + 2'
; 从 str+2 开始显示
e (void)free($str)
; 释放 str 内存
e id $myView = (id)0x7f82b1d01fd0
; 获取指定内存地址的对象
e (void)[$myView setBackgroundColor:[UIColor blueColor]]
; 设置对象属性
caflush
等同于 e (void)[CATransaction flush]
监视 _layer 什么时候这个地址被写入
p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "_layer")) ;
现在我们知道 ($myView + 8) 是被写入的内存地址:
watchpoint set expression -- (int *)$myView + 8
;
这被以 wivar $myView _layer 加入到 Chisel 中。
非重写方法的符号断点
bmessage -[MyViewController viewDidAppear:]
#LLDB 和 Python
script import os
script os.system("open http://www.objc.io/")
def caflushCommand(debugger, command, result, internal_dict):
debugger.HandleCommand("e (void)[CATransaction flush]") ;
复制代码
把语句放到文件 ~/myCommands.py 中:
再 LLDB 中运行:command script import ~/myCommands.py
po $x0
调用者本身.也就是self
x/s $x1
将$x1保存的是方法名 x/s 将内存以字符串形式显示
x2 -- x7 保存方法参数一个6个,超过6个的会保存到栈中
打印内存中栈的值,读入栈中的参数
memory read -force -f A $sp $fp
bt 打印当前方法的调用栈
frame #1: 0x0aaaaaa
;内存地址减去偏移可以对应到二进制文件中的对应地址.可以查看到来自那个方法
在 hopper 中按键盘 g 可以快速跳转到该方法
register read
读取所有寄存器的值
register read $x0
读取某个寄存器的值
register write $x5 1
修改寄存器的值
si
跳到当前指令内部
p/x
x/xg
ni
跳过当前指令
finish
返回到上层调用栈
thread return
不在执行下面的代码,直接从当前调用栈返回一个值
br list
查看当前断点列表
br del
删除断点
br del 1.1.1
删除指定编号断点
br dis 2.1
使断点2.1失效
br endble 2.1
使断点生效
watchpoint set expression -w write -- 0x0aaaaaa
给某个地址设置观察断点,当对改地址的内存进行写操作时就会触发断点
x/10xg 0x0000aaaa
读取目标地址的内存指令. 这里x代表用十六进制来显示结果, g 代表 giant word (8字节)大小. 所以x/10xg 就是显示0x0000aaaa 所在指空间的 10个 64位的元素内容.常见大小格式 b-byte(1字节),h-half word(2字节),w-word(4字节),g-giant word(8字节)
dis -a $sp
反汇编指定地址,这里是sp寄存器所对应的地址
f2
切换到当前调用栈为2的位置,也解释 bt 中的 frame #2
thread info
输出当前线程信息
b ptrace -c xxx
满足条件后程序才会中断
lldb 还有很多命令 可以通过查找命令 help 和 apropos, help 查看所有命令, help 命令名 查看命令的详细用法
如果没有记住某个命令,只记得关键字,可以使用 apropos 关键字 来搜索 关键字相关信息
还可以为命令设置别名,然后将其写到lldb的初始化文件 ~/.lldbinit
文件中
格式 : command alias -H "Reload ~/.lldbinit" -h "Reload ~/.lldbinit" -- reload_lldbinit command source ~/.lldbinit
command alias pcc process connect connect://192.168.31.230:1234
command alias iheap command script import lldb.macosx.heap
在lldb 输入 iheap 加载 工具命令 objc_refs 得到内存指定类的对象 , ptr_refs 得到一个内存所有被引用的地方 , cstr_ref 搜索内存中指定的C 字符串
lldb 添加脚本命令工具 chisel 安装命令 brew install chisel
和 dslldb.py 下载地址 https://github.com/DerekSelander/LLDB.git 下载后把 dslldb.py 文件路径 command script import ~/LLDB/lldb_commands/dslldb.py
添加到 ~/.lldbinit 文件中
lldb 工具 chisel 命令
pviews ; 显示当前页面所有控件,
pblock ; block 内存地址,
pactions ;actions内存地址,
pvc ;显示vc控制器,
presponder ;responder内存地址,
methods ;method内存地址,
复制代码
search UITextFidle 查找所有UITextFidle (cycript 中choose(对象名)), pviews 如果出错 请先记载 expression @import UIkit
为了查看内存分配调用栈,xcode需要开启 MallocStackLogging . edit cheme - run - Envlronment Varlables - 加号 - 填入 MallocStackLogging - value 填入 1
#cycript 介绍
e @import UIkit
e UIApplication *$app = [UIApplication sharedApplication]
e [(scbutton *)0xaaaaa setTitle:@"hui" forState:UIControlStateNonmal]
e (void)[CATransaction flush]
复制代码
点击xcode - debug - debug workflow - shared libranries 可以查看当前加载的模块
点击xcode - debug - debug workflow - View memory - 输入 要查看的内存地址.可以查到某个地址的模块信息
将 command regex bclass 's/(.+)/rb \[%1 /'
写入 ~/.lldbinit 文件中 使用命令 bclass 类名 .可以对某各类的所有方法下断点并跟踪打印调用参数
image lookup -rn login WeChat
可以查看 WeChat 中有个login 的信息
#theos 使用技巧
%c() 就是 objc_getClass()
bgView *bgv = [[%c(bgView) alloc] init];
复制代码
makefile 文件配置信息
指定 arc 模式编译,如果有 mrc 文件那就单独指定该文件编译模式
TWEAK_NAME = tweakDemo // demo 名称
TWEAK_NAME_FILES = tweakDemo.xm temp/temp.xm //编译多个xm文件
TWEAK_NAME_CFLAGS = -fobjc-arc
temp/temp.m_CFLAGS = -fno-objc-arc
复制代码
%new 给一个类添加一个方法
%new(v@:@)
- (void)newMethod:(NSString *)str {
NSLog(@" [newMethod] : %@ ",str);
}
复制代码
%new(v@:@) new后面的括号里面是方法的签名,如果不写theos会自动生成.v表示 返回 void 类型 接着 @ 表示调用者 : 表示 SEL, 最后一个表示参数
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%s",method_getTypeEncoding(class_getInstanceMethod([self class],@selector(printMessage:))));
}
- (int)printMessage:(NSString *)message {
NSLog(@"[test app]: %@",message);
return 1;
}
复制代码
加入文件资源,这是后就需要用到 layout. 首先需要创建存放文件的文件夹,使用命令mkdir -p layout/Library/Application\ Support/TweakDemo/ TweakDemo
然后新建一个plist文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>huig</string>
</dict>
</plist>
复制代码
安装时会将layout文件下的内容按照指定目录放置 获取资源文件
NSString *resourcePath = @"/Library/Application Support/TweakDemo/";
NSDictionary *resourceDict = [[NSDictionary alloc] initWithContentsOfFile:[resourcePath stringByAppendingPathComponent:@"resource.plist"]];
复制代码
继承第三方库 .a 和 framework 两种库,将 库文件放到根目录下 BookLib 和 BookFramework 下,然后编辑 makefile 文件.指定编译参数
TWEAK_NAME_CFLAGS = -fobjc-arc -I ./BookLib/include -F ./BookFramework
TWEAK_NAME_LDFLAGS = BookLib
TWEAK_NAME_FRAMEWORKS = BookFramework
复制代码
在代码的头文件中导入头文件
#import <BookLib/BookLib.h>
#import <BookFramework/BookFramework.h>
复制代码
logify.pl 用于调试 app 打印 log
logify.pl viewController.h >> ./viewController.xm
直接在当前目录下生成 xm 文件
logify.pl viewController.h viewController.xm
直接在控制台显示结果代码
然后在 makefile 中加入编辑列表中加入文件名 viewController.xm 编译打包安装都设备上运行就可以看到 log
有时候 make 编译时 出现 CDUnknownBlockType 错误.将 CDUnknownBlockType 替换成 id 如果是提示某个类名错误那是因为没有导入头文件.只需要用 @class 类名 声明一个这个类就可以了. .cxx 文件直接删除 出现 @" = 0x%x", (unsigned int) r 错误 ,把其替换成 @" = 0x%lx", (uintptr_t) r
#MonkeyDev 使用技巧
使用 MonkeyDev 新建一个 Logos Tweak 工程 然后在 build settings 中对工程进行一些初始化设置
xcode 不能正确显示 xm 格式文件, 可以在右边窗口 type 中选择
objective-c source 然后重启 xcode 即可正常显示代码
MonkeyDevBuildPackageOnAnyBuild = 每次 Build 都会生成 deb 包
MonkeyDevClearUiCacheOnInstall = 安装时清除 uicache
MonkeyDevCopyOnBuild = Build 时将 deb 包复制到设备的 /var/root/MonkeyDevBuilds/ 目录下
MonkeyDevDeviceIP = 目标设备 ip 地址,默认 usb 连接 , localhost
MonkeyDevDevicePost = 目标设备端口,默认22
MonkeyDevDevicePassword = 目标设备 ssh 密码,默认为空使用免密码登录
MonkeyDevInstallOnAnyBuild = 每次 build 都将 deb 安装到设备
MonkeyDevInstallOnProfiling = 点击 profile 才将 deb 安装到设备
MonkeyDevPath = MonkeyDev 的安装路径
MonkeyDevThoesPath = Thoes 的安装路径
MonkeyDevKillProcessOnInstall = 安装时要杀掉指定的进程,填写进程名
复制代码
快捷键 command + b 就会以 debug 模式安装 deb 包到设备上 快捷键 command + Shift + i 就会以 Release 模式安装 deb 包到设备上 这种模式不会打印 log 信息
#使用 MonkeyDev 新建一个 CaptainHook Tweak 工程
CHDeclareClass(<#count#>)
声明一个类
CHLoadClass(<#name#>)
或者CHLoadLateClass(<#name#>)
在 CHConstructor
中加载类
CHOptimizedMethod(<#count#>, <#args...#>)
目标函数, - 对象方法.
CHOptimizedClassMethod(<#count#>, <#args...#>)
目标函数, + 类方法
CHHook(<#count#>, <#args...#>)
在 CHConstructor 中注册 hook
CHSuper(<#count#>, <#args...#>)
调用 hook 的原函数的实现
在 xcode 的工具栏中 Product - perform Action - Perprocess "对应的文件.m" 中就可以查看 生成的 runtime 代码
获取私有属性 可以使用 CHIvar 获取, 也可以通过 KVC 获取
NSString * password = CHIvar_(self,_password, __strong NSString*);
CHDebugLog(@" password : %@ ", password);
复制代码
#增加属性
CHPropertyRetainNonatomic(<#class#>, <#type#>, <#getter#>, <#setter#>)
@interface CustomViewController
@property (nonatomic, copy) NSString* newProperty;
@end
CHDeclareClass(CustomViewController)
CHPropertyRetainNonatomic(CustomViewController, NSString*, newProperty, setNewProperty);
CHConstructor{
CHLoadLateClass(CustomViewController);
CHHook0(CustomViewController, newProperty);
CHHook1(CustomViewController, setNewProperty);
}
复制代码
#增加方法
@interface CustomViewController
- (void)newMethod:(NSString*) output;
@end
CHDeclareMethod1(void, CustomViewController, newMethod, NSString*, output){
NSLog(@"This is a new method : %@", output);
}
复制代码
#编译生成 dylib 动态库
xcrun --sdk iphoneos clang -dynamiclib -arch arm64 -framework Foundation ./InsertDylib.mm -o ./InsertDylib.dylib -compatibility_version 1 -current_version 1 -install_name @executable_path/Frameworks/InsertDylib.dylib
edit s
key : DYLD_INSERT_LIBRARIES value : @executable_path/Frameworks/InsertDylib.dylib
复制代码
#armv8 基础介绍
多级别权限 el0 - el3 ,0 最低
有31个寄存器 r0 - r30 , 64位 寄存器名为 x0 - x30 , 32位 寄存器 w0 - w30
sp 为64位专用堆栈指针寄存器,可通过寄存器名 wsp 访问堆栈指针的最低 32 位
pc 为保存当前指令地址的 64 位程序计数器, 程序不能直接写 pc v0 - v31 主要用于浮点运算
进程状态 pstate
n - 当有符号整数进行运算时, 1 表示结果为负数, 0 表示结果为正数或者0.
z - 零标志 1 表示运算结果为 0 , 0 表示运算结果不为0.
c - 当加法运算时产生进位时为1, 否则为0, 当减法运算时产生借位时为0, 否则为1.
v = 在加减运算中,当操作数和运算结果为二进制的补码表示的带符号数时, 1 表示符号溢出
指令集
add x0, x1, x2 . x0 = x1 + x2 加法运算
sub w0, w1, w2 . w0 = w1 - w2 减法运算
cmp w0,#0x0 . w0 和 0 相减, 并影响标志位 . 比较相等指令
cmn w0,#0x10 . w0 和 0x10 相加, 并影响标志位 . 比较不等指令
adds 或subs adds x0,x1,x2 . 后面带 S 表示计算的结果影响条件标志位
and x0,x1,x2 . 按位与运算,如果S存在,则更新条件位标记
eor w0,w1,w2 . 按位异或运算
orr w0,w1,w2 . 按位或运算
复制代码
TST R1,#%1
;用于测试在寄存器R1中是否设置了最低位(%表示二进制数)
TST R1,#0xffe
;将寄存器R1的值与立即数0xffe按位与,并根据结果设置CPSR的标志位
MOV R1,R0
;将寄存器R0的值传送到寄存器R1
MOV PC,R14
;将寄存器R14的值传送到PC,常用于子程序返回
MOV R1,R0,LSL#3
;将寄存器R0的值左移3位后传送到R1
ADD R0, R1
; R0 += R1
ADD R0, #0x12
; R0 += 12
ADD.W R0, R1, R2
; R0 = R1+R2
#ASR 算术右移, 移动过程中符号位不变
ASR Rd, Rn, #simm5
; Rd = Rn >> #simm5
ASR Rd, Rn
; Rd >> = Rn
ASR.W Rd,Rm,Rn
; Rd = Rm >> Rn
#LSL 逻辑左移, 移位后寄存器空出的低位补0
LSL Rd, Rn, #simm5
; Rd = Rn << simm5
LSL Rd,Rn
; Rd <<= Rn
LSL.W Rd, Rm, Rn
; Rd = Rm << Rn
#逻辑右移, 移位后寄存器空出的高位补0
LSR Rd, Rn, #simm5
; Rd = Rn >> simm5
LSR Rd,Rn
; Rd >>= Rn
LSR.W Rd, Rm, Rn
; Rd = Rm >> Rn
#圆圈右移, 循环右移. 从右端移除的位将被插入左侧空出的位
ROR Rd,Rn
;Rd>>=Rn
ROR.W Rd,Rm,Rn
;Rd= Rm>>Rn
LDR
从存储器中加载字到一个寄存器中
LDR Xn/Wn,addr
从 addr 地址中读取 8/4 字节内容到 Xn/Wn 中
STR
把一个寄存器按字存储到存储器中
STR str Xn/Wn,addr 将 Xn/Wn
写入 addr 地址指向的内存中
LUDR Xn,[base,#simm5]
从 base+simm5 地址中读取数据到 Xn,Unscaled 表示不需要对齐,读取的数据是多少,这里就是多少.
STUR Xn,[base,#simm5]
将 Xn 写入到 base+simm5 地址指向的内存中
STP Xn1,Xn2,addr
将 Xn1 和 Xn2 写入地址为 addr 的内存中
LDP Xn1,Xn2,addr
重地址 addr 出读取内存到 Xn1 和 Xn2 中
#加载指令可以使用立即数 , 寄存器和对齐寄存器作为偏移.
LDR x1, [x2,#4]
; 读取地址 X2+4 处的值到 X1 中
LDR x1, [x2,x3]
; 读取地址为 x2+x3 处的值到 x1 中
LDR x1, [x2,x3,lsl #3]
; 读取地址为 x2+x3*8 处的值到 x1 中
LDR x1, [X2,#4]!
; 读取地址为 x2+4 处的只到 x1 中, 然后执行 x2 = x2 + 4
LDR x1, [sp], #0x4
; 读取地址为 sp 的值到 x1 中,然后执行 sp = sp + 4
#跳转指令
B
; 无条件转移
B<cond>
; 条件转移 B.cond label ; 如果 cond 条件为真 , 则跳转到 label
BL
; 无条件转移并连接。用于呼叫一个子程序,返回地址被存储在 LR(x30) 中
BLR
; BLR Xn 无条件跳转到 Xn 寄存器的地址, 会将下一条指令地址写到 x30 出
BR
; BR Xn 无条件跳转到 Xn 寄存器的地址
BLX #im
; 使用立即数的 BLX 不要在 CM3 中使用
RET
; 子程序返回
堆栈 ; 后入先出原则
PC 寄存器 ; 记录当前执行代码地址
SP 寄存器 ; 指向栈顶的指针.在内存操作指令中通过 x31 寄存器来访问
LR 寄存器 ; 指向返回地址, 对应寄存器 x30
FP 寄存器 ; 执行栈针底部, 对应寄存器 x29
#栈的区域分布
参数区 ; 存放调用函数传递的参数
连接区 ; 存放调用者的下一条指令
栈针指针存放区 ; 存放调用函数的栈针的底部
寄存器存储区 ; 被调用函数返回需要恢复的寄存器内容
局部存放区 ; 用于存放被调用函数的局部变量
sub sp, sp, #0x30
; = 0x30,开辟大小为 0x30 的栈针空间
stp x29, x30, [sp, #0x20]
; 保存 FP 和 LR 寄存器
add x29, sp, #0x20
; = 0x20,设置新的 FP 寄存器,也就是新的栈针的底部
orr w8, wzr, #0x3
; 将 0 和 3 异或, 将结果赋值给 w8, 等同于 mov w8,0x03
stur w0, [x29, #-0x8]
; 保存 w0 到 [x29-0x8] 的内存地址中
str x1, [sp, #0x10]
; 保存 x1 到 [sp + 0x10] 的内存地址中
如果参数是小数不是通过x0 - x7 来传参.小数通过 d0 和 d1 传递 fadd d0, d0, d1 ; fadd 这是小数加法
结果一般使用 x0 寄存器返回. x0 最多只能存放 8个字节的数据 如果大于 8个字节数据