Cycript Tricks

From iPhone Development Wiki

Printing 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:...


Printing 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#

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 Objective-C Objects from Addresses

Use new Instance(0xdeadbabe).

cy# var p = new Instance(0x8614390)
cy# p
["<SKPaymentTransaction: 0x8613d80>"]

Load frameworks

function loadFramework(fw) {
  var h="/System/Library/",t="Frameworks/"+fw+".framework";
  [[NSBundle bundleWithPath:h+t]||[NSBundle bundleWithPath:h+"Private"+t] load];
}

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(self) construct. This binds the self in the original function to the user-specified one. If more than one variable is needed, use func.call(self, arg1, arg2, arg3, ...). 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"].

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'] = ...

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];  [t release];
  var s = [new NSString initWithData:[[p fileHandleForReading] readDataToEndOfFile] encoding:4];
  var r = this.eval(s.toString()); [s release]; return r;
}

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; ....}