Cycript Tricks
Contents |
Getting objects
Objective-C objects using choose()
The function choose(), introduced in version 0.9.502[citation needed] and documented here, allows us to get an array of existing objects of a certain class.
Objective-C objects from addresses
Use new Instance(0xdeadbabe).
cy# var p = new Instance(0x8614390) cy# p ["<SKPaymentTransaction: 0x8613d80>"]
Javascript variables
Requires testing.
cy# typedef int a; cy# for (x in this) if (x == 'a') system.print('yay');
Getting ivars
Often just typing *varName works:
cy# *controller {isa:"PrefsRootController",_contentView:"<UIView: 0x10bd70; frame = (0 0; 320 460); autoresize = W+H; layer = <CALayer: 0x150120>>",_navBar:... cy#
Sometimes it does not...
cy# *UIApp {message:"hasProperty callback returned true for a property that doesn't exist.",name:"ReferenceError"}
Then you can do:
cy# [i for (i in *UIApp)] ["isa","_delegate","_touchMap","_exclusiveTouchWindows","_event",...
You may use this function to get as much ivar values as possible:
function tryPrintIvars(a){ var x={}; for(i in *a){ try{ x[i] = (*a)[i]; } catch(e){} } return x; }
To use:
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:...
Getting methods
Function to get the methods:
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; }
Usage:
cy# printMethods("MailboxPrefsTableCell") [{selector:@selector(layoutSubviews),implementation:0x302bf2e9},{selector:@selector(setCurrentMailbox:),implementation:0x302bee0d},... cy#
You can also just look at the message property of the isa, e.g. to get rootViewControllers methods: UIApp.keyWindow.rootViewController.isa.messages
Get methods matching particular RegExp
function methodsMatching(cls, regexp) { return [[new Selector(m).type(cls), m] for (m in cls.messages) if (!regexp || regexp.test(m))]; }
Usage:
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.messages only contains instance methods. To hook class methods, you need to get to its metaclass. A simple way would be
cy# NSRunLoop->isa.messages['currentRunLoop'] = ...
Replacing existing Objective-C methods
You can simulate MSHookMessage by replacing contents in the messages array, e.g.
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] "<CFRunLoop 0x205ee0 [0x381dbff4]>{locked = false, wait port = 0x1303, stopped = , etc."
Note the func.call(this) construct. This binds the this in the original function to the user-specified one. If more than one variable is needed, use function(arg1, arg2, arg3, ...) {...func.call(self, arg1, arg2, arg3, ...);}, e.g.
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);}
Note that the subsequent arguments will not be automatically mapped to the corresponding Objective-C types, so instead of "foo" you will need to use [NSString stringWithString:"foo"].
Load frameworks
function loadFramework(fw) { var h="/System/Library/",t="Frameworks/"+fw+".framework"; [[NSBundle bundleWithPath:h+t]||[NSBundle bundleWithPath:h+"Private"+t] load]; }
Include other Cycript files
As of 0.9.274-1, there isn't a native file import feature. If cycript will be hooked into another process, since the data will be retained there, you can first load the other .cy file with this:
localhost:~ mobile$ cycript -p SpringBoard main.cy 0x12345678 localhost:~ mobile$ cycript -p SpringBoard cy# ...
If cycript is launched standalone, inclusion can still be faked with a combination of cycript compiler and Javascript's eval function:
// 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()); }
As of version 0.9.502[citation needed], there is. See @import's documentation.
Using NSLog
Type in the console:
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); }
And then you can use NSLog as usual:
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))
If you are attached to a process, the output is going to be in the syslog:
Nov 17 20:26:01 iPhone3GS Foobar[551]: w ivars: {\n contentView = <UIView: 0x233ea0; ....}
Using CGGeometry functions
In order to use functions from the CGGeometry class, you must type this in the cycript prompt:
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)}; }
Writing Cycript output to file
Cycript output is an NSString, so it is possible to call writeToFile and save it somewhere. Example:
[[someObject someFunction] writeToFile:"/var/mobile/cycriptoutput.txt" atomically:NO encoding:4 error:NULL]
You can use this, for example, to get a dump of SpringBoard's view tree.
iPhone:~$ cycript -p SpringBoard cy# [[UIApp->_uiController.window recursiveDescription] writeToFile:"/var/mobile/viewdump.txt" atomically:NO encoding:4 error:NULL]
Printing view hierarchy
cy# ?expand expand == true cy# UIApp.keyWindow.recursiveDescription "<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>>"
The ?expand command toggles between line breaks being displayed as actual line breaks not just /n
Cycript scripts
Custom shell function that loads a cycript file:
cyc () { cycript -p $1 /var/root/common.cy > /dev/null; cycript -p $1; }
Usage: cyc ProcessName
Add this to /etc/profile.d/cycript.sh to make it available in all sessions.
Warning: If you run this command multiple times against a process, the scripts will be loaded into the script multiple times. This could potentially have unexpected consequences depending on the scripts you are loading. It is not a proper way of doing this and saurik recommends against it.
Weak Classdump (Cycript based class-dump)
Link: https://github.com/limneos/weak_classdump
Usage:
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."