Genymotion Personal Edition.
You want to be able to connect to your device with adb
which allows you to e.g. get a shell adb shell
. For a physical device, install the Android SDK Platform Tools. Genymotion emulator has it’s own version of adb.
You need to modify the advanced options of your device’s Wi-Fi connection. Set the proxy to Manual and enter the hostname and port. For the Genymotion emulator, put the special IP 10.0.3.2
for the hostname. Otherwise you probably want the IP of your ZAP proxy. I often put *.google.com,*.googleapis.com,*.gstatic.com
in the bypass section if I need Google services and things are broken.
Now, when you open your device’s browser and visit http://example.com
, you should see the request and response appear in ZAP. When you visit https://caller.xyz
or another HTTPS site, the page should load but with a certificate error.
Clients can access HTTPS servers via the proxy using the CONNECT method. A normal proxy should allow creation of a secure client-server tunnel where the proxy can’t read the plaintext communication. ZAP behaves differently: it connects the client and server via a server-ZAP HTTPS connection and a ZAP-client HTTPS connection. Using two separate connections, ZAP can intercept plaintext communication from the server before forwarding it to the client and vice versa.
Since ZAP doesn’t have a valid certificate to pretend to be the server (in the ZAP-client connection), it dynamically generates a certificate. This would be flagged as insecure as it isn’t signed by a trusted CA. To get around this, add your auto-generated ZAP CA certificate to your device’s certificate store.
adb push path/to/zap.cer
Now the browser should load HTTPS sites without showing a certificate error.
Search for the apk. There are many sites hosting them. Alternatively, download it to your device from the Play store and pull it via adb. I used v1.1.3 with SHA256 b45a5528922eadfed49e38039cff6365aacd8370e98b3d27d5bab2f53690811a.
Install it with adb install railcards.apk
. Run the app. Hmmmm it doesn’t work.
The internet is working in the browser, but no requests from the Railcards app can be seen in ZAP. Wireshark shows the emulator connecting to ZAP, but the ZAP certificate is being rejected by the app.
Let’s decode the apk with apktool and see what’s going on apktool d --no-res railcards.apk
.
Android code is compiled, obfuscated with proguard, and targeted at Dalvik rather than the standard JVM. This means that the tooling for reverse engineering rarely gives you perfect decompilation into Java. A reasonable attempt at browsing the Java decompilation is by running dex2jar railcards.apk
and then opening the railcards_dex2jar.jar
in jd-gui
. I searched around for the code which makes requests, and in com.raildeliverygroup.railcard.app.net.b
found:
public class a
{
CertificatePinner c()
{
return new CertificatePinner.Builder()
.add("prod.digital-railcard.co.uk", new String[] { "sha256/rfNAS9FMyvxACLmHPLTQIHnFNs+MIde1t7Vcym6qMM4=" })
.add("prod.digital-railcard.co.uk", new String[] { "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=" })
.build();
}
}
Apktool created a smali directory. Smali/baksmali is the assembly language for the Dalvik machine code. As such, browsing the smali code is much more reliable than looking at the Java, and it can be reassembled. The smali file at smali/com/raildeliverygroup/railcard/app/net/b/a.smali
(originally from NetworkModule.java
) contains:
.method c()Lokhttp3/CertificatePinner;
.locals 6
.prologue
const/4 v5, 0x1
const/4 v4, 0x0
.line 48
new-instance v0, Lokhttp3/CertificatePinner$Builder;
invoke-direct {v0}, Lokhttp3/CertificatePinner$Builder;-><init>()V
const-string v1, "prod.digital-railcard.co.uk"
new-array v2, v5, [Ljava/lang/String;
const-string v3, "sha256/rfNAS9FMyvxACLmHPLTQIHnFNs+MIde1t7Vcym6qMM4="
aput-object v3, v2, v4
.line 49
invoke-virtual {v0, v1, v2}, Lokhttp3/CertificatePinner$Builder;->add(Ljava/lang/String;[Ljava/lang/String;)Lokhttp3/CertificatePinner$Builder;
move-result-object v0
const-string v1, "prod.digital-railcard.co.uk"
new-array v2, v5, [Ljava/lang/String;
const-string v3, "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w="
aput-object v3, v2, v4
.line 50
invoke-virtual {v0, v1, v2}, Lokhttp3/CertificatePinner$Builder;->add(Ljava/lang/String;[Ljava/lang/String;)Lokhttp3/CertificatePinner$Builder;
move-result-object v0
.line 51
invoke-virtual {v0}, Lokhttp3/CertificatePinner$Builder;->build()Lokhttp3/CertificatePinner;
move-result-object v0
.line 48
return-object v0
.end method
I kept the method signature the same, but deleted the lines specifying the pins, so the method now returns an empty, impotent CertificatePinner
. Removing all 12 command lines after the call to init
and before the call to build
left code which functions like return new CertificatePinner.Builder().build()
.
The altered smali can now be reassembled into machine code to make an apk with altered code. Since we don’t have the proper apk signing key:
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
)apktool b -f -d com.raildeliverygroup.railcard_2018-05-30 && jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore com.raildeliverygroup.railcard_2018-05-30/dist/com.raildeliverygroup.railcard_2018-05-30.apk alias_name && adb uninstall com.raildeliverygroup.railcard && adb install com.raildeliverygroup.railcard_2018-05-30/dist/com.raildeliverygroup.railcard_2018-05-30.apk
On my emulator I can now see traffic , but I can’t get it to work on my phone. This is due to a change since Nougat (7.0 or API level 24). Apps now don’t respect that user CA store we added the ZAP cert to earlier. The apps only use the root CAs, or they can use their own custom request handling.
Since I don’t have the private key of a trusted CA, I instead made the app not care about invalid certificates. It was a bit more involved than the pinning change, so I wrote the Android java code I wanted first. You can play with it at bcaller/unsafe-okhttp3-android. Then I decoded the apk to get the smali code I needed for the railcards project so that the method OkHttpClient.Builder b()
returns a builder with a special custom trust model of accepting all certs. If you’re interested in learning smali, writing Android Java code and disassembling the apk to smali is the second best way (after reading the smali docs).
Oops: It may have been easier to just add network security config to AndroidManifest.xml
Now we have MitM’d the connection, we can see what the app is saying about us and to whom!
If you make a request to https://prod.digital-railcard.co.uk/api/v0.0.2/devices/a/railcards
then you get a 401. Browsing the code, I found smali/com/raildeliverygroup/railcard/app/net/c/a.smali
(originally AuthorizationInterceptor.java
) containing some amusing code.
public class a implements Interceptor
{
public Response intercept(Interceptor.Chain paramChain)
{
return paramChain.proceed(
paramChain.request().newBuilder()
.header("Authorization", "Basic YXBwbGljYXRpb25AZXhhbXBsZS5jb206dGVzdHJhaWxhcGk=").build()
);
}
}
Hard-coded basic auth creds: application@example.com:testrailapi
. It’s not really a security issue since this had to be baked into the client app anyway.
To see and modify the app’s data, try looking for XML and sqlite3 files in /data/data/com.raildeliverygroup.railcard
. Nothing particularly interesting to report for this app.
We can also use ZAP to rewrite intercepted responses (start by setting break points) to give our app a shiny new railcard:
{
"data": [
{
"cardholders": [
{
"cardholder_forename": "Thomas",
"cardholder_id": 1111111,
"cardholder_photo_url": "https://crossvale.com/wp-content/uploads/2018/10/ttte.jpg",
"cardholder_surname": "Engine",
"cardholder_title": "Mr",
"cardholder_type": "Primary"
}
],
"railcard_barcode": "05XTNVW5BYQ 2ABCDEFGHIJKLMNOPQRSTUVWXYZAAAAAAAAAABCDEFGHIJKLMNOPQRSTUVWXYZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCDEFGHIJKLMNOPQRSTUVWXYZAAAAAAAAAAAAAAAAAAABCDEFGHIJKLMNOPQRSTUVWXYZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"railcard_id": "b16b00b5-ffff-ffff-ffff-123456789abc",
"railcard_issued": "Online",
"railcard_name": "TwentysixToThirty",
"railcard_number": "05ABC0123456789",
"railcard_requested_date": "2019-01-19T11:11:11+00:00",
"railcard_transaction_reference": "55555555555555555555555555555",
"railcard_type": "TwentySixToThirty",
"railcard_valid_from": "2019-01-19T00:00:00+00:00",
"railcard_valid_to": "3000-01-18T00:00:00+00:00",
"state": "Active",
"token": "ABCDEF"
}
],
"http_status_code": 200,
"message": "Success",
"status": "success"
}
The National Rail logo in the bottom-right corner is the SecurityFeatureView
which flips when you tap it, and changes colour to show it isn’t just a screenshot (the anti-screenshot protection can be disabled by removing FLAG_SECURE
in android.view.window.setFlags(0x2000)
). While this railcard looks legit, there is nothing in the app describing the contents of the railcard barcode. This is the key security feature of the system. Depending on its purpose and contents, a forged railcard will be rejected by any inspector who scans the AZTEC (not QR) barcode. Since I don’t know what data the barcode contains, I am unable to forge my own railcard without risking detection.