Cycript脚本

/ 0评 / 2

Cycript 是 Objective-C++、ES6(JavaScript)、Java 等语法的混合物,也就是语法格式支持这些语言。可以用来探索、修改、调试正在运行的 iOS/Mac APP。

官网:http://www.cycript.org/
文档:http://www.cycript.org/manual/

在使用 Cycript 之前,需要通过 Cydia 安装 Cycript,然后 Mac 通过 SSH 登录 iPhone。

提示:在编写 Cycript 过程中,按 tab 键会有语法填充和提示。

开启Cycript

开启 Cycript,通过 SSH 登录 iPhone 后,可以直接使用下面命令:

cycript

开启成功,终端则会显示:

cy#

常用快捷键(推荐使用 iTerm 终端,默认终端快捷键会冲突):

Control + D  #关闭Cycript
Command + R  #清屏
Control + C  #取消输入

使用 Cycript 调试程序:

cycript -p 进程ID
或
cycript -p 进程名称

ps 命令是 process ststus 的缩写,使用 ps 命令可以列出系统当前的进程(需要在 Cydia 安装 adv-cmds 插件)。

列出所有进程:

ps -A

列出结果,下面这个进程是喜马拉雅 APP:

15462 ?? 3:30.87 /var/containers/Bundle/Application/2991BBBF-1444-4A60-89FE-4991586C20BF/ting.app/ting

搜索关键词:

ps -A | grep 关键词

所以如果我们要调试喜马拉雅 APP,则通过 SSH 登录 iPhone 后,使用下面命令(进程ID可能会变,建议使用进程名称):

cycript -p ting

常用语法

开启 expand 功能,Cycript 会把格式符号翻译成相应的格式,如 \n 会被翻译成一个换行,让输出的可读性更高:

cy# ?expand
expand == true

获取 UIApplication 对象:

cy# [UIApplication sharedApplication]
#"<XMUIApplication: 0x117e0f9e0>"

cy# UIApp
#"<XMUIApplication: 0x117e0f9e0>"

使用 var 变量名 = 值 来定义变量,定义的变量在 APP 进程结束前一直有效:

cy# var keyWindow = UIApp.keyWindow
#"<UIWindow: 0x118a145b0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2824d6220>; layer = <UIWindowLayer: 0x282ac0600>>"

cy# keyWindow
#"<UIWindow: 0x118a145b0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2824d6220>; layer = <UIWindowLayer: 0x282ac0600>>"

使用 #内存地址 来获取对象:

cy# UIApp.keyWindow 
#"<UIWindow: 0x10931dbd0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2814c5c50>; layer = <UIWindowLayer: 0x281ace6a0>>"

cy# #0x10931dbd0
#"<UIWindow: 0x10931dbd0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2814c5c50>; layer = <UIWindowLayer: 0x281ace6a0>>"

ObjectiveC.classes 打印出所有已加载的 OC 类:

cy# ObjectiveC.classes
{NSLeafProxy:NSLeafProxy,JSExport:JSExport,PFEmbeddedMulticasterImplementation:PFEmbeddedMulticasterImplementation,PFMulticasterDistributionMethods:PFMulticasterDistributionMethods,__NSGenericDeallocHandler:__NSGenericDeallocHandler,__NSAtom:__NSAtom,_NSZombie_:_NSZombie_,__NSMessageBuilder:__NSMessageBuilder,Object:Object,NSProxy:NSProxy,EXTNil:EXTNil,_YYWebImageWeakProxy:_YYWebImageWeakProxy,YYTextWeakProxy:YYTextWeakProxy,_YYImageWeakProxy:_YYImageWeakProxy,WVExtContext:WVExtContext,WVWeakObjectWrapper:WVWeakObjectWrapper,WVExtHandler:WVExtHandler,SDWeakProxy:SDWeakProxy,PHFDelegateChain:PHFDelegateChain,_NMLWebImageWeakProxy:_NMLWebImageWeakProxy,_NMCADisplayLinkWeakProxy:_NMCADisplayLinkWeakProxy,NELayoutComponentDelegateProxy:NELayoutComponentDelegateProxy,MCDelegateIntercept:MCDelegateIntercept,

使用 *对象 语法,来获取对象的所有成员变量,打印出来的键值对是成员变量名和成员变量值:

cy# *UIApp
{isa:NSKVONotifying_XMUIApplication,_responderFlags:@error,_delegate:#"<XMAppDelegate: 0x282bf9540>",_remoteControlEventObservers:1,_topLevelNibObjects:null,_networkResourcesCurrentlyLoadingCount:0,_hideNetworkActivityIndicatorTimer:null,_editAlertController:null,_statusBar:null,_statusBarRequestedStyle:0,...

使用 recursiveDescription() 函数,递归打印 view 所有子控件(跟 LLDB 一样的函数):

cy# UIApp.keyWindow.recursiveDescription().toString()
`<UIWindow: 0x10931dbd0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2814c5c50>; layer = <UIWindowLayer: 0x281ace6a0>>
   | <UITransitionView: 0x109362430; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281c7b180>>
   |    | <UIDropShadowView: 0x1093bad40; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281c7b2e0>>
   |    |    | <UILayoutContainerView: 0x10974a430; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281b18980>>
   |    |    |    | <UIView: 0x10935ec80; frame = (0 0; 375 812); layer = <CALayer: 0x281a2fca0>>
   |    |    |    | <UITransitionView: 0x109364100; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281a2e240>>
   |    |    |    |    | <UIViewControllerWrapperView: 0x1093bbc40; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281c79ca0>>

使用 choose() 函数筛选出某种类型的对象:

cy# choose(UIViewController)
[#"<UIViewController: 0x109380ee0>",#"<UIViewController: 0x1093b25c0>",#"<XMPageViewController: 0x109792ee0>",#"<XMMainTabBarController: 0x10a814000>",#"<XMFindTopViewController: 0x10a846a00>",#"<XMSegmentTabViewController: 0x10a848a00>",#"<XMNavigationController: 0x10a849a00>",#"<XMFindFlowViewController: 0x10a91c400>",#"<XMNavigationController: 0x10a93f600>",#"<XMSoundCustomViewController: 0x10a98d000>",#"<UINavigationController: 0x1098a0e00>",#"<XMNewDiscoveryViewController: 0x109961000>",#"<XMNavigationController: 0x109961600>",#"<XMPersonalCenterViewController: 0x109963200>",#"<XMNavigationController: 0x109963a00>"]

工具库使用

类似 iOS 开发里的各种工具类,我们也可以把自己写的一些 Cycript 代码封装起来,方便快速开发。

MJ 大神封装的工具库:https://github.com/CoderMJLee/mjcript

从 GitHub 下载后,将 mjcript.cy 文件拷贝到 /usr/lib/cycript0.9 目录下,可以重命名为 MJTool.cy

每次都需要 @import 导入才能使用:

cy# @import MJTool
{}

cy# MJAppId
@"com.xr.chuangyin"

cy# MJAppPath
@"/private/var/containers/Bundle/Application/6241E28B-FE27-4F31-8633-9FDEF5D3C30A/Dagou.app"

cy# MJFrontVc()
#"<JFLoginVC: 0x10bb46300>"

cy# MJInstanceMethodNames(JFLoginVC)
[&"toWebViewUrl:",&"requestTicketInfo:qqInfo:wxInfo:",&"handleAppleLoginReuslt:",&"loginAppleHandleAppleId:identityToken:authorizationCode:",&"onClickPhoneLogin:",&"onClickIdLogin:",&"onClickOneKeyLogin:",&"onClickAppleLogin:",&"loginWithWxInfo:qqInfo:type:",&"appleLoginBtn",&"setAppleLoginBtn:",&"thirdPartView",&"setThirdPartView:",&"viewDidLoad",&"setupUI",&"dealloc",&".cxx_destruct",&"viewWillAppear:"]

cy# MJInstanceMethodNames(#0x10bb46300)
[&"toWebViewUrl:",&"requestTicketInfo:qqInfo:wxInfo:",&"handleAppleLoginReuslt:",&"loginAppleHandleAppleId:identityToken:authorizationCode:",&"onClickPhoneLogin:",&"onClickIdLogin:",&"onClickOneKeyLogin:",&"onClickAppleLogin:",&"loginWithWxInfo:qqInfo:type:",&"appleLoginBtn",&"setAppleLoginBtn:",&"thirdPartView",&"setThirdPartView:",&"viewDidLoad",&"setupUI",&"dealloc",&".cxx_destruct",&"viewWillAppear:"]

封装Cycript

除了直接使用别人封装好的 Cycript 代码,我们自己也可以封装。类似 mjcript.py 那样,将常用代码封装在一个 .cy 文件中。

exports 参数名固定,用于向外提供接口:

(function(exports) {
    exports.变量名 = 变量值;

    exports.方法名 = function(参数) {
        return 返回值
    };
})(exports);

调用方式:

文件名.方法名(参数)
文件名.变量名

例如编写以下代码,保存为 test.cy,并将此文件放在 /usr/lib/cycript0.9 目录下。

(function(exports) {
    exports.age = 100;

    exports.sum = function(a, b) {
        return a + b
    };
})(exports);

测试:

cy# @import test
{sum:function (t,e){return t+e}}

cy# test.sum(10, 20)
30

cy# test.age
100

如果在定义变量和函数的时候,不使用 exports,则相当于是定义全局的变量和函数。全局则需要注意标识符命名唯一,防止多个 .cy 文件中的标识符名称冲突,我这里使用了 test_ 前缀。

(function(exports) {
    test_age = 100;

    test_sum = function(a, b) {
        return a + b
    };
})(exports);

测试:

cy# @import test
{}

cy# test_sum(10, 20)
30

cy# test_age
100

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注