Cycript(三):使用技巧

Cycript(三):使用技巧,第1张

目录 获取对象(Getting objects)获取成员变量(Getting ivars)获取 BundleID(Getting bundle identifier)获取方法(Getting methods)列出所有子类(List all subclasses)加载框架(Load frameworks)包含其他 Cycript 文件(Include other Cycript files)使用 NSLog(Using NSLog)使用 CGGeometry 函数(Using CGGeometry functions)使用 NSError(Using NSError)将 Cycript 的输出写入到文件中(Writing Cycript output to file)打印视图层级结构(Printing view hierarchy)Cycript 脚本(Cycript scripts)基于 Cycript class-dump 的弱类转储实用工具(Utils)

获取对象(Getting objects)

使用 choose 函数获取 Objective-C 对象(Objective-C objects using choose)

在 Cycript 0.9.502 版本中引入并在此处记录的 choose() 函数,允许我们获取某个类所有的现有对象的数组
(hcg 注:简单地说,就是在内存的堆空间中查找指定的类及其子类的所有实例对象)

使用地址获取 Objective-C 对象(Objective-C objects from addresses)

cy# var p = #0x8614390
cy# p
[""]

获取 Javascript 变量(Javascript variables)

// 需要测试
cy# typedef int a;
cy# for (x in this) if (x == 'a') system.print('yay');
获取成员变量(Getting ivars)

通常,只需要输入 *varName 即可:

cy# *controller
{isa:"PrefsRootController",_contentView:">",_navBar:...

有时,*varName 会不起作用:

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",...

你也可以使用下面的 tryPrintIvars 函数获取尽可能多的成员变量以及它们的值:

function tryPrintIvars(a){ var x={}; for(i in *a){ try{ x[i] = (*a)[i]; } catch(e){} } return x; }
// 上面的代码经过整理后,如下所示:
function tryPrintIvars(a) { 
	var x={}; 
	for(i in *a) { 
		try { x[i] = (*a)[i]; } 
		catch(e) {} 
	} 
	return x; 
}

tryPrintIvars 函数的使用示例:

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:...
获取 BundleID(Getting bundle identifier)
NSBundle.mainBundle.bundleIdentifier
获取方法(Getting methods)

下面的 printMethods 函数用于获取方法:

// @param.className 类名
// @param.isa		如果为 undefined,则打印对象方法。否则,打印类方法
function printMethods(className, isa) {
	var count = new new Type("I");
	var classObj = (isa != undefined) ? objc_getClass(className).constructor : objc_getClass(className);
	var methods = class_copyMethodList(classObj, 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);
	return methodsArray;
}

printMethods 函数的使用示例:

cy# printMethods("MailboxPrefsTableCell")
[{selector:@selector(layoutSubviews),implementation:0x302bf2e9},{selector:@selector(setCurrentMailbox:),implementation:0x302bee0d},...

你也可以只查看 isaprototype 属性。例如,获取 rootViewController 的方法:

UIApp.keyWindow.rootViewController.isa.prototype

获取名称与特定正则表达式匹配的方法(Get methods matching particular RegExp)

function methodsMatching(cls, regexp) { return [[new Selector(m).type(cls), m] for (m in cls.prototype) if (!regexp || regexp.test(m))]; }
// 上面的代码经过整理后,如下所示:
function methodsMatching(cls, regexp) { 
	return [
			 [new Selector(m).type(cls), m] 
			 for (m in cls.prototype) 
			 	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:"]]

获取类方法(Getting class methods)

class.prototype 只包含对象方法。要 hook 类方法,你需要访问元类。一个简单的方法是:

cy# NSRunLoop.constructor.prototype['currentRunLoop'] = ...

或者,将 printMethods() 中可选的第二个参数设置为 true,例如 printMethods("NSRunLoop", true)

cy# printMethods("NSRunLoop", true)
[{selector:@selector(currentRunLoop),implementation:&(extern "C" id 674681217(id, SEL, ...))}...

替换现有的 Objective-C 方法(Replacing existing Objective-C methods)

你可以通过替换 prototype 数组中的内容来模拟 MSHookMessage,例如:

cy# original_NSRunLoop_description = NSRunLoop.prototype['description'];
(extern "C" id ":description"(id, SEL))
cy# NSRunLoop.prototype['description'] = function() { return original_NSRunLoop_description.call(this).toString().substr(0, 80)+", etc."; }
function (){var e;e=this;return original_NSRunLoop_description.call(e).toString().substr(0,80)+", etc."}
cy# [NSRunLoop currentRunLoop]
#"{wakeup port = 0x1003, stopped = false, ign, etc."
// 上面的代码经过整理后,如下所示:
cy# original_NSRunLoop_description = NSRunLoop.prototype['description'];
(extern "C" id ":description"(id, SEL))
cy# NSRunLoop.prototype['description'] = function () { 
	return original_NSRunLoop_description.call(this).toString().substr(0, 80) + ", etc."; 
}
function (){var e;e=this;return original_NSRunLoop_description.call(e).toString().substr(0,80)+", etc."}
cy# [NSRunLoop currentRunLoop]
#"{wakeup port = 0x1003, stopped = false, ign, etc."

注意 func.call(this) 的构造。这会将原始函数中的 this 绑定到用户指定的函数
如果需要多个变量,则请使用 function(arg1, arg2, arg3, ...) {...func.call(self, arg1, arg2, arg3, ...);},例如:

cy# original_SpringBoard_menuButtonDown = SpringBoard.prototype['menuButtonDown:']
0x17dbab1
cy# SpringBoard.prototype['menuButtonDown:'] = function(arg1) {original_SpringBoard_menuButtonDown.call(this, arg1);}
function (e) {var e;var $cy0=this;original_SpringBoard_menuButtonDown.call($cy0,e);}
// 上面的代码经过整理后,如下所示:
cy# original_SpringBoard_menuButtonDown = SpringBoard.prototype['menuButtonDown:']
0x17dbab1
cy# SpringBoard.prototype['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"

列出所有子类(List all subclasses)
[c for each (c in ObjectiveC.classes) if (class_getSuperclass(c) && [c isSubclassOfClass:UIView])]
// 上面的代码经过整理后,如下所示:
[c for each (c in ObjectiveC.classes) 
	if (class_getSuperclass(c) && [c isSubclassOfClass:UIView])]

(需要使用 class_getSuperclass 函数来防止由于对象所属的类没有继承自 NSObject,而导致的崩溃)

加载框架(Load frameworks)
function loadFramework(fw) {
  var h = "/System/Library/", t = "Frameworks/" + fw + ".framework";
  [[NSBundle bundleWithPath:h+t]||[NSBundle bundleWithPath:h+"Private"+t] load];
}
包含其他 Cycript 文件(Include other Cycript files)

在 Cycript 0.9.274-1 以及后续的几个版本中,Cycript 没有导入原生文件的功能。如果 Cycript 将被挂载到另一个进程,则因为数据将保留在目标进程里,所以你可以首先使用以下命令加载另一个 .cy 文件:

localhost:~ mobile$ cycript -p SpringBoard main.cy
0x12345678
localhost:~ mobile$ cycript -p SpringBoard
cy# ...

如果 Cycript 是独立启动的,则仍然可以通过 Cycript 编译器和 Javascript 的 eval 函数的组合来伪造 include 功能:

// 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());
}
// 上面的代码经过整理后,如下所示:
// 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());
}

从 Cycript 0.9.502 版本开始有导入原生文件的功能。请参阅 @import’s documentation

使用 NSLog(Using NSLog)

在最新版本的 Cycript 中,NSLog 应该可以正常工作。如果 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_ = 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))
// 上面的代码经过整理后,如下所示:
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))

如果你附加到了某个进程,则输出将保存在 syslog 中:

Nov 17 20:26:01 iPhone3GS Foobar[551]: w ivars: {\n    contentView = <UIView: 0x233ea0; ....}
使用 CGGeometry 函数(Using CGGeometry functions)

CGPointCGSizeCGRect 是数字类型的结构体(floatdouble),可以在 Cycript 中用简单的数组来表示:

cy# view.frame = [[10, 10], [100, 100]];
[[10,10],[100,100]]

如果你更喜欢使用 CGXxxxMake 函数,则你可以自己构建它们:

function CGPointMake(x, y) { return [x, y]; }
function CGSizeMake(w, h) { return [w, h]; }
function CGRectMake(x, y, w, h) { return [[x, y], [w, h]]; }
使用 NSError(Using NSError)
cy# var error = new @encode(NSError *)
&null
cy# var thing; [[NSFileManager defaultManager] copyItemAtPath:@"aaadsdsds" toPath:@"bbbdsdsdsds" error:error]; thing = *error
cy# thing
#'Error Domain=NSCocoaErrorDomain Code=260 "The file \xe2\x80\x9caaadsdsds\xe2\x80\x9d couldn\xe2\x80\x99t be opened because there is no such file." UserInfo=0x100310af0 {NSFilePath=aaadsdsds, NSUnderlyingError=0x1003108e0 "The operation couldn\xe2\x80\x99t be completed. No such file or directory"}'
// 上面的代码经过整理后,如下所示:
cy# var error = new @encode(NSError *)
&null
cy# var thing; 
[[NSFileManager defaultManager] copyItemAtPath:@"aaadsdsds" 
										toPath:@"bbbdsdsdsds" 
										 error:error]; 
thing = *error
cy# thing
#'Error Domain=NSCocoaErrorDomain 
		Code=260 
		"The file \xe2\x80\x9caaadsdsds\xe2\x80\x9d couldn\xe2\x80\x99t be opened because there is no such file." 
		UserInfo=0x100310af0 {
			NSFilePath = aaadsdsds, 
			NSUnderlyingError = 0x1003108e0 
			"The operation couldn\xe2\x80\x99t be completed. No such file or directory"
		}'
将 Cycript 的输出写入到文件中(Writing Cycript output to file)

因为 Cycript 的输出是一个 NSString,所以可以调用 -writeToFile: 方法将 Cycript 的输保存到某处。例子:

[[someObject someFunction] writeToFile:"/var/mobile/cycriptoutput.txt" atomically:NO encoding:4 error:NULL]
// 上面的代码经过整理后,如下所示:
[[someObject someFunction] writeToFile:"/var/mobile/cycriptoutput.txt" 
						    atomically:NO 
						      encoding:4 
						         error:NULL]

例如,你可以使用它来获取 SpringBoard 视图树的转储:

iPhone:~$ cycript -p SpringBoard
cy# [[UIApp->_uiController.window recursiveDescription] writeToFile:"/var/mobile/viewdump.txt" atomically:NO encoding:4 error:NULL]
// 上面的代码经过整理后,如下所示:
iPhone:~$ cycript -p SpringBoard
cy# [[UIApp->_uiController.window recursiveDescription] writeToFile:"/var/mobile/viewdump.txt" 
														 atomically:NO 
														   encoding:4 
														      error:NULL]
打印视图层级结构(Printing view hierarchy)
cy# UIApp.keyWindow.recursiveDescription().toString()
"<UIWindow: 0x13a900; frame = (0 0; 320 480); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x13a9d0>>
    <UITextField: 0x13abf0; frame = (20 40; 280 31); text = ''; opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x13ad10>>
        <UITextFieldRoundedRectBackgroundView: 0x143d10; frame = (0 0; 280 31); userInteractionEnabled = NO; layer = <CALayer: 0x143dc0>>
            <UIImageView: 0x144030; frame = (0 0; 8 15); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1440b0>>
            <UIImageView: 0x144400; frame = (8 0; 264 15); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x144430>>
            <UIImageView: 0x144460; frame = (272 0; 8 15); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x144490>>
            <UIImageView: 0x1444c0; frame = (8 0; 0 15); userInteractionEnabled = NO; layer = <CALayer: 0x1444f0>>
            <UIImageView: 0x144520; frame = (0 15; 8 1); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x144550>>
            <UIImageView: 0x144580; frame = (8 15; 264 1); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1445b0>>
            <UIImageView: 0x1445e0; frame = (272 15; 8 1); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x144610>>
            <UIImageView: 0x144640; frame = (8 15; 0 1); userInteractionEnabled = NO; layer = <CALayer: 0x144670>>
            <UIImageView: 0x1446a0; frame = (0 16; 8 15); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1446d0>>
            <UIImageView: 0x144700; frame = (8 16; 264 15); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x144730>>
            <UIImageView: 0x144760; frame = (272 16; 8 15); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x144790>>
            <UIImageView: 0x1447c0; frame = (8 16; 0 15); userInteractionEnabled = NO; layer = <CALayer: 0x1447f0>>
        <UILabel: 0x13aaf0; frame = (9 8; 266 15); text = 'Test'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1399f0>>"
Cycript 脚本(Cycript scripts)

用于加载 Cycript 文件 /var/root/common.cy 的自定义 shell 函数:

cyc () { cycript -p  /var/root/common.cy > /dev/null; cycript -p ; }

将此 shell 函数添加到 /etc/profile.d/cycript.sh 中,以使其在所有会话中都可用

用法:

cyc ProcessName

警告:
如果你对一个进程多次运行此命令,则脚本将多次被加载到 Cycript 中
根据你正在加载的脚本的不同,这可能会产生意想不到的后果
这不是正确的做法,saurik 建议你不要这样做

基于 Cycript class-dump 的弱类转储

链接:https://github.com/limneos/weak_classdump

用法:

root# cycript -p Skype weak_classdump.cy; cycript -p Skype
'Added weak_classdump to "Skype" (1685)'
cy# UIApp
""
cy# weak_classdump(HellcatApplication);
"Wrote file to /tmp/HellcatApplication.h"
cy# UIApp.delegate
""
cy# weak_classdump(SkypeAppDelegate,"/someDirWithWriteAccess/");
"Wrote file to /someDirWithWriteAccess/SkypeAppDelegate.h"

root# cycript -p iapd weak_classdump.cy; cycript -p iapd
'Added weak_classdump to "iapd" (1127)'
cy# weak_classdump(IAPPortManager)
"Wrote file to /tmp/IAPPortManager.h"

root# cycript -p MobilePhone weak_classdump.cy; cycript -p MobilePhone
'Added weak_classdump to "MobilePhone" (385)'
#cy weak_classdump_bundle([NSBundle mainBundle],"/tmp/MobilePhone")
"Dumping bundle... Check syslog. Will play lock sound when done."
实用工具(Utils)

链接:https://github.com/Tyilo/cycript-utils

安装 utils.cy 到已越狱 iOS 设备的 /usr/lib/cycript0.9/com/tyilo 目录下:

mkdir -p /usr/lib/cycript0.9/com
git clone https://github.com/Tyilo/cycript-utils.git /usr/lib/cycript0.9/com/tyilo

然后在 Cycript 中:

cy# @import com.tyilo.utils; 0

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/993906.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存