Reverse Engineering Rema 1000's "Æ" App using SSL Unpinning on Android with Frida.

SSL unpinning is a process that involves bypassing the SSL certificate verification in an Android app. This is useful for reverse engineering the app's API and understanding how it communicates with the server. In this post, I'll show you how to use Frida on Android to perform SSL unpinning using a real-world App, the Norwegian grocery store loyalty app from Rema 1000 called "Æ" .

Steps

Step 1: Set up Android Studio and Frida

  1. Install Android Studio with the emulator and SDK tools.
  2. Install Frida and Python3. Follow the installation instructions at https://frida.re/docs/installation/ and https://www.python.org/downloads/.

Step 2: Set up the emulator and install Frida server

  1. Download the Frida server to your local machine:
wget https://github.com/frida/frida/releases/download/16.0.1/frida-server-16.0.1-android-x86.xz
xz -d frida-server-16.0.1-android-x86.xz

Note: There might be a newer version of frida server available, check the releases.

  1. Create and start an emulator via Android Studio (https://developer.android.com/studio/run/managing-avds).
  2. Install the Frida server on the emulator:
adb root
adb push frida-server-16.0.1-android-x86 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server --listen 0.0.0.0 &"
  1. If the adb command is not recognized, follow the instructions at https://www.geeksforgeeks.org/fix-unable-to-locate-adb-within-sdk-in-android-studio/ to fix it.

Step 3: Install and configure mitmproxy

  1. Download and install https://mitmproxy.org/.
  2. Start mitmproxy in the terminal with the command: mitmweb
  3. Set your PC's IP address as the "proxy" inside the Android emulator, using port 8080 (https://stackoverflow.com/questions/1570627/how-to-set-up-android-emulator-proxy-settings).
  4. Follow the guide to install the certificate on the emulator by opening http://mitm.it in the emulator's browser and following the instructions.

Step 4: Download the target APK file

  1. Download the Rema Æ app from APKPure (Google "apkpure æ", click the first link
  2. Install the APK file in the emulator:
adb install FILENAME-OF-THE-APK-FILE.apk

Note: Make sure that the version of the APK file is supported on the emulator architecture you are using, also that the version of the app supports the android version in the emulator.

Step 5: Download and run Frida scripts for SSL unpinning

This script is the best one I have found so far, but if it does not work for you, try one of the other scripts on frida codeshare:

It's made by HTTPToolkit and combines, fixes and extends several other scripts into one.

Frida Android Unpinning

## Download it
wget https://raw.githubusercontent.com/httptoolkit/frida-android-unpinning/main/frida-script.js

# Run it
frida -U -l frida-script.js -f no.rema.bella

Note: the no.rema.bellais the application ID of the app, you can find it in the url of the app listing page on Google play:

https://play.google.com/store/apps/details?id=no.rema.bella&hl=no&gl=US
                                              | here ^    |

Other scripts on Frida Codeshare

To use these scripts, run the following command:

frida -U --codeshare @pcipolloni/universal-android-ssl-pinning-bypass-with-frida -f no.rema.bella
frida -U --codeshare @akabe1/frida-multiple-unpinning -f no.rema.bella

Aside: A note on Flutter apps

The SSL Unpinning scripts provided above will not work with Flutter apps.

Check out this instead: https://codeshare.frida.re/@TheDauntless/disable-flutter-tls-v1/ Also see THE NVISO blog for more details regarding flutter specifically.

Example: The Coop Medlem loyalty app is written in Flutter, to perform SSL unpinning on it, you can run this script instead:

frida --codeshare TheDauntless/disable-flutter-tls-v1 -f no.coop.members

Note: The flutter unpinning script uses pattern matching, the default script provided did not work properly for me, however modifying it like this seems to work (at the time I tested it, months ago, might be broken now, in which case, figure it out yourself)

function hook_ssl_verify_result(address) {
  Interceptor.replace(
    address,
    new NativeCallback(
      (pathPtr, flags) => {
        return 0
      },
      'int',
      ['pointer', 'int']
    )
  )
}
function disablePinning() {
  var m = Process.findModuleByName('libflutter.so')
  var pattern =
    '55 41 57 41 56 41 55 41 54 53 50 49 89 f? 4c 8b 37 49 8b 46 30 4c 8b a? ?? 0? 00 00 4d 85 e? 74 1? 4d 8b'

  var res = Memory.scan(m.base, m.size, pattern, {
    onMatch: function (address, size) {
      console.log('[+] ssl_verify_result found at: ' + address.toString())

      // Add 0x01 because it's a THUMB function
      // Otherwise, we would get 'Error: unable to intercept function at 0x9906f8ac; please file a bug'
      // hook_ssl_verify_result(address.add(0x01));
      hook_ssl_verify_result(address)
    },
    onError: function (reason) {
      console.log('[!] There was an error scanning memory')
    },
    onComplete: function () {
      console.log('All done')
    },
  })
}
setTimeout(disablePinning, 1000)

Anyways... continuing!

Start Frida on your local machine and connect to the Frida server inside the emulator:

# Run the local script from httptoolkit mentioned earlier
frida -U -f no.rema.bella -l frida-script.js

# or use the codeshare scripts
frida -U --codeshare @pcipolloni/universal-android-ssl-pinning-bypass-with-frida -f no.rema.bella
frida -U --codeshare @akabe1/frida-multiple-unpinning -f no.rema.bella

Step 6: Intercept API requests

  1. If everything has gone smoothly, the Æ app should open in the emulator. You can now log in as you would on regular phone, you need to verify with a code, it will be sent to your actual phone.
  2. Open the MITMProxy window, and you should see a lot of requests. In the filter at the top, type "bella" to only show requests to the Rema API, which is running on Azure with the following URL:
https://esb-production-apim.azure-api.net/v1/bella/*
  1. Click on one of the requests and find the following information under the "Request" tab:
  • Authorization: Bearer eypkjefpqjefw.......[lots of stuff]
  • Ocp-Apim-Subscription-Key: fb5e24884b504d0..etc...

These two headers are used for authentication. As far as we could find out, the API doesn't care about the other values.

Condensed:


# Note: Assuming the APK file is named target.apk and the application id is no.rema.bella

# Download frida server
wget https://github.com/frida/frida/releases/download/16.0.1/frida-server-16.0.1-android-x86.xz
xz -d frida-server-16.0.1-android-x86.xz

# <Start your android emulator>
# <Download the no.rema.bella app from wherever and save it as no.rema.bella.apk>

# Install the app onto the emulator
adb install no.rema.bella.apk

# Install frida server on emulator
adb root
adb push frida-server-16.0.1-android-x86 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server --listen 0.0.0.0 &"

# Start mitmproxy
mitmweb

# Download frida script
wget https://raw.githubusercontent.com/httptoolkit/frida-android-unpinning/main/frida-script.js

# Run frida script
frida -U -l frida-script.js -f no.rema.bella

# OR use codeshare
frida -U --codeshare @pcipolloni/universal-android-ssl-pinning-bypass-with-frida -f no.rema.bella

# Done.

Rema Æ endpoints of interest:

Here are some API endpoints in the Rema Æ app that might be of interest if you want to look at your own receipts and purchases.

Get a list of purchases

GET https://esb-production-apim.azure-api.net/v1/bella/heads

The response looks something like this (personal information stripped out and response has been condensed for brevity):

{
  "bonusTotal": 0,
  "purchaseTotal": 1337.14,
  "discountTotal": 666.14,
  "activateMazeButton": false,
  "transactions": [
    {
      "purchaseDate": 1600695669000,
      "storeId": "0000",
      "amount": 6969.69,
      "bonusPoints": 0,
      "discount": 13.37,
      "storeName": "Rema 1000",
      "id": 11223344556,
      "verified": false,
      "transactionPayments": [
        {
          "meansOfPaymentDesc": "Rounding",
          "amount": 0.0,
          "meansOfPayment": {}
        },
        {
          "meansOfPaymentDesc": "CreditDebit",
          "amount": 6969.69,
          "meansOfPayment": {}
        }
      ],
      "receiptNbr": "2009210000123123123123123"
    }
  ]
}

Get details for a purchase

GET https://esb-production-apim.azure-api.net/v1/bella/rows/10009888888888

The response looks something like this:

[
  {
    "amount": 21.9,
    "amountWithoutDeposit": 21.9,
    "bonusBased": true,
    "deposit": 0.0,
    "discount": 0.0,
    "pieces": 1,
    "prodtxt1": "Potetsalat Klassisk",
    "prodtxt2": "500 G",
    "prodtxt3": "7036110007366",
    "productCode": "10028477001",
    "productDescription": "POTETSALAT KLASSISK",
    "productGroupCode": "113014",
    "productGroupDescription": "TILBEHØRSALATER",
    "unit": "EA",
    "unitPrice": 21.9,
    "volume": 1.0
  },
  {
    "amount": 34.93,
    "amountWithoutDeposit": 34.93,
    "bonusBased": true,
    "deposit": 0.0,
    "discount": -14.97,
    "pieces": 1,
    "prodtxt1": "Blåbær Shake 20X400 Gr Papp",
    "prodtxt2": "Stykk",
    "prodtxt3": "7040511829618",
    "productCode": "10134626001",
    "productDescription": "BLÅBÆR SHAKE 20X400 GR PAPP",
    "productGroupCode": "161011",
    "productGroupDescription": "BÆR",
    "unit": "EA",
    "unitPrice": 49.9,
    "usedOffers": [
      {
        "discount": 14.97,
        "discountPercent": 30.0,
        "offerCode": "6000100",
        "offerDesc": "30% på alle friske bær"
      }
    ],
    "volume": 1.0
  }
]

Have fun.

Other Tutorials

Here are some resources for further reading on SSL unpinning with Frida:

But this is illegal

Legal smegal, don't you fret, Your device, your data, yours to get.

But seriously tho...

The content in this article is provided for educational purposes only and describes a theoretical scenario that should not be replicated as it potentially breaks the terms of service of the app and service given as an example. The author is not liable for your actions either directly or indirectly.

AKA: Don't be an idiot.

So can you help me hack 'insert app name here'?

No.