July 31, 2026
JADX for Android Developers: See What Attackers Can See🕵️
This is an important section to explain because many Android developers underestimate how easy it is to reverse engineer an APK.

By Vignesh Elangovan
4 min read
What is JADX?
JADX is one of the most popular Android reverse engineering tools. It converts an APK (compiled Android application) back into human-readable Java code.
Think of it like this:
Kotlin Source Code → D8/R8 Compiler → DEX Bytecode → APK File
An attacker downloads your APK from:
- Google Play
- APKMirror
- APKPure
- A rooted phone
- Their own device
Then simply runs:
jadx app.apkjadx app.apkor opens the APK using the graphical version:
jadx-guijadx-guiWithin a minute, they can browse almost your entire application.
Installing JADX
Linux --> sudo snap install jadx
Mac --> brew install jadx
Windows --> (Download the ZIP release and run) jadx-gui.exeLinux --> sudo snap install jadx
Mac --> brew install jadx
Windows --> (Download the ZIP release and run) jadx-gui.exeOpening an APK
Suppose your app is MyBank.apk
Run
jadx-gui MyBank.apkjadx-gui MyBank.apkYou'll immediately see something similar to
MyBank.apk
Resources
Sources
com.company.app
authentication
payments
network
repository
ui
utilsMyBank.apk
Resources
Sources
com.company.app
authentication
payments
network
repository
ui
utilsIt almost looks like Android Studio.
This surprises many developers.
What does JADX actually do?
An Android APK mainly contains
classes.dexclasses.dexDEX means Dalvik Executable
Android doesn't execute Java or Kotlin source code.
Instead it executes DEX bytecode.
JADX converts: DEX Bytecode → Java Source
Although comments are gone and variable names may change, the application logic is usually very understandable.
For example, your Kotlin code
fun login(email: String, password: String): Boolean {
return repository.login(email, password)
}fun login(email: String, password: String): Boolean {
return repository.login(email, password)
}may appear as
public boolean login(String email, String password) {
return this.repository.login(email, password);
}public boolean login(String email, String password) {
return this.repository.login(email, password);
}Not identical, but extremely readable.
Information attackers can easily find
1. API URLs
Imagine you hardcode your backend URL.
object ApiConfig {
const val BASE_URL =
"https://api.mybank.com/"
}object ApiConfig {
const val BASE_URL =
"https://api.mybank.com/"
}After opening the APK in JADX
ApiConfig.java
public static final String BASE_URL = "https://api.mybank.com/";ApiConfig.java
public static final String BASE_URL = "https://api.mybank.com/";Now the attacker knows
- backend domain
- environment naming
- versioning
Example
https://api.bank.com/v2/
https://staging.bank.com/
https://internal.bank.com/https://api.bank.com/v2/
https://staging.bank.com/
https://internal.bank.com/Sometimes developers accidentally leave
dev-api.company.com
qa.company.com
staging.company.comdev-api.company.com
qa.company.com
staging.company.cominside production APKs.
These environments may have weaker security.
2. Firebase Keys
Developers often think Firebase API keys are secret.
Example
{
"project_info": {
...
},
"client": [
...
],
"api_key": [
{
"current_key":"AIzaSyAxxxxxxxxxxxxxxxx"
}
]
}{
"project_info": {
...
},
"client": [
...
],
"api_key": [
{
"current_key":"AIzaSyAxxxxxxxxxxxxxxxx"
}
]
}This comes from
google-services.jsongoogle-services.jsonAnyone can open
Resources → google-services.json
and immediately see
- Firebase Project ID
- API Key
- App ID
- Storage Bucket
- Sender ID
Important:
Firebase API keys themselves are not secret credentials. They are designed to be included in client apps. The real protection comes from properly configured Firebase Security Rules, authentication, API restrictions, and backend validation. However, exposing project identifiers can still help an attacker enumerate or target your Firebase project if other security controls are weak.
3. Login Flow
Imagine your login function
fun login() {
api.login(email, password)
if (response.success) {
startActivity(HomeActivity())
}
}fun login() {
api.login(email, password)
if (response.success) {
startActivity(HomeActivity())
}
}JADX may show:
LoginActivity → login() → NetworkRepository.login() → Retrofit → POST /login
The attacker learns
- endpoint → POST /login
- parameters:
email
password
deviceId
otpemail
password
deviceId
otp- headers
Authorization
Client-Version
Device-TypeAuthorization
Client-Version
Device-Type- response model
token
refreshToken
expirytoken
refreshToken
expiryNow they understand exactly how your authentication flow works.
4. Business Logic
Suppose your shopping app gives discounts.
if (amount > 5000) {
discount = 20
}if (amount > 5000) {
discount = 20
}JADX shows
if(amount > 5000){
discount = 20;
}if(amount > 5000){
discount = 20;
}An attacker now understands
- pricing rules
- reward calculations
- coupon validation
- premium membership logic
- feature unlock conditions
Even if they can't directly change your server's behavior, understanding these rules helps them look for weaknesses or manipulate client-side checks.
5. Encryption Methods
Suppose you wrote
Cipher.getInstance("AES/CBC/PKCS5Padding")Cipher.getInstance("AES/CBC/PKCS5Padding")or
Cipher.getInstance("AES/ECB/PKCS5Padding")Cipher.getInstance("AES/ECB/PKCS5Padding")JADX shows
Cipher.getInstance("AES/ECB/PKCS5Padding");Cipher.getInstance("AES/ECB/PKCS5Padding");The attacker immediately knows
- encryption algorithm
- mode
- padding
- key generation method
- IV handling
If they also find
val KEY = "1234567890123456"val KEY = "1234567890123456"then they have discovered a hardcoded encryption key, which is a serious security flaw.
6. API Tokens or Secrets
A common mistake is embedding secrets directly in the app:
const val API_KEY = "sk_live_abc123..."const val API_KEY = "sk_live_abc123..."or
private const val SECRET = "my-secret"private const val SECRET = "my-secret"JADX makes these strings easy to locate.
Anyone can search for
API_KEY
SECRET
TOKEN
PASSWORD
PRIVATE_KEYAPI_KEY
SECRET
TOKEN
PASSWORD
PRIVATE_KEYand recover them.
7. Retrofit Interfaces
A Retrofit interface such as
interface ApiService {
@POST("login")
suspend fun login(
@Body request: LoginRequest
)
}interface ApiService {
@POST("login")
suspend fun login(
@Body request: LoginRequest
)
}is usually reconstructed into readable Java, revealing your application's API surface:
@POST("login")
Call<LoginResponse> login(@Body LoginRequest request);@POST("login")
Call<LoginResponse> login(@Body LoginRequest request);This tells an attacker:
- available endpoints
- HTTP methods
- request models
- response models
Searching inside JADX
JADX includes a global search feature.
An attacker might search for:
Authorization (or) Bearer (or) password (or) AES (or) SECRET (or) firebaseAuthorization (or) Bearer (or) password (or) AES (or) SECRET (or) firebaseWithin seconds, they can jump directly to the relevant classes instead of manually browsing thousands of files.
Does ProGuard or R8 stop JADX?
Many developers think enabling R8 or ProGuard prevents reverse engineering.
Without obfuscation:
class PaymentManager {
fun calculateDiscount() {}
fun verifyUser() {}
}class PaymentManager {
fun calculateDiscount() {}
fun verifyUser() {}
}JADX shows nearly the same class and method names.
With R8/ProGuard enabled:
class a {
void a(){}
void b(){}
}class a {
void a(){}
void b(){}
}The names become meaningless, making the code harder to understand.
However, the underlying logic remains:
ifstatements- loops
- API calls
- string constants
- Retrofit interfaces
- cryptographic operations
An experienced reverse engineer can often reconstruct the application's behavior despite obfuscation.
Real-world takeaway
Reverse engineering an Android APK is not a sophisticated attack requiring advanced hacking skills. With freely available tools like JADX, anyone can inspect much of an app's implementation in minutes. That's why Android developers should always assume that anything shipped inside the APK — code, strings, and resources — can eventually be examined.
The security principle is simple:
Never rely on client-side secrecy._ Keep secrets (API secrets, signing keys, business-critical logic, encryption keys, etc.) on secure backend services whenever possible, validate important decisions on the server, and use obfuscation as a delay mechanism — not as your primary security control._
I'm always humbled by the positive feedback I receive on my articles. Your comments and engagement fuel my passion for writing and exploring new ideas. If you'd like to show your support, the best way is to subscribe to my Medium profile. Join me on this journey!
Vignesh Elangovan - Medium Read writing from Vignesh Elangovan on Medium. Helping shape digital finance as an Android Dev in fintech. Sharing…