轉自 http://lsq.me/2014/01/12/cycript-tricks/ 自己留著看看的
引言
在分析iOS應用程序的時候,經常會用到Cycript這個工具。本文將介紹使用Cycript的一些基本命令和高級技巧。
打印Ivar值
很多時候輸入*varName就可以:
cy# *controller
{isa:"PrefsRootController",_contentView:">",_navBar:...
cy#
然而有時候卻不行:
cy# *UIApp
{message:"hasProperty callback returned true for a property that doesn't exist.",name:"ReferenceError"}
但是你可以這樣:
cy# [i for (i in *UIApp)]
["isa","_delegate","_touchMap","_exclusiveTouchWindows","_event",...
為了取得盡可能多的ivar值,你可以用下面這個函數:
function tryPrintIvars(a){ var x={}; for(i in *a){ try{ x[i] = (*a)[i]; } catch(e){} } return x; }
用法是:
cy# *a
{message:"hasProperty callback returned true for a property that doesn't exist.",name:"ReferenceError"}
cy# tryPrintIvars(a)
{isa:"SBWaveView",_layer:"",_tapInfo:null,_gestureInfo:null,_gestureRecognizers:...
打印方法名
可以取得方法名的函數:
function printMethods(className) {
var count = new new Type("I");
var methods =? ? ? class_copyMethodList(objc_getClass(className), count);
var methodsArray = [];
for(var i = 0; i < *count; i++) {
var method = methods[i];
methodsArray.push({selector:method_getName(method), implementation:method_getImplementation(method)});
}
free(methods);
free(count);
return methodsArray;
}
可以這樣用:
cy# printMethods("MailboxPrefsTableCell")
[{selector:@selector(layoutSubviews),implementation:0x302bf2e9},{selector:@selector(setCurrentMailbox:),implementation:0x302bee0d},...
cy#
你也可以只看isa的消息屬性,例如用UIApp.keyWindow.rootViewController.isa.messages可以取得rootViewControllers的方法。
用正則表達式取方法名
function methodsMatching(cls, regexp) { return [[new Selector(m).type(cls), m] for (m in cls.messages) if (!regexp || regexp.test(m))]; }
用法:
cy# methodsMatching(NSRunLoop, /forKey:$/)
[["v20@0:4I8@12@16","didChange:valuesAtIndexes:forKey:"],["v20@0:4I8@12@16","willChange:valuesAtIndexes:forKey:"],["v16@0:4@8@12","setValue:forKey:"]]
從地址獲取Objective-C對象
用new Instance(0xdeadbabe):
cy# var p = new Instance(0x8614390)
cy# p
[""]
載入框架
function loadFramework(fw) {
var h="/System/Library/",t="Frameworks/"+fw+".framework";
[[NSBundle bundleWithPath:h+t]||[NSBundle bundleWithPath:h+"Private"+t] load];
}
替換Objective-C方法
你可以通過替換messages數組的內容來模擬MSHookMessage:
cy# original_NSRunLoop_description = NSRunLoop.messages['description'];
0x339d94c3
cy# NSRunLoop.messages['description'] = function() { return original_NSRunLoop_description.call(this).toString().substr(0, 80)+", etc."; }
{}
cy# [NSRunLoop currentRunLoop]
"{locked = false, wait port = 0x1303, stopped = , etc."
注意func.call(this)的結構,就是它把原函數的this綁定到用戶指定的那個去了。如果需要更多地參數,用function(arg1, arg2, arg3, ...) {...func.call(self, arg1, arg2, arg3, ...);}來代替,例如:
cy# original_SpringBoard_menuButtonDown = SpringBoard.messages['menuButtonDown:']
0x17dbab1
cy# SpringBoard.messages['menuButtonDown:'] = function(arg1) {original_SpringBoard_menuButtonDown.call(this, arg1);}
function (e) {var e;var $cy0=this;original_SpringBoard_menuButtonDown.call($cy0,e);}
注意參數不會被自動映射到相對應的Objective-C類型,所以參數應該用[NSString stringWithString:"foo"]而不是簡單的"foo"。
獲取類方法
class.messages只包含實例方法。要hook類方法,你需要得到他的metaclass,一個簡單地方法是:
cy# NSRunLoop->isa.messages['currentRunLoop'] = ...
引入其他Cycript文件
從0.9.274-1開始,取消了導入native文件的組件。當Cycript需要hook到其他進程時,既然數據都保留在那兒,你可以首先加載那個.cy文件:
localhost:~ mobile$ cycript -p SpringBoard main.cy
0x12345678
localhost:~ mobile$ cycript -p SpringBoard
cy# ...
如果Cycript是獨立啟動的,使用Cycript編譯器和Javascript的eval表達式相結合也可以使引入作偽:
// include other .cy files
function include(fn) {
var t = [new NSTask init]; [t setLaunchPath:@"/usr/bin/cycript"]; [t setArguments:["-c", fn]];
var p = [NSPipe pipe]; [t setStandardOutput:p]; [t launch]; [t waitUntilExit];
var s = [new NSString initWithData:[[p fileHandleForReading] readDataToEndOfFile] encoding:4];
return this.eval(s.toString());
}
使用NSLog
在控制臺輸入:
NSLog_ = dlsym(RTLD_DEFAULT, "NSLog")
NSLog = function() { var types = 'v', args = [], count = arguments.length; for (var i = 0; i != count; ++i) { types += '@'; args.push(arguments[i]); } new Functor(NSLog_, types).apply(null, args); }
然后就可以像平常一樣使用NSLog:
cy# NSLog_ = dlsym(RTLD_DEFAULT, "NSLog")
0x31451329
cy# NSLog = function() { var types = 'v', args = [], count = arguments.length; for (var i = 0; i != count; ++i) { types += '@'; args.push(arguments[i]); } new Functor(NSLog_, types).apply(null, args); }
{}
cy# NSLog("w ivars: %@", tryPrintIvars(w))
如果已經attach到一個進程了,輸出就已經寫到syslog了:
Nov 17 20:26:01 iPhone3GS Foobar[551]: w ivars: {\n? ? contentView =
使用CGGeometry功能
為了使用CGGeometry類的函數,必須要在Cycript窗體輸入:
function CGPointMake(x, y) { return {x:x, y:y}; }
function CGSizeMake(w, h) { return {width:w, height:h}; }
function CGRectMake(x, y, w, h) { return? ? {origin:CGPointMake(x,y), size:CGSizeMake(w, h)}; }
將Cycript輸出寫入文件
Cycript的輸出時NSString,所以調用writeToFile然后把它保存起來時可能的,例如:
[[someObject someFunction] writeToFile:"/var/mobile/cycriptoutput.txt" atomically:NO encoding:4 error:NULL]
你可以這么用,下面是獲取SpringBoard view的結構的示例:
iPhone:~$ cycript -p SpringBoard
cy# [[UIApp->_uiController.window recursiveDescription] writeToFile:"/var/mobile/viewdump.txt" atomically:NO encoding:4 error:NULL]
打印View的繼承關系
iPhone:~$ cycript -p SpringBoard
cy# ?expand
expand == true
cy# UIApp.keyWindow.recursiveDescription
">
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>"
?expand命令使換行顯示的更加正常,而不僅僅是\n。
參考文獻