In order to reverse engineer iOS applications, we generally need to decrypt the app on jailbroken devices and analyze it using known tools like IDA, Ghidra, etc. However, sometimes we may not have a jailbroken device, or the app we are trying to reverse may require a higher iOS version. In this post, I will show you how we can reverse engineer an iOS application on Apple Silicon simulators.

Required

I want this post to be a hands-on activity so you can follow along. I chose a very large and complex application for this process to demonstrate its feasibility. We will analyze the Trendyol application. It is a large, modular application, and from what I have observed, it is well-architected.

We need an IPA file for our process. You can either decrypt it using a jailbroken device or download it from Decrypted iOS IPA App Store

Convering IPA

  • Download Simforge
  • Unzip ipa file
  • Convert it:
./simforge convert UNZIPPED_PATH/Payload/Trendyol.app
  • Sign it
codesign -f -s - UNZIPPED_PATH/Payload/Trendyol.app/Frameworks/*
codesign -f -s - UNZIPPED_PATH/Payload/Trendyol.app
  • Drag and drop Trendyol.app into the simulator to install it.
  • Launch the app.

Basic Analysis

You can check the app’s bundle directory to find interesting artifacts. For example, developers may accidentally leave some keys or developer settings inside the bundle. You can also use Asset Catalog Tinkerer to view all the assets used by the program. You might find some hidden gems. For instance, did you know that one of the icons is named son_son_son.svg which translates to final_final_final.svg in Turkish. I guess we all struggle with naming things 🤗

Deep analysis

If you need to analyze it further, you can do so either statically using disassemblers (IDA, Ghidra, Hopper, etc.) or dynamically using debuggers (LLDB, Frida, etc.). In this post, we will do both to see if we can uncover some interesting findings.

Disassembly

TThis application has a small number of dynamic libraries, and most external libraries and dependencies are compiled statically to reduce boot time. This is why the main binary, UNZIPPED_PATH/Payload/Trendyol.app/Trendyol, is a large file. You can use any disassembler to inspect its contents, though disassembly may take some time. A quick glance at the binary reveals interesting strings, such as: /Users/jenkins/GitlabRunner/9okrgxec/0/mobile/mobile-all/ios-all/shared/iOS/Tuist/.build/checkouts/Alamofire/Source/Core/DataRequest.swift This is NOT sensitive information, but it gives us insights into the development environment. It suggests that they use a self-hosted GitLab instance via Jenkins and modularize their application using Tuist. I once again emphasize that this is not such a secret thing. Why does this information leak? Because Alamofire uses the following function to report errors:

public func asAFError(orFailWith message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> AFError {
    guard let afError = self as? AFError else {
        fatalError(message(), file: file, line: line)
    }
    return afError
}

Here, the #file macro resolves to the filename.

Let’s see if we can find other interesting things. If you open the Info.plist file inside the payload, you may see important metadata related to the binary, such as its bundle ID and supported iOS versions. However, let’s focus on the following section:

    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>tg</string>
      <string>instagram-stories</string>
      <string>whatsapp</string>
      <string>fb-messenger-share-api</string>
      <string>fbapi</string>
      <string>fb-messenger-api</string>
      <string>fbauth2</string>
      <string>fbshareextension</string>
      <string>dolap</string>
      <string>instagram</string>
      <string>binnaz</string>
      <string>igame1320</string>
      <string>vfss</string>
      <string>turktelekommobil</string>
      <string>comgooglemaps-x-callback</string>
      <string>yandexnavi</string>
      <string>com.amazon.mobile.shopping</string>
      <string>getir</string>
      <string>hbapp</string>
      <string>yemeksepeti</string>
      <string>finansbankmobile</string>
      <string>enparamobile</string>
      <string>AkbankDirekt</string>
      <string>ykbmobilsube</string>
      <string>appsflyeravansas</string>
      <string>bizimtoptan</string>
      <string>metro</string>
      <string>tgo</string>
      <string>tyml</string>
      <string>temu</string>
      <string>sheinlink</string>
      <string>aliexpress</string>
      <string>noon</string>
      <string>allegro</string>
      <string>emag</string>
    </array>

The LSApplicationQueriesSchemes key specifies the URL schemes the app can check using canOpenURL:. This means Trendyol can determine whether certain competitor apps are installed, such as Amazon (com.amazon.mobile.shopping), Hepsiburada (hbapp), Getir (getir), and Yemeksepeti (yemeksepeti). Interestingly, there is also a reference to binnaz, which is a fortune-telling app. You won’t find the above strings inside the app when querying, as they are hidden, and the app is likely querying even more schemes than those listed. I don’t know about you, but this feels like they might be profiling their users. Don’t worry—we will uncover all the schemes this app is querying later. Remember, we are still in the static analysis phase.

What else can we find? This app makes extensive use of deep links, supporting numerous URL schemes. However, one particularly interesting parameter in these deep links is WebUrl. For example:

ty://?Page=OutWeb&WebUrl=https://tgo.gl/tgodownload

You might think you can change this URL to something else, but that won’t work. The application uses a class called WhitelistValidator to validate the host of the WebUrl parameter. But how does WhitelistValidator know which websites to allow or block? The list of allowed domains isn’t stored inside the app—it is downloaded dynamically. Let’s uncover this list.

# find your simulator ID
xcrun simctl list 
# mine was 73D769E5-2D6C-4D34-8FCA-019678EA4FE3
xcrun simctl listapps 73D769E5-2D6C-4D34-8FCA-019678EA4FE3

This command will provide the data directory of the Trendyol app.

"trendyol.com.trendyol" =     {
        ApplicationType = User;
        Bundle = "....";
        BundleContainer =  "....";
        CFBundleDisplayName = Trendyol;
        CFBundleExecutable = Trendyol;
        CFBundleIdentifier = "trendyol.com.trendyol";
        CFBundleName = Trendyol;
        CFBundleVersion = 654;
        DataContainer =  "....";
        GroupContainers =         {
        };
        Path = "....";
        SBAppTags =         (
        );
    };

When we check the contents of DataContainer we find the following files:

|-- Demeter
|   |-- TYCoreCommerce.sqlite
|   |-- TYCoreCommerce.sqlite-shm
|   |-- TYCoreCommerce.sqlite-wal
|   |-- TYInternational.sqlite
|   |-- TYInternational.sqlite-shm
|   `-- TYInternational.sqlite-wal
`-- TrendyolResponseCache
    |-- ABTestDeciderResponse
    |-- ContractTypeResponse
    |-- DemeterEnforcementConfigV2
    |-- RecoABTestDeciderResponse
    `-- ZConfigurationResponse

If you check the contents of ZConfigurationResponse, you’ll find the EXP_iOSInternationalWebDeeplinkWhiteList key, which contains the list of allowed domains for deep linking. Interestingly, the lists differ between iOS and Android—iOS allows 190 domains, while Android only allows 83. This suggests that iOS is more permissive in this regard.

For example, since docs.google.com and tr.surveymonkey.com are included in the whitelist, users could potentially be phished. While this could be considered a security issue, Trendyol’s bug bounty program explicitly lists “Issues About Deeplink” as out of scope.

What else can we find just by examining files? We can easily see that the app uses IOSSecuritySuite to detect jailbroken devices. It also checks for the presence of a debugger using the sysctl call and then sends SuspiciousActivityEvent data to the server. Since I don’t have an account, all of this information comes purely from disassembly—I haven’t tested it myself.

Debugging

Static analysis provides tons of information, but the most exciting part is attaching a debugger and interacting with the app. Earlier, we examined LSApplicationQueriesSchemes, but we didn’t confirm whether these schemes are actually queried. Let’s investigate.

We can hook any function we want from the app and make it dance. Simforge allows to inject a dylib into the simulator process.

  • Download frida gadget, unpack it, and rename it to FridaGadget.dylib
  • Install Frida I recommend using a virtual environment.
  • Run the app with the following command (ensure you provide the full path to the dylib):
./simforge launch --bundleid trendyol.com.trendyol --dylib /FULL_PATH_OF_FRIDA_GADGET/FridaGadget.dylib
  • Create a file named hook.js
Interceptor.attach(ObjC.classes.UIApplication["- canOpenURL:"].implementation, {
    onEnter(args) {
        const path = new ObjC.Object(args[2]).toString();
        console.log(path);
    },
    onLeave(retval) {
    }
});
  • Open another terminal, activate your virtual environment with Frida, and run:
frida --host localhost Gadget -l hook.js

If everything is set up correctly, Frida Gadget will inject into the Trendyol process and hook the - (BOOL) canOpenURL:(NSURL *) url; function. You will see output like this:

turktelekommobil://
vfss://
enparamobile://
com.amazon.mobile.shopping://
getir://
hbapp://
yemeksepeti://
ykbmobilsube://
AkbankDirekt://
appsflyeravansas://
bizimtoptan://
metro://
tgo://
tyml://
emag://
aliexpress://
sheinlink://
noon://
allegro://
temu://
birbank://
umico://
lcw://
lalafo://
ozonplatiqr://
enalibaba://
nesine://
scepsapp://
storytel://
hgapp://
finansbankmobile://
turktelekommobil://
vfss://
enparamobile://
com.amazon.mobile.shopping://
getir://
hbapp://
yemeksepeti://
ykbmobilsube://
AkbankDirekt://
appsflyeravansas://
bizimtoptan://
metro://
tgo://
tyml://
emag://
aliexpress://
sheinlink://
noon://
allegro://
temu://
birbank://
umico://
lcw://
lalafo://
ozonplatiqr://
enalibaba://
nesine://
scepsapp://
storytel://
hgapp://

As you can see, Trendyol indeed checks for the presence of several apps, sometimes multiple times. Interestingly, some of these schemes are not even declared in Info.plist. Without declaration, this call should fail, but it might have worked in previous iOS versions.

This list is fetched remotely, just like the whitelist domains. You can find the entire list inside the iOSAppExist key.

Summary

I hope this post helps you debug and reverse-engineer apps on the simulator. Simforge also opens new possibilities for automation. While iPhone mirroring works remarkably well, it still has limitations when it comes to automation.

I am actively job-hunting and available
Interested? Feel free to reach