Cycript 简介
-
什么是 Cycript ?
Cycript 允许开发者通过具有语法高亮显示和代码补全功能的交互式控制台,使用 Objective-C++ 和 JavaScript 语法的混合体,来探索和修改 iOS 或 macOS 上正在运行的应用程序(Cycript 还可以在 Android 和 Linux 上独立运行,并提供对 Java 的访问,但是不能注入进程)
可以将 Cycript 看成是一个理解 Objective-C 语法的 JavaScript 解释器,Cycript 二进制文件也是该语言的交互式解释器(Read-Eval-Print Loop)
-
Cycript 的特点
① 注入进程(Inject Into Processes)
bash# cycript -p SpringBoard
② Objective-C 消息(Objective-C Messages)
cy# [UIApp description] @"<SpringBoard: 0x10ed05e40>"
③ JavaScript 扩展(JavaScript Extensions)
cy# [for (x of [1,2,3]) x+1] [2,3,4]
④ 毫不费力地进行探索(Effortless Exploration)
cy# choose(CALayer)[0] #"<CALayer: 0x115807910>"
⑤ 桥接对象模型(Bridged Object Model)
cy# @[0,1] instanceof Array true
⑥ 外部函数调用(Foreign Function Calls)
cy# var a = malloc(128) 0x1147c9d00
⑦ 神奇的 Tab 键代码补全(Magical Tab-Complete)
cy# ({m: 4, b: 5}).<Tab><Tab> b m
⑧ C++11 Lambda 语法(C++11 Lambda Syntax)
cy# [&](int a)->int{return a} 0x111001000
-
使用示例:hook fopen 函数
假设我们有一个程序可以打开
/etc/passwd
来检查我们的密码。我们希望它使用/var/passwd-fake
。 首先,我们需要fopen
函数的地址cy# fopen = dlsym(RTLD_DEFAULT, "fopen") (typedef void*)(0x7fff900c34ec)
但是,我们不能在没有类型签名的情况下调用此函数,所以让我们将它转换为
Functor
。使用 Cycript,我们可以使用高级 Ctypedef
语法cy# fopen = (typedef void* (char *, char *))(fopen) (extern "C" void* fopen(char *, char *))
接下来,让我们导入 Substrate,这样我们就可以使用 MS.hookFunction 来修改
fopen
函数// 交换假的 passwd 文件,并记录所有调用参数和调用结果 cy# @import com.saurik.substrate.MS cy# var oldf = {} cy# var log = [] cy# MS.hookFunction(fopen, function(path, mode) { if (path == "/etc/passwd") path = "/var/passwd-fake"; var file = (*oldf)(path, mode); log.push([path.toString(), mode.toString(), file]); return file; }, oldf)
除了我们的恶意修改之外,我们还可以查看所有对
fopen
函数的调用,并跟踪返回的FILE *
的值是什么。 让我们试试看cy# fopen("/etc/passwd", "r"); (typedef void*)(0x7fff72c14280) cy# log [["/var/passwd-fake","r",(typedef void*)(0x7fff72c14280)]]
-
使用示例:JS/ObjC 对象桥接(JS/ObjC Object Bridging)
为了方便,一些原生 JavaScript 类型被桥接到了相应的 Objective-C 类型,所以你可以使用
[[41, "foo", true, [8,6], {a:12,b:46}, 36] indexOfObject:"foo"]
代替
[[NSArray arrayWithObjects: [NSNumber numberWithInt:41], "foo", [NSNumber numberWithBool:YES], [NSArray arrayWithObjects:[NSNumber numberWithInt:8], [NSNumber numberWithInt:6], nil], [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:12], "a", [NSNumber numberWithInt:46], "b", nil], [NSNumber numberWithInt:36], nil] indexOfObject:"foo"]
JS 和 ObjC 数据类型对照表
// JS 类型 -- ObjC 类型 number -- NSNumber (CFNumber) boolean -- NSNumber (CFBoolean) string -- NSString Array -- NSArray object (Associative array) -- NSDictionary
Cycript 中的
null
等价于 Objective-C 中的nil
。此外,Cycript 中还定义了nil
、YES
和NO
-
了解更多信息
- 阅读 Cycript 手册 — 我通过阅读 Visual Basic 3.0 手册学会了 Win32 编程
- #cycript @ irc.saurik.com — 与其他志趣相投的人交谈始终是一项有趣的活动
- Cycript 源代码 — 当你遇到严重的问题时,请阅读源代码
- Cycript 的 iPhone Dev Wiki 页面 — 虽然其中的一些信息已经过时,但是其中还是有很多有用的东西可以查看
- Adam Bell 的 WWJC 演讲 — Adam 就 iOS 内部进行了精彩的演讲,使用 Cycript 进行演示
- ECMAScript 规范 — Cycript 基于 JavaScript:你应该很了解这门语言
-
关于 Cycript 的演讲
Cycript 附加的特性
-
附加语法(Additional syntax)
新语法:
[obj msg:var]
意义:使用参数var
将消息msg:
发送给对象obj
。这是 Objective-C 的消息发送语法
脱糖表示:objc_msgSend(obj, @selector(msg:).value, var ...)
新语法:
@import(module)
意义: 导入一个.cy
文件。类似于 JavaScript 的require()
函数
新语法:
@selector(selname)
意义:使用 Objective-C 的语法,返回名为selname
的方法选择器
脱糖表示:new Selector("selname");
新语法:
obj->ivar
意义:获取 Objective-C 对象obj
的成员变量ivar
脱糖表示:obj.$cyi.ivar
新语法:
*ptr
意义:取消引用指针,或列出对象的所有ivar
(以便你可以使用(*obj).ivar
访问它们)
脱糖表示:ptr.$cyi
新语法:
obj->[key]
意义:等效于(*obj)[key]
新语法:
&var
意义:获取变量的地址。只有 ObjC 类的实例才可以获取地址
脱糖表示:var.$cya()
新语法:
@class classname : superclass {} +methodname { function body } -methodname { function body } ... @end
意义:声明一个 Objective-C 类。 省略类名以声明一个匿名类
新语法:
@class existingclass +methodname { function body } -methodname { function body } ... @end
意义:向现有类插入额外的方法。现有类本身可以是一个表达式,例如
@class ([obj class])
新语法:
new classname
意义:虽然不完全是一种新语法,但是这种构造对于 Objective-C 类具有新的含义。虽然这类似于[classname alloc]
,但是所申请的内存资源将由 JavaScriptCore 的垃圾回收器进行管理。要完全初始化该类,你需要调用[new classname initWithFoo:...]
新语法:
@"str"
意义:相当于"str"
新语法:
[super ...]
意义:用于表示超类的局部变量
脱糖表示:objc_msgSend(???, ... )
新语法:
0bxxxxxx
意义:二进制字面量 -
仅限 REPL 的附加语法(REPL-only additions)
这些附加语法用于进行调试
?debug
:用于切换调试的输出
?bypass
:用于绕过语法错误,以友好的方式打印
?expand
:用于切换是否将换行符等显示为真正的换行符,或只是\n
?gc
:强制进行 JavaScript 垃圾回收
?syntax
:用于切换语法的高亮显示 -
附加的类型(Additional types)
Selector(selname)
:声明一个方法选择器Functor(function body, type encoding)
:将 Objective-C 类型编码与函数相关联
例如,new Functor(function(x,y){return (x+y).toString(16);}, "*dd");
声明了一个(double, double) → char*
的函数Pointer(address, type encoding)
:将输入的数字视为指针
与 C 指针一样,结果可以使用*
取消引用,并使用[i]
下标,但不直接支持指针的算术运算Type(type encoding)
:创建一个类型。结果值可以是new-ed
以获得一个Pointer
例如,var p = new new Type("d");
要释放指针,请使用free()
函数Instance(address)
:将地址视为 Objective-C 对象的一个实例Super(self, selector)
:返回一个对象,当发送消息时,该对象将被转发到自己(self
)的超类此外:
像int
、id
、char
、double
等标识符被预定义为相应的类型(new Type("i")
等)
因此,如果要alloc
一个指针,则可以简单地使用new int
甚至是new int[42]
-
附加的变量和方法(Additional variables and methods)
_
:用于保存最后执行的命令的结果的值的变量(仅限 REPL 环境)ObjectiveC.images
:镜像的关联数组(可以将 JS 的关联数组理解为 OC 中的字典)。键是被加载的库的路径,值是该库中所有的类ObjectiveC.classes
:类的关联数组(可以将 JS 的关联数组理解为 OC 中的字典)。 键是类名,值是类本身ObjectiveC.protocols
:协议的关联数组(可以将 JS 的关联数组理解为 OC 中的字典)。键是协议名称,值是协议本身obj.toJSON()
:将指定的对象obj
转换为 JSONobj.toCYON()
:将指定的对象obj
转换为 CYON(Cycript Object Notation,Cycript 对象表示法)obj.value
:对于某些类型的对象,会返回地址class.messages
:消息的关联数组(包含指定类class
中的所有消息)(可以将 JS 的关联数组理解为 OC 中的字典)
键是方法选择器的名称,值是方法的实现(函数指针)system.print(string)
:将指定的字符串string
打印到系统日志system.args
:当前可执行文件的参数selector.type(class)
:返回指定的类class
中指定的方法选择器selector
的类型编码
例如,@selector(copyWithZone:).type(NSString)
返回@12@0:4^{_NSZone=}8
其他
-
JavaScript 1.6+ 的特性(JavaScript 1.6+ Features)
Cycript 支持以下由 Mozilla 扩展的 JavaScript 1.6+ 的特性:
- for each/in(JS1.6)
- Array comprehensions (JS1.7)
- E4X(不适用于 iOS)
-
其他用途(Other uses)
使用 Cycript 可执行二进制文件的
-c
标志,可以将 Cycript 源文件编译成标准的 JavaScript 1.5 源文件,例如:Your-iPhone:~ mobile$ echo "[x*x for each(x in [1,2,3])]" | cycript -c > x.js Your-iPhone:~ mobile$ cat x.js (function($cyv,x){$cyv=[];(function($cys){$cys=[1,2,3];for(x in $cys){x=$cys[x];$cyv.push(x*x)}})();return $cyv})()
// 上面的代码经过整理后,如下所示: Your-iPhone:~ mobile$ echo "[x*x for each(x in [1,2,3])]" | cycript -c > x.js Your-iPhone:~ mobile$ cat x.js (function($cyv, x) { $cyv=[]; (function($cys) { $cys = [1, 2, 3]; for (x in $cys) { x = $cys[x]; $cyv.push(x * x) } })(); return $cyv })()
-
注意事项(Considerations)
因为输入到控制台的每个命令都在自动释放池中运行
所以在一个命令中声明的变量,可能会在下一个使用它的命令运行时,被释放了