Proving Grounds
A set of intentionally-vulnerable practice apps you download and run on your own device or emulator, each with a full end-to-end CLI walkthrough. Work through them to build real skill with Rutken: static analysis, dynamic capture, and defeating TLS certificate pinning across every major Android network stack. Each app hides a fake secret behind its defenses — your job is to capture it.
Overview
The Proving Grounds are small Android apps we built on purpose to be defeated. Each one implements a real defense — a specific TLS pinning style, a custom trust check, a hardened network stack — and hides a fake API key behind it. You download the app, install it on a device or emulator you control, and use Rutken to find the defense statically and then defeat it at runtime to read the plaintext traffic and capture the key.
Nothing here talks to a real service you don't own: the apps call a public echo endpoint, and the “secret” is a throwaway string that only exists to prove your bypass worked.
Authorization — read this first.
Run these only on a device or emulator you own or are explicitly authorized to test. The dynamic commands repack, reinstall, and instrument the target, and Rutken makes you type a verbatim authorization phrase before it will touch a device — that phrase is you asserting you have permission. The same techniques against an app or device you do not control may be illegal. These practice apps exist so you never need to point Rutken at anything you aren't allowed to.
Get set up
Everything below assumes Rutken is installed (see Download). Static analysis needs nothing else. Dynamic capture additionally needs a device and a signing key.
What you need
- Static analysis — just Rutken and the
downloaded
.apk. Free, no account. - Dynamic capture — an Android device or
emulator with USB debugging on (visible to
adb), a PKCS#12 signing keystore to re-sign the practice app, and a paid tier (the bespoke hooking engine).
One-time keystore
Any keystore works. Generate a throwaway one with the JDK's keytool:
keytool -genkeypair -storetype PKCS12 -keystore practice.p12 \
-alias practice -keyalg RSA -validity 10000 \
-dname "CN=Practice" -storepass changeit
The loop you'll repeat
Every range follows the same four moves:
- Scan —
rutken <app>.apk rulesfinds the defense statically. - Identify — read which pinning style it flagged, and on which network stack.
- Capture —
rutken <app>.apk capture …instruments the app and reads the plaintext. - Confirm — the captured flow contains the fake key. That's the win.
Find the package id and declare it owned. Both capture routes require an allowlist naming the app you're authorized to touch. Get the id from the scan, then:
rutken multipin-debug.apk info # prints the application id
echo <application.id> > owned.txt # e.g. the id the line above printed
The ranges
Start with MultiPin — it's the grand tour. Each card links the app; the walkthrough below it is the full procedure. Debug builds are the friendliest to start on; the release builds add R8 obfuscation (the class and method names are stripped), which is where Rutken's name-independent matching earns its keep.
MultiPin Beginner → Advanced
Seven network stacks pinned in one app — OkHttp, Retrofit, Cronet, Apache, HttpURLConnection and more. The best place to start.
Flutter Intermediate
Pinning inside Flutter's dart:io / BoringSSL — below the Java layer, with native libraries mapped straight from the APK.
Certificate Transparency Intermediate
An SCT-verifying interceptor that rejects certificates with no valid transparency proof — a stricter check than plain pinning.
Custom HostnameVerifier Intermediate
A hand-rolled HostnameVerifier plus a transparency check — defeated by forcing the verifier's result instead of swapping a trust store.
Split / AAB Advanced
An App-Bundle-shaped install: base APK plus per-ABI split configs, native libs not extracted to disk. The hardest delivery shape.
WebView Expert
A WebView that pins by overriding onReceivedSslError. The toughest surface — push the tool and your static reading here.
Download links resolve once the app files are published to the community storage. If a link 404s, the range isn't published yet.
MultiPin — the grand tour
MultiPin pins the same request across seven network stacks. Beat one, you've learned that stack; beat them all, you understand the whole Android TLS surface. Each stack is a “flavour” you can trigger independently.
Part 1 — Static free, no device
Scan it
rutken multipin-debug.apk rules
Rutken reports the pinning findings and names the stack behind
each. On the release build the class names are
stripped by R8 — Rutken matches the pinning code by its
shape, not its name, so the findings still land.
Read the app
rutken multipin-debug.apk info
rutken multipin-debug.apk concepts --list # the catalog of security concepts
rutken multipin-debug.apk concepts # where each concept appears in THIS app
Note the application id from info, then echo <id> > owned.txt.
Part 2 — Dynamic paid tier, device required
The debug build is the friendliest first run. The command below embeds Rutken's own agent into the app, re-signs it with your practice key, installs it, and drives the pinning-bypass hooks so the app trusts the capture proxy. Rutken auto-selects the right agent for your device's CPU, so you don't pass one.
Capture
rutken multipin-debug.apk capture --embed-trust \
--device <adb-serial> \
--owned-allowlist owned.txt \
--keystore practice.p12 --key-alias practice \
--duration 90 \
--i-am-authorized-repack "I am authorized to repack and reinstall this target on this device" \
--i-am-authorized-bespoke "I am authorized to observe this target via the bespoke agent"
Find <adb-serial> with rutken devices
(or adb devices). While it runs, exercise the app's
flavours; Rutken records each decrypted flow.
Beat the obfuscated release
The release build strips names and specializes
method signatures, so the OkHttp pinner can't be found by name.
Add the derived-bypass flags: Rutken finds the pinner
structurally and neutralizes it, and because this modifies the
app's behaviour it requires the active-modification phrase too.
rutken multipin-release.apk capture --embed-trust \
--device <adb-serial> --owned-allowlist owned.txt \
--keystore practice.p12 --key-alias practice --duration 90 \
--embed-derived --embed-force-void-cert-pinner \
--i-am-authorized-repack "I am authorized to repack and reinstall this target on this device" \
--i-am-authorized-bespoke "I am authorized to observe this target via the bespoke agent" \
--i-am-authorized-bespoke-active "I am authorized to MODIFY this target's behaviour via the bespoke agent"Confirm the win
The captured flows are plaintext HTTP over what the app believed was a pinned TLS connection. The response body carries the fake API key. Seeing it means every pin you triggered is defeated.
Prefer not to re-sign? The pinning bypass reference covers the alternate route (patch the app to trust user CAs and run a proxy), and Dynamic capture covers routes, agent delivery, and teardown in full.
Flutter (dart:io)
Flutter apps pin below the Java layer, inside the engine's own TLS (BoringSSL), and ship their native libraries mapped directly from the APK rather than unpacked to disk. That combination defeats naive Java-only tools. Rutken locates the in-APK library and hooks the certificate callback.
# Static: confirm it's a Flutter app and see the pinning surface
rutken flutterpin-debug.apk rules
rutken flutterpin-debug.apk info
# Dynamic: same embed-trust capture as MultiPin
echo <application.id> > owned.txt
rutken flutterpin-debug.apk capture --embed-trust \
--device <adb-serial> --owned-allowlist owned.txt \
--keystore practice.p12 --key-alias practice --duration 90 \
--i-am-authorized-repack "I am authorized to repack and reinstall this target on this device" \
--i-am-authorized-bespoke "I am authorized to observe this target via the bespoke agent"
The app sends two probes — one it expects to succeed and one
it expects the pin to block. When the capture is working, both
come back 200: the pin is bypassed.
Certificate Transparency
This range doesn't just pin a key — it demands a valid Signed Certificate Timestamp, rejecting any certificate without a transparency proof. Rutken defeats it by forcing the interceptor's verification result rather than trying to forge an SCT.
rutken ctfail-debug.apk rules # flags the CT interceptor
rutken ctfail-debug.apk info
echo <application.id> > owned.txt
rutken ctfail-debug.apk capture --embed-trust \
--device <adb-serial> --owned-allowlist owned.txt \
--keystore practice.p12 --key-alias practice --duration 90 \
--i-am-authorized-repack "I am authorized to repack and reinstall this target on this device" \
--i-am-authorized-bespoke "I am authorized to observe this target via the bespoke agent"
Try the ctfail-release.apk next — same procedure,
names stripped.
Custom HostnameVerifier
Here the app rolls its own HostnameVerifier and layers
a transparency check on top. The lesson: you don't always swap a
trust store — sometimes the cleanest bypass is to force a
single verification method to return the answer you want. Rutken
does exactly that, even when the verifier class is loaded lazily
or renamed by R8.
rutken hvct-debug.apk rules
rutken hvct-debug.apk info
echo <application.id> > owned.txt
rutken hvct-debug.apk capture --embed-trust \
--device <adb-serial> --owned-allowlist owned.txt \
--keystore practice.p12 --key-alias practice --duration 90 \
--i-am-authorized-repack "I am authorized to repack and reinstall this target on this device" \
--i-am-authorized-bespoke "I am authorized to observe this target via the bespoke agent"
Split / AAB delivery
Modern apps ship as an App Bundle: a base APK carrying the code plus per-ABI and per-density split APKs, installed together, with native libraries left compressed inside the base rather than extracted to disk. This is the hardest delivery shape — a tool has to re-sign the whole set consistently and find libraries that were never written to the filesystem. Scan the base APK (it holds the code):
# The download is a zip of base.apk + split_config.*.apk. Scan the base:
rutken base.apk rules
rutken base.apk info
echo <application.id> > owned.txt
The capture flow is the same --embed-trust procedure,
pointed at the base APK; Rutken handles the split re-sign and the
in-base native libraries. See
Dynamic capture for the split
specifics.
WebView Expert
The WebView range pins by overriding
onReceivedSslError on a WebViewClient and
refusing to proceed on a bad certificate. It's the toughest range
in the set and the best test of your static reading: scan it, find
the overriding client, and understand exactly where the decision
is made.
rutken webviewpin-debug.apk rules
rutken webviewpin-debug.apk concepts
rutken webviewpin-debug.apk info
Use this range to practice reading an app precisely before you reach for dynamic tooling — a skill that pays off on every hard target.
Where to next
- TLS pinning bypass — the mechanism behind each pin and the exact CLI that defeats it, per flavour.
- Dynamic capture, end to end — routes, agent delivery, teardown, and troubleshooting.
- Static analysis — everything
rules,concepts, and the finding model can tell you before you ever touch a device. - Docs home — the full reference.