Tuesday 16 October 2012

Offline Patching of iOS Applications

During penetration tests, we frequently utilise offline patching of iOS applications to bypass restrictions and protection mechanisms. Offline patching is a convenient way to overcome jailbreak detection, certificate pinning or poorly implemented authentication/authorisation in a permanent fashion: we perform binary autopsy of the application, fire up our favourite hexeditor, change a few bytes and voila! the restrictions are gone.

Let's consider our favourite dummy jailbreak detection application first.

BOOL isJailbroken(AppDelegate* delegate) {
    return [[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"];
}

- (void)jailbreakDetection
{
    if (isJailbroken(self))
    {
        [self dirtyDevice];
    }
    else
    {
        [self cleanDevice];
    }
}

The algorithm relies on the fileExistAtPath: API call to decide whether or not the Cydia app is installed. Our first attempt for patching targets the string object. At this point, all we need is a good hexeditor to pinpoint the string location within the binary and change a few characters. Using IDA, we inspect the cstrings section of the binary. NSStrings in ObjC are immutable (unless created otherwise), and they are stored in the cstring section of the mach-o binary. 

__cstring:00002DC0 ; ===========================================================================
__cstring:00002DC0
__cstring:00002DC0 ; Segment type: Pure data
__cstring:00002DC0                 AREA __cstring, DATA, ALIGN=0
__cstring:00002DC0                 ; ORG 0x2DC0
__cstring:00002DC0 aViewcontroll_4 DCB "ViewController",0  ; DATA XREF: __cfstring:cfstr_Viewcontroll_4 o
__cstring:00002DCF aApplicationsCy DCB "/Applications/Cydia.app",0
__cstring:00002DCF                                         ; DATA XREF: __cfstring:cfstr_ApplicationsCy o

Using a hexeditor, we quickly patch the corresponding section, paying close attention not to change the length of the string (overwriting is the keyword here):


So far so good. Having patched the binary, we re-create the installation package, update the application and push it to the device using iPhone Configuration Utility. However, if we attempt to start the modified binary, the kernel flat refuses to load it, since our tweaked binary fails at the signature (rather integrity) checking step. In order to overcome the issue, we use this script to re-create the signature of the binary:

D:JBT_v2 mac$ resigner.sh JBT_unsigned.ipa randomkey JBT_signed.ipa
APP_NAME=JailbreakDetectionTest.ipa
Resigning application using certificate: keydumperhez
temp/Payload/JailbreakDetectionTest.app: replacing existing signature

Repackaging as JBT_signed.ipa  

Upload the signed package again, and the jailbreak check algorithm is blinded. Easy, isn't it? The above method is the Sex Pistols of patching methods - kind of a no brainer attack, but it works like charm against such very dumb jailbreak checking algorithms. The method however, can be used for more intelligent attacks. Consider the same app, but now take a look at it in disassembly view:

; =============== S U B R O U T I N E =======================================
; AppDelegate - (void)jailbreakDetection
; Attributes: bp-based frame

; void __cdecl __AppDelegate jailbreakDetection_(struct AppDelegate *self, SEL)
__AppDelegate_jailbreakDetection_       ; DATA XREF: __objc_const:00003318 o
                PUSH            {R4,R7,LR}
                MOV             R4, R0
                MOV             R0, (selRef_isJailbroken - 0x2406) ; selRef_isJailbroken
                ADD             R7, SP, #4
                ADD             R0, PC ; selRef_isJailbroken
                LDR             R1, [R0] ; "isJailbroken"
                MOV             R0, R4
                BLX             _objc_msgSend
                TST.W           R0, #0xFF
                BEQ             loc_241E
                [...]
                BLX             _objc_msgSend
                POP             {R4,R7,PC}
; End of function -[AppDelegate jailbreakDetection]

Consider the instructions highlighted in red.
                TST.W           R0, #0xFF
                BEQ             loc_241E
The idea here is to change the branching instruction, therefore the direction of the condition check will be the other way around: our app will consider any clean device jailbroken. IDA could help us but unfortunately, there is no ARM assembler included in the demo version, therefore we have to use manual pen-and-paper methods - this is the point where we need the ARM Assembly Reference Guide, attached to every xCode installation.  It can be found in /Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/Frameworks/DTISAReferenceGuide.framework/Versions/A/Resources/ARMISA.pdf on my test machine.

Most ARM instructions include a condition section, which makes the difference i.e. between BEQ (Branch-if-equal) and BNE (Branch-if-not-equal). Take a look at page 314 in the Reference Guide, which contains the list of possible condition flags.

Take a look at the Hex view of the BEQ loc_241e line in IDA (highlighted in red):

 05 D0 41 F2 CE 00 C0 F2  00 00 78 44 04 E0 41 F2  ..A.......xD..A.

To manually disassemble the instruction, 0xD005 is the correct form (because of the endianness of iOS). In binary format, 0xd005 is b1101 0000 0000 0101. On page 350, the following table represents the meaning of the individual bits. We recognise that the compiler used the Thumb1 encoding of the B** instruction in this case:


Therefore, the 'cond' bits correspond to the EQUAL condition, which in this case is the b0000 flagbit combination. In order to 'reverse' the condition, we need to change the bits to b0001, which in turn corresponds to 0x05D1. In order to verify our results, we use IDA before and after patching and compare the results:

Before patching:
After patching:



Again, we re-create and resign the modified installation package, and now the app is blind again.