The first method of testing for this is to check is to create a tweak using Theos. To get all the necessary information, I use a combination of other tools, namely:
UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 ! */setTimeout(function(){ Java.perform(function (){ console.log(""); console.log("[.] Cert Pinning Bypass/Re-Pinning");var CertificateFactory = Java.use("java.security.cert.CertificateFactory"); var FileInputStream = Java.use("java.io.FileInputStream"); var BufferedInputStream = Java.use("java.io.BufferedInputStream"); var X509Certificate = Java.use("java.security.cert.X509Certificate"); var KeyStore = Java.use("java.security.KeyStore"); var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory"); var SSLContext = Java.use("javax.net.ssl.SSLContext");// Load CAs from an InputStream console.log("[+] Loading our CA...") var cf = CertificateFactory.getInstance("X.509");
var bufferedInputStream = BufferedInputStream.$new(fileInputStream); var ca = cf.generateCertificate(bufferedInputStream); bufferedInputStream.close();var certInfo = Java.cast(ca, X509Certificate); console.log("[o] Our CA Info: " + certInfo.getSubjectDN());// Create a KeyStore containing our trusted CAs console.log("[+] Creating a KeyStore for our CA..."); var keyStoreType = KeyStore.getDefaultType(); var keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore..."); var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); var tmf = TrustManagerFactory.getInstance(tmfAlgorithm); tmf.init(keyStore); console.log("[+] Our TrustManager is ready...");console.log("[+] Hijacking SSLContext methods now...") console.log("[-] Waiting for the app to invoke SSLContext.init()...")SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) { console.log("[o] App invoked javax.net.ssl.SSLContext.init..."); SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c); console.log("[+] SSLContext initialized with our custom TrustManager!"); } }); },0);
File Integrity Checks (MSTG-RESILIENCE-3 and MSTG-RESILIENCE-11)
Overview
There are two topics related to file integrity:
Application source code integrity checks:In the "Tampering and Reverse Engineering" chapter, we discussed the iOS IPA application signature check. We also saw that determined reverse engineers can easily bypass this check by re-packaging and re-signing an app using a developer or enterprise certificate. One way to make this harder is to add an internal run-time check that determines whether the signatures still match at run time.
File storage integrity checks:When files are stored by the application, key-value pairs in the Keychain,UserDefaults/NSUserDefaults, a SQLite database, or a Realm database, their integrity should be protected.
Sample Implementation - Application Source Code
Apple takes care of integrity checks with DRM. However, additional controls (such as in the example below) are possible. Themach_headeris parsed to calculate the start of the instruction data, which is used to generate the signature. Next, the signature is compared to the given signature. Make sure that the generated signature is stored or coded somewhere else.
intxyz(char *dst) { conststruct mach_header * header; Dl_info dlinfo; if (dladdr(xyz, &dlinfo) == 0 || dlinfo.dli_fbase == NULL) { NSLog(@" Error: Could not resolve symbol xyz"); [NSThread exit]; } while(1) { header = dlinfo.dli_fbase; // Pointer on the Mach-O headerstruct load_command * cmd = (struct load_command *)(header + 1); // First load command// Now iterate through load command//to find __text section of __TEXT segmentfor (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) { if (cmd->cmd == LC_SEGMENT) { // __TEXT load command is a LC_SEGMENT load commandstruct segment_command * segment = (struct segment_command *)cmd; if (!strcmp(segment->segname, "__TEXT")) { // Stop on __TEXT segment load command and go through sections// to find __text sectionstruct section * section = (struct section *)(segment + 1); for (uint32_t j = 0; section != NULL && j < segment->nsects; j++) { if (!strcmp(section->sectname, "__text")) break; //Stop on __text section load command section = (struct section *)(section + 1); } // Get here the __text section address, the __text section size// and the virtual memory address so we can calculate// a pointer on the __text sectionuint32_t * textSectionAddr = (uint32_t *)section->addr; uint32_t textSectionSize = section->size; uint32_t * vmaddr = segment->vmaddr; char * textSectionPtr = (char *)((int)header + (int)textSectionAddr - (int)vmaddr); // Calculate the signature of the data,// store the result in a string// and compare to the original oneunsignedchar digest[CC_MD5_DIGEST_LENGTH]; CC_MD5(textSectionPtr, textSectionSize, digest); // calculate the signaturefor (int i = 0; i < sizeof(digest); i++) // fill signaturesprintf(dst + (2 * i), "%02x", digest[i]); // return strcmp(originalSignature, signature) == 0; // verify signatures matchreturn0; } } cmd = (struct load_command *)((uint8_t *)cmd + cmd->cmdsize); } } }
Sample Implementation - Storage
When ensuring the integrity of the application storage itself, you can create an HMAC or signature over either a given key-value pair or a file stored on the device. The CommonCrypto implementation is best for creating an HMAC. If you need encryption, make sure that you encrypt and then HMAC as described inAuthenticated Encryption.
When you generate an HMAC with CC:
Get the data asNSMutableData.
Get the data key (from the Keychain if possible).
Calculate the hash value.
Append the hash value to the actual data.
Store the results of step 4.
// Allocate a buffer to hold the digest and perform the digest.NSMutableData* actualData = [getData]; //get the key from the keychainNSData* key = [getKey]; NSMutableData* digestBuffer = [NSMutableDatadataWithLength:CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, [actualData bytes], (CC_LONG)[key length], [actualData bytes], (CC_LONG)[actualData length], [digestBuffer mutableBytes]); [actualData appendData: digestBuffer];
Alternatively, you can use NSData for steps 1 and 3, but you'll need to create a new buffer for step 4.
When verifying the HMAC with CC, follow these steps:
Extract the message and the hmacbytes as separateNSData.
Repeat steps 1-3 of the procedure for generating an HMAC on theNSData.
Compare the extracted HMAC bytes to the result of step 1.
Bypassing File Integrity ChecksWhen you're trying to bypass the application-source integrity checks
Patch the anti-debugging functionality and disable the unwanted behavior by overwriting the associated code with NOP instructions.
Patch any stored hash that's used to evaluate the integrity of the code.
Use Frida to hook file system APIs and return a handle to the original file instead of the modified file.
When you're trying to bypass the storage integrity checks
Retrieve the data from the device, as described in the "Device Binding" section.
Alter the retrieved data and return it to storage.
Effectiveness Assessment
For the application source code integrity checksRun the app on the device in an unmodified state and make sure that everything works. Then apply patches to the executable using optool, re-sign the app as described in the chapter "Basic Security Testing", and run it. The app should detect the modification and respond in some way. At the very least, the app should alert the user and/or terminate the app. Work on bypassing the defenses and answer the following questions:
Can the mechanisms be bypassed trivially (e.g., by hooking a single API function)?
How difficult is identifying the anti-debugging code via static and dynamic analysis?
Did you need to write custom code to disable the defenses? How much time did you need?
What is your assessment of the difficulty of bypassing the mechanisms?
For the storage integrity checksA similar approach works. Answer the following questions:
Can the mechanisms be bypassed trivially (e.g., by changing the contents of a file or a key-value pair)?
How difficult is obtaining the HMAC key or the asymmetric private key?
Did you need to write custom code to disable the defenses? How much time did you need?
What is your assessment of the difficulty of bypassing the mechanisms??