The Flow Chart#
We need a bit more than just a proxy and a neat interception tool like Burp Suite in order to inspect the traffic of some android applications. Let’s examine this flow chart from hextree.io:
First you want to examine if the AndroidManifest.xml
file contains the android:networkSecurityConfig
.
For background, the android manifest file for your application is an XML file that describes essential information about your app to the Android operating system. In this case the network security configuration feature lets you customize, as you might guess, the network security settings for the app.
All the granular details are here in the documentation, but we are most concerned with seeing if the application is using a custom certificate, if the config trusts user certificates, if the config trusts system certificates, or if no certificates or trust anchors are being used.
When the setting is missing in the manifest or no certificate/trust anchor is used, check which API version is in use. Anything before API level 23 can be treated the same as if the config trusts user certificates - anything at or above API level 24 will be treated the same as if the config trusts the system certificates.
For the edge cases - if you don’t have root access and/or the application is using a custom cert (or pinning the cert) you can still use dynamic instrumentation to get around the cert and intercept the traffic.
When the app trusts user certificates (<certificates src="user" />
) - you should be able to install your own certificate in the Android trust store and then proxy the traffic. Sometimes the app will ignore the proxy being used and you can try using a VPN to intercept the traffic.
When the app trusts system certificates (<certificates src="system" />
) - and you have root access, you want to check if the Emulator/Device you are using is above Android 14 or before Android 13. Anything before and including Android 13, you would want to use mount -t tmpfs tmpfs /system/etc/security/cacerts
to overlay the tmpfs
directory over the system certificates. Then you can copy your user certificate from /data/misc/user/0/cacerts-added/<cert-id>
to the system certificate store. This should allow the app to trust your imported cert, then follow the proxy and VPN steps from earlier. If you are using Android 14 or above you can try using HTTP toolkit to install certs.
If you don’t have root access or if the app is using certificate pinning - you will probably need to use dynamic instrumentation to intercept the traffic.
Networking with Android Apps#
We can practice seeing how networking and making requests might be implemented in an android app by doing it ourselves. First we add the internet permission to our AndroidManifest.xml
file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application ---SNIP---
Then, we can implement some basic logic in an empty views activity where a button will trigger a web request:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
TextView homeText = findViewById(R.id.home_text);
homeText.setText("Changing Text");
Button homeButton = findViewById(R.id.home_button);
homeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try {
URL url = new URL("http://www.android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
String result = sb.toString();
runOnUiThread(() -> homeText.setText(result));
} catch (Exception e) {
e.printStackTrace();
}
});
}
});
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.MainActivity), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}
But when we run this application and try to send the request we will get an error:
Android will try and prevent you from making the mistake of allowing unencrypted HTTP traffic - no big deal, we can switch the code to say https
and it should work:
Now sometimes you might have a legitimately good reason to allow HTTP traffic - so you can add the following permission to your manifest under the application section:
android:usesCleartextTraffic="true"
You might also want to go further and add another file res/xml/network_security_config.xml
to separately configure some of these things:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">secure.example.com</domain>
<domain includeSubdomains="true">cdn.example.com</domain>
<trust-anchors>
<certificates src="@raw/trusted_roots"/>
</trust-anchors>
</domain-config>
</network-security-config>
You as a developer could also just use TCP sockets directly to effectively bypass the built-in protections, but it still needs the basic internet permissions.
Demonstration with HTB Pinned#
Let’s walk through the process we might go through when testing a mobile app by practicing on this challenge. First you want to download the APK and look at the included README:
Install this application in an API Level 29 or earlier (i.e. Android 10.0 (Google APIs)).
The emulator I am using through android studio (and don’t want to change at the moment) is using API level 34 and doesn’t have the google APIs in use. So before we can even install this, we need to re-align the APK, make a key, and sign it:
zipalign -p -f -v 4 pinned.apk align-pinned.apk
keytool -genkey -v -keystore research.keystore -alias research_key -keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks research.keystore align-pinned.apk
Now we should be able to install it:
adb install .\align-pinned.apk
Now we can begin trying to intercept network traffic. Following the flow chart, let’s begin by using jadx to decompile the app and look at it’s network security configuration.
We now know that the application has internet access and if we investigate the MainActivity
we will see how a certificate is used. It checks if the provided certificate is the same as the pinned certificate:
Then we can see the certificate being loaded and use this to actually track it down in the code:
The cert at: /res/raw/certificate.der
:
So we still need to just intercept the traffic - looking at the flow chart we are using a newer version of android and it is using pinning so we will likely need to jump to the last resort of utilizing dynamic instrumentation in combination with HTTP toolkit.
We can use frida
to see running processes and list if they are ones we can interact with using Dynamic Instrumentation.
frida-ps -Uia
PID Name Identifier
----- -------------- ------------------------------
9732 Calendar com.android.calendar
11811 Files com.android.documentsui
11603 HT2048 io.hextree.privacyfriendly2048
12503 HTTP Toolkit tech.httptoolkit.android.v1
12258 Hidden Secrets io.hextree.reversingexample
12379 Pinned com.example.pinned
9817 Rethink com.celzero.bravedns
1199 SIM Toolkit com.android.stk
11471 Settings com.android.settings
- Camera com.android.camera2
- Clock com.android.deskclock
- Contacts com.android.contacts
- FridaTarget io.hextree.fridatarget
- Gallery com.android.gallery3d
- Messaging com.android.messaging
- Phone com.android.dialer
- PocketHexMaps io.hextree.pocketmaps
- Search com.android.quicksearchbox
- WebView Shell org.chromium.webview_shell
The above command lists the processes running on our emulator, using -i
to only list instrumentable processes and -a
to show additional application data.
We see that com.example.pinned
is our target here. To bypass the SSL pinning we can use objection
which is a part of frida
that can make this part of the process easier. In order to do this make sure you have the right version of the frida-server running on the android device or emulator.
#I opted to push it to the tmp directory
adb push frida-server /data/local/tmp
#then get a shell and make it excecutable and run it
adb root
adb shell
cd /data/local/tmp
chmod +x frida-server
./frida.server
Then we can inject objection
into the running app:
objection -g com.example.pinned explore
Using USB device `Android Emulator 5554`
Agent injected and responds ok!
_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.11.0
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
com.example.pinned on (Android: 14) [usb] #
We can use android sslpinning disable
to, as you may have guessed, disable SSL pinning.
com.example.pinned on (Android: 14) [usb] # android sslpinning disable
(agent) Custom TrustManager ready, overriding SSLContext.init()
(agent) Found com.android.org.conscrypt.TrustManagerImpl, overriding TrustManagerImpl.verifyChain()
(agent) Found com.android.org.conscrypt.TrustManagerImpl, overriding TrustManagerImpl.checkTrustedRecursive()
(agent) Registering job 443476. Type: android-sslpinning-disable
com.example.pinned on (Android: 14) [usb] # (agent) [443476] Called SSLContext.init(), overriding TrustManager with empty one.
Then on the app we will go ahead and log in and in your traffic inspector (Burp or HTTP Toolkit) we will see the flag:
So, we kinda jumped past all the would-be conditions to show how this could work so this isn’t a great example. I highly recommend the courses from Hextree.io as they cover each case in detail.