Cycript(三):使用技巧

本文详细介绍了Cycript中的关键功能,如获取Objective-C对象、成员变量操作、Bundle ID获取、方法查找、子类列举、框架加载、文件包含、NSLog使用等,是深入理解Cycript的强大工具指南。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

获取对象(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
    ["<SKPaymentTransaction: 0x8613d80>"]
    
  • 获取 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:"<UIView: 0x10bd70; frame = (0 0; 320 460); autoresize = W+H; layer = <CALayer: 0x150120>>",_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:"<CALayer: 0x2a5160>",_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]
    #"<CFRunLoop 0x13750a630 [0x1a103e150]>{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]
    #"<CFRunLoop 0x13750a630 [0x1a103e150]>{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 $1 /var/root/common.cy > /dev/null; cycript -p $1; }

将此 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
"<HellcatApplication: 0x1734e0>"
cy# weak_classdump(HellcatApplication);
"Wrote file to /tmp/HellcatApplication.h"
cy# UIApp.delegate
"<SkypeAppDelegate: 0x194db0>"
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值