INFO
🚨 App Protection Update (PairIP)
Since this article was originally written in April 2024, newer builds of the Pop Android app are protected with Google Play's PairIP (libpairipcore.so) integrity and tamper-detection library. Re-signing and repackaging the APK via objection patchapk alone will now trip PairIP's signature verification, so additional bypass steps are required. This page will be updated in the future to cover those steps.
Many transport systems make real-time information on the position of trains or their estimated arrival at a station. In Newcastle, we are blessed with the Tyne and Wear Metro - the not-always reliable, not-always fast light-rail system.

Nexus makes real-time information (RTI) of this sort available through their Pop app, available for Android and iOS. Timetables are available on the website, however these aren’t always to be relied on.
It would be great if this information was available for use in other projects. Think widgets for Smart Mirrors and watch apps, live travel dashboards. All able to provide at a glance information for a local station, instead of having to open a clunky app every time I want to know when the next train is.
WARNING
🚨 PSA — I would strongly encourage Nexus to open up their RTI backend to the public under a permissive licence, like rail and bus data which is freely available to anyone. It is worth noting that this may break the End User License Agreement of the Pop App - Nexus please don’t take me to court! 😝
In this post I will document the steps I took to reverse engineer the API from the app, so that you can do the same for other undocumented APIs! For the full endpoint reference captured during this process, see the companion post: Nexus Metro Real Time Information - REST API Documentation.
The Man In The Middle
Intercepting HTTPS Traffic
INFO
💡 You can follow this guide if you want to try this yourself.
Clearly, we need to intercept the traffic in order to observe the request flow. It would be great from our perspective, though poor from a development and security perspective, if the app was using plaintext HTTP to encode requests. However this is incredibly unlikely, so the starting assumption is that the API is accessed over HTTPS.
We can approach this by placing a HTTPS proxy between the device running the app and the API endpoint. I used mitmproxy (man-in-the-middle), a free and open source swiss-army knife for debugging, testing, privacy measurements, and penetration testing. The proxy terminates all SSL connections, generating on-the-fly self-signed certificates for any SNI it comes across in a request. It then forwards the request to the original endpoint and the response back to the device.
Because the proxy acts as it’s own certificate authority (CA), it is required to install the certificate generated by the proxy into your device so that it can be trusted. (Security and privacy → More security and privacy → Encryption and credentials → Install a certificate) Then, you can set up your device to send all HTTP traffic through the proxy. (Wi-Fi Settings → Proxy)
TIP
🚨 As an aside - since Android 7 apps ignore user provided certificates unless they are configured to use them. As most applications do not explicitly opt-in to use user certificates, it is required to place our CA certificate in the system certificate store or patch apps individually. See the documentation for workarounds.
Did it work?
It would be great if this was all that was required, and we would simply launch the mitmweb dashboard and examine the traffic however the app can be configured to use SSL Certificate Pinning to defeat a MITM attack.
The app holds a copy of either the server certificate or the corresponding public key, and compares this with the provided certificate in the connection request. If they do not match, nothing happens! We therefore can’t see the traffic with just this step, and must patch the app to be effective.
App Patching
APK Runtime Exploration
Fortunately, tools exist that can patch android APKs to remove pinned certificates and trust user-installed CAs. For this I used Objection, a runtime mobile exploration toolkit powered by Frida.
INFO
💡 You can follow this guide in conjunction with the below steps. I found most issues I had were largely down to outdated dependencies.
- Install
objectionand obtain up to date dependenciesaapt,adb,jarsigner, andapktool. - After connecting the device via
adb, you can obtain the package files. First we find the package name. Then we get the file paths from the device and obtain them using$ adb pull $PATH.
$ adb shell pm list packages | grep nexus
package:uk.co.nebulalabs.nexusnextgeneration$ adb shell pm path uk.co.nebulalabs.nexusnextgeneration
package:/data/app/uk.co.nebulalabs.nexusnextgeneration/base.apk
package:/data/app/uk.co.nebulalabs.nexusnextgeneration/split_config.arm64_v8a.apk
package:/data/app/uk.co.nebulalabs.nexusnextgeneration/split_config.en.apk
package:/data/app/uk.co.nebulalabs.nexusnextgeneration/split_config.xxxhdpi.apk- We can then patch the app, embedding the Frida gadget within the app. In this case, we need to add a flag to use
aapt2because the app uses a specific resource in it’s manifest.
$ objection patchapk --source base.apk --use-aapt2- If this is successful the app is patched, however because the app is supplied as a bundle (multi-apk), the certificates are now mismatched. Luckily, objection provides a command to update the signing of the other APKs.
$ objection signapk split_*.apk- If all is successful, you can now uninstall the original app and replace it with yours!
$ adb uninstall uk.co.nebulalabs.nexusnextgeneration- Launch the app and it will hang, waiting for you to launch the Objection runtime CLI.
$ objection explore
_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.11.0
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
...nebulalabs.nexusnextgeneration on (google: 14) [usb] # android
Unknown or ambiguous command: `android`. Try `help android`.
...nebulalabs.nexusnextgeneration on (google: 14) [usb] # android sslpinning disable
(agent) Found okhttp3.CertificatePinner, overriding CertificatePinner.check()
(agent) Found okhttp3.CertificatePinner, overriding CertificatePinner.check$okhttp()
(agent) Found com.android.org.conscrypt.TrustManagerImpl, overriding TrustManagerImpl.verifyChain()
(agent) Found com.android.org.conscrypt.TrustManagerImpl, overriding TrustManagerImpl.checkTrustedRecursive()
(agent) Registering job 378778. Type: android-sslpinning-disable- Because of the patching, the application has been told to trust any user-installed CA, and we have just disabled SSL pinning. We can now return to the
mitmproxyweb interface and start recording and observing the API request flow as we use the app’s features!
Conclusion
If you read through all this you should now have a good idea of how to reverse engineer an undocumented back-end API. I've compiled the full endpoint reference in Nexus Metro Real Time Information - REST API Documentation, and used it to build my own Magic Mirror² RTI module!