Our Blog

Advanced Cycript and Substrate

Reading time ~9 min

Mobile assessments are always fun as the environment is constantly evolving. A recent trend has been the use of custom protocols for communication between the application and server. This holds particularly true for financial institutes who are aiming to protect both the confidentiality and integrity of data. Most of these custom protocols are over TCP, wrap data in custom crypto, which usually includes signing of the payload to prevent tampering. Even when transmitted over HTTPS, we have noticed a trend where data within the HTTP body gets encrypted and signed using some custom crypto. Both of these processes can greatly frustrate testers using standard network intercepting tools.

The most interesting one to go after are the custom protocols as they give us the greatest challenge. How do we intercept the traffic, how to we reverse the protocol?

Attacking a protocol:

On iOS; let us assume we are faced with a custom protocol, what do we do when we want to break into the protocol and we can’t intercept on the wire? Or how do you bypass cert pinning, jailbreak detection, without patching the application binary?

The answer should always be cycript and substrate. This powerful combination allows you to interact with an application and runtime and dynamically inject code into the application.  Now I’m not going to do a cycript intro here other than “how do you inject into a process”, for the basic features of cycript, there are a TON of tutorials online. All of these will show you how to dynamically bypass a password screen or use method swizzling (replacing methods) to bypass simple root detection.

For our needs the method swizzling isn’t always good enough, as we don’t always want to replace application functionality, sometimes you want to see how the function operates (parameter values and output from the function).

Basic – must know.

To inject into a process with cycript (well install cycript through cydia first) – you need to either know the PID or the Application Name.

$ ps aux | grep -i myapp
mobile    2045   0.0  5.5   689604  55996   ??  Ss    3:11PM   0:07.16 /var/mobile/Containers/Bundle/Application/E34D065B-269D-48E4-B7A3-87BAFCE58738/MyApp-QA.app/MyApp-QA

Now you can inject into the process with the PID or Application Name:

$ cycript -p 2045
OR
$ cycript -p MyApp-QA

The last thing to know, is that you can create a cycript script file and load this, so any common functions you may need. I’ve created the following common.cy file that I use to load substrate and to setup a new function that allows me to log to the system log:

@import com.saurik.substrate.MS
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); }

To load this file into your session:

$ cycript -p MyApp-QA common.cy
$ cycript -p MyApp-QA

Note that you call cycript twice, the first to load the file into the cycript session, and the second to start the interactive console.

To read from the system log, you can use the following command:

$ socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock

In the syslog session, use ‘watch’ to get scrolling output from the syslog.

Advanced…

Here I want to show you how to hook functions and hook messages. This is what the other tutorials don’t show you and what created numerous headaches for me.

The first thing to know is that the content to follow is applicable to Objective-C based apps, I have not tested against SWIFT applications, but in theory it should be easy enough to do the same against swift apps.

Once you’ve chosen your target app, you need to do the usual steps of clutch (or any other way you have of getting an unencrypted copy of the ipa) and use class-dump-z to get the header files. Looking at our header files, we need to identify the functions/selectors that we wish to hook. This is the first pit fall, you need to identify what is a function and what is a selector.  The basic principle here being that we are either trying to hook a Class method (think static methods from other languages [you don’t need an instance of the class to be initialised]) or Instance methods (you need to use an object made from the class to reference the relevant method). How do you identify these?

@interface Transport_Service : eSecretChannelObject {
@private
 NSString* _serviceId;
}
@property(retain, nonatomic) NSString* serviceId;
+(id)serviceForServiceId:(id)serviceId;
+(id)services;
-(void).cxx_destruct;
-(id)initWithServiceId:(id)serviceId;
-(id)save;
-(BOOL)isRegistered;
-(id)hashed_serviceId;
@end

The function definitions starting with a + are the class methods.     The indicates an instance method. We treat these two differently when doing our hooking.

The instance methods will be hooked using hookMessage and the class methods will be hooked using hookFunction. This difference caused massive headaches for me at first, as I was trying to use hookFunction for everything (makes sense, we are going after functions? ).
Another way to check which type of hooking you should be doing is to try and instantiate the target function from cycript. If you can using the [className methodName], then you have a Class method, otherwise, it’s an instance method.

cy# [Transport_Service services] //class method
@[]
cy# [Transport_Service isRegistered] //instance method
Error: unrecognized selector isRegistered sent to object 0x19e688

The outline for the hookMessage and hookFunction are outlined here: http://www.cycript.org/manual/#6635fc86-3c73-4176-b0b0-75dd3aa99ce3

The easiest to do is hookMessage, the basic pattern is as follows and will allow us to print out the arguments of the function:

var old_method_pointer = {}
MS.hookMessage(ClassName, @selector(methodname), function(arg0, arg1) {
    NSLog("Arguments: arg0 -> %@, arg1 -> %@",arg0,arg1)
    return old_method_pointer->call(this,arg0,arg1)
},old_method_pointer)

The hookFunction requires a little bit more magic. First you need to get a pointer to the function, ensure that you know the function signature and then do the hooking.

var old_method_pointer = {}
var functionp = className->isa.messages["functionName"]
functionp = @encode(id(id,SEL,id,unsigned int*))(functionp)
MS.hookFunction(functionp, function(arg0, arg1,arg2,arg3) {
    NSLog("Arguments: arg0 -> %@, arg1 -> %@",arg2,arg3)
    return (*old_method_pointer)(arg0,arg1,arg2,arg3)
},old_method_pointer)

The tricky parts here are:

  1. find the function pointer
  2. find the function structure
  3. remember to ALWAYS include arg0 and arg1 (even when there are zero arguments)

To find the function pointer, you need to use cycript and a little bit of digging. The easiest is to use the output from your class-dump-z and see if you can access the target class. Then reference the isa instance for that class and grab the function pointer out of the “messages” object array.

cy# *Transport_Service
{isa:object_getClass(Transport_Service)}
cy# Transport_Service->isa.messages
{"serviceForServiceId:":0xc2d5d,services:0xc2ca5,"propertiesForHeirarchyFromClass:":...
cy# Transport_Service->isa.messages["services"]
0xc2ca5 

Next you need to know the function structure, this is easy to obtain with cycript and also forces you into remembering about arg0 and arg1.

cy# Transport_Service->isa.messages["services"].type
@encode(id(id,SEL))

Here we can see that the function takes arguments arg0 and arg1 even though there are no arguments defined in the header file.

Case-Study

So how well does this actually work? While assessing a banking application, we ran into a custom network protocol that encrypts all data and prevents us from messing around with certain actions. How about we use cycript and substrate to actually see what the internal workings of this pesky protocol looks like.

First we select our target methods/functions. For this demo we go after the snedJSON function found in eSecretConnection:

@interface eSecretConnection : NSObject <NSStreamDelegate, eSecret_ESocketFrameDelegate, eSecretConnection> {
@private
BOOL _connected;
...
}
@property(readonly, copy) NSString* debugDescription;
@property(readonly, copy) NSString* description;
...
+(void)initialize;
...
-(id)connect;
-(id)sendKeepAlive;
-(id)sendJson:(id)json;
...

As can be seen from our class-dump-z, we have an instance method and can thus use hookMessage. Our hookMessage code looks as follows:

var olddita = {}
MS.hookMessage(eSecretConnection, @selector(sendJson:), function(a) {
   NSLog("JSON: %@",a);
   return olddita->call(this,a);
}, olddita)

Now we simply need to watch our syslogs and any JSON message sent from the app to the custom protocol’s server will pass through our hook first.

And there we go, we are no longer blind! The really cool thing is that we can now change the values of our arguments before they are sent to the protocol’s server. We don’t need to be able to decrypt the traffic or use a proxy server, we get the app to do it for us..

If we wanted to replace all instances of 1 with a single-quote, we can define a new function, mod, and call this to modify our argument.

mod = function(arg){ return arg.replace("1","'");}
var oldm = {}
MS.hookMessage(eSecretTypeTwoConnection, @selector(sendJson:), function(a) {
NSLog("JSON: %@",a);
a = mod(a);
NSLog("Modded: %@",a);
return oldm->call(this,a);
}, oldm)

And now we are testing for SQL-injection..

Fin

That’s it for now, I’m in the process of creating more hooking functions for the custom protocol. And in theory these should all work on Android as well (seeing as there is a substrate for Android too, and it is said to be even more powerful than the iOS version).

./et