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.

No comments:

Post a Comment