Method patching on iOS applications

Summary

These are the steps needed to crack (method patch the jailbreak function) an iOS application to work on a rooted iOS device. This example uses “Sample.app” from within OSX Lion. Mileage may vary.

Requirements

iOS Physical device rooted with cydia and some basic tools:

  • syslogd – See all console output in /var/log/
  • openssh
  • adv-cmds – Tools like ps, kill, etc
  • GNU debugger – (If GDB is working unexpectedly, install from radare.org)
  • Darwin CC Tools – otool and such

Get the application and install it. This guide is assuming an encrypted IPA (compiled for ARM) distribution.

Decrypting

In the cases in which the binary is encrypted, you must decrypt. The easiest way to do this is to find a program to do it for you (Google this if you want to skip this step). The surefire (manual) way  to do this is to execute the binary breaking at the end of the decoding stub. This will leave the entire un-ecrypted binary in memory where you can then dump it to disk.

Locate application binary within the application folder on the device. e.g.:

/private/var/mobile/Applications/BC2DA09D-7189-44E8-B190-2EE03BAAAAA8/SampleApp.app/SampleApp

Then check application for encryption:

root# otool -l SampleApp | grep -B5 cryptid
Load command 11
 cmd LC_ENCRYPTION_INFO
 cmdsize 20
 cryptoff 4096
 cryptsize 94208
 cryptid 1
--

Given cryptid 1 (0 == Not Encrypted). Keep note of the cryptsize, we will use this value later.

Verify application is not FAT (That is does not contain multiple versions):

root# otool -f SampleApp

If the application contains multiple versions, you must use lipo to extract the correct (armv7) version and continue.

Given a single encrypted binary:

root# gdb --quiet -e ./SampleApp
...
(gdb) set sharedlibrary load-rules ".*" ".*" none
(gdb) set inferior-auto-start-dyld off
(gdb) set sharedlibrary preload-libraries off
(gdb) rb doModInitFunctions
Breakpoint 1 at 0x2fe0cece
<function, no debug info> __dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE;
(gdb) r
Starting program: /private/var/mobile/Applications/BC2DA09D-7189-44E8-B190-2EE03BAAAAA8/SampleApp.app/SampleApp.app/SampleApp
Breakpoint 1, 0x2fe0cece in __dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE ()

Dump the binary from memory to disk:

dump binary memory /var/root/dump 0x2000 0x18000

See python -c ‘print(hex(4096+94208))’ from crytpid analysis for the end limit, which is 0x18000 bytes in the example. You will have to substitute your own value in.

Then pull the binary off phone and use classdump

scp root@iphone:/var/root/dump ./SampleApp
./class-dump SampleApp > SampleAppDump

Examine dump for a root checking function (probably returns BOOL or _Bool) with some grep-fu. You will most likely find multiple functions. If the application is more complicated, cycript may help you find it.

Setup

You can either follow my other instructions on compiling iOS libraries using Theos (Reccomended!), or do the following:

Install MobileSubstrate on the iOS device. You can check if it’s installed by looking for /Library/MobileSubstrate.

Writing

Checkout CaptainHook into the project into the project. Mine is called NoCheck saved in ~/Documents/workspace

user@box> cd ~/Documents/workspace/NoCheck/NoCheck
user@box> git clone git://github.com/rpetrich/CaptainHook.git

Write the code itself using the lib. This sample code was referenced from the MDSec iOS doc. Read it if you haven’t.

#import "NoCheck.h"
#import "Foundation/Foundation.h"
#import "CaptainHook/CaptainHook.h"
#include "notify.h"

@implementation NoCheck
-(id)init
{
    if ((self = [super init])){} return self;
}
@end

@class SampleAppViewController;
CHDeclareClass(SampleAppViewController);
CHOptimizedMethod(0, self, _Bool, SampleAppViewController, isDeviceRooted)
{
    NSLog(@"####### isJailBroken hooked"); // Logging saves lives
    return true;
}

CHConstructor {
    @autoreleasepool {
        CHLoadLateClass(SampleAppViewController);
        CHHook(0, SampleAppViewController, isDeviceRooted); // register hook
    }
}

 

All is needed now compile to dylib by running the application and copying it over. Press the run button to compile. Find it by looking at the left hand bar, mine was located at /Users/user/Library/Developer/Xcode/DerivedData/NoCheck-abheoijxmwkxefbirkgyhsismoxg/Build/Products/Debug-iphoneos

Copy the output file to /Library/MobileSubstrate/DynamicLibraries/

user@box> cd /Users/user/Library/Developer/Xcode/DerivedData/NoCheck-abheoijxmwkxefbirkgyhsismoxg/Build/Products/Debug-iphoneos
user@box> scp ./NoCheck.dylib root@iphone:/Library/MobileSubstrate/DynamicLibraries/

Run application and be happy. If you’re not sure it worked tail your syslog output to see if the module is loading (it will do so often) and the NSLog is output.

Posted in iOS, mobile | Leave a comment

Pretty Print XML

Put this python file in your path to get the following:

#!/usr/bin/env python

"""
Command-line tool to validate and pretty-print XML.

Based on `pjson` but without the crap.

Usage::

    $ echo '<bunk atr="hello">world</bunk>' | pxml
	<?xml version="1.0" ?>
	<bunk atr="hello">
		world
	</bunk>

Original Author: Igor Guerrero <igfgt1@gmail.com>, 2012
Contributor: Matthew Gill, 2012
"""

import xml.dom.minidom
import sys

from pygments import highlight
from pygments.formatters import TerminalFormatter
from pygments.lexers import XmlLexer

def format_xml_code(code):
    """
    Parses XML and formats it
    """
    x = xml.dom.minidom.parseString(code)
    return x.toprettyxml()

def color_yo_shit(code):
    """
    Calls pygments.highlight to color yo shit
    """
    return highlight(code, XmlLexer(), TerminalFormatter())

if __name__ == '__main__':
    code = format_xml_code(sys.stdin.read())
    print color_yo_shit(code)

The original can be found here (https://github.com/igorgue/pjson)

Posted in uncategorized | Leave a comment

SiteMesh 2 Undecorated AJAX requests

Note: Only after testing and writing this did I realize this is not a good idea. I will leave this up in the case it helps someone do something else, but this is a poor way to do this. I will update it later with a beter technique.

Recently I had the problem where I wanted my JavaScript requests to return undecorated content. The solutions I found online weren’t quite what I wanted so I constructed my own method using a custom mapper. The first two solutions below are the once I came across in my searches, the third is mine.

API Path

One simple solution is to keep all requests which you want undecorated in a secondary path, such as /api, and set your decorators.xml to exclude them all from decoration.

<excludes>
   <pattern>/api**</pattern>
</excludes>

Parameter Patterns

The request posted at http://issues.appfuse.org/browse/APF-1020 made the suggestion to add a key-value parameter pair such as ajax=true to every request you want undecorated. Inelegant and not transparent at all, but effective. See http://raibledesigns.com/rd/entry/ajaxified_body for a little more detail.

Header

Instead of doing excludes on URL pattern matching, I wanted every javascript request to be undecorated by examining the X-Requested-With header. To implement this, ensure you have a custom sitemesh.xml.

I create a blank decorator, this is my /WEB-INF/decorators/api.jsp

<%@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator"%><decorator:body />

And ensure that you declare this as a decorator within your /WEB-INF/decorators.xml (or wherever you might have put it)

<decorators defaultdir="/WEB-INF/decorators">
    <decorator name="api" page="api.jsp" >
        <pattern>/api*</pattern> <!-- This line is OPTIONAL -->
    </decorator>
</decorators>

and the last XML modification is to insert another custom mapper into the sitemesh configuration. If you don’t have one, simply create /WEB-INF/sitemesh.xml populated with the default content from sitemesh-default.xml.

...
   <decorator-mappers>
      <mapper class="com.your.package.structure.mapper.HeaderDecoratorMapper">
         <param name="decorator" value="api" />
         <param name="X-Requested-With" value="XMLHttpRequest" />
      </mapper>
   </decorator-mappers>
...

Notice the custom class we map to. This is the class that will inspect the headers for the correct values and respond accordingly. Here is HeaderDecoratorMapper.java:

package com.your.package.structure.mapper;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;

import com.opensymphony.module.sitemesh.Config;
import com.opensymphony.module.sitemesh.Decorator;
import com.opensymphony.module.sitemesh.DecoratorMapper;
import com.opensymphony.module.sitemesh.Page;
import com.opensymphony.module.sitemesh.mapper.AbstractDecoratorMapper;

public class HeaderDecoratorMapper extends AbstractDecoratorMapper
{
	private final Logger logger = Logger.getLogger(HeaderDecoratorMapper.class);

	private Map<String, String> headerMap = null;

	public void init(Config config, Properties properties, DecoratorMapper parent) throws InstantiationException
	{
		super.init(config, properties, parent);
		headerMap = new HashMap<String, String>();
		initMap(properties);
	}

	public Decorator getDecorator(HttpServletRequest request, Page page)
	{
		try
		{
			Decorator result = null;

			final List<String> headers = Collections.list(request.getHeaderNames());
			for(final String header : headerMap.keySet())
			{
				if(headers.contains(header) && request.getHeader(header).matches(headerMap.get(header)))
				{
					// We know we want to differ decorators
					if(logger.isDebugEnabled()) {
						logger.debug("Decorating header request with the decorator: " + headerMap.get("decorator"));
					}
					result = super.getNamedDecorator(request, headerMap.get("decorator"));
					break;
				}
			}

			return result == null ? super.getDecorator(request, page) : result;
		}
		catch (NullPointerException e)
		{
			return super.getDecorator(request, page);
		}
	}

	/** Initialize the header mappings. */
	private void initMap(final Properties props)
	{
		final Iterator<Entry<Object, Object>> it = props.entrySet().iterator();
		while (it.hasNext())
		{
			final Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it.next();
			final String key = (String) entry.getKey();
			final String ext = (String) entry.getValue();
			headerMap.put(key, ext);
			if(logger.isDebugEnabled()) {
				logger.debug("Header mapping '"+key+"' with value '"+ext+"' ");
			}
		}
	}
}

With all of these in place you should be able to redirect any header key:value combination, with the value of the value being regex matched, to any view you want.

Thanks! Hopefully this helps.

 

Posted in uncategorized | 2 Comments