4.2 Cycript

Cycript是由saurik推出的一款脚本语言(如图4-9所示),可以看作是Objective-JavaScript。

图4-9 Cycript

很多朋友可能会因为不了解JavaScript,所以在潜意识里认为Cycript很晦涩。事实上,笔者也不懂JavaScript,因为懒得学习新知识,所以在知道Cycript的很长一段时间里故意无视它,直到有一次在公司的无聊会议中把玩MTerminal,在Cycript中完成了几个函数的测试,节省了不少时间,才重新认识这门语法简单而功能强大的语言。其实对于熟悉Objective-C的朋友们来说,脚本语言不难上手,只要克服自己的畏难情绪,就一定能快速掌握它,Cycript当然也不例外。Cycript具备脚本语言的便利,可以直接用来写App,但saurik自己都说:“This isn't quite‘ready for primetime’”;笔者认为,Cycript最为贴心和实用的功能是它可以帮助我们轻松测试函数效果,整个过程安全无副作用,效果十分显著,实乃业界良心!因此,本书只对此功能作简单介绍,更多详细资料可参阅它的官网http://www.cycript.org

可以从MTerminal中执行Cycript,也可以ssh到iOS中执行Cycript。输入“cycript”,出现“cy#”提示符,说明已成功启动Cycript,如下:


FunMaker-5:~ root# cycript
cy#

在启动Cycript之后,就可以开始编写App了。因为这里主要用到它测试函数的功能,而不是用它来写App,所以需要把代码注入一个现成的进程中,让代码运行起来。按下“control+D”,先退出Cycript。一般来说,选择注入哪个进程,要依测试的具体函数而定,这个函数所属的类存在于哪些进程,则注入这些进程,从而保证这个类是存在的。这句话的含义有些难以理解,举例说明如下。

假如现在要测试PhoneApplication类的+sharedNumberFormatter函数功能及其返回值,则必须注入MobilePhone这个进程,因为PhoneApplication类只存在于MobilePhone进程中;同理,如果要测试SBUIController类的-lockFromSource:函数功能,则必须注入SpringBoard这个进程;当然,如果要测试NSString类的-length函数功能及其返回,则可注入任意链接了Foundation库的进程。因为需要用Cycript测试的一般都是私有函数,所以一个总的准则是从哪个进程逆向出的函数,就注入这个进程来测试;从哪个库逆向出的函数,就注入链接这个库的进程来测试。

通过进程注入方式调用Cycript测试函数的步骤很简单,以SpringBoard为例,首先找到进程名或PID,如下:


FunMaker-5:~ root# ps -e | grep SpringBoard
 4567 ??         0:27.45 /System/Library/CoreServices/SpringBoard.app/SpringBoard
 4634 ttys000    0:00.01 grep SpringBoard

SpringBoard进程的PID是4634。接下来输入“cycript-p 4634”或“cycript-p SpringBoard”,把Cycript注入SpringBoard,这时,Cycript就已经运行在SpringBoard进程里,可以开始测试了。

我们都知道,UIAlertView是iOS中使用最多的弹框类。使用Objective-C语言,弹出一个对话框只需要3行代码,如下:


UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"iOSRE" message:@"snakeninny" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];

上面的Objective-C语言转化成Cycript非常简单,如下:


FunMaker-5:~ root# cycript -p SpringBoard
cy# alertView = [[UIAlertView alloc] initWithTitle:@"iOSRE" message:@"snakeninny" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]
#"<UIAlertView: 0x1700e580; frame = (0 0; 0 0); layer = <CALayer: 0x164146c0>>"
cy# [alertView show]
cy# [alertView release]

不需要声明对象类型,也不需要结尾的分号,就是这么简单。如果函数有返回值,Cycrip会把它在内存中的地址及一些基本信息实时打印出来,非常直观。执行上面的语句后,SpringBoard会弹出对话框,如图4-10所示。

图4-10 用Cycript执行代码

如果知道一个对象在内存中的地址,可以通过“#”操作符来获取这个对象,例如:


cy# [[UIAlertView alloc] initWithTitle:@"iOSRE" message:@"snakeninny" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]
#"<UIAlertView: 0x166b4fb0; frame = (0 0; 0 0); layer = <CALayer: 0x16615890>>"
cy# [#0x166b4fb0 show]
cy# [#0x166b4fb0 release]

如果知道一个类对象存在于当前的进程中,却不知道它的地址,不能通过“#”操作符来获取它,此时,不妨试试choose命令,它的用法如下:


cy# choose(SBScreenShotter)
[#"<SBScreenShotter: 0x166e0e20>"]
cy# choose(SBUIController)
[#"<SBUIController: 0x16184bf0>"]

只需要choose一个类,Cycript就能帮你在内存中找出一个它的对象,供你使用。太方便了是不是?不过,choose命令并不是百发百中的,当它不能返回给你一个可用对象时,就必须手动寻找了。这部分内容会在第6章详细介绍。

测试私有函数的方法用到的一般也就是上面几个命令,下面以登录iMessage的Apple ID为例,用Cycript测试并实现这个功能。先拿到iMessage的登录管理器,命令如下:


FunMaker-5:~ root# cycript -p SpringBoard
cy# controller = [CNFRegController controllerForServiceType:1]
#"<CNFRegController: 0x166401e0>"

然后登录自己的iMessage,命令如下:


cy# [controller beginAccountSetupWithLogin:@"snakeninny@gmail.com" password:@"bbs.iosre.com" foundExisting:NO]
#"IMAccount: 0x166e7b30 [ID: 5A8E19BE-1BC9-476F-AD3B-729997FAA3BC Service: IMService[iMessage] Login: E:snakeninny@gmail.com Active: YES LoginStatus: Connected]"

这一步相当于是在图4-11所示的界面上做了登录iMessage的操作。

函数返回了一个登录成功的IMAccount,也就是iMessage账号。接着选择用于收发iMessage的地址,命令如下:


cy# [controller setAliases:@[@"snakeninny@gmail.com"] onAccount:#0x166e7b30]
1

这一步相当于是在图4-12所示的界面上做了选择iMessage地址的操作。

返回值表明操作成功。最后检查一下此账号是否完成了登录过程,如下:


cy# [#0x166e7b30 CNFRegSignInComplete]
1

返回值表明已完成了iMessage账号的登录。

很简单吧?不用我再解释什么了吧?作为本节的练习,下面请自行把刚才登录iMessage的Cycript语言翻译成Objective-C语言,并编写一个tweak来验证你的翻译是否正确,好好体会一下Cycript的用法。注意,Apple ID的用户名和密码要改成你自己的!

图4-11 登录iMessage

图4-12 选择iMessage地址