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.

Friday, 10 August 2012

Manual Bypass of Naive Jailbreak Detection Methods

Apple is a strict vendor. Out of the box, you cannot just push any app on your device and start running stuff. There is no magic check box that allows you to change this (a la Android) - you have the AppStore and that's it. Jailbreaking primarily means that this restriction is patched (hacked) out of the operating system - on a jailbroken device, any binary, compiled for iOS, can be executed.

Before going into details, it might be useful to clarify what iOS checks before loading and executing a binary.
  1. A digital signature is enclosed in every installation package (IPA). The signature contains a hash for each file in the installation package and a cryptographic signature, provided by Apple (directly or indirectly). Broken IPAs cannot be installed. Jailbreaking means that this restriction is relieved so that any signing authority is accepted - that is, a self-signed certificate is sufficient for signing IPAs.
  2. Every executable is supplied with an embedded XML, which gives a detailed description about what that particular executable is allowed to do. For instance this is the place to list the allowed keychain access groups, or flags that indicate that the application is allowed to hook into other processes through the kernel (~gdb).
  3. Memory pages are signed and the kernel checks their signature before executing their contents. Therefore, it is very hard to inject payload (shellcode) directly into a process memory space - however, this restriction does not mean that we are unable to tamper with the process at runtime.
Having said that, consider a Proof-of-Concept example for very basic jailbreak checking. The most naive approach checks for known residual artifacts of the jailbreaking process ('/Applications/Cydia.app', '/var/log/cydia.log' etc.) The process utilises an array of strings and existence of any of the files indicates a jailbroken device. 

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

- (void)jailbreakDetection
{
    if (isJailbroken(self))
    {
        [self vanIlyen];
    }
    else
    {
        [self nincsIlyen];
    }
}

- (void)vanIlyen
{
    [[[UIAlertView alloc] initWithTitle:@"Jailbroken device!"
                               message:@"Indeed, it is."
                              delegate:nil
                     cancelButtonTitle:@"Got it."
                     otherButtonTitles: nil] show];
}

- (void)nincsIlyen
{
    [[[UIAlertView alloc] initWithTitle:@"Clean device.:)"
                               message:@"Oh yes."
                              delegate:nil
                     cancelButtonTitle:@"Got it."
                     otherButtonTitles: nil] show];

The algorithm is simple. We check whether the '/Applications/Cydia.app' exists and update the user interface accordingly. From a pentester's point of view, the source is the documentation and it is a rare and happy moment when we have access to it while testing. Therefore, take a look at the 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
                MOV             R0, (selRef_vanIlyen - 0x241E) ; selRef_vanIlyen
                ADD             R0, PC ; selRef_vanIlyen
                B               loc_2428
; ---------------------------------------------------------------------------
loc_241E                                ; CODE XREF: -[AppDelegate jailbreakDetection]+1C j
                MOV             R0, (selRef_nincsIlyen - 0x242A) ; selRef_nincsIlyen
                ADD             R0, PC ; selRef_nincsIlyen

loc_2428                                ; CODE XREF: -[AppDelegate jailbreakDetection]+28 j
                LDR             R1, [R0]
                MOV             R0, R4
                BLX             _objc_msgSend
                POP             {R4,R7,PC}
; End of function -[AppDelegate jailbreakDetection]
; ---------------------------------------------------------------------------

The assembly view of the isJailbroken function looks as follows:

; =============== S U B R O U T I N E =======================================
; AppDelegate - (char)isJailbroken
; char __cdecl __AppDelegate isJailbroken_(struct AppDelegate *self, SEL)
__AppDelegate_isJailbroken_             ; DATA XREF: __objc_const:00003324 o
                PUSH            {R7,LR}
                MOVW            R1, #(selRef_defaultManager - 0x244C) ; selRef_defaultManager
                MOV             R7, SP
                MOVT.W          R1, #0
                MOV             R0, (classRef_NSFileManager - 0x244E) ; classRef_NSFileManager
                ADD             R1, PC ; selRef_defaultManager
                ADD             R0, PC ; classRef_NSFileManager
                LDR             R1, [R1] ; "defaultManager"
                LDR             R0, [R0] ; _OBJC_CLASS_$_NSFileManager
                BLX             _objc_msgSend
                MOV             R1, (selRef_fileExistsAtPath_ - 0x2460) ; selRef_fileExistsAtPath_
                ADD             R1, PC ; selRef_fileExistsAtPath_
                LDR             R1, [R1] ; "fileExistsAtPath:"
                MOV             R2, (cfstr_ApplicationsCy - 0x246C) ; "/Applications/Cydia.app"
                ADD             R2, PC  ; "/Applications/Cydia.app"
                BLX             _objc_msgSend
                POP             {R7,PC}
; End of function -[AppDelegate isJailbroken]

There are plenty of ways to bypass this check. The easiest and most trivial is to patch the binary so that the file name string is different - indeed, the patch works in this case perfectly.


However, it is much more interesting to do the whole magic in dynamic mode - although string patching is viable and perfectly working option in this case, it is not applicable in most cases (especially when it comes to more complex checks).

We use gdb to perform the bypass. In order to stop execution in the correct moment, we create a breakpoint in the [NSFileManager fileExistsAtPath:] API call.

Attaching to process 1172.
Reading symbols for shared libraries + done
Reading symbols for shared libraries ++ done
Reading symbols for shared libraries + done
0x2feb8470 in __dyld_strcmp ()
(gdb) b fileExistsAtPath:
Breakpoint 1 at 0x37fd9c3a
(gdb) c

The app runs and we hit the breakpoint.

Breakpoint 1, 0x37fd9c3a in -[NSFileManager fileExistsAtPath:] ()
(gdb)

Now take a look at the source in the beginning. It is easy to assemble the calling stack, even manually:
[NSFileManager fileExistsAtPath:] 
[AppDelegate isJailbroken]
[AppDelegate jailbreakDetection]
[...]

gdb confirms our theory:

(gdb) bt
#0  0x37fd9c3a in -[NSFileManager fileExistsAtPath:] ()
#1  0x000a146e in ?? ()
#2  0x000a140c in ?? ()
#3  0x000a13ec in ?? ()
#4  0x31515c30 in -[UIApplication _stopDeactivatingForReason:] ()
#5  0x31503914 in -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] ()
[...]

We create another breakpoint, in the [AppDelegateisJailbroken] function, right after the return of the [NSFileManager fileExistsAtPath:].

(gdb) b *0x000a146e
Breakpoint 2 at 0xa146e
(gdb) c
Continuing.

We hit the second breakpoint - at this point, in accordance with ARM calling conventions, the returned value from the fileExistsAtPath: is in $r0. All we need to do is to set it to 0.

(gdb) i r $r0
r0             0x1 1
(gdb) set $r0=0
(gdb) c
Continuing.

...and the application pops up an alert with "Clean device. :)" Easy, isn't it?

Wednesday, 8 August 2012

Not All Hash Functions Are Created Equal

During another iOS app pentest, I found a mistake which is very easy to commit when doing iOS devving - the problem posed by the API calls that are easy to misunderstand or misinterpret. Function names in such API calls suggest one thing but actual implementations do another.

The application itself is a common one at the client: designed to store loads of sensitive data, stuffed with crypto magic, ready to handle the scenario when the iPad is stolen/lost. No biggie there. When the user starts the application for the first time, he has to initialise the crypto engine with entering a complex, >10 char long password with numbers and special symbols. The entropy of the password space is estimated around 60 bits, which can be considered kind of strong from a brute force perspective.

Unfortunately, the vendor did not give us the source (as the pentest was supposed to model a black-box scenario with a highly skilled attacker), therefore we used IDA to do binary autopsy. When the user initialises the crypto, the following code snippet is executed:

CreatePasswordViewController - (void)okButtonPressed:(id)
[lots of checks for password length, complexity etc.]
MOV             R0, (_OBJC_IVAR_$_CreatePasswordViewController.leftPasswordField - 0x2FFC6) ; UITextField *leftPasswordField;
LDR             R1, [SP,#0xC0+var_94]
ADD             R0, PC  ; UITextField *leftPasswordField;
LDR             R0, [R0] ; UITextField *leftPasswordField;
LDR             R0, [R6,R0]
BLX             _objc_msgSend
MOV             R7, R7
BLX             _objc_retainAutoreleasedReturnValue
MOV             R4, R0
MOV             R0, (selRef_hash - 0x2FFE0) ; selRef_hash
ADD             R0, PC ; selRef_hash
LDR             R1, [R0] ; "hash"
MOV             R0, R4
BLX             _objc_msgSend
MOV             R2, R0
MOV             R0, (selRef_storePassword_ - 0x2FFF4) ; selRef_storePassword_
ADD             R0, PC ; selRef_storePassword_
LDR             R1, [R0] ; "storePassword:"
MOV             R0, R6
BLX             _objc_msgSend
[...]

Looks kind of okay - we take the text from the leftPasswordField UITextField, and feed the contents to the [CreatePasswordViewController storePassword:] function, which looks like this in a disassembler:

; CreatePasswordViewController - (void)storePassword:(unsigned int)
PUSH            {R4-R7,LR}
ADD             R7, SP, #0xC
SUB             SP, SP, #4
MOV             R5, R0
MOV             R0, (selRef_securedSHA256DigestHashForPIN_ - 0x2FC28) ; selRef_securedSHA256DigestHashForPIN_
MOV             R6, (classRef_KeychainWrapper - 0x2FC2A) ; classRef_KeychainWrapper
ADD             R0, PC ; selRef_securedSHA256DigestHashForPIN_
ADD             R6, PC ; classRef_KeychainWrapper
LDR             R1, [R0] ; "securedSHA256DigestHashForPIN:"
LDR             R0, [R6] ; _OBJC_CLASS_$_KeychainWrapper
BLX             _objc_msgSend
MOV             R7, R7
BLX             _objc_retainAutoreleasedReturnValue
MOV             R4, R0
MOV             R0, (selRef_createKeychainValue_forIdentifier_ - 0x2FC46) ; selRef_createKeychainValue_forIdentifier_
MOV             R2, R4
ADD             R0, PC ; selRef_createKeychainValue_forIdentifier_
LDR             R1, [R0] ; "createKeychainValue:forIdentifier:"
LDR             R0, [R6] ; _OBJC_CLASS_$_KeychainWrapper
MOV             R3, (cfstr_Passsaved - 0x2FC54) ; "passSaved"
ADD             R3, PC  ; "passSaved"
BLX             _objc_msgSend
TST.W           R0, #0xFF
[error handling etc.]

The operation is simple: we take the input, create a SHA256 hash for the PIN in a smart way (salt and SHA the input 10.000 times in order to thwart brute force attacks), and save the result to the 'passSaved'  keychain item. So far, so good. Looks like things are OK from a crypto perspective, the password is stored after salted secure hashing. A note here: the idea of storing the password in any form suggests usage of the stored value. Most of the time, this involves a comparison of two values: one resulting from chewing the entered password, the other sitting on the keychain. This mode of operation is insecure on mobile devices - I'll show why later. However, this is irrelevant from the implementational glitch I'd like to point out now.

What did the developers get wrong?

The first clue is the header of the CreatePasswordViewController: function. Take a closer look at it.

; CreatePasswordViewController - (void)storePassword:(unsigned int)

Weird... the contents of a UITextField are usually stored in an NSString object, not an unsigned int. The set of acceptable passwords (stored in NSString objects) is much bigger than the set of unsigned integers - a crucial point in cryptographic applications. Going back to our first snippet:

CreatePasswordViewController - (void)okButtonPressed:(id)
[lots of checks for password length, complexity etc.]
MOV             R0, (_OBJC_IVAR_$_CreatePasswordViewController.leftPasswordField - 0x2FFC6) ; UITextField *leftPasswordField;
[...]
MOV             R4, R0
MOV             R0, (selRef_hash - 0x2FFE0) ; selRef_hash
ADD             R0, PC ; selRef_hash
LDR             R1, [R0] ; "hash"
MOV             R0, R4
BLX             _objc_msgSend
MOV             R2, R0
MOV             R0, (selRef_storePassword_ - 0x2FFF4) ; selRef_storePassword_
ADD             R0, PC ; selRef_storePassword_
LDR             R1, [R0] ; "storePassword:"
MOV             R0, R6
BLX             _objc_msgSend
[...]

Consider the bits highlighted in red. The snippet uses the [NSObject hash] function:

hash
Returns an integer that can be used as a table address in a hash table structure. (required)
- (NSUInteger)hash
Return Value
An integer that can be used as a table address in a hash table structure.

This hash function, despite what it's name suggests is not a cryptographic hash. Using it therefore results in a significant shrink of the key space - from ~60 bits to ~32 bits. Due to this implementational glitch, the overall cost of mounting a brute force attack against the crypto is a fraction of the cost suggested by the complexity of the password space.

Saturday, 4 August 2012

Case study - Spot The Self-propelling Crypto in iOS

Last week I was assigned to the penetration test of an iOS application. The vendor was very secretive about the source, and they flat refused to disclose the source to our testing team. Well... not a serious problem, really if you have a proper disassembler in your toolkit.

The application itself was what can be described as a 'very expensive sales pitch tool': salespeople pick up a company branded iPad, walk to the prospective clients' office and they use this fancy application to feed the content to the projected presentation. Absolutely unnecessary, but looks awesome, picks up chicks and the working setup has the certain 'wow'-factor which is worth the whole hassle with the development of the application. Well...

Put that aside - from our point of view, the application was equipped with a set of very sensitive documents and the main concern of the application owners was the scenario where a presentation iPad gets lost or stolen. The main purpose of the test was to find out whether it is possible to extract plain-text documents from the locked application instance, provided physical access to the containing device.

The app developers were particularly proud that the application was stuffed with crypto: there is no plain-text data at rest, all the presentation material is stored using AES256 symmetric encryption and plain-text data lives only in the volatile memory of the device. After the user enters the password, it gets hashed 10.000 times using SHA1 and the result is used to build up the actual encryption key using the PBKDF2 (Password Based Key Derivation Function) algorithm.

Not bad, not bad at all - most of the time, the Achilles heel of such crypto scheme lies in the tiny implementation details. As a first step, let's take a look at the snippet which is executed when the user creates his password. (Unfortunately, we did not have a source code for the application, therefore we use assembly here. Don't worry, it is actually much easier to understand than it might seem first.)

; createPasswordViewController - (void)storePassword:(unsigned int)
; Attributes: bp-based frame
; void __cdecl __createPasswordViewController storePassword__(struct createPasswordViewController *self, SEL, unsigned int)
__createPasswordViewController_storePassword__
var_10= -0x10
PUSH            {R4-R7,LR}
ADD             R7, SP, #0xC
SUB             SP, SP, #4
MOV             R5, R0
MOV             R0, (selRef_securedSHA256DigestHashForPIN_ - 0x2FC28) ; selRef_securedSHA256DigestHashForPIN_
MOV             R6, (classRef_KeychainWrapper - 0x2FC2A) ; classRef_KeychainWrapper
ADD             R0, PC ; selRef_securedSHA256DigestHashForPIN_
ADD             R6, PC ; classRef_KeychainWrapper
LDR             R1, [R0] ; "securedSHA256DigestHashForPIN:"
LDR             R0, [R6] ; _OBJC_CLASS_$_KeychainWrapper
BLX             _objc_msgSend
MOV             R7, R7
BLX             _objc_retainAutoreleasedReturnValue
MOV             R4, R0
MOV             R0, (selRef_createKeychainValue_forIdentifier_ - 0x2FC46) ; selRef_createKeychainValue_forIdentifier_
MOV             R2, R4
ADD             R0, PC ; selRef_createKeychainValue_forIdentifier_
LDR             R1, [R0] ; "createKeychainValue:forIdentifier:"
LDR             R0, [R6] ; _OBJC_CLASS_$_KeychainWrapper
MOV             R3, (cfstr_Passsaved - 0x2FC54) ; "passSaved"
ADD             R3, PC  ; "passSaved"
BLX             _objc_msgSend
TST.W           R0, #0xFF
BNE             loc_2FCB2
[error checking, exception handling etc.]

In order to make the whole thing more obvious, I highlighted the important bits. Using Objective-C-like syntax, something like this might happen when the user's password is set:

@interface createPasswordViewController :
- (void)storePassword:(UInteger PIN)
        NSString StoredPassword = [KeychainWrapper securedSHA256DigestHashForPIN:PIN];
        if ([createKeychainValue: storedPassword forIdentifier:@"passSaved"]) == 0{
            success;
        } else {
            whatever;
        }
The user's PIN/password is securely hashed and put on the keychain. Now take a look at the snippet which initialises the decryption routine.

; SecurityManager - (void)decryptFileAtPath:(id)
; Attributes: bp-based frame
; void __cdecl __SecurityManager decryptFileAtPath__(struct SecurityManager *self, SEL, id)
SecurityManager_decryptFileAtPath:


PUSH            {R4-R7,LR}
ADD             R7, SP, #0x
[...we create a temporary file, open the encrypted file on the disk, read the contents...]
MOV             R1, (selRef_keychainStringFromMatchingIdentifier_ - 0x3898E) ; selRef_keychainStringFromMatchingIdentifier_
MOV             R4, R0
MOVW            R0, #0xD664
ADD             R1, PC ; selRef_keychainStringFromMatchingIdentifier_
MOVT.W          R0, #2
ADD             R0, PC ; classRef_KeychainWrapper
LDR             R1, [R1] ; "keychainStringFromMatchingIdentifier:"
LDR             R0, [R0] ; _OBJC_CLASS_$_KeychainWrapper
MOV             R2, (cfstr_Passsaved - 0x389A2) ; "passSaved"
ADD             R2, PC  ; "passSaved"
BLX             _objc_msgSend
MOV             R7, R7
BLX             _objc_retainAutoreleasedReturnValue
MOV             R5, R0
MOV             R0, (selRef_AESKeyForPassword_ - 0x389BA) ; selRef_AESKeyForPassword_
MOV             R2, R5
ADD             R0, PC ; selRef_AESKeyForPassword_
LDR             R1, [R0] ; "AESKeyForPassword:"
MOV             R0, R4
BLX             _objc_msgSend
[...actual decryption routine...]

What is wrong? Again, using ObjC-like syntax, something like this happens here:

@interface SecurityManager :
- (void)decryptFileAtPath:(id):
         [we open a tempfile for writing]
         [we open the encrypted container for reading]
         NSString decrytPass = [KeychainWrapper keychainStringFromMatchingIdentifier:@"passSaved"];
         NSString decryptKey = [SecurityManager AESKeyForPassword:decryptPass];
        [actual decryption routine]

Issue No1. The main problem is that the actual decryption key is derived with a deterministic algorithm from the contents of the keychain, therefore in a cryptographic sense there is no secret. Having access to the device, we know the encryption algorithm, the ciphertext, the key derivation algorithm and its inputs. If I were an attacker, I could perform the following attack to extract the juicy stuff:
  1. Jailbreak the device and install SSH.
  2. Install the keychain-dumper utility to get the key from of the keychain.
  3. Extract the encryption algoritm, reverse engineer it and create an own implementation. 
  4. Profit.
Note that though it is possible to crack the user's password, there is no need for the actual password, the crypto engine runs fine even without it.

Issue No2. There is a much more sophisticated attack against this crypto scheme, which does not require jumping head first into reversing the encryption routine. Apparently, when the user logs in, the applied logic SHA1's the entered password a million times and compares the result with the stored 'passSaved' value on the keychain. Indeed, analysis of the assembly confirms our theory:
; LoginViewController - (void)loginButtonPressed:(id)
; Attributes: bp-based frame
; void __cdecl __LoginViewController loginButtonPressed__(structLoginViewController *self, SEL, id)
__LoginViewController_loginButtonPressed__
PUSH            {R4-R7,LR}
ADD             R7, SP, #0xC
[lots of magic going on here]
MOV             R0, (_OBJC_IVAR_$_LoginViewController.enterPasswordTextField - 0x30BDA) ; UITextField *enterPasswordTextField;
MOVW            R1, #0x42A0
ADD             R0, PC  ; UITextField *enterPasswordTextField;
MOVT.W          R1, #3
ADD             R1, PC ; selRef_text
LDR             R0, [R0] ; UITextField *enterPasswordTextField;
LDR             R1, [R1] ; "text"
LDR.W           R0, [R10,R0]
BLX             _objc_msgSend
MOV             R0, (selRef_compareKeychainValueForMatchingPIN_ - 0x30C20) ; selRef_compareKeychainValueForMatchingPIN_
MOV             R2, (classRef_KeychainWrapper - 0x30C22) ; classRef_KeychainWrapper
ADD             R0, PC ; selRef_compareKeychainValueForMatchingPIN_
ADD             R2, PC ; classRef_KeychainWrapper
LDR             R1, [R0] ; "compareKeychainValueForMatchingPIN:"
LDR             R0, [R2] ; _OBJC_CLASS_$_KeychainWrapper
MOV             R2, R4
BLX             _objc_msgSend
TST.W           R0, #0xFF
BEQ             loc_30C84
[the logic decides whether the returned value is YES or NO and takes the corresponding action]
Indeed:
@interface LoginViewController:
- (void)okButtonPressed:(id):
   [...]
   NSString enteredPassword = [enteredPasswordTextField text];
   BOOL Result = [KeychainWrapper compareKeychainValueForMatchingPIN:enteredPassword];
   if Result {
     [...]
   } else {
     [....]
   }
This logic be easily defeated using runtime ObjC manipulation.