Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vadimkozhin/df99a4f97cb98c15c580db9d0ef9b674 to your computer and use it in GitHub Desktop.
Save vadimkozhin/df99a4f97cb98c15c580db9d0ef9b674 to your computer and use it in GitHub Desktop.
Static and Dynamic iOS inspection

Static / Dynamic iOS app inspection

Get App Store encrypted iPAs

https://ipa.rocks/

Unzip the IPA file to reveal the Payload folder

unzip myApp.ipa

Check binary AppStore Encrypted
otool -l foobar | grep -i LC_ENCRYPTION -B1 -A4
Load command 12
          cmd LC_ENCRYPTION_INFO
      cmdsize 20
     cryptoff 16384
    cryptsize 12910592
      cryptid 1
--
--
Load command 12
          cmd LC_ENCRYPTION_INFO_64
      cmdsize 24
     cryptoff 16384
    cryptsize 14041088
      cryptid 1
Decrypt iPA
// download apps from the AppStore on Jailbroken device
// this script will extract and decrypt the IPA
https://github.com/AloneMonkey/frida-ios-dump

Logs

Simulator filter on Debug String

xcrun simctl spawn booted log stream --level=debug | grep App_Debug_String

Simulator filter on App Name

xcrun simctl spawn booted log stream --predicate 'processImagePath endswith "MyAppName"'

Real device

idevicesyslog -u <Device ID> | myPipedProgram

Files

Files big files inside unzipped IPA

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'

Property lists in Payload. 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 an .app folder
grep "Requires" foobar.app -R
foobar.app/Info.plist:    <key>UIRequiresFullScreen</key>
foobar.app/Info.plist:    <key>LSRequiresIPhoneOS</key>
Inspect any device logs you find

grep -i -B 10 'error'

Build information

Check platform

lipo -info libprogressbar.a

Check for build errors

jtool -arch arm64 -L <binary inside app bundle>

Check minimum iOS version & restrict linker flag

jtool -arch arm64 -l <binary inside app bundle>

Check Load Commands

rabin2 -H playground

Sections of the Binary

objdump -macho -section-headers Payload/myApp.app/myApp

iOS app entitlements

jtool -arch arm64 --ent <binary inside app bundle>

Simple Permissions check

cat Payload/my.app/Info.plist | grep -i NS

Device Support

https://gist.github.com/adamawolf/3048717

Check binary was stripped

rabin2 -I -a arm_64 <binary inside app bundle> | grep -E 'stripped|canary'

Check Position Independent Code set

rabin2 -I -a arm_64 <binary inside app bundle> | grep -E 'pic|bits

Check for Bitcode enabled
otool -l libprogressbar.a | grep __LLVM
otool -arch arm64 -l tinyDynamicFramework | grep __LLVM
// Remember this command won't work on a locally built Simulator / iPhone app. Bitcode happens after setting `Archive`

Symbols

nm

nm libprogressbar.a | less

rabin2

rabin2 -s file

radare2

is~FUNC

Strings

Check URLs
strings <binary inside app bundle>  | grep -E 'session|https'
strings <binary inside app bundle>  | grep -E 'pinning'
rabin2 -qz <binary inside app bundle>                                   // in Data Section
rabin2 -qzz <binary inside app bundle>                                  // ALL strings in binary
​

jtool -dA __TEXT.__cstring c_playground
Dumping C-Strings from address 0x100000f7c (Segment: __TEXT.__cstring)..
Address : 0x100000f7c = Offset 0xf7c
0x100000f7c: and we have a winner @ %ld\r
0x100000f98: and that's a wrap folks!\r

Repackage with Applesign

Get provisioning file

Following Xcode/New/Project/Swift template app within xCode. Build for Generic (ARM) Device not the simulator. You don’t need to run the app unless you need to automagically add your device’s UUID to the Provisioning Profile. Now extract it from the built app bundle.

Read the Provisioning Profile

Read the Provisioning Profile to ensure your device is in the profile & it has not expired.


security cms -D -i embedded.mobileprovision

List all of your Code signing identities

You want to make sure your Code Signing key is not revoked.


security find-identity -v -p codesigning

Find the ID you want to use…

AAA.................FFFF

Resign the iPA: change bundle ID and use a custom provision file

applesign -7 -i AAA.....FFFF --bundleid funky-chicken.resigned -m embedded.mobileprovision original.ipa

Resign the iPA: set output IPA name

applesign -7 -i AAA.....FFFF myapp.ipa -o resigned.ipa

Speed up repackaging
rm -v unsigned.ipa | rm -v ready.ipa | 7z a unsigned.ipa Payload

// Keep original Bundle ID
applesign -7 -i <CODE SIGNING ID> -m embedded.mobileprovision unsigned.ipa -o ready.ipa

// Set Bundle ID
// applesign -7 -i <CODE SIGNING ID>  -b yd.com.rusty.repackaged -m embedded.mobileprovision unsigned.ipa -o ready.ipa

ios-deploy -b ready.ipa
Common issues

Open Console.app for macOS. Select your device and set process:mobile_installation_proxy. This will give details behind the sideloaded IPA error message.

Title Detail
Missing Device ID Check Provisioning Profile (embedded.mobileprovision) included device's UUID
Wrong Code-Signing Key check the Code Signing Key was NOT an iPhone Distribution key
Code Signing Keys Match check the Code Signing Key used when generating the Provisioning Profile matched the Key specified 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.

Frida

Frida-Server
frida-ls-devices                // list available devices
frida-ps -Uai                   // list processes and bundle ID from USB connected device
frida-ps -U | grep -i myapp     // get the target app's process ID from USB connected device
frida -U "My App"               // Attach Frida to app over USB
frida -f objc_play              // Open objc_play on macOS. %resume required to run.
Frida - get familiar
ObjC.available
true

ObjC.classes.UIDevice.currentDevice().systemVersion().toString()
"11.1"

ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()

ObjC.classes.UIWindow.keyWindow().toString()
RET: <WKNavigation: 0x106e165c0>

var b = "hello frida"

console.log(b)
string text

c = Memory.allocUtf8String(b)
"0x105e5da00"

Memory.readUtf8String(c)
"hello frida"
Objective-C Classes, Methods and Instance Variables
// Attach to playground process ID
frida -p $(ps -ax | grep -i -m1 playground |awk '{print $1}')

// 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"
Frida with xCode Simulator
// 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
[iPhone]-> Process.enumerateModules()      
// this will print all loaded Modules

[iPhone]-> Process.findModuleByName("libboringssl.dylib")
{
    "base": "0x1861e2000",
    "name": "libboringssl.dylib",
    "path": "/usr/lib/libboringssl.dylib",
    "size": 712704
}


[iPhone]-> Process.findModuleByName("Reachability")
{
    "base": "0x102fa0000",
    "name": "Reachability",
    "path": "/private/var/containers/Bundle/Application/643949D8-1B45-4B98-9CAB-12AA59B9F078/Yolt.app/Frameworks/Reachability.framework/Reachability",
    "size": 49152
}
Exports inside a Module
a = Process.findModuleByName("Reachability")
a.enumerateExports()
....
{
    "address": "0x102fab020",
    "name": "ReachabilityVersionString",
    "type": "variable"
},
{
    "address": "0x102fab058",
    "name": "ReachabilityVersionNumber",
    "type": "variable"
}
....
....
....
Frida Objective-C syntax

Objective-C's Class Methods used : and @. These characters were not used in the Frida Javascript API.

var b = ObjC.classes.NSString.stringWithString_("foo");

b.isKindOfClass_(ObjC.classes.NSString)
1

b.isKindOfClass_(ObjC.classes.NSUUID)
0

b.isEqualToString_("foo")
1

b.description().toString()
"foo"

b.isEqualToString_("foo")
1
Frida from NSString to NSData back to Hex String
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 y = ObjC.classes.NSStringy.stringWithUTF8String_[d.bytes]


var x = d.CKHexString()				//	get you the Byte array as a Hex string

console.log(x)
666f6f

x.$className
"NSTaggedPointerString"
Frida Intercepter
[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
// 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
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
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.

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()
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.

-----------
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:

[+] _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
frida-ps -Uai  // get your bundle ID

frida --codeshare mrmacete/objc-method-observer -U -f funky-chicken.push-demo

[+] At the Frida prompt...

observeSomething('*[ABC* *]'); // any Class beginning with ABC, regardless of instance or static class
observeSomething('-[WKWebsiteDataStore httpCookieStore]');
observeSomething('-[WKWebAllowDenyPolicyListener *]');
observeSomething('-[WKWebView loadRequest:]');                // dump the URL to hit
observeSomething('-[WKWebView load*]');                       // you get all HTML, js, css, etc
observeSomething('-[WKWebView loadHTMLString:baseURL:]')      // really effective; see the entire request
observeSomething('-[WKWebView *Agent]');                      // try to see if somebody set a custom UserAgent
observeSomething('*[* isEqualToString*]');                    // watch string compares

Persisted data

Inspect App bundle
cd /private/var/containers/Bundle/Application/<guid>/myapp.app
// Contains compiled code, statically linked files, compressed NIB files.
Inspect sandboxed data
cd /private/var/mobile/Containers/Data/Application/
ls -lrt  // Your freshly installed IPA is at the bottom of list
cd [app guid]/Documents/
cd [app guid]/Library/
Databases to pull off a device
#### /private/var/Keychains
TrustStore.sqlite3
keychain-2.db
pinningrules.sqlite3
Files
// Extract IPA (whether App Store encrypted or not)
scp -r -P 2222 root@localhost:/var/containers/Bundle/Application/<app GUID>/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/<App GUID>/Library/Caches/Snapshots/com.my.app

// from local machine to remote Jailbroken device
scp -P 2222 hello.txt root@localhost:/var/root/

Cookies

Find Persisted Cookies
/private/var/mobile/Containers/Data/Application/<app guid, given at install time>/Library/Cookies/Cookies.binarycookies
Extract
scp -P 2222 root@localhost:/private/var/mobile/Containers/Data/Application/<App GUID>/Library/Cookies/Cookies.binarycookies cookies.bin

BinaryCookieReader: Written By Satishb3 (http://www.securitylearn.net
python BinaryCookieReader.py Cookie.Binarycookies-FilePath

Cookie : s_fid=0BBD745EA9BCF67F-366EC6EDEFA2A0E6; domain=.apple.com; path=/; expires=Thu, 14 Dec 2023;
Cookie : s_pathLength=homepage%3D2%2C; domain=.apple.com; path=/; expires=Fri, 14 Dec 2018;
Cookie : s_vi=[CS]v1|2E09D702852E4ACE-60002D37A0008393[CE]; domain=.apple.com; path=/; expires=Sun, 13 Dec 2020;
............
............
Find Cookies in Memory with Frida ( on iOS Simulator )
$) ps -ax | grep -i WebKit.Networking
29163 ??         0:00.21 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/WebKit.framework/XPCServices/com.apple.WebKit.Networking.xpc/com.apple.WebKit.Networking

$) frida --codeshare mrmacete/objc-method-observer -p 29163

[PID::29163]-> %resume                           
[PID::29163]-> observeSomething('*[* cookiesWithResponseHeaderFields:forURL:]');

Results:

+[NSHTTPCookie cookiesWithResponseHeaderFields:forURL:]
cookiesWithResponseHeaderFields: {
    "Set-Cookie" = "EuConsent=<removed for brevity>; path=/; expires=Sat, 16 Nov 2019 14:51:01 GMT;";
} (__NSSingleEntryDictionaryI)
forURL: https://uk.yahoo.com/?p=us&guccounter=1 (NSURL)

RET: (
    "<NSHTTPCookie
	version:0
	name:EuConsent
	value:<removed for brevity>
	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>"
)

Sideload iOS app

ios-deploy -b myapp-resigned.ipa        // defaults to send over wifi
ios-deploy -b -W myapp-resigned.ipa     // uses USB
ios-deploy -B | grep -i funky           // list Bundle IDs

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.

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment