前言
我们怎么动态分析与调试别人的应用,然后注入脚本以达到我们自己的目的?都听说过Hook,那么它的原理是什么,有哪几种Hook技术?所谓磨刀不误砍柴工,下面就这几个问题罗列总结一下Hook原理与动态调试,作为一个笔记仅供参考和学习。
1.0 Hook原理
HOOK
,中文译为“挂钩”或“钩子”。在iOS逆向中是指改变程序运行流程
的一种技术。通过hook可以让别人的程序执行自己所写的代码,我们重点要了解其原理,这样能够对恶意代码进行有效的防护。下面就几种hook技术分析一下。
1.1 MethodSwizzle
这个是我们比较熟悉的Hook方式,调用OC系统API
,利用OC的Runtime
特性,动态改变SEL(方法编号)和IMP
(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法
。在OC中,SEL 和 IMP 之间的关系,就好像一本书的“目录”
。SEL是方法编号,就“标题”
一样。IMP是方法实现的真实地址,就像“页码”一样。他们是一一对应的关系,如下图所示我们通过改变这种对应关系达到hook的目的。
使用案例
下面以hook
微信登录事件为例,当点击登录按钮
时,在不影响原有登录逻辑下
,获取输入的密码
。
- 参考 逆向与砸壳文章,新建一个demo工程,将砸完壳的微信ipa包放进APP文件夹中,
先运行工程
把demo安装进手机 - 用
appSign.sh
脚本重签名微信
并安装进手机,注意appSign.sh脚本中除了要删除macho
中不能签名的Watch、plugin
文件夹,最新版本的微信8.0.16还需要删除com.apple.WatchPlaceholder
文件夹,如下
Xcode
运行重签名的微信,lldb
附加调试微信登录界面
- 点击登录按钮,可以看到登录的消息发送
id
为WCAccountMainLoginViewController
,SEL
为onNext
,找到了登录方法,我们还需要知道密码控件是哪一个UITextField,Class-dump
微信头文件,静态分析WCAccountMainLoginViewController.h
头文件如下
没有找到和密码相关的UI组件,但是有一个属性名为
_mainPageView
的文件,打开WCAccountLoginMainPageView.h
文件寻找密码相关的UI
貌似找到了一个很密码相关的属性
_passwordTextItem
,打开文件WCRedesignTextItem.h
文件
WCRedesignTextItem.h
中存在一个属性_textFiled
,打开WCUITextField.h
文件
WCUITextField
继承自UITextField
,至此我们已经定位到了密码组件,keyvalue
关系为:WCAccountMainLoginViewController->_mainPageView->_passwordTextItem->_textField
。
onNext
方法找到了,密码组件也找到了,就可以撸hook代码了,demo中新建一个wgy.framework
,编写hook代码如下
密码是获取到了,但是
[self newnext]
调用原来的逻辑却奔溃
了,断点在23行处,打印此时的id和sel,id为WCAccountMainLoginViewController
,sel为newNext
,但是WCAccountMainLoginViewController
里并没有newNext方法
,所以需要把这个方法添加进类里,修改为如下
+(void)load{
Method oldmethod=class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
const char * typeencode= method_getTypeEncoding(oldmethod);
class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(newNext),method_getImplementation(class_getInstanceMethod(self, @selector(newNext))), typeencode);
method_exchangeImplementations(oldmethod, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(newNext)));
}
-(void)newNext{
UITextField* password=[[[self valueForKey:@"_mainPageView"] valueForKey:@"_passwordTextItem"] valueForKey:@"_textField"];
NSLog(@"%@",password.text);
[self newNext];
}
复制代码
method_exchangeImplementations
虽然可以交换方法,但是在调用原方法时稍微不注意就可能奔溃
,一般可以使用class_replaceMethod
代替
+(void)load{
Method oldmethod=class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
const char * typeencode= method_getTypeEncoding(oldmethod);
oldimp= class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), newNext, typeencode);
}
IMP (*oldimp)(id self,SEL _cmd);
void newNext(id self,SEL _cmd){
UITextField* password=[[[self valueForKey:@"_mainPageView"] valueForKey:@"_passwordTextItem"] valueForKey:@"_textField"];
NSLog(@"%@",password.text);
oldimp(self,_cmd);
}
复制代码
分析:IMP=函数名=函数实现地址
,这里用oldimp
保存onNext函数地址
以便调用原来的逻辑,oldimp
是一个函数地址所以要用*指针指向
,等价关系MP test=(*oldimp)(id self,SEL _cmd)
。class_replaceMethod
确实简洁了一点点,但是很多第三方hook框架使用setImp
和getImp
这两个api来实现hook,比如Monkey
框架,我也感觉setImp和getImp使用更符合逻辑,修改如下
+(void)load{
oldimp=method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), newNext);
}
IMP (*oldimp)(id self,SEL _cmd);
void newNext(id self,SEL _cmd){
UITextField* password=[[[self valueForKey:@"_mainPageView"] valueForKey:@"_passwordTextItem"] valueForKey:@"_textField"];
NSLog(@"%@",password.text);
oldimp(self,_cmd);
}
复制代码
- 逻辑代码写好后,最后需要把这个
FrameWork
注入进Macho LoadCommands
段中,这里面涉及dyld
加载原理,使用yololib工具修改MachO
,yololib
下载下来拷贝进工程目录中,在appSign.sh
最下面添加如下脚本,重新运行APP就注入成功了
yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/wgyFramework.framework/wgyFramework"
复制代码
MethodSwizzle主要用来Hook OC
方法,常使用如下api
method_exchangeImplementations
:交换两个api,使用时要注意,避免调用原来方法奔溃的问题class_replaceMethod
:替换原来方法的IMP,注意保存原函数指针地址
。method_getImplementation
、method_setImplementation
:getImp保存原函数指针地址,setImp设置新函数指针地址,使用上更符合逻辑,推荐使用
。
1.2 FishHook
FaceBook
提供的一个工具,利用MachO文件加载原理,通过动态修改懒加载
和非懒加载
两个表的指针地址
达到Hook C函数
的目的。特别注意这里的C函数
指的是系统自带的C函数
,比如MethodSwizzle函数、NSLog函数等等系统共享缓存中的C函数,并不能Hook自己写的C函数
。先看看怎么使用,然后再分析一下原理
使用案例
这里以Hook系统api”method_exchangeImplementations
“C函数为例,同时也可以感受一下逆向的防护
,我们把method_exchangeImplementations
系统函数替换成我们自定义的一个函数,这样别人使用系统函数method_exchangeImplementations
攻击你APP时就会失效。步骤如下
- 新建
fishhookdemo
工程,主界面定义一个UIButton按钮,点击按钮触发事件onNext
,弹出提示"欢迎您",目的是演示防护。 - demo工程中新建
protected.framework
用于防护,注意
防护代码一般使用Framework,因为FrameWork比MachO主工程先加载
,而自己写的FrameWork比别人注入的FrameWork先加载
,所以我们要尽可能的先执行我们的防护代码 - 下载fishHook源文件,拷贝
fishhook.c和fishhook.h
文件进protected.framework,新建防护文件saveProtect.h和saveProtect.m
- saveProtect防护代码编写如下所示,注意
注释
中函数的使用
@interface saveProtect : NSObject
//原函数地址 暴露给自己使用
CF_EXPORT void (*old_exchange)(Method m1,Method m2);
@end
@implementation saveProtect
+ (void)load{
struct rebinding rebind;
rebind.name="method_exchangeImplementations";
rebind.replacement=new_exchangeImp;
rebind.replaced=(void*)&old_exchange;
struct rebinding rebs[]={rebind};
rebind_symbols(rebs, 1);
}
//原始函数指针,可以放在头文件暴露给自己工程使用
void (*old_exchange)(Method m1,Method m2);
//新函数
void new_exchangeImp(Method m1,Method m2){
NSLog(@"检测到Hook");
}
@end
复制代码
- 打包
fishhookdemo.ipa
- 再新建一个demo工程破解fishhookdemo.ipa,编写
MethodSwizzle
代码HookonNext
方法
+(void)load{
class_addMethod(objc_getClass("ViewController"), @selector(newNext), newNext, "v@:");
method_exchangeImplementations(class_getInstanceMethod(objc_getClass("ViewController"), @selector(onNext)), class_getInstanceMethod(objc_getClass("ViewController"), @selector(newNext)));
}
void newNext(id self,SEL _cmd){
NSLog(@"hook到了");
}
复制代码
如果method_exchangeImplementations
Hook成功,点击Button按钮应该打印“hook到了”,而事实是依然弹出“欢迎您”提示,说明method_exchangeImplementations函数已经失效
,成功防护。
FishHook原理
ios有个特殊的位置,存放系统动态库,即动态共享缓存
,FishHook利用PIC
技术动态修改重绑定
时指针地址的值。
- 由于
外部的函数
调用,在编译时没法确定内存位置。 - 苹果采用
PIC
技术(位置无关代码)。在macho
文件Data
段,建立两张表,懒加载和非懒加载表
,存放执行外部函数的指针
首次
调用懒加载函数
,会去找桩代码
执行,首次执行会执行binder
函数进行绑定
FishHook
利用stringtable->symbols>indirect sybols->懒加载符号表
之间的对应关系,通过重绑定
修改指针的值
1.3 InlineHook
所谓inlineHook
就是直接修改目标函数的头部代码
,让它跳转到我们自定义的函数
里执行我们的代码,从而达到Hook的目的
,这种Hook技术一般用在静态语言
的Hook上,比如自定义的C函数
,或者像swift
语言的C函数,这很好解决了FishHook不能Hook C函数的问题。这里推荐一个牛逼的框架Dobby
,它可以像使用FishHook一样简单,话不多说直接上步骤。
编译Dobby
git clone https://github.com/jmpews/Dobby.git --depth=1
,把Dobby源下载到本地,depth用于指定克隆深度
,为1表示只克隆最近一次commit- 由于
Dobby
是跨平台的,所以需要编译成Xcode工程
。运行以下命令,创建一个build_for_ios_arm64
文件夹放编译后的Xcode工程
cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
cmake .. -G Xcode \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 \
-DARCHS=arm64 \
-DENABLE_BITCODE=0 \
-DENABLE_ARC=0 \
-DENABLE_VISIBILITY=1 \
-DDEPLOYMENT_TARGET=9.3 \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DDynamicBinaryInstrument=ON -DNearBranchTrampoline=ON \
-DPlugin.FindSymbol=ON -DPlugin.HideLibrary=ON -DPlugin.ObjectiveC=ON
复制代码
- 编译完成后
build_for_ios_arm64
文件夹下Xcode工程如下,再编译xcode工程,生成DobbyX.framework
使用案例
- 新建
demo
工程 - 拷贝
DobbyX.framework
进demo工程,运行demo工程可能出现两种错误bitcode错误
:要么在编译DobbyX.framework时设置支持bitcode,要么demo中bitcode=NOdyld: Library not loaded
: @rpath/DobbyX.framework/DobbyX Reason: image not found错误:Framework首次拖入工程,Xcode不会帮我们拷贝进Macho,所以需要手动拷贝一下
,如下所示
3. 主界面添加测试函数
sum()
,Dobby
勾住sum()
函数替换成new_sum(
),点击屏幕调用sum()
函数,代码如下
@implementation ViewController
int sum(int a,int b){
return a+b;
}
- (void)viewDidLoad {
[super viewDidLoad];
//参数1:需要hook的函数地址
//参数2:新函数地址
//参数3:保留原始函数的指针的地址
DobbyHook(sum, new_sum, (void*)&originsum);
}
//保存原始函数指针地址,以便后续调用
int (*originsum)(int a,int b);
int new_sum(int a,int b){
NSLog(@"原来的结果为:%i",originsum(a,b));
return a*b;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"结果:%i",sum(10, 20));
}
复制代码
点击屏幕控制台输出如下:
很显然已经成功Hook住了sum()函数,并且执行了我们自己的函数。C函数看汇编
,看下Dobb
y怎么实现的,断住sum()函数,hook前和hook后汇编如下图
hook后
只有前三行不同,而且没有拉伸栈空间
,跳进br x17
,看一下x17
x17
就是新函数new_sum()
,Dobby
直接在原始函数sum()头部插入新函数
,所以说inlineHook
就是直接修改目标函数的头部代码
。跟进new_sum()
看汇编,由于在new_sum()里调用了原始函数sum(),所以进入了x8
跟进blr x8
看下汇编
如果
调用了原始函数
就拉伸栈空间,br x17
回原始函数执行。inlinehook
可以hook自定义的C函数
,那么能否Hook系统函数
呢?继续Hook NSLog()
试试,如下图所示
Dobby果然很强大,
系统外部函数也可以Hook
,这样的话似乎就可以不用FishHook了,直接整Dobby就行了。
原理总结
inlineHook
就是直接修改目标函数的头部代码
。- 如果新函数中调用了原始函数,才会拉伸栈空间并且调用原始函数否则原始函数不执行。
inlinehook
是在头部动态插入
新函数,__text
段是只读的。
地址替换函数
逆向中很难知道自定义函数名称
的,毕竟release包是脱符号
的,我们往往通过分析Macho
拿到函数的偏移地址
,然后再拿到Macho的首地址ASLR
,偏移地址+ASLR
即为函数真实地址
。以sum()
函数为例,这里简化下步骤,直接断住sum()
函数lldb
看一下sum()地址
,然后对照Macho验证一下。
如图所示sum()函数偏移值为:
0x104119e18-0x1041114000=0x5e18
,macho
中看一下0x5e18
偏移量 对比
lldb
sum汇编和Macho中汇编,这应该是sum()
函数,知道了sum()函数偏移量为0x5e18
,那么做戏做全套,打包demo工程,像上面那样新建一个示例工程重签名demo
工程,如下图所示
分析:
-
结果显示很成功的Hook住了sum()函数,特别注意通过代码获取的
ASLR
是没有加上0x100000000这样一个偏移基地址pagezero
的,所以这里要在sum()偏移地址0x5e18前需要加上0x100000000
。 -
把
DobbyX.framework
拖进主工程后,记得在wgyFramework中要链接DobbyX.framework
,否则编译找不到这个动态库
- 同时也需要在
wgyFramework
中设置Framework search path
引用路径,否则导入头文件会报找不到头文件的错误。(Library Search Paths
是这是.a
的路径)
1.4 Monkey
这是一个为越狱和非越狱
开发人员准备的工具,集成了四个模块Logos Tweak、CaptainHook Tweak、Command-line Tool、Monke4App
,它是集重签名、hook、动态调试等等于一身的强大工具,也是逆向必玩的一个工具,工具虽然很强大,但也不意味着前面说的hook原理就没有用,工具毕竟是工具,知道其原理才能不掉队,比如Monkey hook的原理就是封装的getImp
和setImp
,下面看下如何使用Monkey
。
- 安装thoes
//1.先安装最新theos recursive表示循环递归安装依赖库
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos
//2.安装ldid签名工具,越狱插件签名工具,codesign是官方的签名工具,
brew install ldid
复制代码
- Xcode安装插件
sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-install)
复制代码
- 安装成功后,
/opt
目录下会有theos、MonkeyDev、frida-ios-dump
文件夹,同时xcode新建工程会出现MonkeyApp
选项
提醒
:如果安装过程中出现错误,可以参考下面两篇文章解决
- 注意
:编译工程如果出现# libstdc++ not found
的错误,参考错误解决方法
- 修改
/opt/MonkeyDev/Tools/pack.sh
文件,添加删除com.apple.WatchPlaceholder
,否则最新的微信不能重签名
使用案例
还是以hook微信登录
事件为例,当点击登录按钮时,在不影响原有登录逻辑下,获取输入的密码。Xcode
新建demo工程选择MonkeyApp
,把砸完壳的微信.ipa
包拷贝进TargetApp
文件夹
在Logos
文件夹下编写Hook代码如下,运行程序就能很简单地Hook onNext
方法。
Logos语法
上面的简单案例是通过Logos语法
编写的Hook代码,所以有必要了解一下Logos语法,根据官网Logos语法分为如下三块,记忆是没用的,需要自己一般看文档一边使用,也就这么几个指令。
总结:
%hook,%end
:勾住某个类%group,%end
:分组,每一组都需要%ctor()函数构造,通过%init(组名称)进行初始化%log
输出方法的详细信息(调用者、方法名、方法参数)%orig
调用原始方法,可以传递参数、接收返回值%c
类似getClass函数,获取一个类对象%new
添加某个方法- 实际应用中经常使用
MSHookIvar
获取类的属性
,比如MSHookIvar<UITableView*>(self,"_tableView"),可以获取self类中_tableView属性 - 实际开发中经常使用
响应链条
找到特定的类,比如[tableview.nextResponder.nextResponder isKindofClass:%c(WeiXinViewController)]
,通过tableview响应链条判断tableview是否属于类WeiXinViewController
2.0 动态调试
逆向是没有源码
的,所以不能在源码中设置断点,其次调试别人上架的应用是脱符号的,不能
直接给某个符号下断点
,只能动态调试找到内存地址,然后给具体的内存地址下断点。所以就需要熟练掌握动态调试
的方法,然后再配合静态分析
就可以很轻松的玩转逆向。下面说下动态调试工具lldb
和cycript
以及Reveal
。
2.1 LLDB
默认内置于Xcode
中的动态调试工具
。标准的 LLDB 提供了一组广泛的命令,旨在与老版本的 GDB 命令兼容。 除了使用标准配置外,还可以很容易地自定义 LLDB 以满足实际需要。
常用指令如下:
普通断点
设置断点
:breakpoint set -n xxx
,给xxx方法下断点,如果是某个类的某个方法,需要加双引号,比如“-[viewController touchbegin:]”。breakpoint set -r xxx
,断住所有包含“xxx”的地方。breakpoint -a 地址
,给地址下断点。(b -n xxx,是简写)- 查看所有断点:
breakpoint list
- 删除断点:
breakpoint delete
- 禁用/启用断点:
breakpoint disable/breakpoint enable
- 继续执行:
c
(continue简写) - 单步执行,将子函数单做整体一步执行:
n
(next简写) - 单步运行,遇到子函数会进去:
s
函数调用栈类型
- 查看函数调用栈:
bt
- 选择进入具体堆栈:
frame select 12
,进去编号为12的堆栈,只是进入堆栈,数据不不会变 - 走完当前方法,返回上层
调用栈frame
:finish
查看当前函数栈的id和方法参数
:frame variable
,这个还是很方便使用的。- 函数调用栈回滚:
up/down
- 回滚函数栈:
thread return
,直接返回不执行后面的代码,区别于up/down,它是会影响执行结果的
内存断点
- 监听内存值的变化,当发生变化时会进入断点:
watchpoint set variable p1->name
,或者watchpoint set expression 内存地址
- 内存断点移除:
whatch delete
,还有disable等等,与breakpoint相似 - 全局断点监听:
target stop-hook add -o “frame variable”
,stop-hook断点的意思,只要断点来了就显示指令frame variable
其他
- 执行代码:
p
po表示调用对象的discraption方法,这个指令不多说,用的最多了。 - 查看指令:
help breakpoint
- 查看镜像列表:
image list
- 寄存器读写:
register read/write x0
- 读取内存值:
Memory read 或者 x
只记是没有用的,必须要参照着练习,更多指令请参考lldb官网。使用这些指令太繁琐,很多时候需要些好多指令才能定位到自己的视图,所以我们需要借助封装了lldb api好用的工具。
chisel
Facebook封装的lldb api
,使用python调用的工具。首先下载安装chisel,具体安装步骤参照这个网址,配置好.lldbinit
之后就可以使用了。
常用便捷指令
pviews
:打印视图层级,pview -u,打印上一级视图层级pvc
:打印当前控制器pactions 指针地址
:打印按钮所在页面和它的点击事件方法presponder 指针地址
:获取按钮的响应链条pclass 指针地址
:打印控制器继承关系pmethods 指针地址
:打印所有方法pinternals 指针地址
:打印所有成员属性fvc -v 指针地址
:定位属于哪个控制器fv UI名称
:打印当前控制器有几个这样的view,比如 fv WCUITextFiledflicker 指针地址
:定位到的控件会闪烁,这个很好用哦vs 指针地址
:进入具体控件并且可以调试 ,q退出调试。这个对于寻找组件很方便。(w)
move to superview (移动到父视图)(s)
move to first subview(移动到第一个子视图)(a)
move to previous sibling(同级往下移动)(d)
move to next sibling(同级往上移动)(p)
print the hierarchy(打印层次结构)
这里以定位微信登录界面
中的登录按钮
为例玩一下。首先pviews
打印所有视图,随便找一个组件地址,flicker
定位一下这个组件,定位到了会闪烁,然后vs组件地址进入组件中调试,通过w、s、a、d、p
指令定位到登录按钮,如下图所示。
DerekSelander
这个lldb工具结合chisel使用的话更方便一点,所以一般同时安装这两个工具一起使用。首先把DerekSelander下载到本地,然后解压缩放到指定的目录下,比如我这边放在/opt
目录下,编辑.lldbinit
指定dslldy.py
路径,如下图所示就搞定了。
常用便捷指令
search UIView
:全局搜索视图methods 指针地址
:打印所有方法(而且还有方法地址,可以给地址下断点)sbt
:打印调用堆栈信息, 注意这个堆栈信息是恢复部分方法符号
的,bt命令打印的话是没有符号的。
更多指令参考DerekSelander,这里结合chisel指令看一下主界面有哪些方法。
2.2 cycript
Cycript
是由Cydia创始人Saurik推出的一款脚本语言,Cycript混合了OC、JavaScript语法的解释器
,这意味着我们能够在一个命令中使用OC或者JavaScript,甚至两者并用。它能够挂钩正在运行的进程,能够在运行时修改很多东西。区别于lldb,cycript不会阻塞进程
,所以动态调试应用很方便,这也玩逆向经常使用的工具,而lldb附加进程是阻塞的状态
。安装步骤如下
- 官网: www.cycript.org/
- 下载后使用Cycript这个可执行文件
- 为了方便,我们可以放在
/opt/cycript_0.9.594
(opt目录有可选的意思),同时在~/.bash_profile中配置环境变量(执行文件路径)
特别提醒
:如果上面安装了Monkey
,可以不用下载安装配置cycript了,因为MonkeyDev/bin
中已经有这个可执行文件了,工具的使用当然是以方便为前提,所以强烈建议安装并且配置环境变量如下,这样控制台可以直接使用MonkeyDev/bin
里面的工具,如class-dump、dump.py、cycript
。
非越狱调试
cycript
可以附加进程动态调试的前提是手机里必须得有cycript静态库,Monkey
工程会自动把cycript静态库
打包进APP里
,所以只能动态调试这个APP而不能调试别的APP,Monkey默认
注入进手机里的cycript的端口号是6666
,Mac电脑上的cycript工具通过端口号连接APP里的cycript。
Mac连接手机端APP里cycript命令如下:
cycript -r 192.168.1.98:6666
复制代码
- 此处
192.168.2.2
是手机的ip
地址,6666
是手机上APP里cycript的端口号,可以把cycript
命令封装成shell脚本
,但是这里不推荐,毕竟一旦无线网变了ip就变了,所以还是封装成USB连接方便一点。 - 在越狱与砸壳文章中我们写了一个
usbConnect.sh
脚本,封装了SSH通过USB
链接手机。这里同样封装两个shell脚本,一个cyusbConnect.sh
做USB端口映射,一个cyLogin.sh
做连接APP端的cycript。这里比较苦恼不知道怎么把cyusbConnect.sh和usbConnect.sh这两个端口映射脚本合在一起,有知道的小伙伴一定要给我留言。
常用命令
- 强烈建议把
UIApp、UIApp.keyWindow.rootViewController
等等常用命令封装成自定义的cy
文件,然后导入到手机里使用,无论在越狱环境下还是非越狱环境下自定义cy文件还是很方便的,比如下面的自定义GY.cy
。
//IIFE 匿名函数自执行表达式
(function(exports){
APPID = [NSBundle mainBundle].bundleIdentifier,
APPPATH = [NSBundle mainBundle].bundlePath,
//如果有变化,就用function去定义!!
GYRootVc = function(){
return UIApp.keyWindow.rootViewController;
};
GYKeyWindow = function(){
return UIApp.keyWindow;
};
GYGetFrontVcFromRootVc = function(rootVC){
var currentVC;
if([rootVC presentedViewController]){
rootVC = [rootVC presentedViewController];
}
if([rootVC isKindOfClass:[UITabBarController class]]){
currentVC = GYGetFrontVcFromRootVc(rootVC.selectedViewController);
}else if([rootVC isKindOfClass:[UINavigationController class]]){
currentVC = GYGetFrontVcFromRootVc(rootVC.visibleViewController);
}else{
currentVC = rootVC;
}
return currentVC;
};
//当前正在显示的控制器
GYFrontVc = function(){
return GYGetFrontVcFromRootVc(GYRootVc());
};
GYVcViews=function(vc){
if (![vc isKindOfClass:[UIViewController class]]) throw new Error(invalidParamStr);
return vc.view.recursiveDescription().toString();
}
//当前正在显示的UI层级
GYFrontViews = function(){
var currentVC=GYGetFrontVcFromRootVc(GYRootVc());
return GYVcViews(currentVC);
};
// 获取按钮绑定的所有TouchUpInside事件的方法名
GYTouchUpEvent = function(btn) {
var events = [];
var allTargets = btn.allTargets.allObjects();
var count = allTargets.count;
for (var i = count - 1; i >= 0; i--) {
if (btn != allTargets[i]) {
var e = [btn actionsForTarget:allTargets[i] forControlEvent:UIControlEventTouchUpInside];
events.push(e);
}
}
return events;
};
// 递归打印view的层级结构
GYSubviews = function(view) {
if (![view isKindOfClass:[UIView class]]) throw new Error(invalidParamStr);
return view.recursiveDescription().toString();
};
var _GYClass = function(className) {
if (!className) throw new Error(missingParamStr);
if (MJIsString(className)) {
return NSClassFromString(className);
}
if (!className) throw new Error(invalidParamStr);
// 对象或者类
return className.class();
};
// 打印所有的子类
GYSubclasses = function(className, reg) {
className = GYClass(className);
return [c for each (c in ObjectiveC.classes)
if (c != className
&& class_getSuperclass(c)
&& [c isSubclassOfClass:className]
&& (!reg || reg.test(c)))
];
};
var _GYGetMethods = function(className, reg, clazz) {
className = GYClass(className);
var count = new new Type('I');
var classObj = clazz ? className.constructor : className;
var methodList = class_copyMethodList(classObj, count);
var methodsArray = [];
var methodNamesArray = [];
for(var i = 0; i < *count; i++) {
var method = methodList[i];
var selector = method_getName(method);
var name = sel_getName(selector);
if (reg && !reg.test(name)) continue;
methodsArray.push({
selector : selector,
type : method_getTypeEncoding(method)
});
methodNamesArray.push(name);
}
free(methodList);
return [methodsArray, methodNamesArray];
};
var _GYMethods = function(className, reg, clazz) {
return GYGetMethods(className, reg, clazz)[0];
};
var _GYMethodNames = function(className, reg, clazz) {
return GYGetMethods(className, reg, clazz)[1];
};
//打印所有的对象方法
GYInstanceMethods = function(className, reg) {
return _GYMethods(className, reg);
};
// 打印所有的对象方法名字
GYInstanceMethodNames = function(className, reg) {
return _GYMethodNames(className, reg);
};
// 打印所有的类方法
GYClassMethods = function(className, reg) {
return _GYMethods(className, reg, true);
};
// 打印所有的类方法名字
GYClassMethodNames = function(className, reg) {
return _GYMethodNames(className, reg, true);
};
// 打印所有的成员变量
GYIvars = function(obj, reg){
if (!obj) throw new Error(missingParamStr);
var x = {};
for(var i in *obj) {
try {
var value = (*obj)[i];
if (reg && !reg.test(i) && !reg.test(value)) continue;
x[i] = value;
} catch(e){}
}
return x;
};
// 打印所有的成员变量名字
GYIvarNames = function(obj, reg) {
if (!obj) throw new Error(missingParamStr);
var array = [];
for(var name in *obj) {
if (reg && !reg.test(name)) continue;
array.push(name);
}
return array;
};
})(exports);
复制代码
Xcode
打开Monkey项目,导入GY.cy
文件如下,Xcode重新运行项目,GY.cy
就被打包进APP中了,使用自定义cy文件时,需要@import
Monkey
工程会自动给我们下载md.cy
和MS.cy
文件并打包进APP,md.cy提供一些便捷指令如pviews()、pvcs()、rp()、pactions()
,MS.cy提供动态插入代码功能,注意这两个cy文件不需要@import
导入。
但常常因为网络原因这两个文件下载不下来,所以需要更换git源地址,把这两个url地址替换为raw.fastgit.org/AloneMonkey… 和 raw.fastgit.org/AloneMonkey…
演示如下:
越狱调试
cycript
在手机越狱的情况下调试有个好处,首先不需要用Monkey运行项目,然后可以附加手机里的所有APP
,前提是在越狱商店Cydia里下载cycript插件
,插件下载好之后,SSH连接手机就可以cycript附加进程了。
cycript
插件文件夹如上图所示,虽然可以附加手机里的应用,但是一些快捷指令比如APPID、pvcs()并不能用,所以需要导入
我们自定义的GY.cy
,和git上下载下来的md.cy
,scp拷贝cy文件进手机/usr/lib/cycript0.9/com/
目录下,这里在com目录下新建一个gy文件夹
专门放自己的cy文件,如下所示
使用的时候
@import
导入一下就可以愉快地使用快捷指令了,附加正版的微信演示如下
分析:
cycript -p 进程id/appid
,附加进程。导入自定义的GY.cy是可以使用的,但是导入md.cy却不能使用,这个还没有搞清楚,有知道的小伙伴给我留言。
2.3 Reveal
Reveal
是一款可以动态调试APP UI
的工具,相比于xcode自带的View Debugger
,它不会阻塞APP进程而且可以动态调试UI显示效果。这里就越狱环境下如何使用Reveal简单介绍下。
- 越狱手机在
cydia商店
里安装Reveal Loader
SSH连接手机
,cd Library目录下新建mkdir RHRevealLoader
文件夹。Mac
电脑安装Reveal工具
,这里推荐一个破解工具下载平台- Ma电脑
拷贝RevealServer
可执行程序进手机端RHRevealLoader文件夹
。打开Reveal程序,Help
-->Show Reveal Library in Finder
-->iOS Library
-->RevealServer.framework
-->RevealServer
。scp拷贝RevealServer进手机,并且重命名为libReveal.dylib
重启手机
- 手机进入
设置-->Reveal-->打开想要调试的APP
- Mac重新打开Reveal工具,此时就可以动态调试UI了,当然你也必须先SSH连接上手机。