摘自官方文档:
Cycript is a hybrid of ECMAScript some-6, Objective-C++, and Java. It is implemented as a Cycript-to-JavaScript compiler and uses (unmodified) JavaScriptCore for its virtual machine. It concentrates on providing "fluent FFI" with other languages by adopting aspects of their syntax and semantics as opposed to treating the other language as a second-class citizen.node
The primary users of Cycript are currently people who do reverse engineering work on iOS. Cycript features a highly interactive console that features live syntax highlighting and grammar-assisted tab completion, and can even be injected into a running process (similar to a debugger) using Cydia Substrate. This makes it an ideal tool for "spelunking".git
However, Cycript was specifically designed as a programming environment and maintains very little (if any) "baggage" for this use case. Many modules from node.js can be loaded into Cycript, while it also has direct access to libraries written for Objective-C and Java. It thereby works extremely well as a scripting language.github
简言之:
Cycript 是由 Cydia 创始人 Saurik 推出的一款脚本语言,它混合了 ECMAScript 6.0(简称ES6,是JavaScript 语言的下一代标准)、Objective-C ++ 和 Java 的语法解释器。这意味着咱们可以在一个命令中使用 OC 或者 JavaScript,甚至二者并用。Cycript 目前的主要用途是在 iOS 上进行逆向工做,使用 Cydia Substrate 能够注入正在运行的进程(相似于调试器),这使它成为“探险”的理想工具。面试
在这里下载 SDK到本地,为了方便每次直接可使用,建议将可执行文件 cycript 的路径配置到环境变量中(在 .bash_profile/.zshrc [取决于你用哪一个终端] 中 export 一下),打开终端,执行 cycript
命令:api
如上图所示,cy# 提示符表示进入了 JavaScript 控制台。你键入的全部内容都将由 JavaScriptCore 运行,这是 Apple 对 Safari 使用的 JavaScript 语言的实现。且在你键入时,你的命令将使用 Cycript 的词法分析器进行语法突出显,若是出现语法错误,则会出现提示。你可使用 ctrl+C
取消键入,或 ctrl+D
退出该环境。数组
关于 Cycript 的用法,这一篇只围绕 iOS 逆向工程来展开讲述,这也是 Cycript 目前用的最广的领域。对比上一篇提到的 LLDB ,Cycript 的亮点在于它能够动态注入,在运行时能够随时获取、修改程序中对象的值。而 LLDB 不论是在正向开发仍是逆向工程中,它只能进行断点静态调试分析,效率相比 Cycript 有明显的不足。bash
用 Cycript 实现动态调试应用的前提,是你的应用为其开好了一个可链接的端口,鉴于越狱机并非人人都有,此篇我主要为你们介绍非越狱环境下如何使用 Cycript 进行调试,让你们都有实操的条件。在开始使用 Cycript 以前,咱们还须要准备另外一个工具。微信
在过去两个多月的系列文章中,我将 iOS 的应用签名原理、自动重签名脚本以及代码注入等知识串讲了一遍,其实这些工做全都有工具帮咱们集成好了,相信你也猜到了,没错,这个工具就是 MonkeyDev ---- 原有 iOSOpenDev 的升级,非越狱插件开发集成神器!关于 MonkeyDev 的安装 这里就不展开赘述了,安装成功后,新建一个 MonkeyApp 项目 (MonkeyDevDemo): 网络
打开 MonkeyDevDemo,只需将你要调试的 ipa/app (脱壳仍是必要的) 丢到新建项目的这个目录下: 数据结构
运行项目,就能够将应用直接运行到你的真机上了:
上图中,红框标注出的 CYListenServer(6666);
正是咱们前面提到的,用 Cycript 实现动态调试应用的前提:一个可远程链接的端口号--6666,在控制台中一样能够找到打印日志:
我相信你必定注意到了日志中的这一行:
Download cycript(https://cydia.saurik.com/api/latest/3) then run: ./cycript -r 192.168.199.236:6666
没错,它就是在告诉你,server 端口绑定成功,终端执行 ./cycript -r 192.168.199.236:6666
就能链接到运行中的应用了。192.168.199.236
是我当前手机的 ip 地址。
其实现原理,简单来说,就是 hook 了 AppDelegate 里的 application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)
方法,在该方法里开启 Cycript 并绑定到6666端口。
在上一篇 LLDB 中推荐的插件 chisel 里,不少好用的命令在 MonkeyDev 中也都作了支持 :
NSString* pvc(void);
NSString* pviews(void);
NSString* pactions(vm_address_t address);
NSString* pblock(vm_address_t address);
NSString* methods(const char * classname);
NSString* ivars(vm_address_t address);
NSString* choose(const char* classname);
NSString* vmmap();
复制代码
赶快来试试手:找到淘宝首页底部 “淘” 按钮并将其隐藏掉:
是否是忽然想拿微信发个 ¥0.01 的红包,而后用新学的这招操做一波:
言归正传,上面列出的几个命令,基本能够知足你快速摸清一个 app 各个复杂页面的结构,同时也能够精准的定位并修改目标视图的UI。有的同窗若是没接触过 Cycript ,建议先看一下 官方文档,熟悉下支持的语法和数据结构,多找几个小case有目的的练习,很快就能上手玩了,这对于想经过学习大厂优秀 app 的设计与实现思路的同窗来讲,是个不可错过的好途径。
Tips:
1. 进入了 cy# JavaScript 控制台以后,至关于处在一个进程中,所以定义的变量在进程生命周期中一直可用。
2. #0x10c144d00 :#+对象地址=拿到该对象
复制代码
与 增强版 LLDB —— 修改 .lldbinit 文件 & 插件安装 相似,Cycript 支持加载自定义脚本,这极大的提升了它的调试效率,在前面简单使用中列出的可用快捷命令可不是 Cycript 原本就有的,而是 MonkeyDev 的做者加载了本身写的网络脚本才支持的:
而后呢?而后咱们也能够本身搞一份本身调试时经常使用的脚本,这里推荐一个小码哥写的 mjcript 。
加载.cy脚本的方式也为你准备好了:经过MonkeyDev加载网络或者本身的cy脚本。
来感觉一波自定义脚本的效率:
(function(exports) {
var invalidParamStr = 'Invalid parameter';
var missingParamStr = 'Missing parameter';
// app id
CJAppId = [NSBundle mainBundle].bundleIdentifier;
// mainBundlePath
CJAppPath = [NSBundle mainBundle].bundlePath;
// document path
CJDocPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
// caches path
CJCachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
// 加载系统动态库
CJLoadFramework = function(name) {
var head = "/System/Library/";
var foot = "Frameworks/" + name + ".framework";
var bundle = [NSBundle bundleWithPath:head + foot] || [NSBundle bundleWithPath:head + "Private" + foot];
[bundle load];
return bundle;
};
// keyWindow
CJKeyWin = function() {
return UIApp.keyWindow;
};
// 根控制器
CJRootVc = function() {
return UIApp.keyWindow.rootViewController;
};
// 找到显示在最前面的控制器
var _CJFrontVc = function(vc) {
if (vc.presentedViewController) {
return _CJFrontVc(vc.presentedViewController);
}else if ([vc isKindOfClass:[UITabBarController class]]) {
return _CJFrontVc(vc.selectedViewController);
} else if ([vc isKindOfClass:[UINavigationController class]]) {
return _CJFrontVc(vc.visibleViewController);
} else {
var count = vc.childViewControllers.count;
for (var i = count - 1; i >= 0; i--) {
var childVc = vc.childViewControllers[i];
if (childVc && childVc.view.window) {
vc = _CJFrontVc(childVc);
break;
}
}
return vc;
}
};
CJFrontVc = function() {
return _CJFrontVc(UIApp.keyWindow.rootViewController);
};
// 递归打印UIViewController view的层级结构
CJVcSubviews = function(vc) {
if (![vc isKindOfClass:[UIViewController class]]) throw new Error(invalidParamStr);
return vc.view.recursiveDescription().toString();
};
// 递归打印最上层UIViewController view的层级结构
CJFrontVcSubViews = function() {
return CJVcSubviews(_CJFrontVc(UIApp.keyWindow.rootViewController));
};
// 获取按钮绑定的全部TouchUpInside事件的方法名
CJBtnTouchUpEvent = 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;
};
// CG函数
CJPointMake = function(x, y) {
return {0 : x, 1 : y};
};
CJSizeMake = function(w, h) {
return {0 : w, 1 : h};
};
CJRectMake = function(x, y, w, h) {
return {0 : CJPointMake(x, y), 1 : CJSizeMake(w, h)};
};
// 递归打印controller的层级结构
CJChildVcs = function(vc) {
if (![vc isKindOfClass:[UIViewController class]]) throw new Error(invalidParamStr);
return [vc _printHierarchy].toString();
};
// 递归打印view的层级结构
CJSubviews = function(view) {
if (![view isKindOfClass:[UIView class]]) throw new Error(invalidParamStr);
return view.recursiveDescription().toString();
};
// 判断是否为字符串 "str" @"str"
CJIsString = function(str) {
return typeof str == 'string' || str instanceof String;
};
// 判断是否为数组 []、@[]
CJIsArray = function(arr) {
return arr instanceof Array;
};
// 判断num是否为数字
CJIsNumber = function(num) {
return typeof num == 'number' || num instanceof Number;
};
var _CJClass = function(className) {
if (!className) throw new Error(missingParamStr);
if (CJIsString(className)) {
return NSClassFromString(className);
}
if (!className) throw new Error(invalidParamStr);
// 对象或者类
return className.class();
};
// 打印全部的子类
CJSubclasses = function(className, reg) {
className = _CJClass(className);
return [c for each (c in ObjectiveC.classes)
if (c != className
&& class_getSuperclass(c)
&& [c isSubclassOfClass:className]
&& (!reg || reg.test(c)))
];
};
// 打印全部的方法
var _CJGetMethods = function(className, reg, clazz) {
className = _CJClass(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 _CJMethods = function(className, reg, clazz) {
return _CJGetMethods(className, reg, clazz)[0];
};
// 打印全部的方法名字
var _CJMethodNames = function(className, reg, clazz) {
return _CJGetMethods(className, reg, clazz)[1];
};
// 打印全部的对象方法
CJInstanceMethods = function(className, reg) {
return _CJMethods(className, reg);
};
// 打印全部的对象方法名字
CJInstanceMethodNames = function(className, reg) {
return _CJMethodNames(className, reg);
};
// 打印全部的类方法
CJClassMethods = function(className, reg) {
return _CJMethods(className, reg, true);
};
// 打印全部的类方法名字
CJClassMethodNames = function(className, reg) {
return _CJMethodNames(className, reg, true);
};
// 打印全部的成员变量
CJIvars = 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;
};
// 打印全部的成员变量名字
CJIvarNames = 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);
复制代码
只要你想,只要你能,更多姿式,等你解锁。
无使用场景的学习多半都是在浪费时间,不常用的知识也没法产生价值。Cycript 也不例外,若是仅仅是出于好奇,花了两个小时玩了一下下,而后今后别过,其实意义真的不大。有些同窗以为普遍涉猎,在面试的时候能够夸夸其谈,能增长一点“大佬”感,我我的是不认同的,稍微深刻一点的问题你就说不上来或者干脆不懂装懂反而会拔苗助长。因此我的仍是建议,既然学了一个东西,就尽力学的深刻一点,并在工做中不断思考,如何利用已学知识去提升效率。Cycript 除了在逆向工程中, 在正向开发和平常学习中,依然很是好用。
愿你有所收获~