Skip to content

This is our second of the now not quite biweekly updates we want to give you on the progress of our work on the tweasel project, where we want to build a web app that detects privacy violations in mobile apps. The last weeks, we did a lot of work to iron out bugs and improve the stability of the tools and had some people test the toolchain as a whole. We also met up with a local data protection authority shortly after the last update and took home some todos, which we have been working on.



Appstraction is an abstraction layer for common instrumentation functions on Android and iOS. It allows you to install, uninstall, start, stop apps and configure their permissions, as well as manage device settings like emulator snapshots, clipboard, proxy, and certificates. Appstraction can also be used for purposes other than mobile privacy.

  • We finished most of the work we were talking about last time:
    • I investigated pymobiledevice3 and liked it a lot. It packs quite a lot more features than libimobiledevice, has a really simple python API as well as a CLI that, usefully, outputs JSON and best of all, is distributed via pip, meaning we can depend on it via autopy, a library Benjamin developed so that we can manage our python dependencies from JavaScript. It is also cross-platform, so we don’t have to worry about that anymore. Naturally, we switched, removing another host dependency the user has to manage.
    • We also wanted to streamline the setup of the on-device dependencies like frida on iOS, just as we already do on Android. This is really easy on a jailbroken device, since the popular package managers for jailbreaks, like Cydia or Sileo, are based on apt. Because of that, all I needed to do was to write a simple script to run via SSH which adds the necessary sources to apt and installs the software we need present on the device.
    • I also looked into a frida bug, which prevented it to enable the frida-server service during install on iOS devices. It turns out that the reason is a faulty configuration I was able to fix in the setup step I just implemented.
  • Then, we also found some problems when we presented our tools to the DPO’s experts, so we started fixing them:
    • We learned that on vanilla Android devices rooted debugging is not available and so adb root (on which we had been relying) didn’t work. Until then we only tested on emulators or devices we rooted ourselves which were running LineageOS. To work around that problem, I rewrote our adb shell commands to execute them with su 0 -c, which is provided by Magisk. Extremely annoyingly, Android decided to use a non-POSIX-compliant su binary in their emulators, so this took quite a lot more work than I initially expected.
    • To run an already installed app on a device, we needed an app or bundle ID. Finding such an ID of an app, though, is actually not a trivial task, especially for non-expert users. On Android, you can sometimes get that information in the “App Info” or in the Play Store URL:<app id>. On iOS however, the easiest way is to interface with the App Store API via ipatool. So, Benjamin wrote a simple platform.listApps() method, which we can use to present users all currently available runnable apps to choose from, without the need to find an app ID.
    • Another problem, that was really bad UX and even tripped us up all the time, was frida failing if more than one device is connected at a time. While, eventually, we want to support multiple devices, we now fail in ensureDevice() if more than one device is connected. For that I needed to implement a platform-agnostic listDevices() utility function.
  • There is this annoying thing on iOS, which requires you to interact with the CA trust settings manually at least once before the certificate is automatically accepted from our script. I wanted to automate that away and started investigating and stumbled upon device supervision, which allows for installing CAs without any user interaction, there is only one catch: You are supposed to reset your device to set up supervision. I wanted to find a better way:
    • It turns out, that supervision is actually only managed in a single configuration file (/var/containers/Shared/SystemGroup/ that conveniently just contains an IsSupervised flag you need to set to true. Bam, device supervised. But how does one authenticate to the device now?
    • After I supervised one of my devices using the “Apple Configurator” software, I saw there is also a SupervisorHostCertificates array in the configuration file, if you do the supervision properly. That just contains an unsigned DER-encoded X.509 SSL certificate that matches up to a private key on the host machine I supervised the device with. So I implemented a way to generate the certificate and place it in the config file.
    • To enable the supervision, the device needs to restart. To preserve the jailbreak, we restart using launchctl userspace reboot, but this also cuts off the SSH connection and locks the device again. Since the app automation only works with an unlocked device, we need to recover from that. To remedy, I implemented another method to unlock the device by hijacking the “Assistive Touch” feature on iOS. On Android, adb can just send a lock button signal via adb shell keyevent 26.
    • The implementation lives in ios._internal.ensureSupervision() right now and has not been merged, yet, since we don’t consider it a priority.
    • Finally, the configuration profiles can be installed through a lockdownd service via one of the many interfaces iOS exposes via USB. To avoid user interaction, though, one needs to authenticate the session using the Escalate request, which requires you to sign Challenge bytes using the private key we have set up for supervision. In go-ios this was already implemented, but we just switched to pymobiledevice3, so I implemented it there as well.
  • Right now, to use SSH-based features on iOS devices, you need to provide the IP address of the device — contrary to how it works on Android. To get rid of this asymmetry, I started work on SSH connections via USB port forwarding, which can be done using pymobiledevice3 usbmux forward <host port> <device port>.
    • Adjacent to that, I wanted to simplify the setup on iOS to a minimum. Our current standard jailbreak palera1n enables a dropbear SSH server right on the first startup, but after bootstrapping in the palera1n loader app the login stops working. So, we require our users to install OpenSSH manually via Sileo. This enables an SSH server on the local port 22, which we can log into as the user mobile. Since root can not log in without a password being set up, I needed to adapt our current code to run commands via sudo. This reduces the manual setup on iOS to the currently possible minimum.

Certificate pinning bypass


In order to record the traffic of apps, we inject ourselves in the connections the app makes to servers. If the connection is encrypted, we have a certificate authority we control that we trusted in the operating system. Some apps, however, use additional security measures to check for such injections. They check if the certificate used for transaction is the specific certificate they expect. To circumvent that, certificate pinning bypasses try to disable or change the app behavior so that the app accepts all certificates, including ours.

  • We were a bit worried our certificate pinning bypass might not be working well, crash too many apps and also were unhappy with the UX of objection, because it left us with a running process we didn’t quite know when to close. Because of that Benjamin started an investigation.
    • To check whether the certificate pinning works, we look for TLS errors in the mitmproxy log. We were already logging those events in cyanoacrylate, but we weren’t exposing those in the results, so Benjamin changed that.
    • With this change in place, Benjamin ran an experiment in which he captured the traffic and cert pinning errors of 1000 popular apps from the Play Store. He found that our favorite alternative to objection, httptoolkit’s certificate pinning bypass script, generated significantly fewer crashes, while both are very effective at disabling certificate pinning (about 80% of cert pinning related error were solved by both scripts).
    • With the data from the analysis, Benjamin then went through the apps with errors one by one to find one for which it made sense to write a new bypass. He did that for the O2 app. The new bypass has already been merged into the httptoolkit script.
    • From the investigation, we also concluded that it makes sense to replace objection, which Benjamin started to do.

Everything else

  • We want to publish the data we gathered in analyses like the certificate pinning experiment publicly, so other people can start research on it without having to run complicated experimentation setups. For that, Benjamin started work on a public database for request data. We want to keep it simple and just host it using Datasette. This database is also supposed to interface with to provide example data dynamically.
  • We just started work on, where we want to document the host and device setup in detail, write usage tutorials for the tools and provide background information.
written by Lorenz Sieben
licensed under: Creative Commons Attribution 4.0 International License