/ private

Substrate - hooking C on Android and iOS part 1/2

This post is the first of a two part walkthrough on hooking C functionality on iOS and Android concerning the use of substrate for hooking code on the two supported mobile platforms. The aim is to provide you with a start to finish demonstration of how you can hook C functions on Android and iOS. The Android post is written by John Kozyrakis. You can check it out here:
https://hexplo.it/substrate-android/

Hooking C functions on iOS

Preparing the environment

Although it’s possible to set up the same tools in Windows and Unix I’m going to assume you’re using a mac to follow along. We will be installing Theos on to our OSX machine and using it to build our hooks. There are guides as to how you can install Theos onto a jailbroken device itself but for the purpose of this article, I’m not going to document how to do that.

Other pre-requisites are:

  • iOS SDK – The easiest way is to download Xcode from the App Store.
  • Jailbroken device - You need to have a jailbroken device to run the code on and for ease you should also have OpenSSH installed on the device. The recent iOS8 jailbreak by Pangu installs OpenSSH by default. *
  • Clang – We’ll use clang when building our sample application and it is also used by Theos. It’s a front-end for the LLVM compiler. Clang will likely be installed on OS X already, otherwise download Xcode and install the command-line developer tools.
  • strip utility for iOS – Through Cydia, on the device, search for and install the “Big Boss recommended tools” package.
  • nm utility for iOS - Through Cydia, on the device, search for and install the “Big Boss recommended tools” package.
  • Hopper Disassembler for OSX – A fantastic application for disassembling and reverse engineering binaries. http://hopperapp.com

* Make sure you change the default root user’s password from “alpine” if you have not already.

Setting up Theos

Open up terminal on your OSX machine and set up an environment variable for Theos. This will be the home of Theos when we install it.

$ export THEOS=/opt/theos

Next grab the latest and greatest copy of Theos from the Dustin Howett (author)’s repo:

git clone git://github.com/DHowett/theos.git $THEOS

Notice that in the above command we specify the download location as $THEOS (/opt/theos)

Next, download the ldid utility from Saurik's repo:

git clone git://git.saurik.com/ldid.git
cd ldid
git submodule update --init
./make.sh
cp -f ./ldid $THEOS/bin/ldid

Finally, set up the variables which are used to install the packages to your device via SSH (replace insertIPHere with the IP address of the iOS device and remove the < and > symbols):

export THEOS_DEVICE_IP=<insertIPHere> THEOS_DEVICE_PORT=22

Note: For readers using a 64bit iOS device, you will need to do this extra step;

wget -O $THEOS/lib/libsubstrate.dylib http://cdn.hbang.ws/dl/libsubstrate_arm64.dylib

You will also need to add the armv7 and arm64 architecture declarations to your makefile later however don’t worry about this just now, I’ll remind you of this and all of the details when we get there.

See iPhone Dev Wiki page for more details

Creating our sample application

For the purposes of our demonstration, let’s create a small C application which can be cross-compiled for iOS on OSX using clang/LLVM:

Here is the source code for our demo application:

#include <stdlib.h>
#include <stdio.h>

int getAge(void)
{
    return 21;
}

int main(int argc, char* argv[])
{
    int r = arc4random() % 10000; //arc4random is part of std lib C
    printf("[+] John Smith is %d years old.\n", getAge());
    printf("[+] The totally reliable random seed is: %d\n\n", r);
    return 0;
}

As you can see, this is going to print out two statements:

  • John Smith’s age is the return value from our getAge function which is hardcoded so should always be 21.
  • The “totally reliable random seed” value is the result of our call to arc4random() which should be different each time we run the executable. Note: arc4random is part of standard lib C (stdlib.h).

We’re going to compile the code on our OSX machine and to do so, we’ll need to use the iOS SDK. The iOS 8.1 SDK on my OSX system lives here:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk

Open up terminal and store this path in a variable for ease of use:
iSDK=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk

Now, using the above SDK, we can compile this using for iOS devices like so:
clang -arch armv7 -arch armv7s -arch arm64 -isysroot $iSDK demo.c -o test

You will now need to copy the ‘test’ executable you just compiled on to the iOS device via scp (copy via ssh). I copied it to the /var/root/ directory.

Here are two example runs of the executable on the iOS device (SSH to the device to do this):

Hexploitable:~ root# clang test.c -o test
Hexploitable:~ root# ./test
[+] John Smith is 21 years old.
[+] The totally reliable random seed is: 9960

Hexploitable:~ root# ./test
[+] John Smith is 21 years old.
[+] The totally reliable random seed is: 8843

Creating our Tweak

A “tweak” is the term used to describe a set of substrate hooks which modify or extend the behaviour of an existing application, library or executable. Substrate (or Cydia Substrate) is a code modification platform for jailbroken iOS devices and rooted Android devices. Substrate allows users to modify the behaviour of applications and the platform and can also be used maliciously to target users of jailbroken or rooted devices.

In this instance, we want to create a tweak which is going to manipulate the behaviour of the test application we created in the previous section. We’re going to change the return values for getAge and arc4random.

Using Theos on the OSX machine, we’ll create a tweak. I’ve called mine demoTweak but this can be anything you like. We’ll also want to choose template 5 iphone/tweak when Theos prompts us as shown below:

hexploitable@hexploiablesmbp:~ /opt/theos/bin/nic.pl demoTweak
NIC 2.0 - New Instance Creator
------------------------------
  [1.] iphone/application
  [2.] iphone/library
  [3.] iphone/preference_bundle
  [4.] iphone/tool
  [5.] iphone/tweak
Choose a Template (required): 5
Package Name [com.yourcompany.demotweak]: com.hexploitable.demoTweak
Author/Maintainer Name [Grant Douglas]:
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: -
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: -
Instantiating iphone/tweak in demotweak/...
Done.

Theos asks us for a bundle filter because Substrate can use the filter to automatically load your tweak into the appropriate classes, applications or executables. However, we’re not going to be using this feature of Substrate so it doesn’t matter what value we enter. Just insert a hyphen like I did.

As mentioned earlier, if you’re creating a tweak for a 64bit device then you need to make a change to your tweak’s makefile and should do so now. Here’s the makefile in it’s current form, as created by Theos:

hexploitable@hexploiablesmbp:~ cat demotweak/Makefile
include theos/makefiles/common.mk

TWEAK_NAME = demoTweak
demoTweak_FILES = Tweak.xm

include $(THEOS_MAKE_PATH)/tweak.mk

Simply insert the line ARCHS = armv7 armv7s arm64 at the top of the makefile so it now looks like this:

Example of hooking with symbols

Currently our executable, test, has not been stripped of it’s symbols. By default (unless disabled), when applications are built for release in Xcode, the symbols are stripped.

We can verify if there are symbols by running nm (this can be downloaded by using Cydia on the device to search for and install the “BigBoss Recommended Tools” package). We use nm like so:

Hexploitable:~ root# nm /var/root/test  
test (for architecture armv7):
0000bf5c s stub helpers
 U ___umodsi3
00004000 T __mh_execute_header
 U _arc4random
0000bef4 T _getAge
0000befc T _main
 U _printf
 U dyld_stub_binder

test (for architecture armv7s):
0000bf5c s stub helpers
 U ___umodsi3
00004000 T __mh_execute_header
 U _arc4random
0000bef4 T _getAge
0000befc T _main
 U _printf
 U dyld_stub_binder

test (for architecture arm64):
0000000100000000 T __mh_execute_header
 U _arc4random
0000000100007e94 T _getAge
0000000100007e9c T _main
 U _printf
 U dyld_stub_binder

As you can see, both the _getAge and _arc4random symbols are present so we can use substrate’s MSFindSymbol to find these methods, allowing us to hook them easily.

When writing our tweak, we’re going to want to use the MSFindSymbol API, to find the symbol for _getAge in our test executable. To do this, we need to provide the path to the binary to MSGetImageByName and then pass the returned reference to MSFindSymbol, along with the symbol name we’re looking for. This will look like this:

MSImageRef image = MSGetImageByName("/var/root/test");
void *sym = MSFindSymbol(image, "_getAge");

Next, we can hook the getAge function by passing the symbol to the Substrate MSHookFunction API, like so:

MSHookFunction((void *)sym, (void *)replaced_getAge, (void **)&original_getAge);

replaced_getAge and original_getAge will need to be defined in our tweak.xm file too, like so:

int (*original_getAge)(void);  
int replaced_getAge(void) {  
    return 99;
}

original_getAge is a pointer to the true getAge function and can be useful in scenarios where our replacement function should perform some actions and then we wish to call the original function after. In this scenario however, we don’t want to call the original so we don’t use it.

We can hook arc4random without having to use MSFindSymbol because the symbol is exported in stdlib.h. This means that as long as we have have imported the header file for our target API then we can just pass the symbol to MSHookFunction, like so:

MSHookFunction((void *)arc4random, (void *)replaced_arc4random, (void **)&original_arc4random);

As with before, we’ll want to define our replacement function:

int (*original_arc4random)(void);  
int replaced_arc4random(void)  
{
    return 1234;
}

When we put all of this together, here is how the Tweak.xm source code looks:

#include <substrate.h>
#include <stdio.h>
#include <stdlib.h>

int (*original_getAge)(void);  
int replaced_getAge(void) {  
    return 99;
}


int (*original_arc4random)(void);  
int replaced_arc4random(void)  
{
    return 1234;
}

%ctor {
    //Specify our target executable
    MSImageRef image = MSGetImageByName("/var/root/test");
    //Look for our target method
    void *sym = MSFindSymbol(image, "_getAge");

    MSHookFunction((void *)sym, (void *)replaced_getAge, (void **)&original_getAge);

    //Hook arc4random using it's own symbol (We don't have to look this up)
    MSHookFunction((void *)arc4random, (void *)replaced_arc4random, (void **)&original_arc4random);
}

Let’s go ahead and check that our tweak compiles:

hexploitable@hexploiablesmbp:~/demotweak make
Making all for tweak demoTweak...
 Preprocessing Tweak.xm...
 Compiling Tweak.xm...
 Linking tweak demoTweak...
 Stripping demoTweak...
 Signing demoTweak...

It seems everything compiled OK so lets now install the tweak on the device, using our OSX machine. When we run the below command, Theos will obtain the device IP from the environment variables we configured earlier and initiate the installation process via SSH. You will be prompted to enter the password for the iOS device’s root user.

hexploitable@hexploiablesmbp:~/demotweak make package install
Making all for tweak demoTweak...
make[2]: Nothing to be done for `internal-library-compile'.
Making stage for tweak demoTweak...
dpkg-deb: building package `com.hexploitable.demotweak' in `./com.hexploitable.demoTweak_0.0.1-1_iphoneos-arm.deb'.
install.exec "cat > /tmp/_theos_install.deb; dpkg -i /tmp/_theos_install.deb && rm /tmp/_theos_install.deb" < "./com.hexploitable.demoTweak_0.0.1-1_iphoneos-arm.deb"
root@192.168.1.89's password:
Selecting previously deselected package com.hexploitable.demotweak.
(Reading database ... 13395 files and directories currently installed.)
Unpacking com.hexploitable.demotweak (from /tmp/_theos_install.deb) ...
Setting up com.hexploitable.demotweak (0.0.1-1) ..

To run the test executable using our tweak we can simply load the the tweak into the binary using the DYLD_INSERT_LIBRARIES environment variable, as shown below.

Hexploitable:~ root# DYLD_INSERT_LIBRARIES=/Library/MobileSubstrate/DynamicLibraries/demoTweak.dylib ./test
[+] John Smith is 99 years old.
[+] The totally reliable random seed is: 1234

As you can see our tweak worked and the return values of the getAge and arc4random functions were changed to the values we specified in our Tweak.xm file.

Example of hooking without symbols

As I mentioned earlier, the symbols of application C functions are not normally available on iOS, so how do we hook those functions if we can’t look up where they are? Let’s strip the binary to remove the symbols:

Hexploitable:~ root# strip test

Now let’s re-examine the symbols within the binary:

Hexploitable:~ root# nm /var/root/test  

test (for architecture armv7):
 U ___umodsi3
00004000 T __mh_execute_header
 U _arc4random
 U _printf
 U dyld_stub_binder

test (for architecture armv7s):
 U ___umodsi3
00004000 T __mh_execute_header
 U _arc4random
 U _printf
 U dyld_stub_binder

test (for architecture arm64):
0000000100000000 T __mh_execute_header
 U _arc4random
 U _printf
 U dyld_stub_binder

As you can see, the getAge symbol is no longer present. Notice though, that the symbol for arc4random still exists as this is part of std lib C as the application needs to know where arc4random is. For this reason, when we run our tweak now, we should find that the hook of getAge will fail but the arc4random hook should still work:

Hexploitable:~ root# DYLD_INSERT_LIBRARIES=/Library/MobileSubstrate/DynamicLibraries/demoTweak.dylib ./test  
[+] John Smith is 21 years old.
[+] The totally reliable random seed is: 1234

We can still hook getAge but we are now going to have to tell Substrate where the function is.

Locating the method in Hopper Disassembler

To disassemble and reverse engineer the application, we’re going to use a tool called Hopper Disassembler, both a demo and full version can be obtained here:

http://hopperapp.com

Open the test executable in Hopper by selecting “Read Executable to Disassemble” from the file menu (on OSX the keyboard shortcut is cmd+shift+O). Hopper will ask you to select which architecture you would like to disassemble, choose either armv7 or armv7s, it doesn’t matter which.

Now we have to find the getAge function and as the symbols have been stripped, we are going to have to find it manually. On the left hand side of the screen you can see there is a list of labels, which will look something like the below:

Function label inside Hopper

The label which starts “sub_” is getAge, click on the text and Hopper will jump to that location. You will a disassembled output similar to this:

 sub_bef4:
 0000bef4 movs r0, #0x15 ; XREF=0x40ac, EntryPoint+36
 0000bef6 movt r0, #0x0
 0000befa bx lr
 ; endp

In this case the method is easy to spot as there’s only one label which starts with sub_ in Hopper. It also helps that we know what the function is meant to look like, i.e we know it returns a decimal value of 21. We can see that the above returns 0x15 which when converted from hex to decimal is 21.

At the start of the above snippet we can see that the method is located at address 0xbef4. We can now use this address with MSHookFunction in our tweak to hook the function.

Updating our tweak to use an address

As we now have the address of the method we’re targetting, we can simply change our Tweak.xm %ctor block to look like this:

%ctor {
    //Specify address to method
    unsigned int *sym = (unsigned int *)0xbef5;

    MSHookFunction((void *)sym, (void *)replaced_getAge, (void **)&original_getAge);

    //Hook arc4random using it's own symbol (We don't have to look this up)
    MSHookFunction((void *)arc4random, (void *)replaced_arc4random, (void **)&original_arc4random);
}

The reason we use 0xbef5 instead of 0xbef4 as shown in Hopper is because the instruction at that address is a thumb instruction. You can check whether an instruction is thumb or not by selecting the instruction with the cursor in Hopper and then looking under the “Instruction Encoding” heading in the right hand column. In this case, the CPU mode is “Thumb”.

Let’s run make package install once more and test out the tweak again to see if the getAge mehod get’s hooked this time or not:

Hexploitable:~ root# DYLD_INSERT_LIBRARIES=/Library/MobileSubstrate/DynamicLibraries/demoTweak.dylib ./test  
[+] John Smith is 99 years old.
[+] The totally reliable random seed is: 1234

As you can see, it worked – great!

Summary

To summarise the post, we now know that we can hook C functions within an application, executable or library using Cydia Substrate.

We found out just how trivial it is to hook a function if the symbols are present.

We also learned that by stripping the symbols of our application we make it a more cumbersome process to create hooks for functions. You can imagine that if there were hundreds or thousands of functions when we opened the executable in Hopper, it would have taken us significantly longer to find our target function.

As a final note, it’s important to remember that this was possible only because the device is jailbroken. From an application owner’s perspective, it shows how vulnerable and exposed our applications are when they reside on a jailbroken device. As the adoption and enthusiasm for bring your own device continues to grow, solutions which offer jailbreak detection and other binary protections become ever more popular.

Grant Douglas

Grant Douglas

Senior Security Consultant @ Cigital/Synopsys. Working on everything appsec but mainly security, strategy and SAST & DAST tooling in the mobile vertical.

Read More