前言
本篇文章将介绍逆向实战
中经常使用的一门语言 👉 Cycript
,它是由Cydia创始人Saurik
推出的一款脚本语言
,Cycript
混合了OC、JavaScript
语法的解释器,这意味着我们能够在一个命令中使用OC
或者JavaScript
,甚至两者并用
。它能够挂钩
正在运行的进程
,能够在运行时
修改很多东西。
一、Cycript安装
Cycript官网,目前最新版本0.9.594
。直接下载SDK
解压放在/opt
目录中👇
接着在~/.bash_profile
、~/.zshrc
中配置环境变量👇
export PATH=/opt/cycript_0.9.594/cycript
接着验证一下👇
cycript
进入cy#
表示配置成功。
⚠️注意:如果已经安装了
Monkey
则不需要
配置和安装cycript
了。因为monkey自带了cycript
,并且配置环境变量应改为👇
export CY=/opt/cycript_0.9.594/
export PATH=/opt/MonkeyDev/bin:$PATH:$CY
二、基本使用
只要将cycript
注入到应用中,我们就可以调用其中的命令了,Monkey重签名
注入的时候帮我们注入了,例如我们之前文章15-Hook原理(二)反Hook防护 & MokeyDev
中的例子MonKeyDemo
👇
app包里面就包含了libcycript.dylib
,当Cycript
注入到目标应用,应用进程就会调用Cycript
的方法,开启
相应的端口
,以供第三方监听
。第三方可通过端口链接
进程,进入cy
环境,HOOK
当前进程
中的内存数据
。
2.1 进入Cycript环境
cycript
2.2 退出Cycript环境
control + d
2.3 附加进程
连接进程进入Cycript环境,例如👇
cycript -r 192.168.3.127:6666
这里手机和电脑要在同一wifi
,并且进入应用程序进程。ip为手机的ip
。端口号默认为6666
。
2.4 cycript调试命令
以上附加进程
链接好了后,就可以使用cycript
调试命令查看信息了,例如👇
- 获取
keyWindow
UIWindow.keyWindow()
-------------------------
#"<iConsoleWindow: 0x125f560b0; baseClass = UIWindow; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x283f09680>; layer = <UIWindowLayer: 0x283179fc0>>"
- 获取
UIApplication
单例对象
UIApp
-------------------------
#"<UIApplication: 0x14aa16c80>"
- 定义变量并赋值
var keyWd = UIWindow.keyWindow()
-------------------------
#"<iConsoleWindow: 0x125f560b0; baseClass = UIWindow; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x283f09680>; layer = <UIWindowLayer: 0x283179fc0>>"
keyWd.rootViewController
-------------------------
#"<MMUINavigationController: 0x1270e9200> ChildViewControllers:(\n \"<WCAccountLoginFirstViewController: 0x1270d7600>\"\n)"
当程序的
进程结束
,定义的所有变量
会一起释放
。
-
#对象地址
👉 拿到该对象,可用于调用方法
#0x1270e9200
-------------------------
#"<MMUINavigationController: 0x1270e9200> ChildViewControllers:(\n \"<WCAccountLoginFirstViewController: 0x1270d7600>\"\n)"
-
*对象
👉 可以取出对象的成员变量
*keyWd
-------------------------
{isa:iConsoleWindow,_responderFlags:@error,_constraintsExceptingSubviewAutoresizingConstraints:null,...
- 查看
视图结构
keyWd.recursiveDescription()
-------------------------
@"<iConsoleWindow: 0x125f560b0; baseClass = UIWindow; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x283f09680>; layer = <UIWindowLayer: 0x283179fc0>>\n | <UITransitionView: 0x125f51960; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x28317a500>>\n...
也可格式化
打印,遇\n
换行👇
keyWd.recursiveDescription().toString()
-------------------------
`<iConsoleWindow: 0x125f560b0; baseClass = UIWindow; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x283f09680>; layer = <UIWindowLayer: 0x283179fc0>>
| <UITransitionView: 0x125f51960; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x28317a500>>
| | <UIDropShadowView: 0x125f59770; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x28317a680>>
| | | <UILayoutContainerView: 0x125e71790; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x283f0ff90>; layer = <CALayer: 0x283140140>>
| | | | <UINavigationTransitionView: 0x125e7a190; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x283140360>>
| | | | | <UIViewControllerWrapperView: 0x125f37540; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x28315abc0>>
...
- 查询
当前进程
中指定类型
的对象
choose (UIButton)
-------------------------
[#"<FixTitleColorButton: 0x125e2b280; baseClass = UIButton; frame = (20 18; 177 47); clipsToBounds = YES; opaque = NO; autoresize = RM; layer = <CALayer: 0x2831463c0>>",#"<FixTitleColorButton: 0x125e55b20; baseClass = UIButton; frame = (217 18; 177 47); clipsToBounds = YES; opaque = NO; autoresize = LM; layer = <CALayer: 0x283138820>>",...
这和LLDB
中search
很像。
三、进阶
3.1 脚本自动连接
- 创建
cyShell目录
- 在
cyShell目录
下,创建cyConnect.sh
脚本 - 打开cyConnect.sh脚本,写入以下代码👇
cycript -r 10.165.44.19:6666
- 增加
可执行
权限👇
chmod +x cyConnect.sh
- 配置环境变量👇
export cyShell=/Users/Aron/cyShell/
export PATH=/opt/MonkeyDev/bin:$cyShell:$PATH
这样能全局使用👇
cyConnect.sh
cy#
直接就能自动连接
了。
3.2 Cycript 修改内存
接下来通过几个案例,使用Cycript
脚本,看看如何修改内存。
案例一:修改应用图标的红点数
- 使用
MonkeyDev
安装并运行wx8.0.2.ipa
- 使用
cyConnect.sh
附加进程 - 修改
BadgeString
👇
[UIApp setApplicationBadgeString: @"999"]
⚠️注意:修改当前内存中的数据,应用
重复挂起唤醒
,数据就会被刷新。
案例二:修改红包金额
例如:正常的红包内容如下👇
- 首先通过
choose(UILabel)
找到红包金额
👇
choose(UILabel).toString()
搜索1.00
得到地址0x149eaef00
👇
- 修改红包的金额👇
cy# #0x149eaef00.text = @"¥88888888.00"
同样的方式,找到借款
UILabel的地址 0x14c33b460
,将text改为还款
👇
cy# #0x14c33b460.text = @"还款"
- 修改后的内容👇
案例三:修改聊天内容
有2种方式👇
- 通过
cycript
搜索控件进行修改 - 通过
Xcode
的view debug
找到控件进行修改
可以看到内容在accessibility
的description
中👇
接着我们在logos文件夹下的.xm
文件中,使用logos
语法hook方法,代码如下👇
%hook RichTextView
- (_Bool)setPrefixContent:(id)arg1 TargetContent:(NSString *)arg2 TargetParserString:(id)arg3 SuffixContent:(id)arg4 {
// arg2 为文案
if([arg2 isEqualToString:@"钱已经借给你了。"]) {
arg2 = @"钱已经换给你了,请查收!";
}
return %orig;
}
%end
至此整个内容就修改完成了👇
3.3 高级用法
- 获取
bundle identifier
使用APPID
命令👇
cy# APPID
-------------------------
@"com.xxxxxx.MonKeyDemo"
- 获取视图层级
使用pviews()
命令👇
pviews()
-------------------------
`<iConsoleWindow: 0x113790b60; baseClass = UIWindow; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x2811ce0d0>; layer = <UIWindowLayer: 0x281f12020>>
| <UITransitionView: 0x113797170; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281f13120>>
| | <UIDropShadowView: 0x113798100; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281f13240>>
| | | <UILayoutContainerView: 0x113793ba0; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x2811cf060>; layer = <CALayer: 0x281f124c0>>
| | | | <UINavigationTransitionView: 0x1137950f0; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281f12760>>
| | | | | <UIViewControllerWrapperView: 0x11378b1d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281fd9400>>
| | | | | | <UIView: 0x113794990; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x281fe8180>>
| | | | | | | <UIView: 0x113793980; frame = (0 20; 375 732); autoresize = W; layer = <CALayer: 0x281fe8ac0>>
| | | | | | | | <UIImageView: 0x113316830; frame = (0 -20; 375 667); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x281fec780>>
| | | | | | | <UIView: 0x113316140; frame = (0 582; 375 65); autoresize = W+TM; layer = <CALayer: 0x281fecc60>>
| | | | | | | | <FixTitleColorButton: 0x113306f70; baseClass = UIButton; frame = (20 18; 157.5 47); clipsToBounds = YES; opaque = NO; autoresize = RM; layer = <CALayer: 0x281fecea0>>
| | | | | | | | | <UIButtonLabel: 0x113328f90; frame = (60.5 12.5; 37 22); text = '\u767b\u5f55'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283c4acb0>>
| | | | | | | | <FixTitleColorButton: 0x1137562e0; baseClass = UIButton; frame = (197.5 18; 157.5 47); clipsToBounds = YES; opaque = NO; autoresize = LM; layer = <CALayer: 0x281fc70a0>>
| | | | | | | | | <UIButtonLabel: 0x1137590e0; frame = (60.5 12.5; 37 22); text = '\u6ce8\u518c'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283c46710>>
| | | | | | | <UIButton: 0x11375a080; frame = (287 20; 88 49); opaque = NO; autoresize = LM; layer = <CALayer: 0x281fc72e0>>
| | | | | | | | <UIButtonLabel: 0x113762910; frame = (15 16; 58 17); text = '\u7b80\u4f53\u4e2d\u6587'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283c46850>>
| | | | <MMUINavigationBar: 0x113793d20; baseClass = UINavigationBar; frame = (0 20; 375 44); opaque = NO; autoresize = W; layer = <CALayer: 0x281f124e0>>
| | | | | <_UIBarBackground: 0x113794170; frame = (0 -20; 375 64); userInteractionEnabled = NO; layer = <CALayer: 0x281f12560>>
| | | | | | <UIImageView: 0x11378df60; frame = (0 0; 375 64); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x281fd9a80>>
| | | | | | <_UIBarBackgroundShadowView: 0x1137957a0; frame = (0 64; 375 0); layer = <CALayer: 0x281f129a0>> clientRequestedContentView effect=none
| | | | | | | <_UIBarBackgroundShadowContentImageView: 0x113795b10; frame = (0 0; 375 0); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x281f12a60>>
| | | | | <_UINavigationBarContentView: 0x113794350; frame = (0 0; 375 44); layer = <CALayer: 0x281f12580>> layout=0x1137945d0
| | | | | | <_UITAMICAdaptorView: 0x11378e920; frame = (187 4; 1 36); autoresizesSubviews = NO; layer = <CALayer: 0x281fe81a0>>
| | | | | | | <MMTitleView: 0x113338b90; frame = (0 0; 1 36); layer = <CALayer: 0x281f36fa0>>
| | | | | | | | <MMUILabel: 0x1133382f0; baseClass = UILabel; frame = (0 0; 0 36); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x283c48a00>>
| | | | | <UIView: 0x11373a720; frame = (0 44; 375 0.5); hidden = YES; autoresize = W+TM; layer = <CALayer: 0x281f5a3a0>>
| | | | | <UIView: 0x1137903d0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x281f127a0>>`
也可以查看pviews方法
的代码实现
👇
pviews
-------------------------
function (){return UIApp.keyWindow.recursiveDescription().toString()}
- 获取视图控制器
使用pvcs()
命令👇
cy# pvcs()
-------------------------
"<MMUINavigationController 0x11396f600>, state: appeared, view: <UILayoutContainerView 0x113793ba0>\n | <WCAccountLoginFirstViewController 0x11400fa00>, state: appeared, view: <UIView 0x113794990>"
pvcs方法的代码实现👇
pvcs
-------------------------
function (){return UIWindow.keyWindow().rootViewController._printHierarchy().toString()}
上面这些功能,Cycript
脚本也有,但是这些命令却不是Cycript
自带的,那么它们是哪里来的呢?
在Monkey工程我们可以在
Config->MDConfig.plist
中发现Cycript
下有ms
和md
两个.cy
文件👇
这些命令就封装在这两个文件中(工程运行的时候自动从网络上下载)。
ms.cy
md.cy
使用
Cycript
找到指定控制器
的某个控件
,过程比较繁琐
。
更好的方式 👉 在非越狱设备上,使用应用重签名
,通过Debug View Hierarchy
快速定位控件,找到对应的地址
,然后使用Cycrip
t对其进行修改
。
四、cy文件的封装
案例一:封装简单案例
- 在
MokeyDemo
项目中,创建test.cy
文件 - 打开
test.cy
文件,写入以下代码👇
sum = function(a,b){
return a + b;
}
- 将
test.cy
文件拖入项目,在MokeyDemo
项目中,使用Copy Files
添加test.cy
👇
⚠️注意:
test.cy
是脚本文件,不是MachO
,不需要
勾选签名
。
-
重新运行项目,使用
cyConnect.sh
附加进程 -
导入脚本👇
@import test
-------------------------
{}
调用sum方法👇
sum(10,20)
-------------------------
30
案例二:实现获取当前控制器
- 修改
test.cy
文件的代码👇
(function(exports){
APPID = [NSBundle mainBundle].bundleIdentifier,
APPPATH = [NSBundle mainBundle].bundlePath,
APPHOME = NSHomeDirectory(),
rootVC = function(){
return UIApp.keyWindow.rootViewController;
};
keyWindow = function(){
return UIApp.keyWindow;
};
getCurrentVC = function(rootVC){
var currentVC;
if([rootVC presentedViewController]){
rootVC = [rootVC presentedViewController];
}
if([rootVC isKindOfClass:[UITabBarController class]]){
currentVC = getCurrentVC(rootVC.selectedViewController);
}
else if([rootVC isKindOfClass:[UINavigationController class]]){
currentVC = getCurrentVC(rootVC.visibleViewController);
}
else{
currentVC = rootVC;
}
return currentVC;
};
currentVC = function(){
return getCurrentVC(rootVC());
};
})(exports);
- 重新运行项目,使用
cyConnect.sh
附加进程 - 同样导入脚本👇
@import test
-------------------------
{}
- 调用方法👇
- 获取
APPID
APPID
-------------------------
@"com.xxxxxx.MokeyDemo"
- 获取
APPPATH
APPPATH
-------------------------
@"/private/var/containers/Bundle/Application/D620C178-5030-48E4-9276-981150FF7299/MokeyDemo.app"
- 获取
APPHOME
APPHOME
-------------------------
@"/var/mobile/Containers/Data/Application/C2ED1E99-47C4-4C29-8AE6-9C5C136CEE04"
- 调用
currentVC()
currentVC()
-------------------------
#"<WCAccountLoginFirstViewController: 0x14b0b4200>"
总结
- Cycipt
- 是一种
脚本语言
,兼容了多种语法
(支持多种语法的解释器
) - Cycipt可以
附加
到进程,用来动态调试
- 也可
自定义脚本
,配置
到环境变量
中,方便执行
- 也可
- 将常用功能封装为
.cy文件
- 是一种
网友评论