# iOS app introspection - [Jailbreak in 2024](#jailbreak-in-2024) - [Steps](#steps) - [Find jailbreak](#find-jailbreak) - [Prepare iOS device](#prepare-ios-device) - [Prepare XCode for older iOS versions](#prepare-xcode-for-older-ios-versions) - [Create Provisioning Profile in XCode](#create-provisioning-profile-in-xcode) - [Sign and deploy Jailbreak IPA](#sign-and-deploy-jailbreak-ipa) - [Verify you can SSH onto iOS device](#verify-you-can-ssh-onto-ios-device) - [Get decrypted IPA off Jailbroken device](#get-decrypted-ipa-off-jailbroken-device) - [Strings](#strings) - [Get App Store iPAs](#get-app-store-ipas) - [Quick-start run-time introspection](#quick-start-run-time-introspection) - [Objection](#objection) - [Inspect files inside ipa](#inspect-files-inside-ipa) - [Inspect sandbox data on Jailbroken device](#inspect-sandbox-data-on-jailbroken-device) - [iOS file structure](#ios-file-structure) - [Logs](#logs) - [Build information](#build-information) - [Applesign](#applesign) - [Prepare for code signing](#prepare-for-code-signing) - [Troubleshoot code signing](#troubleshoot-code-signing) - [Sideload iOS app](#sideload-ios-app) - [Frida-Server](#frida-server) - [Frida-Gadget](#frida-gadget) - [Frida basics](#frida-basics) - [Frida's --eval flag](#fridas---eval-flag) - [Frida Intercepter](#frida-intercepter) - [Frida-Trace](#frida-trace) - [Bypass anti-Frida checks](#bypass-anti-frida-checks) - [Cookies](#cookies) - [Change iOS Version](#change-ios-version) - [LLVM Instrumentation](#llvm-instrumentation) ## Jailbreak in 2024 Please read if you have a Jailbreak - `checkra1n`, `unc0ver`, `TrollInstallerX` or `dopamine` - and want to get it working. There is no magic "download and it works". Forget `Cydia Impactor`. > [!WARNING] > If you just download a jailbreak `ipa` - even if it is [TrollInstallerX](https://github.com/alfiecg24/TrollInstallerX/) it won't be code-signed. It won't install. ### Steps Preparing an old iOS device for Jailbreak takes time and effort: 1. Find jailbreak 2. Prepare iOS Device 3. Prepare XCode for older iOS versions 4. Create Provisioning Profile in XCode 5. Sign and deploy Jailbreak IPA 6. Verify you can SSH onto iOS device ### 1. Find jailbreak - [CheckRa1n](https://checkra.in/) great option when you have an older iOS device. - [PaleRa1n](https://palera.in/) iOS 15+. Requires older iOS device. - [Electra](https://coolstar.org/electra/) iOS 11. But iOS 11 was now old. Most apps dropped support. - [Dopamine]https://github.com/opa334/Dopamine/releases/tag/2.2.2 16.x - [Unc0ver](https://unc0ver.dev/) iOS 14.2 device Unc0ver seems ok. ### 2. Prepare iOS device - Trust the `Profile` of the Developer. - Ensure the device `Trusts` the machine it connects with. - On newer iOS version this includes turning on Developer Settings; iOS 16.0 to 16.6.1 `Settings -> Privacy & Security` and then scroll down until you see Developer Mode, tap that option. ### 3. Prepare XCode for older iOS versions Open `XCode/Window/Devices and Simulators`. Let XCode see the device. If you are trying to use an older device with an older OS, keep reading; for example, with Unc0ver, the latest XCode doesn't support iOS 14. You can still update XCode Device Support. Why ? This is required so you can create a `provisioning profile` for iOS 14.2 [ or deploy any iOS to that old device ]. - Grab the Device Support files [here](https://github.com/iGhibli/iOS-DeviceSupport/tree/master/DeviceSupport). - Open `Applications`. Right-click on Xcode, in Finder, and select `“package contents”`. - Navigate to `contents/Developer/platform/iPhoneOS.platform/DeviceSupport`. - Unzip the downloaded file and copy the folder to above location. - Restart XCode. - The older iOS device should now be a possible build target. ### 4. Create Provisioning Profile in XCode We need to get a Provisioning Profile ( called the `embedded.mobileprovision` file). - Open `Xcode` and select `File/New/Project/Swift` and call it anything. You don't need tests or any CoreData pieces. Just a basic "HelloWorld" app. - Select `build` for your connected device. > [!INFO] > You don’t need to `Run` the app on the device. - Right click on the `/Product/HelloWorld.app` - in the left hand view pane - and select `show in Finder`. If you look inside the folder ( remember `HelloWorld.app` is a folder ) you will find a fresh `embedded.mobileprovision`. This contains the uniques IDs and an expiry date for the developer profile associated to the app. ### 5. Sign and deploy Jailbreak IPA ```shell # find your "Apple Development" ID security find-identity -v -p codesigning # tools to inspect device ( works on jailed and jailbroken devices ) brew install libimobiledevice # check if you can see attached iOS device idevice_id 0000FFFF-0011111111122222 (USB) # sign Jailbreak ipa applesign -p -7 \ -i ${CODESIGNID} \ -b rm.helloworld \ --clone-entitlements \ -m embedded.mobileprovision \ TrollInstallerX.ipa -o $SIGNED_IPA # Deploy to the device over USB ios-deploy -b $SIGNED_IPA ``` ### 6. Verify you can SSH onto iOS device ```shell # macOS - install iProxy brew install libusbmuxd # For SSH over USB access iproxy 2222 22 & # most jailbreaks: Password is well known ssh -p 2222 root@localhost # dopamine uses the `mobile` user not root # user sets password when invoking the jailbreak ssh -p 2222 mobile@localhost ``` ## Get decrypted IPA off Jailbroken device **Decrypting the app binary is essential**. You cannot skip this step if you want to find good strings, debug the app or repackage the iPA. ```shell # Get script to decrypt iPA https://github.com/AloneMonkey/frida-ios-dump # Attach a jailbroken iPhone and create tunnel over USB iproxy 2222 22 & # Ensure Frida is running on iOS device. Then run frida-ios-dump ./dump.py foo.bar.bundleid # Check AppStore binary is now decrypted ( cryptid 0 decrypted vs cryptid 1 encrypted ) otool -l Payload/foo.app/foo | grep -i LC_ENCRYPTION -B1 -A4 Load command 12 cmd LC_ENCRYPTION_INFO_64 cryptid 0 -- ``` ## Strings If you didn't decrypt the app you won't find good strings. ```bash strings $APP_BINARY | grep -E 'session|https|pinning|secret' ``` ## Get App Store iPAs - Install Apple's utility [Apple Configurator 2](https://apps.apple.com/us/app/apple-configurator-2/id1037126344?mt=12) from macOS store - Install the target iOS app on the target device - Then open `Apple Configurator 2` and "sign in" with the same Apple account used on the target device - Sign-out and sign-in to refresh the known app list - Right click on device and select `Add/Apps` - Select the app you want to copy At this point `Apple Configurator 2` will download a copy of the app to: --- >`~/Library/Group Containers/K36BKF7T3D.group.com.apple.configurator/Caches/Assets/TemporaryItems/MobileApps/` When you hit the `"Skip App / Replace / Stop"` modal, select nothing. Go to Finder and grab the IPA. [Full instructions](https://medium.com/@contact.jmeyers/download-the-ipa-file-for-any-ios-app-on-your-iphone-8298c7d6d812). ## Quick-start run-time introspection Tools like Frida and Objection can work with apps straight from the app store. To work on a `jailed` device, the app needs to be re-signed as it adds a dynamic library ( `gadget` ) to the app's list of frameworks. ##  Objection ```shell # Install Objection pip3 install objection # jailed device objection --gadget $BUNDLE_ID explore # jailbroken device objection -g $BUNDLE_ID explore #### Objection commands #### # sandbox location on device env # search for useful methods ios hooking search methods pincode [MySwiftApp.Services - validatePinCode:error:] [MySwiftApp.Services - disablePinCode] # search for useful classes ios hooking search classes pincode # watch method calls inside a Class ios hooking watch class MySwiftApp.Services # verify Objection can see Class ios hooking search classes MySwiftApp.Services # list Class Methods of a Class ios hooking list class_methods MySwiftApp.Services # read info.plist ios plist cat Info.plist # download file file download Info.plist # dump memory memory dump all myapp_memory.dump Will dump 111 rw- images, totalling 718.5 MiB Dumping 512.0 MiB from base: 0x280000000 [####################################] 100% # search memory for strings strings myapp_memory.dump | grep -i session # KeyChain dump ios keychain dump --json output.json # list bundles used by app ios bundles list_bundles # list classes ios hooking list classes # list frameworks used by app ios bundles list_frameworks # list modules memory list modules # print UI hierarchy of view controllers, labels, buttons, etc ios ui dump ``` ## Inspect files inside ipa ```shell # Unzip the IPA file to reveal the Payload folder unzip myApp.ipa # big files inside ipa file find Payload -size +2M # Files that were mistakingly shipped inside of App Bundle find . -name '*.json' -or -name '*.txt' # Check for ReactNative find . -name main.jsbundle # Check for Certificates find . -name '*.crt' -or -name '*.cer' -or -name '*.der' # Property lists inside Payload folder. Recursive search. find Payload/ -name '*.plist' # Provisioning Profiles find . -name '*.mobileprovision' # Dynamically linked frameworks find . -name '*.framework' # Locally linked javascript find Payload -name '*.js' # Search all plist files for a value find . -name '*.plist' | xargs grep "LSApplicationQueriesSchemes" # Search all plist files for Device Permissions or App Transport Security find . -name '*.plist' | xargs grep "NS" # Search all files using only grep grep "LSApplicationQueriesSchemes" . -R # Recursive search all files using grep inside Payload folder grep "Requires" Payload -R # foobar.app/Info.plist: UIRequiresFullScreen # foobar.app/Info.plist: LSRequiresIPhoneOS ``` ## Inspect sandbox data on Jailbroken device ### iOS file structure ```bash # Sandbox. Look here for Cookies, json files, etc /var/mobile/Containers/Data/Application/[GUID given at install time]/ # Folder of App Bundle that was installed. Executables, frameworks, fonts, CSS, html. NIB files. /private/var/containers/Bundle/Application/[GUID given at app install]/foo.app # App executable /private/var/containers/Bundle/Application/[GUID given at app install]/foo.app/foo # freshly installed IPA is at the bottom of list cd /private/var/mobile/Containers/Data/Application/ && ls -lrt cd [app guid]/Documents/ cd [app guid]/Library/ # Databases to pull off a device /private/var/Keychains TrustStore.sqlite3 keychain-2.db pinningrules.sqlite3 ``` #### File sharing ```bash # Extract IPA (whether App Store encrypted or not) scp -r -P 2222 root@localhost:/var/containers/Bundle/Application//hitme.app ~/hitme.app # Different to SSH, the uppercase P for Port with SCP. Order important. scp -P 2222 root@localhost:/var/root/overflow.c localfilename.c # from Jailbroken device to local machine # Caution:no space after the root@localhost: Otherwise you copy the entire filesystem! scp -P 2222 root@localhost:/private/var/mobile/Containers/Data/Application//Library/Caches/Snapshots/com.my.app # from local machine to remote Jailbroken device scp -P 2222 hello.txt root@localhost:/var/root/ ``` ## Logs ```shell # physical device idevicesyslog -u | myPipedProgram # Get logs from iOS Simulator xcrun simctl spawn booted log stream --level=debug # Get logs from iOS Simulator by App Name xcrun simctl spawn booted log stream --predicate 'processImagePath endswith "MyAppName"' ``` ## Build information ```shell # Check platform lipo -info libprogressbar.a # Check for build errors jtool -arch arm64 -L # Check minimum iOS version & restrict linker flag jtool -arch arm64 -l # Check binary was stripped rabin2 -I -a arm_64 | grep -E 'stripped|canary' # Check Position Independent Code set rabin2 -I -a arm_64 | grep -E 'pic|bits # Check for Bitcode enabled # [!] this command won't work on a locally built Simulator / iPhone app. Bitcode happens after setting `Archive` otool -l libprogressbar.a | grep __LLVM otool -arch arm64 -l tinyDynamicFramework | grep __LLVM ``` ## Applesign `Applesign` is a wrapper around `Codesigning` tools from Apple. ```shell npm install -g applesign ``` #### Create provisioning file First, you want to get hold of an `embedded.mobileprovision` file: - Open `Xcode` and select `File/New/Project/Swift` and call it anything. You don't need tests or any CoreData pieces. Just a basic "HelloWorld" app. - Select `build` for your connected device. > [!INFO] > You don’t need to `Run` the app on the device. - Right click on the `/Product/HelloWorld.app` - in the left hand view pane - and select `show in Finder`. If you look inside the folder ( remember `HelloWorld.app` is a folder ) you will find a fresh `embedded.mobileprovision`. This contains the uniques IDs and an expiry date for the developer profile associated to the app. ### Prepare for code signing ```shell # Read the Provisioning Profile # ensure your device ID is in the profile and the profile is fresh. security cms -D -i embedded.mobileprovision # find code signing key security find-identity -v -p codesigning export CODESIGNID= # tell Applesign to use your Provisioning Profile applesign -7 -i ${CODESIGNID} -m embedded.mobileprovision $UNSIGNED_IPA -o $SIGNED_IPA # Speed up repackaging rm -v $UNSIGNED_IPA | rm -v $SIGNED_IPA | 7z a $UNSIGNED_IPA Payload # Re-sign IPA and set new Bundle ID # caution,changing the Bundle ID can cause ios-deploy issues // applesign -7 -i ${CODESIGNID} -b funky-chicken.resigned -m embedded.mobileprovision $UNSIGNED_IPA -o $SIGNED_IPA ``` ### Troubleshoot code signing > [!CAUTION] > You can't resign an encrypted app store app. Remember to decrypt that app ! Title | Detail --|-- Missing Device ID | Check Provisioning Profile (`embedded.mobileprovision`) included device's UUID Code signing key expired | Timeframe for paid iOS Developer license is one-year versus one-week for free developer signing key. Wrong Code-Signing Key | check the Code Signing Key was NOT an `iPhone Distribution key` Mismatched bundle ID | `Error 0xe8008001`. When you create the Provisioning Profile it knows of the Bundle ID. So when you resign an IPA, it must match this value. identity is no longer valid | `Error 0xe8008018: The identity used to sign the executable is no longer valid.` Make sure that the `Apple Development` key was selected when running `security find-identity -v -p codesigning`, I hit this error when I selected a `Developer ID Application`. I should have selected the ID associated to `Apple Development` credential. Code Signing Keys Match | check the `Code Signing Key` used when creating the `Provisioning Profile` matched the `Code Signing Key` selected when repackaging and code signing. XCode check | When generating an app - to get hold of `embedded.mobileprovision` file - remember the `Code signing` options are different for each Project Target and ProjectTests. Delete Old Apps | check no old app is installed on the phone [ that was signed with a different key ] but has the same Bundle ID. Entitlements overload | You can have a `Provisioning Profile` (embedded.mobileprovision) that contained more `Capabilities` than the app you are re-signing. Clone Entitlements | When the app is complicated, with many entitlements, sometimes it is easier just to `--clone-entitlements` with `Applesign`. Wrong Bundle ID | When you add specific `Entitlments` you need a unique Bundle ID. Check whether you need to change Bundle ID when re-signing. Network settings | `Settings\General\Profiles and Device Management` to trust the Developer Profile and App. This won't happen if you are manually proxying or setting a local DNS server., when installing with `iOS-deploy`. Watch Extensions | `iOS-deploy` spits out lots of messages if you change the bundle ID when resigning an app ( `Error 0xe800009e: This app contains an app extension with an illegal bundle identifier`. If you use the free developer account, changing the Bundle ID is the only to repackage another party's app. I ended up deleting the Watch.app from the Payload bundle as I wasn't interested in testing. That worked. --- If none of the above work open `Console.app` on macOS. Select your device and set `process:mobile_installation_proxy` in the `Search Bar`. This will give details behind the sideloaded IPA error message. ## Sideload iOS app ```bash ios-deploy -b $SIGNED_IPA // defaults to send over wifi ios-deploy -b -W $SIGNED_IPA // uses USB ios-deploy -B | grep -i funky // list Bundle IDs ``` ## Frida-Server ```shell #### update host machine pip3 install --upgrade frida # list available devices frida-ls-devices # list processes and bundle ID from USB connected device frida-ps -Uai # Force open Calender on USB attached device frida -U -f com.apple.mobilecal # open foobar over usb and force start. starts app running frida -U -f com.apple.mobilecal --no-pause # get the target app's process ID from USB connected device frida-ps -U | grep -i myapp # Run script and quit Frida frida -U -f foobar --no-pause -q --eval 'console.log("Hi Frida");' ``` ## Frida-Gadget Since `Frida version ~12.7`, it was quick and simple to Frida on a Jailed device: ```shell # Get Frida-Gadget # Unzip gunzip frida-gadget-12.xx.xx-ios-universal.dylib.gz # Create directory for Frida-Gadget mkdir -p ~/.cache/frida # Move Frida-Gadget cp frida-gadget-12.xx.xx-ios-universal.dylib ~/.cache/frida/gadget-ios.dylib # Invoke Frida-Gadget on Clean device frida -U -f funky-chicken.debugger-challenge ``` ## Frida basics ```bash frida -U "My App" // Attach Frida to app over USB Process.id 419 Process.getCurrentThreadId() 3843 var b = "hello frida" console.log(b) "hello frida" c = Memory.allocUtf8String(b) "0x1067ec510" Memory.readUtf8String(c) "hello frida" console.log(c) 0x1067ec510 console.log(c.readUtf8String(5)) hello console.log(c.readUtf8String(11)) hello frida ptrToC = new NativePointer(c); "0x1067ec510" console.log(ptrToC) 0x1067ec510 console.log(ptrToC.readCString(8)) hello fr Memory.readUtf8String(ptrToC) "hello frida" ``` #### Frida - Objective-C Objective-C's syntax includes the `:` and `@` characters. These characters were not used in the `Frida Javascript API`. ```bash // Attach to playground process ID frida -p $(ps -ax | grep -i -m1 playground |awk '{print $1}') ObjC.available true ObjC.classes.UIDevice.currentDevice().systemVersion().toString() "11.1" ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String() ObjC.classes.UIWindow.keyWindow().toString() RET: // shows Static Methods and Instance Methods ObjC.classes.NSString.$ownMethods ObjC.classes.NSString.$ivars var myDate = ObjC.classes.NSDate.alloc().init() console.log(myDate) 2019-04-19 19:03:46 +0000 myDate.timeIntervalSince1970() 1555700626.021566 myDate.description().toString() "2019-04-19 19:03:46 +0000" var a = ObjC.classes.NSUUID.alloc().init() console.log(a) 4645BFD2-94EE-413D-9CE5-8982D41ED6AE a.UUIDString() { "handle": "0x7ff3b2403b20" } a.UUIDString().toString() "4645BFD2-94EE-413D-9CE5-8982D41ED6AE" ``` #### NSString ```bash var b = ObjC.classes.NSString.stringWithString_("foo"); b.isKindOfClass_(ObjC.classes.NSString) true b.isKindOfClass_(ObjC.classes.NSUUID) false b.isEqualToString_("foo") true b.description().toString() "foo" var c = ObjC.classes.NSString.stringWithFormat_('foo ' + 'bar ' + 'lives'); console.log(c) foo bar lives ``` #### NSURL ```bash var url = ObjC.classes.NSURL.URLWithString_('www.foobar.com') console.log(url) www.foobar.com url.isKindOfClass_(ObjC.classes.NSURL) true console.log(url.$class) NSURL ``` #### Frida from NSString to NSData back to Hex String ```bash var b = ObjC.classes.NSString.stringWithString_("foo"); var d = ObjC.classes.NSData d = b.dataUsingEncoding_(1) // NSASCIIStringEncoding = 1, NSUTF8StringEncoding = 4, console.log(d) <666f6f> // This prints the Hex value "666f6f = foo" d.$className "NSConcreteMutableData" var x = d.CKHexString() // get you the Byte array as a Hex string console.log(x) 666f6f x.$className "NSTaggedPointerString" var newStr = ObjC.classes.NSString.stringWithUTF8String_[d.bytes] ``` #### Frida with xCode Simulator ```bash // demoapp is the iOS app name myapp=$(ps x | grep -i -m1 demoapp | awk '{print $1}') frida-trace -i "getfsent*" -p $myapp // Connect to process with Frida script frida --codeshare mrmacete/objc-method-observer -p 85974 ``` #### Frida find Modules ```frida Process.enumerateModules() // this will print all loaded Modules Process.findModuleByName("libboringssl.dylib") { "base": "0x1861e2000", "name": "libboringssl.dylib", "path": "/usr/lib/libboringssl.dylib", "size": 712704 } Process.findModuleByAddress("0x1c1c4645c") { "base": "0x1c1c2a000", "name": "libsystem_kernel.dylib", "path": "/usr/lib/system/libsystem_kernel.dylib", "size": 200704 } ``` #### Find Address and Module of function name ( Export ) ``` DebugSymbol.fromAddress(Module.findExportByName(null, 'strstr')) { "address": "0x183cb81e8", "fileName": "", "lineNumber": 0, "moduleName": "libsystem_c.dylib", "name": "strstr" } ``` #### Find Address of Export and use Address to find Module ``` Module.findExportByName(null, 'strstr') "0x183cb81e8" Module.getExportByName(null,'strstr') "0x183cb81e8" Process.findModuleByAddress("0x183cb81e8") { "base": "0x183cb6000", "name": "libsystem_c.dylib", "path": "/usr/lib/system/libsystem_c.dylib", "size": 516096 } ``` #### Exports inside a Module ```frida a = Process.findModuleByName("Reachability") a.enumerateExports() .... { "address": "0x102fab020", "name": "ReachabilityVersionString", "type": "variable" }, { "address": "0x102fab058", "name": "ReachabilityVersionNumber", "type": "variable" } .... ... .. ``` ## Frida's --eval flag #### Enumerate all Exports, grepping for one function, and quit ```javascript frida -U -f funky-chicken.debugger-challenge --no-pause -q --eval 'var x={};Process.enumerateModulesSync().forEach(function(m){x[m.name] = Module.enumerateExportsSync(m.name)});' | grep -B 1 -A 1 task_threads "address": "0x1c1c4645c", "name": "task_threads", "type": "function" ``` #### Search for Module, with the Exports' Address ```javascript frida -U -f funky-chicken.debugger-challenge --no-pause -q --eval 'var x={};Process.findModuleByAddress("0x1c1c4645c");' { "base": "0x1c1c2a000", "name": "libsystem_kernel.dylib", "path": "/usr/lib/system/libsystem_kernel.dylib", "size": 200704 } ``` ## Frida Intercepter ```javascript [objc_playground]-> var a = ObjC.classes.NSString.stringWithString_("foo"); [objc_playground]-> a.superclass().toString() "NSString" [objc_playground]-> a.class().toString() "NSTaggedPointerString" // PASTE THIS CODE INTO THE FRIDA INTERFACE... Interceptor.attach(ObjC.classes.NSTaggedPointerString['- isEqualToString:'].implementation, { onEnter: function (args) { var str = new ObjC.Object(ptr(args[2])).toString() console.log('[+] Hooked NSTaggedPointerString[- isEqualToString:] ->' , str); } }); // TRIGGER YOUR INTERCEPTOR [objc_playground_2]-> a.isEqualToString_("foo") [+] Hooked NSTaggedPointerString[- isEqualToString:] -> foo 1 // TRUE [objc_playground_2]-> a.isEqualToString_("bar") [+] Hooked NSTaggedPointerString[- isEqualToString:] -> bar 0 // FALSE ``` #### Frida Intercepter - monitor file open ```javascript // frida -U -l open.js --no-pause -f com.yd.demoapp // the below javascript code is the contents of open.js var targetFunction = Module.findExportByName("libsystem_kernel.dylib", "open"); Interceptor.attach(targetFunction, { onEnter: function (args) { const path = Memory.readUtf8String(this.context.x0); console.log("[+] " + path) } }); ``` #### Frida Intercepter - monitor Swift Mangled function ```javascript try { var targetFunctPtr = Module.findExportByName("YDAppModule", "$s9YDAppModule17ConfigC33publicKeyVerifyCertsSayypGvpfi"); if (targetFunctPtr == null) { throw "[*] Target function not found"; } Interceptor.attach(targetFunctPtr, { onLeave: function(retval) { var array = new ObjC.Object(retval); console.log('[*]ObjC Class Type:\t' + array.$className); return retval; } }); console.log("[*] publicKeyVerifyCertificates called "); } catch(err){ console.log("[!] Exception: " + err.message); } ``` ## Frida-Trace ```javascript frida-trace --v // check it works frida-trace --help // excellent place to read about Flags frida-trace -f objc_playground // spawn and NO trace frida-trace -m "+[NSUUID UUID]" -U "Debug CrackMe" // trace ObjC UUID static Class Method frida-trace -m "*[ComVendorDebugger* *]" -U -f com.robot.demo.app // ObjC wildcard trace on Classes frida-trace -m "*[YDDummyApp.UserProfileMngr *]" -U -f com.robot.demo.app // Trace mangled Swift functions Instrumenting functions... /* TID 0x403 */ 1128 ms -[YDDummyApp.UserProfileMngr init] 1130 ms -[YDDummyApp.UserProfileMngr .cxx_destruct] frida-trace -i "getaddrinfo" -i "SSLSetSessionOption" -U -f com.robot.demo // trace C function on iOS frida-trace -m "*[*URLProtection* *]" -U -f com.robot.demo // for https challenge information frida-trace -m "*[NSURLSession* *didReceiveChallenge*]" -U -f com.robot.demo // check whether https check delegate used frida-trace -U -f com.robot.demo.app -I libsystem_c.dylib // Trace entire Module. Bad idea! frida-trace -p $myapp -I UIKit // Trace UIKit Module. Bad idea. frida-trace -f objc_playground -I CoreFoundation // Trace CoreFoundation Module. Terrible idea. frida-trace -I YDRustyKit -U -f com.yd.mobile // Trace my own module. frida-trace -m "-[NSURLRequest initWithURL:]" -U -f com.robot.demo // Get app files and APIs frida-trace -m "-[NSURL initWithString:]" -U -f com.robot.demo // find the API endpoints frida-trace -m "*[NSURL absoluteString]" -U -f com.robot.demo // my favorite of these ``` Edit the Frida-Trace auto-generated, template file. ```javascript onEnter: function (log, args, state) { log("-[NSURLRequest initWithURL:" + args[2] + "]"); var str = new ObjC.Object(ptr(args[2])).toString() console.log('[*] ' , str); }, // results [*] https://secretserver.nl/SignIn ``` #### Frida-Trace strcpy() ```bash frida-trace -i "*strcpy" -f hitme aaaa bbbb Instrumenting functions... _platform_strcpy: Loaded handler at "/.../__handlers__/libSystem.B.dylib/_platform_strcpy.js" Started tracing 1 function. Press Ctrl+C to stop. ``` Edit the auto-generated, template Javascript file. ```javascript ----------- onEnter: function (log, args, state) { // strcpy() arg1 is the Source. arg0 is the Destination. console.log('\n[+] _platform_strcpy()'); var src_ptr = args[1].toString() var src_string = Memory.readCString(args[1]); var src_byte_array = Memory.readByteArray(args[1],4); var textDecoder = new TextDecoder("utf-8"); var decoded = textDecoder.decode(src_byte_array); console.log('[+] src_ptr\t-> ' , src_ptr); console.log('[+] src_string\t-> ' + src_string); console.log('[+] src_byte_array\t-> ' + src_byte_array); console.log('[+] src_byte_array size\t-> ' + src_byte_array.byteLength); console.log('[+] src_byte_array decoded\t-> ' + decoded); }, ``` The results: ```javascript [+] _platform_strcpy() [+] src_ptr -> 0x7ffeefbffaa6 [+] src_string -> aaaa [+] src_byte_array -> [object ArrayBuffer] [+] src_byte_array size -> 4 [+] decoded -> aaaa [+] _platform_strcpy() [+] src_ptr -> 0x7ffeefbffaab [+] src_string -> bbbb [+] src_byte_array -> [object ArrayBuffer] [+] src_byte_array size -> 4 [+] decoded -> bbbb ``` #### Frida Objective-C Observer ```javascript frida-ps -Uai // get your bundle ID frida --codeshare mrmacete/objc-method-observer -U -f $BUNDLE_ID [+] At the Frida prompt... // Method isJailbroken observeSomething('*[* isJail*]') // Observe String compares observeSomething('*[* isEqualToString*]'); // A Class ( ObjC ) or Module (Symbol ). The first asterix indicates it can be eith Instance or Class method observeSomething('*[ABC* *]'); // Watch Cookies observeSomething('-[WKWebsiteDataStore httpCookieStore]'); observeSomething('-[WKWebAllowDenyPolicyListener *]'); // dump the URL to hit observeSomething('-[WKWebView loadRequest:]'); // you get all HTML, js, css, etc observeSomething('-[WKWebView load*]'); // Read the entire request observeSomething('-[WKWebView loadHTMLString:baseURL:]') // Check for a custom UserAgent observeSomething('-[WKWebView *Agent]'); ``` ## Bypass anti-Frida checks ```shell # Rename Frida process bash -c "exec -a YDFooBar ./frida-server &" # Set Frida-Server on host to a specific interface and port frida-server -l 0.0.0.0:19999 & # Call Frida-server from Host frida-ps -ai -H 192.168.0.38:19999 # Trace on custom port frida-trace -m "*[NSURLSession* *didReceiveChallenge*]" -H 192.168.0.38:19999 -f $BUNDLE_ID ``` ## Cookies #### Find ```bash /private/var/mobile/Containers/Data/Application//Library/Cookies/Cookies.binarycookies ``` #### Extract ```bash scp -P 2222 root@localhost:/private/var/mobile/Containers/Data/Application//Library/Cookies/Cookies.binarycookies cookies.bin ``` The original `BinaryCookieReader` script out of date ( still Python 2 only ): ```shell # install cookie file parser pip3 install binary-cookies-parser # read file bcparser cookies.bin ``` #### Find Cookies in Memory with Frida ```shell # script from https://github.com/interference-security/frida-scripts/blob/master/iOS/show_binarycookies.js frida -U -p 1990 -l show_binarycookies.js $) ps -ax | grep -i WebKit.Networking 29163 ?? /.../com.apple.WebKit.Networking $) frida --codeshare mrmacete/objc-method-observer -p 29163 [PID::29163]-> %resume [PID::29163]-> observeSomething('*[* cookiesWithResponseHeaderFields:forURL:]'); ###################################### +[NSHTTPCookie cookiesWithResponseHeaderFields:forURL:] cookiesWithResponseHeaderFields: { "Set-Cookie" = "EuConsent=; path=/; expires=Sat, 16 Nov 2019 14:51:01 GMT;"; } (__NSSingleEntryDictionaryI) forURL: https://uk.yahoo.com/?p=us&guccounter=1 (NSURL) RET: ( " expiresDate:'2019-11-16 14:51:01 +0000' created:'2019-11-15 14:51:01 +0000' sessionOnly:FALSE domain:yahoo.com partition:none sameSite:none path:/ isSecure:FALSE path:"/" isSecure:FALSE>" ) ``` ## Change iOS Version _WARNING_: only change the minimum iOS version of a specific app's plist and not for the entire device. Things start to break - like calls into C libraries - when you change the device's read-only iOS version. ```bash ssh onto device root# cd /System/Library/CoreServices/ root# cat SystemVersion.plist root# nano SystemVersion.plist EDIT THE VALUE. KEEP THE OLD VALUE! ``` ## LLVM Instrumentation ```bash https://developer.apple.com/library/archive/qa/qa1964/_index.html otool -l -arch all my_framework | grep __llvm_prf nm -m -arch all my_app | grep gcov ```