Bypassing Cert Pinning in the Steam Mobile App
Routing a device’s web traffic through a proxy like mitmproxy is a great first step in reverse engineering a mobile app’s API. However, some apps protect against these types of man-in-the-middle attacks (whether the attacker is the user or a shady network admin trying to snoop) using certificate pinning, which enforces a policy rejecting all certificates other than the one hardcoded into the app itself. One such app is Steam‘s mobile app, and in this post, I’ll disassemble and rebuild it with modified code using jadx and apktool.
Obtaining the APK
There are some sketchy websites out there that allow you to directly download an app’s APK, but I’ll pull the file from my phone to be safe.
With ADB shell, find out the name of the Steam app’s APK:
device:/ $ pm list packages | grep valve
package:com.valvesoftware.android.steam.community
Then, find its location:
device:/ $ pm path com.valvesoftware.android.steam.community
package:/data/app/com.valvesoftware.android.steam.community-1/base.apk
From my PC, ADB pull the file from my device and rename it to steam.apk
:
$ adb pull /data/app/com.valvesoftware.android.steam.community-1/base.apk steam.apk
Disassembling the APK using apktool
apktool is a great piece of software that can disassemble an APK into editable smali code that’s like assembly/bytecode but not.
I’ve never used it before, but following the sample usage on the site,
$ apktool d steam.apk
$ cd steam
$ ls
assets original res smali unknown AndroidManifest.xml apktool.yml
Finding the certificate pinning code
I don’t know what I’m looking for, but whatever code controls certificate pinning probably contains something about ssl
so I’ll start there.
$ grep -ri ssl
I get matches in a bunch of internal android libraries, the web view client, and files called DevHttpsTrustManager.smali
and DevHttpsTrustManager$1.smali
(the anonymous inner classes of DevHttpsTrustManager
). DevHttpsTrustManager? That sounds promising! I’ll pop it open in my favorite text editor, and take a look!
Here are copies of the files:
DevHttpsManager.smali and DevHttpsManager$1.smali
I don’t actually know what I’m looking at here, so I’ll go one level of abstraction up, and use jadx to look at some sweet java code. The catch is that jadx can only view, but not edit the decompiled java.
Looking at the code in jadx
$ jadx-gui steam.apk
Here’s a copy of the decompiled code.
The relevant snippet:
public boolean verify(String arg0, SSLSession arg1) {
return arg0 != null && (arg0.contains("valvesoftware.com") || arg0.contains("valve.org"));
}
Looks like it’s just checking arg0
, whatever that is, for if it contains valvesoftware.com
or valve.org
(note: this usually isn’t how you do cert pinning, and I’m almost certain this is a vulnerability). I remember seeing those strings in DevHttpsManager$1.smali
!
Editing the smali code
I remember that string.contains("")
always evaluates to true
, so as a quick hack, replacing
const-string v0, "valvesoftware.com"
...
const-string v0, "valve.org"
with
const-string v0, ""
...
const-string v0, ""
should make the conditional evaluate to true
, and I’ve bypassed it!
Rebuilding the APK
Now that I’ve edited the code, I’m ready to rebuild an APK to try it out:
$ apktool b steam
The built APK is in steam/dist
, but it’s not ready to install yet. All Android apps must be signed, so sign them by following Google’s documentation (I won’t include the steps here since they’re boring).
ADB push the built and signed APK onto my phone:
$ adb push steam.apk /sdcard
Uninstall the official Steam app, install my own modified APK, start up and connect to mitmproxy, and…