commit iniziale
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "android-reverse-engineering",
|
||||
"version": "1.0.0",
|
||||
"description": "Decompile Android APK/JAR/AAR with jadx, trace call flows through libraries, and document extracted APIs.",
|
||||
"author": {
|
||||
"name": "simonea"
|
||||
},
|
||||
"repository": "https://github.com/simonea/android-reverse-engineering-skill",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
86
plugins/android-reverse-engineering/commands/decompile.md
Normal file
86
plugins/android-reverse-engineering/commands/decompile.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
allowed-tools: Bash, Read, Glob, Grep, Write, Edit
|
||||
description: Decompile an Android APK/XAPK/JAR/AAR and analyze its structure
|
||||
user-invocable: true
|
||||
argument: path to APK, XAPK, JAR, or AAR file (optional)
|
||||
---
|
||||
|
||||
# /decompile
|
||||
|
||||
Decompile an Android application and perform initial structure analysis.
|
||||
|
||||
## Instructions
|
||||
|
||||
You are starting the Android reverse engineering workflow. Follow these steps:
|
||||
|
||||
### Step 1: Get the target file
|
||||
|
||||
If the user provided a file path as an argument, use that. Otherwise, ask the user for the path to the APK, XAPK, JAR, or AAR file they want to decompile.
|
||||
|
||||
### Step 2: Check and install dependencies
|
||||
|
||||
Run the dependency check:
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/check-deps.sh
|
||||
```
|
||||
|
||||
Parse the output looking for `INSTALL_REQUIRED:` and `INSTALL_OPTIONAL:` lines.
|
||||
|
||||
**If required dependencies are missing**, install them one by one:
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/install-dep.sh java
|
||||
bash skills/android-reverse-engineering/scripts/install-dep.sh jadx
|
||||
```
|
||||
|
||||
The install script auto-detects the OS and installs without sudo when possible (user-local install to `~/.local/`). If sudo is needed, it will prompt — if the user declines or sudo is unavailable, the script prints exact manual instructions (exit code 2). Show those instructions to the user and stop.
|
||||
|
||||
**For optional dependencies** (`INSTALL_OPTIONAL:vineflower`, `INSTALL_OPTIONAL:dex2jar`, etc.), ask the user if they want to install them. Recommend vineflower and dex2jar for better results.
|
||||
|
||||
After any installations, re-run `check-deps.sh` to verify. Do not proceed until all required dependencies pass.
|
||||
|
||||
### Step 3: Decompile
|
||||
|
||||
Run the decompile script on the target file. Choose the engine based on the input:
|
||||
|
||||
- **APK or XAPK** → use jadx first (handles resources natively; XAPK is auto-extracted):
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/decompile.sh <file>
|
||||
```
|
||||
|
||||
- **JAR/AAR** and Fernflower is available → prefer fernflower for better Java output:
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/decompile.sh --engine fernflower <file>
|
||||
```
|
||||
|
||||
- **If jadx output has warnings** or the user wants the best quality → run both and compare:
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/decompile.sh --engine both <file>
|
||||
```
|
||||
|
||||
For obfuscated apps (if the user mentions it or you detect single-letter package names), add `--deobf`:
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/decompile.sh --deobf <file>
|
||||
```
|
||||
|
||||
### Step 4: Analyze structure
|
||||
|
||||
After decompilation completes:
|
||||
|
||||
1. Read `AndroidManifest.xml` from the resources directory. For XAPK, check the base APK's output first.
|
||||
2. If XAPK, review `xapk-manifest.json` in the output directory to understand the split structure.
|
||||
3. List the top-level package structure
|
||||
4. Identify the app's main Activity, Application class, and architecture pattern
|
||||
5. Report a summary to the user
|
||||
|
||||
### Step 5: Offer next steps
|
||||
|
||||
Tell the user what they can do next:
|
||||
- **Trace call flows**: "I can follow the execution flow from any Activity to its API calls"
|
||||
- **Extract APIs**: "I can search for all HTTP endpoints and document them"
|
||||
- **Analyze specific classes**: "Point me to a specific class or feature to analyze"
|
||||
- **Re-decompile with Fernflower**: If jadx output has warnings, offer to re-run with `--engine both` for comparison
|
||||
|
||||
Refer to the full skill documentation in `skills/android-reverse-engineering/SKILL.md` for the complete workflow.
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
trigger: decompile APK|decompile XAPK|reverse engineer Android|extract API|analyze Android|jadx|fernflower|vineflower|follow call flow|decompile JAR|decompile AAR|Android reverse engineering|find API endpoints
|
||||
---
|
||||
|
||||
# Android Reverse Engineering
|
||||
|
||||
Decompile Android APK, XAPK, JAR, and AAR files using jadx and Fernflower/Vineflower, trace call flows through application code and libraries, and produce structured documentation of extracted APIs. Two decompiler engines are supported — jadx for broad Android coverage and Fernflower for higher-quality output on complex Java code — and can be used together for comparison.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This skill requires **Java JDK 17+** and **jadx** to be installed. **Fernflower/Vineflower** and **dex2jar** are optional but recommended for better decompilation quality. Run the dependency checker to verify:
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/check-deps.sh
|
||||
```
|
||||
|
||||
If anything is missing, follow the installation instructions in `skills/android-reverse-engineering/references/setup-guide.md`.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase 1: Verify and Install Dependencies
|
||||
|
||||
Before decompiling, confirm that the required tools are available — and install any that are missing.
|
||||
|
||||
**Action**: Run the dependency check script.
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/check-deps.sh
|
||||
```
|
||||
|
||||
The output contains machine-readable lines:
|
||||
- `INSTALL_REQUIRED:<dep>` — must be installed before proceeding
|
||||
- `INSTALL_OPTIONAL:<dep>` — recommended but not blocking
|
||||
|
||||
**If required dependencies are missing** (exit code 1), install them automatically:
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/install-dep.sh <dep>
|
||||
```
|
||||
|
||||
The install script detects the OS and package manager, then:
|
||||
- Installs without sudo when possible (downloads to `~/.local/share/`, symlinks in `~/.local/bin/`)
|
||||
- Uses sudo and the system package manager when necessary (apt, dnf, pacman)
|
||||
- If sudo is needed but unavailable or the user declines, it prints the exact manual command and exits with code 2 — show these instructions to the user
|
||||
|
||||
**For optional dependencies**, ask the user if they want to install them. Vineflower and dex2jar are recommended for best results.
|
||||
|
||||
After installation, re-run `check-deps.sh` to confirm everything is in place. Do not proceed to Phase 2 until all required dependencies are OK.
|
||||
|
||||
### Phase 2: Decompile
|
||||
|
||||
Use the decompile wrapper script to process the target file. The script supports three engines: `jadx`, `fernflower`, and `both`.
|
||||
|
||||
**Action**: Choose the engine and run the decompile script. The script handles APK, XAPK, JAR, and AAR files.
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/decompile.sh [OPTIONS] <file>
|
||||
```
|
||||
|
||||
For **XAPK** files (ZIP bundles containing multiple APKs, used by APKPure and similar stores): the script automatically extracts the archive, identifies all APK files inside (base + split APKs), and decompiles each one into a separate subdirectory. The XAPK manifest is copied to the output for reference.
|
||||
|
||||
Options:
|
||||
- `-o <dir>` — Custom output directory (default: `<filename>-decompiled`)
|
||||
- `--deobf` — Enable deobfuscation (recommended for obfuscated apps)
|
||||
- `--no-res` — Skip resources, decompile code only (faster)
|
||||
- `--engine ENGINE` — `jadx` (default), `fernflower`, or `both`
|
||||
|
||||
**Engine selection strategy**:
|
||||
|
||||
| Situation | Engine |
|
||||
|---|---|
|
||||
| First pass on any APK | `jadx` (fastest, handles resources) |
|
||||
| JAR/AAR library analysis | `fernflower` (better Java output) |
|
||||
| jadx output has warnings/broken code | `both` (compare and pick best per class) |
|
||||
| Complex lambdas, generics, streams | `fernflower` |
|
||||
| Quick overview of a large APK | `jadx --no-res` |
|
||||
|
||||
When using `--engine both`, the outputs go into `<output>/jadx/` and `<output>/fernflower/` respectively, with a comparison summary at the end showing file counts and jadx warning counts. Review classes with jadx warnings in the Fernflower output for better code.
|
||||
|
||||
For APK files with Fernflower, the script automatically uses dex2jar as an intermediate step. dex2jar must be installed for this to work.
|
||||
|
||||
See `references/jadx-usage.md` and `references/fernflower-usage.md` for the full CLI references.
|
||||
|
||||
### Phase 3: Analyze Structure
|
||||
|
||||
Navigate the decompiled output to understand the app's architecture.
|
||||
|
||||
**Actions**:
|
||||
|
||||
1. **Read AndroidManifest.xml** from `<output>/resources/AndroidManifest.xml`:
|
||||
- Identify the main launcher Activity
|
||||
- List all Activities, Services, BroadcastReceivers, ContentProviders
|
||||
- Note permissions (especially `INTERNET`, `ACCESS_NETWORK_STATE`)
|
||||
- Find the application class (`android:name` on `<application>`)
|
||||
|
||||
2. **Survey the package structure** under `<output>/sources/`:
|
||||
- Identify the main app package and sub-packages
|
||||
- Distinguish app code from third-party libraries
|
||||
- Look for packages named `api`, `network`, `data`, `repository`, `service`, `retrofit`, `http` — these are where API calls live
|
||||
|
||||
3. **Identify the architecture pattern**:
|
||||
- MVP: look for `Presenter` classes
|
||||
- MVVM: look for `ViewModel` classes and `LiveData`/`StateFlow`
|
||||
- Clean Architecture: look for `domain`, `data`, `presentation` packages
|
||||
- This informs where to look for network calls in the next phases
|
||||
|
||||
### Phase 4: Trace Call Flows
|
||||
|
||||
Follow execution paths from user-facing entry points down to network calls.
|
||||
|
||||
**Actions**:
|
||||
|
||||
1. **Start from entry points**: Read the main Activity or Application class identified in Phase 3.
|
||||
|
||||
2. **Follow the initialization chain**: Application.onCreate() often sets up the HTTP client, base URL, and DI framework. Read this first.
|
||||
|
||||
3. **Trace user actions**: From an Activity, follow:
|
||||
- `onCreate()` → view setup → click listeners
|
||||
- Click handler → ViewModel/Presenter method
|
||||
- ViewModel → Repository → API service interface
|
||||
- API service → actual HTTP call
|
||||
|
||||
4. **Map DI bindings** (if Dagger/Hilt is used): Find `@Module` classes to understand which implementations are provided for which interfaces.
|
||||
|
||||
5. **Handle obfuscated code**: When class names are mangled, use string literals and library API calls as anchors. Retrofit annotations and URL strings are never obfuscated.
|
||||
|
||||
See `references/call-flow-analysis.md` for detailed techniques and grep commands.
|
||||
|
||||
### Phase 5: Extract and Document APIs
|
||||
|
||||
Find all API endpoints and produce structured documentation.
|
||||
|
||||
**Action**: Run the API search script for a broad sweep.
|
||||
|
||||
```bash
|
||||
bash skills/android-reverse-engineering/scripts/find-api-calls.sh <output>/sources/
|
||||
```
|
||||
|
||||
Targeted searches:
|
||||
```bash
|
||||
# Only Retrofit
|
||||
bash skills/android-reverse-engineering/scripts/find-api-calls.sh <output>/sources/ --retrofit
|
||||
|
||||
# Only hardcoded URLs
|
||||
bash skills/android-reverse-engineering/scripts/find-api-calls.sh <output>/sources/ --urls
|
||||
|
||||
# Only auth patterns
|
||||
bash skills/android-reverse-engineering/scripts/find-api-calls.sh <output>/sources/ --auth
|
||||
```
|
||||
|
||||
Then, for each discovered endpoint, read the surrounding source code to extract:
|
||||
- HTTP method and path
|
||||
- Base URL
|
||||
- Path parameters, query parameters, request body
|
||||
- Headers (especially authentication)
|
||||
- Response type
|
||||
- Where it's called from (the call chain from Phase 4)
|
||||
|
||||
**Document each endpoint** using this format:
|
||||
|
||||
```markdown
|
||||
### `METHOD /path`
|
||||
|
||||
- **Source**: `com.example.api.ApiService` (ApiService.java:42)
|
||||
- **Base URL**: `https://api.example.com/v1`
|
||||
- **Path params**: `id` (String)
|
||||
- **Query params**: `page` (int), `limit` (int)
|
||||
- **Headers**: `Authorization: Bearer <token>`
|
||||
- **Request body**: `{ "email": "string", "password": "string" }`
|
||||
- **Response**: `ApiResponse<User>`
|
||||
- **Called from**: `LoginActivity → LoginViewModel → UserRepository → ApiService`
|
||||
```
|
||||
|
||||
See `references/api-extraction-patterns.md` for library-specific search patterns and the full documentation template.
|
||||
|
||||
## Output
|
||||
|
||||
At the end of the workflow, deliver:
|
||||
|
||||
1. **Decompiled source** in the output directory
|
||||
2. **Architecture summary** — app structure, main packages, pattern used
|
||||
3. **API documentation** — all discovered endpoints in the format above
|
||||
4. **Call flow map** — key paths from UI to network (especially authentication and main features)
|
||||
|
||||
## References
|
||||
|
||||
- `references/setup-guide.md` — Installing Java, jadx, Fernflower/Vineflower, dex2jar, and optional tools
|
||||
- `references/jadx-usage.md` — jadx CLI options and workflows
|
||||
- `references/fernflower-usage.md` — Fernflower/Vineflower CLI options, when to use, APK workflow
|
||||
- `references/api-extraction-patterns.md` — Library-specific search patterns and documentation template
|
||||
- `references/call-flow-analysis.md` — Techniques for tracing call flows in decompiled code
|
||||
@@ -0,0 +1,119 @@
|
||||
# API Extraction Patterns
|
||||
|
||||
Patterns and grep commands for finding HTTP API calls in decompiled Android source code.
|
||||
|
||||
## Retrofit
|
||||
|
||||
Retrofit is the most common HTTP client in Android apps. API endpoints are declared as annotated interface methods.
|
||||
|
||||
### Annotations to search for
|
||||
|
||||
```bash
|
||||
# HTTP method annotations
|
||||
grep -rn '@GET\|@POST\|@PUT\|@DELETE\|@PATCH\|@HEAD' sources/
|
||||
|
||||
# Parameter annotations
|
||||
grep -rn '@Query\|@QueryMap\|@Path\|@Body\|@Field\|@FieldMap\|@Part\|@Header\|@HeaderMap' sources/
|
||||
|
||||
# Headers annotation (static headers)
|
||||
grep -rn '@Headers' sources/
|
||||
|
||||
# Base URL configuration
|
||||
grep -rn 'baseUrl\|\.baseUrl(' sources/
|
||||
```
|
||||
|
||||
### Typical Retrofit interface
|
||||
|
||||
```java
|
||||
public interface ApiService {
|
||||
@GET("users/{id}")
|
||||
Call<User> getUser(@Path("id") String userId);
|
||||
|
||||
@POST("auth/login")
|
||||
@Headers({"Content-Type: application/json"})
|
||||
Call<LoginResponse> login(@Body LoginRequest request);
|
||||
}
|
||||
```
|
||||
|
||||
When documenting, capture: HTTP method, path, path parameters, query parameters, request body type, response type, and any static headers.
|
||||
|
||||
## OkHttp
|
||||
|
||||
OkHttp is often used directly or as the transport layer for Retrofit.
|
||||
|
||||
```bash
|
||||
# Request building
|
||||
grep -rn 'Request\.Builder\|Request.Builder\|\.url(\|\.post(\|\.put(\|\.delete(\|\.patch(' sources/
|
||||
|
||||
# URL construction
|
||||
grep -rn 'HttpUrl\|\.addQueryParameter\|\.addPathSegment' sources/
|
||||
|
||||
# Interceptors (often add auth headers)
|
||||
grep -rn 'Interceptor\|addInterceptor\|addNetworkInterceptor\|intercept(' sources/
|
||||
|
||||
# Response handling
|
||||
grep -rn '\.execute()\|\.enqueue(' sources/
|
||||
```
|
||||
|
||||
## Volley
|
||||
|
||||
```bash
|
||||
grep -rn 'StringRequest\|JsonObjectRequest\|JsonArrayRequest\|Volley\.newRequestQueue\|RequestQueue' sources/
|
||||
```
|
||||
|
||||
Volley requests typically pass the URL as a constructor argument and override `getHeaders()` or `getParams()` for custom headers/parameters.
|
||||
|
||||
## HttpURLConnection (legacy)
|
||||
|
||||
```bash
|
||||
grep -rn 'HttpURLConnection\|HttpsURLConnection\|openConnection\|setRequestMethod\|setRequestProperty' sources/
|
||||
```
|
||||
|
||||
## WebView
|
||||
|
||||
```bash
|
||||
grep -rn 'loadUrl\|evaluateJavascript\|addJavascriptInterface\|WebViewClient\|shouldOverrideUrlLoading' sources/
|
||||
```
|
||||
|
||||
WebView-based apps may load API endpoints via JavaScript bridges. Look for `@JavascriptInterface` annotated methods.
|
||||
|
||||
## Hardcoded URLs and Secrets
|
||||
|
||||
```bash
|
||||
# HTTP/HTTPS URLs
|
||||
grep -rn '"https\?://[^"]*"' sources/
|
||||
|
||||
# API keys and tokens
|
||||
grep -rni 'api[_-]\?key\|api[_-]\?secret\|auth[_-]\?token\|bearer\|access[_-]\?token\|client[_-]\?secret' sources/
|
||||
|
||||
# Base URL constants
|
||||
grep -rni 'BASE_URL\|API_URL\|SERVER_URL\|ENDPOINT\|API_BASE' sources/
|
||||
```
|
||||
|
||||
## Documentation Template
|
||||
|
||||
For each discovered API endpoint, document it using this template:
|
||||
|
||||
```markdown
|
||||
### `METHOD /path/to/endpoint`
|
||||
|
||||
- **Source**: `com.example.app.api.ApiService` (file:line)
|
||||
- **Base URL**: `https://api.example.com/v1`
|
||||
- **Full URL**: `https://api.example.com/v1/path/to/endpoint`
|
||||
- **Path parameters**: `id` (String)
|
||||
- **Query parameters**: `page` (int), `limit` (int)
|
||||
- **Headers**:
|
||||
- `Authorization: Bearer <token>`
|
||||
- `Content-Type: application/json`
|
||||
- **Request body**: `LoginRequest { email: String, password: String }`
|
||||
- **Response type**: `ApiResponse<User>`
|
||||
- **Notes**: Called from `LoginActivity.onLoginClicked()`
|
||||
```
|
||||
|
||||
## Search Strategy
|
||||
|
||||
1. Start with **base URL constants** — find where the API root is configured
|
||||
2. Search for **Retrofit interfaces** — they give the clearest picture of all endpoints
|
||||
3. Check **interceptors** — they reveal auth schemes and common headers
|
||||
4. Search for **hardcoded URLs** — catch any one-off API calls outside the main client
|
||||
5. Look for **WebView URLs** — some apps use hybrid web/native approaches
|
||||
@@ -0,0 +1,176 @@
|
||||
# Call Flow Analysis
|
||||
|
||||
Techniques for tracing execution flows in decompiled Android applications, from entry points down to network calls.
|
||||
|
||||
## 1. Start from AndroidManifest.xml
|
||||
|
||||
The manifest declares all entry points. After decompilation, find it at:
|
||||
|
||||
```
|
||||
<output-dir>/resources/AndroidManifest.xml
|
||||
```
|
||||
|
||||
Key elements to look for:
|
||||
|
||||
```bash
|
||||
# Activities (UI screens)
|
||||
grep -n 'android:name=.*Activity' resources/AndroidManifest.xml
|
||||
|
||||
# Services (background work)
|
||||
grep -n 'android:name=.*Service' resources/AndroidManifest.xml
|
||||
|
||||
# BroadcastReceivers
|
||||
grep -n '<receiver' resources/AndroidManifest.xml
|
||||
|
||||
# ContentProviders
|
||||
grep -n '<provider' resources/AndroidManifest.xml
|
||||
|
||||
# Launcher activity (main entry point)
|
||||
grep -A5 'MAIN' resources/AndroidManifest.xml | grep 'android:name'
|
||||
```
|
||||
|
||||
## 2. Follow the Android Lifecycle
|
||||
|
||||
Typical call chain from UI to network:
|
||||
|
||||
```
|
||||
Activity.onCreate()
|
||||
→ setContentView(R.layout.activity_main)
|
||||
→ findViewById() / View Binding
|
||||
→ button.setOnClickListener()
|
||||
→ onClick()
|
||||
→ viewModel.doSomething()
|
||||
→ repository.fetchData()
|
||||
→ apiService.getEndpoint()
|
||||
→ HTTP request
|
||||
```
|
||||
|
||||
Key lifecycle methods to search:
|
||||
|
||||
```bash
|
||||
grep -rn 'onCreate\|onResume\|onStart\|onViewCreated' sources/
|
||||
```
|
||||
|
||||
## 3. Identify Click Handlers
|
||||
|
||||
User interactions trigger API calls. Common patterns:
|
||||
|
||||
```bash
|
||||
# XML onClick
|
||||
grep -rn 'setOnClickListener\|onClick\|OnClickListener' sources/
|
||||
|
||||
# Data Binding
|
||||
grep -rn '@BindingAdapter\|android:onClick' sources/ resources/
|
||||
|
||||
# Navigation actions
|
||||
grep -rn 'findNavController\|NavController\|navigate(' sources/
|
||||
```
|
||||
|
||||
## 4. Application Class Initialization
|
||||
|
||||
The `Application` subclass initializes global singletons (HTTP clients, DI frameworks, analytics):
|
||||
|
||||
```bash
|
||||
# Find Application subclass
|
||||
grep -rn 'extends Application\|: Application()' sources/
|
||||
|
||||
# Check onCreate for initialization
|
||||
# Then read the class to see what gets configured at startup
|
||||
```
|
||||
|
||||
Look for:
|
||||
- Retrofit/OkHttp client setup
|
||||
- Dagger/Hilt component initialization
|
||||
- Firebase/analytics initialization
|
||||
- Base URL configuration
|
||||
|
||||
## 5. Dependency Injection (Dagger / Hilt)
|
||||
|
||||
Modern Android apps use DI. Trace bindings to find implementations:
|
||||
|
||||
```bash
|
||||
# Hilt modules
|
||||
grep -rn '@Module\|@InstallIn\|@Provides\|@Binds' sources/
|
||||
|
||||
# Hilt entry points
|
||||
grep -rn '@HiltAndroidApp\|@AndroidEntryPoint\|@HiltViewModel' sources/
|
||||
|
||||
# Dagger components
|
||||
grep -rn '@Component\|@Subcomponent' sources/
|
||||
|
||||
# Injected fields
|
||||
grep -rn '@Inject' sources/
|
||||
```
|
||||
|
||||
To trace a call flow through DI:
|
||||
1. Find where an interface is used (e.g., `ApiService` injected into a repository)
|
||||
2. Find the `@Provides` or `@Binds` method that creates the implementation
|
||||
3. Follow the implementation to the actual HTTP call
|
||||
|
||||
## 6. Find Constants and Configuration
|
||||
|
||||
Hardcoded values are rarely obfuscated:
|
||||
|
||||
```bash
|
||||
# Base URLs
|
||||
grep -rni 'BASE_URL\|API_URL\|SERVER_URL\|HOST' sources/
|
||||
|
||||
# API keys
|
||||
grep -rni 'API_KEY\|CLIENT_ID\|APP_KEY\|SECRET' sources/
|
||||
|
||||
# BuildConfig values
|
||||
grep -rn 'BuildConfig\.' sources/
|
||||
|
||||
# SharedPreferences keys (runtime config)
|
||||
grep -rn 'getSharedPreferences\|getString(\|putString(' sources/
|
||||
```
|
||||
|
||||
## 7. Navigating Obfuscated Code
|
||||
|
||||
When code is obfuscated (ProGuard/R8):
|
||||
|
||||
### What gets obfuscated
|
||||
- Class names → `a`, `b`, `c`
|
||||
- Method names → `a()`, `b()`, `c()`
|
||||
- Field names → `f1234a`, `f1235b`
|
||||
|
||||
### What does NOT get obfuscated
|
||||
- **String literals** — URLs, keys, error messages remain readable
|
||||
- **Android framework classes** — `Activity`, `Fragment`, `Intent` keep their names
|
||||
- **Library public APIs** — Retrofit annotations, OkHttp builders retain names
|
||||
- **AndroidManifest entries** — Activity/Service names must be real
|
||||
|
||||
### Strategy for obfuscated code
|
||||
|
||||
1. **Start from strings**: Search for URLs, error messages, and known constants
|
||||
2. **Start from framework classes**: Activities and Fragments are named in the manifest
|
||||
3. **Follow library calls**: Retrofit `@GET`/`@POST` annotations are readable even when the interface class name is obfuscated
|
||||
4. **Use `--deobf`**: jadx can generate readable replacement names
|
||||
5. **Cross-reference**: If `class a` calls `Retrofit.create(b.class)`, then `b` is a Retrofit service interface
|
||||
|
||||
## 8. Tracing a Complete Call Flow: Example
|
||||
|
||||
Goal: Find how login works in an obfuscated app.
|
||||
|
||||
```
|
||||
1. grep for "login" in strings → find "auth/login" URL in class `c.a.b.d`
|
||||
2. Class `c.a.b.d` has @POST("auth/login") → it's a Retrofit interface
|
||||
3. grep for `c.a.b.d` usage → class `c.a.b.f` calls it (the repository)
|
||||
4. grep for `c.a.b.f` usage → class `c.a.a.g` calls it (the ViewModel)
|
||||
5. grep for `c.a.a.g` usage → `LoginActivity` has a field of this type
|
||||
6. Read LoginActivity.onCreate() → sets click listener → calls ViewModel method
|
||||
```
|
||||
|
||||
Result: `LoginActivity → ViewModel → Repository → Retrofit @POST("auth/login")`
|
||||
|
||||
## 9. Tools and Commands Summary
|
||||
|
||||
| Goal | Command |
|
||||
|---|---|
|
||||
| Find entry points | `grep 'android:name' resources/AndroidManifest.xml` |
|
||||
| Find lifecycle methods | `grep -rn 'onCreate\|onResume' sources/` |
|
||||
| Find click handlers | `grep -rn 'setOnClickListener\|onClick' sources/` |
|
||||
| Find DI bindings | `grep -rn '@Provides\|@Binds\|@Inject' sources/` |
|
||||
| Find constants | `grep -rni 'BASE_URL\|API_KEY' sources/` |
|
||||
| Find usages of a class | `grep -rn 'ClassName' sources/` |
|
||||
| Follow a string | `grep -rn '"some text"' sources/` |
|
||||
@@ -0,0 +1,115 @@
|
||||
# Fernflower / Vineflower CLI Reference
|
||||
|
||||
Fernflower is the JetBrains analytical Java decompiler. [Vineflower](https://github.com/Vineflower/vineflower) is the actively maintained community fork with better output quality and published releases. They share the same CLI interface.
|
||||
|
||||
## When to Use Fernflower vs jadx
|
||||
|
||||
| Scenario | Recommended |
|
||||
|---|---|
|
||||
| APK with resources needed | jadx |
|
||||
| Standard Java JAR/library | Fernflower |
|
||||
| jadx output has warnings/errors on specific classes | Fernflower on those classes |
|
||||
| Complex lambdas, generics, streams | Fernflower |
|
||||
| Large APK (>50MB), quick overview | jadx |
|
||||
| Obfuscated Android app | jadx first, Fernflower on problem areas |
|
||||
| Both decompilers available | Use `--engine both` and compare |
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash
|
||||
java -jar fernflower.jar [options] <source>... <destination>
|
||||
```
|
||||
|
||||
- `<source>` — JAR file, class file, or directory containing class files
|
||||
- `<destination>` — output directory
|
||||
|
||||
For a JAR input, Fernflower produces a JAR in the destination containing `.java` source files. Extract it with `unzip` to browse the sources.
|
||||
|
||||
## Key Options
|
||||
|
||||
Options use the format `-<key>=<value>`. Boolean options: `1` = enabled, `0` = disabled.
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `-dgs=1` | 0 | Decompile generic signatures (recommended) |
|
||||
| `-ren=1` | 0 | Rename obfuscated identifiers |
|
||||
| `-mpm=60` | 0 | Max seconds per method — prevents hangs (recommended) |
|
||||
| `-hes=0` | 1 | Show empty super() calls |
|
||||
| `-hdc=0` | 1 | Show empty default constructors |
|
||||
| `-udv=1` | 1 | Use debug variable names if available |
|
||||
| `-ump=1` | 1 | Use debug parameter names if available |
|
||||
| `-lit=1` | 0 | Output numeric literals as-is |
|
||||
| `-asc=1` | 0 | Encode non-ASCII as unicode escapes |
|
||||
| `-lac=1` | 0 | Decompile lambdas as anonymous classes |
|
||||
| `-log=WARN` | INFO | Reduce output verbosity |
|
||||
| `-e=<lib>` | — | Add library for context (not decompiled, improves type resolution) |
|
||||
|
||||
## Recommended Presets
|
||||
|
||||
### General use
|
||||
|
||||
```bash
|
||||
java -jar fernflower.jar -dgs=1 -mpm=60 input.jar output/
|
||||
```
|
||||
|
||||
### Obfuscated code
|
||||
|
||||
```bash
|
||||
java -jar fernflower.jar -dgs=1 -ren=1 -mpm=60 input.jar output/
|
||||
```
|
||||
|
||||
### Maximum detail
|
||||
|
||||
```bash
|
||||
java -jar fernflower.jar -dgs=1 -hes=0 -hdc=0 -mpm=60 input.jar output/
|
||||
```
|
||||
|
||||
### With Android SDK context (better type resolution)
|
||||
|
||||
```bash
|
||||
java -jar fernflower.jar -dgs=1 -mpm=60 -e=$ANDROID_HOME/platforms/android-34/android.jar input.jar output/
|
||||
```
|
||||
|
||||
## Working with APK Files
|
||||
|
||||
Fernflower cannot read APK/DEX files directly. Use dex2jar first:
|
||||
|
||||
```bash
|
||||
# Step 1: Convert DEX to JAR
|
||||
d2j-dex2jar -f -o app-converted.jar app.apk
|
||||
|
||||
# Step 2: Decompile with Fernflower
|
||||
java -jar fernflower.jar -dgs=1 -mpm=60 app-converted.jar output/
|
||||
|
||||
# Step 3: Extract the resulting source JAR
|
||||
unzip -o output/app-converted.jar -d output/sources/
|
||||
```
|
||||
|
||||
The `decompile.sh --engine fernflower` script automates these steps.
|
||||
|
||||
## Supported Input Formats
|
||||
|
||||
| Format | Direct support | Via dex2jar |
|
||||
|---|---|---|
|
||||
| `.jar` | Yes | — |
|
||||
| `.class` | Yes | — |
|
||||
| `.zip` (with classes) | Yes | — |
|
||||
| `.apk` | No | Yes |
|
||||
| `.dex` | No | Yes |
|
||||
| `.aar` | No | Yes |
|
||||
|
||||
## Output Format
|
||||
|
||||
- **JAR input** → Produces `<destination>/<input-name>.jar` containing `.java` files
|
||||
- **Class file input** → Produces `.java` files directly in the destination
|
||||
- **No resource decoding** — Fernflower only produces Java source, never XML/resources
|
||||
|
||||
## Fernflower vs Vineflower
|
||||
|
||||
Vineflower is the recommended fork. Improvements over upstream Fernflower:
|
||||
|
||||
- Published releases on GitHub and Maven Central
|
||||
- Better handling of modern Java (records, sealed classes, pattern matching)
|
||||
- More accurate lambda and switch expression decompilation
|
||||
- Active bug fixes and community maintenance
|
||||
- Same CLI interface — drop-in replacement
|
||||
@@ -0,0 +1,116 @@
|
||||
# jadx CLI Reference
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash
|
||||
jadx [options] <input-file>
|
||||
```
|
||||
|
||||
Input can be an `.apk`, `.jar`, `.aar`, `.dex`, or `.zip` file.
|
||||
|
||||
## Key Options
|
||||
|
||||
| Option | Description |
|
||||
|---|---|
|
||||
| `-d <dir>` | Output directory for decompiled sources |
|
||||
| `--deobf` | Enable deobfuscation — renames obfuscated classes/methods to readable names |
|
||||
| `--show-bad-code` | Show partially decompiled code instead of error comments |
|
||||
| `--no-res` | Skip resource decoding — faster when you only need code |
|
||||
| `--no-src` | Skip source decompilation — only decode resources |
|
||||
| `--export-gradle` | Generate a Gradle project structure (useful for importing into IDE) |
|
||||
| `-e` | Same as `--export-gradle` |
|
||||
| `--threads-count <N>` | Number of processing threads (default: CPU count) |
|
||||
| `-Xmx<size>` | Set maximum Java heap (e.g., `-Xmx4g` for large APKs) |
|
||||
|
||||
## Decompiling Different File Types
|
||||
|
||||
### APK (Android Application Package)
|
||||
|
||||
```bash
|
||||
jadx -d output-dir app.apk
|
||||
```
|
||||
|
||||
Produces:
|
||||
- `output-dir/sources/` — Decompiled Java source files
|
||||
- `output-dir/resources/` — Decoded resources (AndroidManifest.xml, layouts, drawables, etc.)
|
||||
|
||||
### JAR (Java Archive)
|
||||
|
||||
```bash
|
||||
jadx -d output-dir library.jar
|
||||
```
|
||||
|
||||
Useful for analyzing third-party libraries bundled within an APK.
|
||||
|
||||
### AAR (Android Archive)
|
||||
|
||||
```bash
|
||||
jadx -d output-dir library.aar
|
||||
```
|
||||
|
||||
AAR files contain both compiled code and Android resources. jadx handles them directly.
|
||||
|
||||
## Handling Obfuscated Code
|
||||
|
||||
Apps built with ProGuard or R8 produce obfuscated bytecode with single-letter class and method names.
|
||||
|
||||
### Strategies
|
||||
|
||||
1. **Use `--deobf`** to generate readable replacement names:
|
||||
```bash
|
||||
jadx --deobf -d output-dir app.apk
|
||||
```
|
||||
jadx creates a mapping file at `output-dir/deobf-mapping.txt` that maps original obfuscated names to generated names.
|
||||
|
||||
2. **Use the ProGuard mapping file** if available (sometimes shipped in the APK under `assets/` or obtainable from build artifacts):
|
||||
```bash
|
||||
jadx --deobf-map mapping.txt -d output-dir app.apk
|
||||
```
|
||||
|
||||
3. **Focus on string constants and API calls** rather than class names when navigating obfuscated code. URL strings, annotation values, and library classes are not obfuscated.
|
||||
|
||||
## jadx-gui
|
||||
|
||||
For interactive exploration, use the GUI version:
|
||||
|
||||
```bash
|
||||
jadx-gui app.apk
|
||||
```
|
||||
|
||||
Features:
|
||||
- Full-text search across all decompiled sources
|
||||
- Click-through navigation (jump to definition)
|
||||
- Deobfuscation with live renaming
|
||||
- Smali view alongside Java
|
||||
|
||||
jadx-gui is included in the same distribution as the CLI tool.
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Code-only decompilation (fastest)
|
||||
|
||||
```bash
|
||||
jadx --no-res --show-bad-code -d output app.apk
|
||||
```
|
||||
|
||||
### Full decompilation with deobfuscation
|
||||
|
||||
```bash
|
||||
jadx --deobf --show-bad-code -d output app.apk
|
||||
```
|
||||
|
||||
### Export as Gradle project for IDE import
|
||||
|
||||
```bash
|
||||
jadx -e -d output app.apk
|
||||
# Then open output/ in Android Studio or IntelliJ
|
||||
```
|
||||
|
||||
### Decompile a specific DEX from a multi-dex APK
|
||||
|
||||
Extract the APK (it's a ZIP), then target individual DEX files:
|
||||
|
||||
```bash
|
||||
unzip app.apk -d extracted/
|
||||
jadx -d output extracted/classes2.dex
|
||||
```
|
||||
@@ -0,0 +1,221 @@
|
||||
# Setup Guide: Dependencies for Android Reverse Engineering
|
||||
|
||||
## Java JDK 17+
|
||||
|
||||
jadx requires Java 17 or later.
|
||||
|
||||
### Ubuntu / Debian
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install openjdk-17-jdk
|
||||
```
|
||||
|
||||
### Fedora
|
||||
|
||||
```bash
|
||||
sudo dnf install java-17-openjdk-devel
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
|
||||
```bash
|
||||
sudo pacman -S jdk17-openjdk
|
||||
```
|
||||
|
||||
### macOS (Homebrew)
|
||||
|
||||
```bash
|
||||
brew install openjdk@17
|
||||
```
|
||||
|
||||
After installation on macOS, follow the symlink instructions printed by Homebrew, or add to your shell profile:
|
||||
|
||||
```bash
|
||||
export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
java -version
|
||||
# Should show version 17.x or higher
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## jadx
|
||||
|
||||
jadx is the Java decompiler used to convert APK/JAR/AAR files to readable Java source.
|
||||
|
||||
### Option 1: GitHub Releases (recommended)
|
||||
|
||||
1. Go to <https://github.com/skylot/jadx/releases/latest>
|
||||
2. Download the `jadx-<version>.zip` file (not the source archive)
|
||||
3. Extract and add to PATH:
|
||||
|
||||
```bash
|
||||
unzip jadx-*.zip -d ~/jadx
|
||||
export PATH="$HOME/jadx/bin:$PATH"
|
||||
# Add the export line to your ~/.bashrc or ~/.zshrc for persistence
|
||||
```
|
||||
|
||||
### Option 2: Homebrew (macOS / Linux)
|
||||
|
||||
```bash
|
||||
brew install jadx
|
||||
```
|
||||
|
||||
### Option 3: Build from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/skylot/jadx.git
|
||||
cd jadx
|
||||
./gradlew dist
|
||||
# Binaries will be in build/jadx/bin/
|
||||
export PATH="$(pwd)/build/jadx/bin:$PATH"
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
jadx --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fernflower / Vineflower (optional, recommended)
|
||||
|
||||
Fernflower is the JetBrains Java decompiler. It produces better output than jadx on complex Java constructs, lambdas, and generics. [Vineflower](https://github.com/Vineflower/vineflower) is the actively maintained community fork with published releases — prefer it over upstream Fernflower.
|
||||
|
||||
### Option 1: Vineflower from GitHub Releases (recommended)
|
||||
|
||||
1. Go to <https://github.com/Vineflower/vineflower/releases/latest>
|
||||
2. Download `vineflower-<version>.jar`
|
||||
3. Place it and set the environment variable:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/vineflower
|
||||
mv vineflower-*.jar ~/vineflower/vineflower.jar
|
||||
export FERNFLOWER_JAR_PATH="$HOME/vineflower/vineflower.jar"
|
||||
# Add the export to ~/.bashrc or ~/.zshrc for persistence
|
||||
```
|
||||
|
||||
### Option 2: Build Fernflower from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/JetBrains/fernflower.git
|
||||
cd fernflower
|
||||
./gradlew jar
|
||||
# Produces: build/libs/fernflower.jar
|
||||
export FERNFLOWER_JAR_PATH="$(pwd)/build/libs/fernflower.jar"
|
||||
```
|
||||
|
||||
### Option 3: Homebrew (Vineflower)
|
||||
|
||||
```bash
|
||||
brew install vineflower
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
java -jar "$FERNFLOWER_JAR_PATH" --version
|
||||
```
|
||||
|
||||
> **Note**: Fernflower only works on JVM bytecode (JAR, class files). For APK/DEX files, you also need **dex2jar** (see below) as an intermediate conversion step.
|
||||
|
||||
---
|
||||
|
||||
## dex2jar (optional, needed for Fernflower on APK files)
|
||||
|
||||
Converts Android DEX bytecode to standard Java JAR files.
|
||||
|
||||
### GitHub Releases
|
||||
|
||||
1. Go to <https://github.com/pxb1988/dex2jar/releases/latest>
|
||||
2. Download and extract:
|
||||
|
||||
```bash
|
||||
unzip dex-tools-*.zip -d ~/dex2jar
|
||||
export PATH="$HOME/dex2jar:$PATH"
|
||||
```
|
||||
|
||||
### Homebrew
|
||||
|
||||
```bash
|
||||
brew install dex2jar
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
d2j-dex2jar --help
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Convert APK (or DEX) to JAR
|
||||
d2j-dex2jar -f -o output.jar app.apk
|
||||
|
||||
# Then decompile with Fernflower
|
||||
java -jar vineflower.jar output.jar decompiled/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional Tools
|
||||
|
||||
### apktool
|
||||
|
||||
Useful for decoding resources (XML layouts, drawables) that jadx sometimes handles poorly.
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install apktool
|
||||
|
||||
# macOS
|
||||
brew install apktool
|
||||
|
||||
# Manual: https://apktool.org/docs/install
|
||||
```
|
||||
|
||||
### adb (Android Debug Bridge)
|
||||
|
||||
Useful for pulling APKs directly from a connected Android device.
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install adb
|
||||
|
||||
# macOS
|
||||
brew install android-platform-tools
|
||||
```
|
||||
|
||||
Pull an APK from a device:
|
||||
|
||||
```bash
|
||||
# List installed packages
|
||||
adb shell pm list packages | grep <keyword>
|
||||
|
||||
# Get APK path
|
||||
adb shell pm path com.example.app
|
||||
|
||||
# Pull the APK
|
||||
adb pull /data/app/com.example.app-xxxx/base.apk ./app.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| `jadx: command not found` | Ensure the jadx `bin/` directory is in your `$PATH` |
|
||||
| `Error: Could not find or load main class` | Java is missing or wrong version — verify with `java -version` |
|
||||
| jadx runs out of memory on large APKs | Increase heap: `jadx -Xmx4g -d output app.apk` or set `JAVA_OPTS="-Xmx4g"` |
|
||||
| Decompiled code has many `// Error` comments | Try `--show-bad-code` to see partial output, or use `--deobf` for obfuscated apps |
|
||||
| Fernflower hangs on a method | Use `-mpm=60` to set a 60-second timeout per method |
|
||||
| Fernflower JAR not found | Set `FERNFLOWER_JAR_PATH` env variable to the full path of the JAR |
|
||||
| dex2jar fails with `ZipException` | The APK may have a non-standard ZIP structure — try `jadx` instead |
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-deps.sh — Verify dependencies and report what's missing
|
||||
# Output includes machine-readable INSTALL:<dep> lines for each missing dependency.
|
||||
# The install-dep.sh script can install each one.
|
||||
set -euo pipefail
|
||||
|
||||
REQUIRED_JAVA_MAJOR=17
|
||||
errors=0
|
||||
missing_required=()
|
||||
missing_optional=()
|
||||
|
||||
echo "=== Android Reverse Engineering: Dependency Check ==="
|
||||
echo
|
||||
|
||||
# --- Java ---
|
||||
java_ok=false
|
||||
if command -v java &>/dev/null; then
|
||||
java_version_output=$(java -version 2>&1 | head -1)
|
||||
java_version=$(echo "$java_version_output" | sed -n 's/.*"\([0-9]*\)\..*/\1/p')
|
||||
if [[ -z "$java_version" ]]; then
|
||||
java_version=$(echo "$java_version_output" | grep -oP '\d+' | head -1)
|
||||
fi
|
||||
if [[ "$java_version" == "1" ]]; then
|
||||
java_version=$(echo "$java_version_output" | sed -n 's/.*"1\.\([0-9]*\)\..*/\1/p')
|
||||
fi
|
||||
|
||||
if [[ -n "$java_version" ]] && (( java_version >= REQUIRED_JAVA_MAJOR )); then
|
||||
echo "[OK] Java $java_version detected"
|
||||
java_ok=true
|
||||
else
|
||||
echo "[WARN] Java detected but version $java_version is below $REQUIRED_JAVA_MAJOR"
|
||||
errors=$((errors + 1))
|
||||
missing_required+=("java")
|
||||
fi
|
||||
else
|
||||
echo "[MISSING] Java is not installed or not in PATH"
|
||||
errors=$((errors + 1))
|
||||
missing_required+=("java")
|
||||
fi
|
||||
|
||||
# --- jadx ---
|
||||
if command -v jadx &>/dev/null; then
|
||||
jadx_version=$(jadx --version 2>/dev/null || echo "unknown")
|
||||
echo "[OK] jadx $jadx_version detected"
|
||||
else
|
||||
echo "[MISSING] jadx is not installed or not in PATH"
|
||||
errors=$((errors + 1))
|
||||
missing_required+=("jadx")
|
||||
fi
|
||||
|
||||
# --- Fernflower / Vineflower ---
|
||||
ff_found=false
|
||||
if command -v vineflower &>/dev/null; then
|
||||
echo "[OK] vineflower CLI detected"
|
||||
ff_found=true
|
||||
elif command -v fernflower &>/dev/null; then
|
||||
echo "[OK] fernflower CLI detected"
|
||||
ff_found=true
|
||||
else
|
||||
for candidate in \
|
||||
"${FERNFLOWER_JAR_PATH:-}" \
|
||||
"$HOME/.local/share/vineflower/vineflower.jar" \
|
||||
"$HOME/fernflower/build/libs/fernflower.jar" \
|
||||
"$HOME/vineflower/build/libs/vineflower.jar" \
|
||||
"$HOME/fernflower/fernflower.jar" \
|
||||
"$HOME/vineflower/vineflower.jar"; do
|
||||
if [[ -n "$candidate" ]] && [[ -f "$candidate" ]]; then
|
||||
echo "[OK] Fernflower/Vineflower JAR found: $candidate"
|
||||
ff_found=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [[ "$ff_found" == false ]]; then
|
||||
echo "[MISSING] Fernflower/Vineflower not found (optional — better output on complex Java code)"
|
||||
missing_optional+=("vineflower")
|
||||
fi
|
||||
|
||||
# --- dex2jar ---
|
||||
if command -v d2j-dex2jar &>/dev/null || command -v d2j-dex2jar.sh &>/dev/null; then
|
||||
echo "[OK] dex2jar detected"
|
||||
else
|
||||
echo "[MISSING] dex2jar not found (optional — needed to use Fernflower on APK/DEX files)"
|
||||
missing_optional+=("dex2jar")
|
||||
fi
|
||||
|
||||
# --- Optional: apktool ---
|
||||
if command -v apktool &>/dev/null; then
|
||||
echo "[OK] apktool detected (optional)"
|
||||
else
|
||||
echo "[MISSING] apktool not found (optional — useful for resource decoding)"
|
||||
missing_optional+=("apktool")
|
||||
fi
|
||||
|
||||
# --- Optional: adb ---
|
||||
if command -v adb &>/dev/null; then
|
||||
echo "[OK] adb detected (optional)"
|
||||
else
|
||||
echo "[MISSING] adb not found (optional — useful for pulling APKs from devices)"
|
||||
missing_optional+=("adb")
|
||||
fi
|
||||
|
||||
# --- Machine-readable summary ---
|
||||
echo
|
||||
if [[ ${#missing_required[@]} -gt 0 ]]; then
|
||||
for dep in "${missing_required[@]}"; do
|
||||
echo "INSTALL_REQUIRED:$dep"
|
||||
done
|
||||
fi
|
||||
if [[ ${#missing_optional[@]} -gt 0 ]]; then
|
||||
for dep in "${missing_optional[@]}"; do
|
||||
echo "INSTALL_OPTIONAL:$dep"
|
||||
done
|
||||
fi
|
||||
|
||||
echo
|
||||
if (( errors > 0 )); then
|
||||
echo "*** ${#missing_required[@]} required dependency/ies missing. ***"
|
||||
echo "Run install-dep.sh <name> to install, or see references/setup-guide.md."
|
||||
exit 1
|
||||
else
|
||||
if [[ ${#missing_optional[@]} -gt 0 ]]; then
|
||||
echo "Required dependencies OK. ${#missing_optional[@]} optional dependency/ies missing."
|
||||
echo "Run install-dep.sh <name> to install optional tools."
|
||||
else
|
||||
echo "All dependencies are installed. Ready to decompile."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env bash
|
||||
# decompile.sh — Decompile APK/JAR/AAR using jadx, fernflower, or both
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: decompile.sh [OPTIONS] <file>
|
||||
|
||||
Decompile an Android APK, XAPK, JAR, or AAR file.
|
||||
|
||||
Arguments:
|
||||
<file> Path to the .apk, .xapk, .jar, or .aar file
|
||||
|
||||
Options:
|
||||
-o, --output DIR Output directory (default: <filename>-decompiled)
|
||||
--deobf Enable deobfuscation of names
|
||||
--no-res Skip resource decoding (faster, code-only)
|
||||
--engine ENGINE Decompiler engine: jadx, fernflower, or both (default: jadx)
|
||||
-h, --help Show this help message
|
||||
|
||||
Engines:
|
||||
jadx Use jadx (default). Handles APK/JAR/AAR natively, decodes resources.
|
||||
fernflower Use Fernflower/Vineflower. Better on complex Java, lambdas, generics.
|
||||
For APK files, requires dex2jar as intermediate step.
|
||||
both Run both decompilers side by side for comparison.
|
||||
jadx output → <output>/jadx/
|
||||
fernflower → <output>/fernflower/
|
||||
|
||||
Environment:
|
||||
FERNFLOWER_JAR_PATH Path to fernflower.jar or vineflower.jar
|
||||
|
||||
Examples:
|
||||
decompile.sh app-release.apk
|
||||
decompile.sh app-bundle.xapk
|
||||
decompile.sh --engine both --deobf app-release.apk
|
||||
decompile.sh --engine fernflower library.jar
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Parse arguments ---
|
||||
OUTPUT_DIR=""
|
||||
DEOBF=false
|
||||
NO_RES=false
|
||||
ENGINE="jadx"
|
||||
INPUT_FILE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-o|--output) OUTPUT_DIR="$2"; shift 2 ;;
|
||||
--deobf) DEOBF=true; shift ;;
|
||||
--no-res) NO_RES=true; shift ;;
|
||||
--engine) ENGINE="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) echo "Error: Unknown option $1" >&2; usage ;;
|
||||
*) INPUT_FILE="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Validate input ---
|
||||
if [[ -z "$INPUT_FILE" ]]; then
|
||||
echo "Error: No input file specified." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ ! -f "$INPUT_FILE" ]]; then
|
||||
echo "Error: File not found: $INPUT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ext="${INPUT_FILE##*.}"
|
||||
ext_lower=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
|
||||
case "$ext_lower" in
|
||||
apk|xapk|jar|aar) ;;
|
||||
*)
|
||||
echo "Error: Unsupported file type '.$ext'. Expected .apk, .xapk, .jar, or .aar" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$ENGINE" in
|
||||
jadx|fernflower|both) ;;
|
||||
*)
|
||||
echo "Error: Unknown engine '$ENGINE'. Use jadx, fernflower, or both." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
BASENAME=$(basename "$INPUT_FILE" ".$ext_lower")
|
||||
INPUT_FILE_ABS=$(realpath "$INPUT_FILE")
|
||||
|
||||
if [[ -z "$OUTPUT_DIR" ]]; then
|
||||
OUTPUT_DIR="${BASENAME}-decompiled"
|
||||
fi
|
||||
|
||||
# --- XAPK handling ---
|
||||
# XAPK is a ZIP containing one or more APKs, optional OBB files, and a manifest.json.
|
||||
# We extract it, find all APKs inside, and decompile each one.
|
||||
XAPK_EXTRACTED_DIR=""
|
||||
XAPK_APK_FILES=()
|
||||
|
||||
if [[ "$ext_lower" == "xapk" ]]; then
|
||||
XAPK_EXTRACTED_DIR=$(mktemp -d "${TMPDIR:-/tmp}/xapk-extract-XXXXXX")
|
||||
echo "=== Extracting XAPK archive ==="
|
||||
unzip -qo "$INPUT_FILE_ABS" -d "$XAPK_EXTRACTED_DIR"
|
||||
|
||||
# Show manifest.json if present
|
||||
if [[ -f "$XAPK_EXTRACTED_DIR/manifest.json" ]]; then
|
||||
echo "XAPK manifest found:"
|
||||
cat "$XAPK_EXTRACTED_DIR/manifest.json"
|
||||
echo
|
||||
fi
|
||||
|
||||
# Find all APK files inside
|
||||
while IFS= read -r -d '' apk_file; do
|
||||
XAPK_APK_FILES+=("$apk_file")
|
||||
done < <(find "$XAPK_EXTRACTED_DIR" -name "*.apk" -print0 | sort -z)
|
||||
|
||||
if [[ ${#XAPK_APK_FILES[@]} -eq 0 ]]; then
|
||||
echo "Error: No APK files found inside XAPK archive." >&2
|
||||
rm -rf "$XAPK_EXTRACTED_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found ${#XAPK_APK_FILES[@]} APK(s) inside XAPK:"
|
||||
for f in "${XAPK_APK_FILES[@]}"; do
|
||||
echo " - $(basename "$f")"
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
# --- Locate fernflower JAR ---
|
||||
find_fernflower_jar() {
|
||||
if [[ -n "${FERNFLOWER_JAR_PATH:-}" ]] && [[ -f "$FERNFLOWER_JAR_PATH" ]]; then
|
||||
echo "$FERNFLOWER_JAR_PATH"
|
||||
return
|
||||
fi
|
||||
# Check common locations
|
||||
for candidate in \
|
||||
"$HOME/fernflower/build/libs/fernflower.jar" \
|
||||
"$HOME/vineflower/build/libs/vineflower.jar" \
|
||||
"$HOME/fernflower/fernflower.jar" \
|
||||
"$HOME/vineflower/vineflower.jar"; do
|
||||
if [[ -f "$candidate" ]]; then
|
||||
echo "$candidate"
|
||||
return
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- Locate dex2jar ---
|
||||
find_dex2jar() {
|
||||
if command -v d2j-dex2jar &>/dev/null; then
|
||||
echo "d2j-dex2jar"
|
||||
elif command -v d2j-dex2jar.sh &>/dev/null; then
|
||||
echo "d2j-dex2jar.sh"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- jadx decompilation ---
|
||||
run_jadx() {
|
||||
local out_dir="$1"
|
||||
|
||||
if ! command -v jadx &>/dev/null; then
|
||||
echo "Error: jadx is not installed or not in PATH." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local args=()
|
||||
args+=("-d" "$out_dir")
|
||||
[[ "$DEOBF" == true ]] && args+=("--deobf")
|
||||
[[ "$NO_RES" == true ]] && args+=("--no-res")
|
||||
args+=("--show-bad-code")
|
||||
args+=("$INPUT_FILE_ABS")
|
||||
|
||||
echo "Running: jadx ${args[*]}"
|
||||
jadx "${args[@]}"
|
||||
|
||||
echo "jadx output: $out_dir/sources/"
|
||||
if [[ -d "$out_dir/sources" ]]; then
|
||||
local count
|
||||
count=$(find "$out_dir/sources" -name "*.java" | wc -l)
|
||||
echo "Java files decompiled by jadx: $count"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Fernflower decompilation ---
|
||||
run_fernflower() {
|
||||
local out_dir="$1"
|
||||
local jar_to_decompile=""
|
||||
|
||||
local ff_jar
|
||||
if ! ff_jar=$(find_fernflower_jar); then
|
||||
echo "Error: Fernflower/Vineflower JAR not found." >&2
|
||||
echo "Set FERNFLOWER_JAR_PATH or see references/setup-guide.md" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
# For APK/AAR, we need dex2jar first to convert DEX→JAR
|
||||
if [[ "$ext_lower" == "apk" || "$ext_lower" == "aar" ]]; then
|
||||
local d2j
|
||||
if ! d2j=$(find_dex2jar); then
|
||||
echo "Error: dex2jar is required to use Fernflower on .$ext_lower files." >&2
|
||||
echo "Install dex2jar — see references/setup-guide.md" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Converting $ext_lower to JAR with dex2jar..."
|
||||
local converted_jar="$out_dir/${BASENAME}-dex2jar.jar"
|
||||
"$d2j" -f -o "$converted_jar" "$INPUT_FILE_ABS" 2>&1 || true
|
||||
if [[ ! -f "$converted_jar" ]]; then
|
||||
echo "Error: dex2jar conversion failed." >&2
|
||||
return 1
|
||||
fi
|
||||
jar_to_decompile="$converted_jar"
|
||||
else
|
||||
jar_to_decompile="$INPUT_FILE_ABS"
|
||||
fi
|
||||
|
||||
# Build fernflower args
|
||||
local ff_args=()
|
||||
ff_args+=("-dgs=1") # decompile generic signatures
|
||||
ff_args+=("-mpm=60") # 60s max per method to avoid hangs
|
||||
if [[ "$DEOBF" == true ]]; then
|
||||
ff_args+=("-ren=1") # rename obfuscated identifiers
|
||||
fi
|
||||
ff_args+=("$jar_to_decompile")
|
||||
ff_args+=("$out_dir")
|
||||
|
||||
echo "Running: java -jar $ff_jar ${ff_args[*]}"
|
||||
java -jar "$ff_jar" "${ff_args[@]}"
|
||||
|
||||
# Fernflower outputs a JAR containing .java files — extract it
|
||||
local result_jar="$out_dir/$(basename "$jar_to_decompile")"
|
||||
if [[ -f "$result_jar" ]]; then
|
||||
local sources_dir="$out_dir/sources"
|
||||
mkdir -p "$sources_dir"
|
||||
unzip -qo "$result_jar" -d "$sources_dir"
|
||||
rm -f "$result_jar"
|
||||
echo "Fernflower output: $sources_dir/"
|
||||
local count
|
||||
count=$(find "$sources_dir" -name "*.java" | wc -l)
|
||||
echo "Java files decompiled by Fernflower: $count"
|
||||
fi
|
||||
|
||||
# Clean up intermediate dex2jar output
|
||||
if [[ -n "${converted_jar:-}" ]] && [[ -f "${converted_jar:-}" ]]; then
|
||||
rm -f "$converted_jar"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Summary helper ---
|
||||
print_structure() {
|
||||
local src_dir="$1"
|
||||
local label="$2"
|
||||
if [[ -d "$src_dir" ]]; then
|
||||
echo
|
||||
echo "Top-level packages ($label):"
|
||||
find "$src_dir" -mindepth 1 -maxdepth 3 -type d | head -20 | sed "s|$src_dir/||" | grep -v '^$' | sort
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Decompile a single file with the selected engine ---
|
||||
decompile_single() {
|
||||
local file_abs="$1"
|
||||
local out_dir="$2"
|
||||
local label="$3"
|
||||
|
||||
# Temporarily override INPUT_FILE_ABS for run_jadx/run_fernflower
|
||||
local saved_input="$INPUT_FILE_ABS"
|
||||
local saved_ext="$ext_lower"
|
||||
INPUT_FILE_ABS="$file_abs"
|
||||
ext_lower="${file_abs##*.}"
|
||||
ext_lower=$(echo "$ext_lower" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [[ -n "$label" ]]; then
|
||||
echo "=== Decompiling $label (engine: $ENGINE) ==="
|
||||
fi
|
||||
|
||||
case "$ENGINE" in
|
||||
jadx)
|
||||
run_jadx "$out_dir"
|
||||
print_structure "$out_dir/sources" "jadx"
|
||||
;;
|
||||
fernflower)
|
||||
run_fernflower "$out_dir"
|
||||
print_structure "$out_dir/sources" "fernflower"
|
||||
;;
|
||||
both)
|
||||
echo "--- Pass 1: jadx ---"
|
||||
run_jadx "$out_dir/jadx"
|
||||
echo
|
||||
echo "--- Pass 2: Fernflower ---"
|
||||
run_fernflower "$out_dir/fernflower"
|
||||
|
||||
print_structure "$out_dir/jadx/sources" "jadx"
|
||||
print_structure "$out_dir/fernflower/sources" "fernflower"
|
||||
|
||||
echo
|
||||
echo "=== Comparison ==="
|
||||
local jadx_count=0 ff_count=0
|
||||
if [[ -d "$out_dir/jadx/sources" ]]; then
|
||||
jadx_count=$(find "$out_dir/jadx/sources" -name "*.java" | wc -l)
|
||||
fi
|
||||
if [[ -d "$out_dir/fernflower/sources" ]]; then
|
||||
ff_count=$(find "$out_dir/fernflower/sources" -name "*.java" | wc -l)
|
||||
fi
|
||||
echo "jadx: $jadx_count Java files"
|
||||
echo "Fernflower: $ff_count Java files"
|
||||
|
||||
if [[ -d "$out_dir/jadx/sources" ]]; then
|
||||
local jadx_errors
|
||||
jadx_errors=$(grep -rl 'JADX WARNING\|JADX WARN\|JADX ERROR\|Code decompiled incorrectly' "$out_dir/jadx/sources" 2>/dev/null | wc -l || echo 0)
|
||||
echo "jadx files with warnings/errors: $jadx_errors"
|
||||
fi
|
||||
echo
|
||||
echo "Tip: compare specific classes between jadx/ and fernflower/ to pick the better output."
|
||||
;;
|
||||
esac
|
||||
|
||||
INPUT_FILE_ABS="$saved_input"
|
||||
ext_lower="$saved_ext"
|
||||
}
|
||||
|
||||
# --- Run ---
|
||||
echo "=== Decompiling $INPUT_FILE (engine: $ENGINE) ==="
|
||||
echo "Output directory: $OUTPUT_DIR"
|
||||
echo
|
||||
|
||||
if [[ "$ext_lower" == "xapk" ]]; then
|
||||
# Decompile each APK found inside the XAPK
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Copy XAPK manifest for reference
|
||||
if [[ -f "$XAPK_EXTRACTED_DIR/manifest.json" ]]; then
|
||||
cp "$XAPK_EXTRACTED_DIR/manifest.json" "$OUTPUT_DIR/xapk-manifest.json"
|
||||
fi
|
||||
|
||||
# Copy OBB file list for reference
|
||||
obb_files=()
|
||||
while IFS= read -r -d '' obb; do
|
||||
obb_files+=("$obb")
|
||||
done < <(find "$XAPK_EXTRACTED_DIR" -name "*.obb" -print0 2>/dev/null)
|
||||
if [[ ${#obb_files[@]} -gt 0 ]]; then
|
||||
echo "OBB files found (not decompiled, data-only):"
|
||||
for obb in "${obb_files[@]}"; do
|
||||
echo " - $(basename "$obb") ($(du -h "$obb" | cut -f1))"
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
for apk_file in "${XAPK_APK_FILES[@]}"; do
|
||||
apk_name=$(basename "$apk_file" .apk)
|
||||
echo
|
||||
echo "======================================================"
|
||||
decompile_single "$apk_file" "$OUTPUT_DIR/$apk_name" "$apk_name.apk"
|
||||
done
|
||||
|
||||
# Cleanup extracted XAPK
|
||||
rm -rf "$XAPK_EXTRACTED_DIR"
|
||||
|
||||
echo
|
||||
echo "=== XAPK decompilation complete ==="
|
||||
echo "Subdirectories in $OUTPUT_DIR/:"
|
||||
ls -1 "$OUTPUT_DIR/"
|
||||
else
|
||||
decompile_single "$INPUT_FILE_ABS" "$OUTPUT_DIR" ""
|
||||
echo
|
||||
echo "=== Decompilation complete ==="
|
||||
fi
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
# find-api-calls.sh — Search decompiled source for API calls and HTTP endpoints
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: find-api-calls.sh <source-dir> [OPTIONS]
|
||||
|
||||
Search decompiled Java/Kotlin source for HTTP API calls and endpoints.
|
||||
|
||||
Arguments:
|
||||
<source-dir> Path to the decompiled sources directory
|
||||
|
||||
Options:
|
||||
--retrofit Search only for Retrofit annotations
|
||||
--okhttp Search only for OkHttp patterns
|
||||
--volley Search only for Volley patterns
|
||||
--urls Search only for hardcoded URLs
|
||||
--auth Search only for auth-related patterns
|
||||
--all Search all patterns (default)
|
||||
-h, --help Show this help message
|
||||
|
||||
Output:
|
||||
Results are printed as file:line:match for easy navigation.
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
SOURCE_DIR=""
|
||||
SEARCH_RETROFIT=false
|
||||
SEARCH_OKHTTP=false
|
||||
SEARCH_VOLLEY=false
|
||||
SEARCH_URLS=false
|
||||
SEARCH_AUTH=false
|
||||
SEARCH_ALL=true
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--retrofit) SEARCH_RETROFIT=true; SEARCH_ALL=false; shift ;;
|
||||
--okhttp) SEARCH_OKHTTP=true; SEARCH_ALL=false; shift ;;
|
||||
--volley) SEARCH_VOLLEY=true; SEARCH_ALL=false; shift ;;
|
||||
--urls) SEARCH_URLS=true; SEARCH_ALL=false; shift ;;
|
||||
--auth) SEARCH_AUTH=true; SEARCH_ALL=false; shift ;;
|
||||
--all) SEARCH_ALL=true; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) echo "Error: Unknown option $1" >&2; usage ;;
|
||||
*) SOURCE_DIR="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$SOURCE_DIR" ]]; then
|
||||
echo "Error: No source directory specified." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ ! -d "$SOURCE_DIR" ]]; then
|
||||
echo "Error: Directory not found: $SOURCE_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GREP_OPTS="-rn --include=*.java --include=*.kt"
|
||||
|
||||
section() {
|
||||
echo
|
||||
echo "==== $1 ===="
|
||||
echo
|
||||
}
|
||||
|
||||
run_grep() {
|
||||
local pattern="$1"
|
||||
# shellcheck disable=SC2086
|
||||
grep $GREP_OPTS -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# --- Retrofit ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_RETROFIT" == true ]]; then
|
||||
section "Retrofit Annotations"
|
||||
run_grep '@(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|HTTP)\s*\('
|
||||
section "Retrofit Headers & Parameters"
|
||||
run_grep '@(Headers|Header|Query|QueryMap|Path|Body|Field|FieldMap|Part|PartMap|Url)\s*\('
|
||||
section "Retrofit Base URL"
|
||||
run_grep '(baseUrl|base_url)\s*\('
|
||||
fi
|
||||
|
||||
# --- OkHttp ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_OKHTTP" == true ]]; then
|
||||
section "OkHttp Request Building"
|
||||
run_grep '(Request\.Builder|HttpUrl|\.newCall|\.enqueue|addInterceptor|addNetworkInterceptor)'
|
||||
section "OkHttp URL Construction"
|
||||
run_grep '(\.url\s*\(|\.addQueryParameter|\.addPathSegment|\.scheme\s*\(|\.host\s*\()'
|
||||
fi
|
||||
|
||||
# --- Volley ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_VOLLEY" == true ]]; then
|
||||
section "Volley Requests"
|
||||
run_grep '(StringRequest|JsonObjectRequest|JsonArrayRequest|ImageRequest|RequestQueue|Volley\.newRequestQueue)'
|
||||
fi
|
||||
|
||||
# --- Hardcoded URLs ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_URLS" == true ]]; then
|
||||
section "Hardcoded URLs (http:// and https://)"
|
||||
run_grep '"https?://[^"]+'
|
||||
section "HttpURLConnection"
|
||||
run_grep '(openConnection|setRequestMethod|HttpURLConnection|HttpsURLConnection)'
|
||||
section "WebView URLs"
|
||||
run_grep '(loadUrl|loadData|evaluateJavascript|addJavascriptInterface|WebViewClient|WebChromeClient)'
|
||||
fi
|
||||
|
||||
# --- Auth patterns ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_AUTH" == true ]]; then
|
||||
section "Authentication & API Keys"
|
||||
run_grep -i '(api[_-]?key|auth[_-]?token|bearer|authorization|x-api-key|client[_-]?secret|access[_-]?token)'
|
||||
section "Base URLs and Constants"
|
||||
run_grep -i '(BASE_URL|API_URL|SERVER_URL|ENDPOINT|API_BASE|HOST_NAME)'
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Search complete ==="
|
||||
@@ -0,0 +1,448 @@
|
||||
#!/usr/bin/env bash
|
||||
# install-dep.sh — Install a single dependency for Android reverse engineering
|
||||
# Usage: install-dep.sh <dependency>
|
||||
# Dependencies: java, jadx, vineflower, dex2jar, apktool, adb
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 — installed successfully
|
||||
# 1 — installation failed
|
||||
# 2 — requires manual action (e.g. sudo needed but not available)
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: install-dep.sh <dependency>
|
||||
|
||||
Install a dependency required for Android reverse engineering.
|
||||
|
||||
Available dependencies:
|
||||
java Java JDK 17+
|
||||
jadx jadx decompiler
|
||||
vineflower Vineflower (Fernflower fork) decompiler
|
||||
dex2jar DEX to JAR converter
|
||||
apktool Android resource decoder
|
||||
adb Android Debug Bridge
|
||||
|
||||
The script detects your OS and package manager, then:
|
||||
- Installs directly if possible (brew, or user-local install)
|
||||
- Uses sudo if available and needed
|
||||
- Prints manual instructions if neither option works
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
if [[ $# -lt 1 || "$1" == "-h" || "$1" == "--help" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
DEP="$1"
|
||||
|
||||
# --- Detect environment ---
|
||||
OS="unknown"
|
||||
PKG_MANAGER="none"
|
||||
HAS_SUDO=false
|
||||
ARCH=$(uname -m)
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux) OS="linux" ;;
|
||||
Darwin) OS="macos" ;;
|
||||
esac
|
||||
|
||||
# Detect package manager
|
||||
if command -v brew &>/dev/null; then
|
||||
PKG_MANAGER="brew"
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
PKG_MANAGER="apt"
|
||||
elif command -v dnf &>/dev/null; then
|
||||
PKG_MANAGER="dnf"
|
||||
elif command -v pacman &>/dev/null; then
|
||||
PKG_MANAGER="pacman"
|
||||
fi
|
||||
|
||||
# Check sudo availability
|
||||
if command -v sudo &>/dev/null; then
|
||||
if sudo -n true 2>/dev/null; then
|
||||
HAS_SUDO=true
|
||||
else
|
||||
# sudo exists but may need password — we'll try it and let it prompt
|
||||
HAS_SUDO=true
|
||||
fi
|
||||
fi
|
||||
|
||||
info() { echo "[INFO] $*"; }
|
||||
ok() { echo "[OK] $*"; }
|
||||
fail() { echo "[FAIL] $*" >&2; }
|
||||
manual() {
|
||||
echo "[MANUAL] $*" >&2
|
||||
echo " Cannot install automatically. Please install manually and retry." >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
# --- Helper: install via system package manager (needs sudo on Linux) ---
|
||||
pkg_install() {
|
||||
local pkg="$1"
|
||||
case "$PKG_MANAGER" in
|
||||
brew)
|
||||
info "Installing $pkg via Homebrew..."
|
||||
brew install "$pkg"
|
||||
;;
|
||||
apt)
|
||||
if [[ "$HAS_SUDO" == true ]]; then
|
||||
info "Installing $pkg via apt..."
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq "$pkg"
|
||||
else
|
||||
manual "Run: sudo apt-get install $pkg"
|
||||
fi
|
||||
;;
|
||||
dnf)
|
||||
if [[ "$HAS_SUDO" == true ]]; then
|
||||
info "Installing $pkg via dnf..."
|
||||
sudo dnf install -y "$pkg"
|
||||
else
|
||||
manual "Run: sudo dnf install $pkg"
|
||||
fi
|
||||
;;
|
||||
pacman)
|
||||
if [[ "$HAS_SUDO" == true ]]; then
|
||||
info "Installing $pkg via pacman..."
|
||||
sudo pacman -S --noconfirm "$pkg"
|
||||
else
|
||||
manual "Run: sudo pacman -S $pkg"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
manual "No supported package manager found. Install $pkg manually."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- Helper: download a file ---
|
||||
download() {
|
||||
local url="$1" dest="$2"
|
||||
if command -v curl &>/dev/null; then
|
||||
curl -fsSL -o "$dest" "$url"
|
||||
elif command -v wget &>/dev/null; then
|
||||
wget -q -O "$dest" "$url"
|
||||
else
|
||||
fail "Neither curl nor wget available."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Helper: get latest GitHub release tag ---
|
||||
gh_latest_tag() {
|
||||
local repo="$1"
|
||||
local url="https://api.github.com/repos/$repo/releases/latest"
|
||||
if command -v curl &>/dev/null; then
|
||||
curl -fsSL "$url" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/'
|
||||
elif command -v wget &>/dev/null; then
|
||||
wget -q -O - "$url" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/'
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Helper: add a line to shell profile if not already present ---
|
||||
add_to_profile() {
|
||||
local line="$1"
|
||||
local profile=""
|
||||
if [[ -f "$HOME/.zshrc" ]]; then
|
||||
profile="$HOME/.zshrc"
|
||||
elif [[ -f "$HOME/.bashrc" ]]; then
|
||||
profile="$HOME/.bashrc"
|
||||
elif [[ -f "$HOME/.profile" ]]; then
|
||||
profile="$HOME/.profile"
|
||||
fi
|
||||
|
||||
if [[ -n "$profile" ]]; then
|
||||
if ! grep -qF "$line" "$profile" 2>/dev/null; then
|
||||
echo "$line" >> "$profile"
|
||||
info "Added to $profile: $line"
|
||||
info "Run 'source $profile' or start a new shell to apply."
|
||||
fi
|
||||
else
|
||||
info "Add this to your shell profile: $line"
|
||||
fi
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# Dependency installers
|
||||
# =====================================================================
|
||||
|
||||
install_java() {
|
||||
if command -v java &>/dev/null; then
|
||||
local ver
|
||||
ver=$(java -version 2>&1 | head -1 | sed -n 's/.*"\([0-9]*\)\..*/\1/p')
|
||||
if [[ -n "$ver" ]] && (( ver >= 17 )); then
|
||||
ok "Java $ver already installed"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Installing Java JDK 17+..."
|
||||
case "$PKG_MANAGER" in
|
||||
brew) brew install openjdk@17 ;;
|
||||
apt) pkg_install "openjdk-17-jdk" ;;
|
||||
dnf) pkg_install "java-17-openjdk-devel" ;;
|
||||
pacman) pkg_install "jdk17-openjdk" ;;
|
||||
*) manual "Install Java JDK 17+ from https://adoptium.net/" ;;
|
||||
esac
|
||||
|
||||
# Verify
|
||||
if command -v java &>/dev/null; then
|
||||
ok "Java installed: $(java -version 2>&1 | head -1)"
|
||||
else
|
||||
fail "Java installation may require PATH update."
|
||||
if [[ "$PKG_MANAGER" == "brew" ]]; then
|
||||
add_to_profile 'export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"'
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_jadx() {
|
||||
if command -v jadx &>/dev/null; then
|
||||
ok "jadx already installed: $(jadx --version 2>/dev/null || echo 'unknown')"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try brew first (cleanest)
|
||||
if [[ "$PKG_MANAGER" == "brew" ]]; then
|
||||
info "Installing jadx via Homebrew..."
|
||||
brew install jadx
|
||||
ok "jadx installed via Homebrew"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# User-local install from GitHub releases (no sudo needed)
|
||||
info "Installing jadx from GitHub releases..."
|
||||
local tag
|
||||
tag=$(gh_latest_tag "skylot/jadx")
|
||||
if [[ -z "$tag" ]]; then
|
||||
fail "Could not determine latest jadx version."
|
||||
manual "Download from https://github.com/skylot/jadx/releases/latest"
|
||||
fi
|
||||
|
||||
local version="${tag#v}"
|
||||
local url="https://github.com/skylot/jadx/releases/download/${tag}/jadx-${version}.zip"
|
||||
local tmp_zip
|
||||
tmp_zip=$(mktemp /tmp/jadx-XXXXXX.zip)
|
||||
|
||||
info "Downloading jadx $version..."
|
||||
download "$url" "$tmp_zip"
|
||||
|
||||
local install_dir="$HOME/.local/share/jadx"
|
||||
rm -rf "$install_dir"
|
||||
mkdir -p "$install_dir"
|
||||
unzip -qo "$tmp_zip" -d "$install_dir"
|
||||
rm -f "$tmp_zip"
|
||||
chmod +x "$install_dir/bin/jadx" "$install_dir/bin/jadx-gui" 2>/dev/null || true
|
||||
|
||||
# Add to PATH
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
ln -sf "$install_dir/bin/jadx" "$HOME/.local/bin/jadx"
|
||||
ln -sf "$install_dir/bin/jadx-gui" "$HOME/.local/bin/jadx-gui"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
add_to_profile 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
|
||||
if command -v jadx &>/dev/null; then
|
||||
ok "jadx $version installed to $install_dir"
|
||||
else
|
||||
ok "jadx $version installed to $install_dir"
|
||||
info "Run: export PATH=\"\$HOME/.local/bin:\$PATH\" to use it now"
|
||||
fi
|
||||
}
|
||||
|
||||
install_vineflower() {
|
||||
# Check if already available
|
||||
if command -v vineflower &>/dev/null || command -v fernflower &>/dev/null; then
|
||||
ok "Vineflower/Fernflower CLI already installed"
|
||||
return 0
|
||||
fi
|
||||
for candidate in \
|
||||
"${FERNFLOWER_JAR_PATH:-}" \
|
||||
"$HOME/vineflower/vineflower.jar" \
|
||||
"$HOME/fernflower/fernflower.jar" \
|
||||
"$HOME/fernflower/build/libs/fernflower.jar" \
|
||||
"$HOME/vineflower/build/libs/vineflower.jar"; do
|
||||
if [[ -n "$candidate" ]] && [[ -f "$candidate" ]]; then
|
||||
ok "Vineflower/Fernflower JAR already exists: $candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Try brew
|
||||
if [[ "$PKG_MANAGER" == "brew" ]]; then
|
||||
info "Installing vineflower via Homebrew..."
|
||||
if brew install vineflower 2>/dev/null; then
|
||||
ok "Vineflower installed via Homebrew"
|
||||
return 0
|
||||
fi
|
||||
info "Homebrew formula not available, falling back to direct download."
|
||||
fi
|
||||
|
||||
# Download JAR from GitHub releases (no sudo needed)
|
||||
info "Installing Vineflower from GitHub releases..."
|
||||
local tag
|
||||
tag=$(gh_latest_tag "Vineflower/vineflower")
|
||||
if [[ -z "$tag" ]]; then
|
||||
fail "Could not determine latest Vineflower version."
|
||||
manual "Download from https://github.com/Vineflower/vineflower/releases/latest"
|
||||
fi
|
||||
|
||||
local version="${tag#v}"
|
||||
local url="https://github.com/Vineflower/vineflower/releases/download/${tag}/vineflower-${version}.jar"
|
||||
local install_dir="$HOME/.local/share/vineflower"
|
||||
mkdir -p "$install_dir"
|
||||
|
||||
info "Downloading Vineflower $version..."
|
||||
download "$url" "$install_dir/vineflower.jar"
|
||||
|
||||
# Create wrapper script
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
cat > "$HOME/.local/bin/vineflower" <<'WRAPPER'
|
||||
#!/usr/bin/env bash
|
||||
exec java -jar "$HOME/.local/share/vineflower/vineflower.jar" "$@"
|
||||
WRAPPER
|
||||
chmod +x "$HOME/.local/bin/vineflower"
|
||||
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
export FERNFLOWER_JAR_PATH="$install_dir/vineflower.jar"
|
||||
add_to_profile 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
add_to_profile "export FERNFLOWER_JAR_PATH=\"$install_dir/vineflower.jar\""
|
||||
|
||||
ok "Vineflower $version installed to $install_dir/vineflower.jar"
|
||||
info "FERNFLOWER_JAR_PATH set to $install_dir/vineflower.jar"
|
||||
}
|
||||
|
||||
install_dex2jar() {
|
||||
if command -v d2j-dex2jar &>/dev/null || command -v d2j-dex2jar.sh &>/dev/null; then
|
||||
ok "dex2jar already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try brew
|
||||
if [[ "$PKG_MANAGER" == "brew" ]]; then
|
||||
info "Installing dex2jar via Homebrew..."
|
||||
if brew install dex2jar 2>/dev/null; then
|
||||
ok "dex2jar installed via Homebrew"
|
||||
return 0
|
||||
fi
|
||||
info "Homebrew formula not available, falling back to direct download."
|
||||
fi
|
||||
|
||||
# Download from GitHub (no sudo needed)
|
||||
info "Installing dex2jar from GitHub releases..."
|
||||
local tag
|
||||
tag=$(gh_latest_tag "pxb1988/dex2jar")
|
||||
if [[ -z "$tag" ]]; then
|
||||
# Fallback: pxb1988 hasn't released in a while, try known version
|
||||
tag="v2.4"
|
||||
fi
|
||||
|
||||
local version="${tag#v}"
|
||||
local url="https://github.com/pxb1988/dex2jar/releases/download/${tag}/dex-tools-${version}.zip"
|
||||
local tmp_zip
|
||||
tmp_zip=$(mktemp /tmp/dex2jar-XXXXXX.zip)
|
||||
|
||||
info "Downloading dex2jar $version..."
|
||||
if ! download "$url" "$tmp_zip"; then
|
||||
# Try alternate naming
|
||||
url="https://github.com/pxb1988/dex2jar/releases/download/${tag}/dex-tools-v${version}.zip"
|
||||
download "$url" "$tmp_zip" || {
|
||||
fail "Download failed."
|
||||
manual "Download from https://github.com/pxb1988/dex2jar/releases/latest"
|
||||
}
|
||||
fi
|
||||
|
||||
local install_dir="$HOME/.local/share/dex2jar"
|
||||
rm -rf "$install_dir"
|
||||
mkdir -p "$install_dir"
|
||||
unzip -qo "$tmp_zip" -d "$install_dir"
|
||||
rm -f "$tmp_zip"
|
||||
|
||||
# The zip may contain a top-level directory — find the actual bin location
|
||||
local bin_dir=""
|
||||
if [[ -f "$install_dir/d2j-dex2jar.sh" ]]; then
|
||||
bin_dir="$install_dir"
|
||||
else
|
||||
bin_dir=$(find "$install_dir" -name "d2j-dex2jar.sh" -exec dirname {} \; | head -1)
|
||||
fi
|
||||
|
||||
if [[ -z "$bin_dir" ]]; then
|
||||
fail "Could not find d2j-dex2jar.sh in extracted archive."
|
||||
manual "Download and extract manually from https://github.com/pxb1988/dex2jar/releases"
|
||||
fi
|
||||
|
||||
chmod +x "$bin_dir"/*.sh 2>/dev/null || true
|
||||
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
for script in "$bin_dir"/d2j-*.sh; do
|
||||
local name
|
||||
name=$(basename "$script" .sh)
|
||||
ln -sf "$script" "$HOME/.local/bin/$name"
|
||||
done
|
||||
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
add_to_profile 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
|
||||
ok "dex2jar $version installed to $install_dir"
|
||||
}
|
||||
|
||||
install_apktool() {
|
||||
if command -v apktool &>/dev/null; then
|
||||
ok "apktool already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$PKG_MANAGER" in
|
||||
brew) info "Installing apktool via Homebrew..."; brew install apktool ;;
|
||||
apt) pkg_install "apktool" ;;
|
||||
*) manual "Install apktool from https://apktool.org/docs/install" ;;
|
||||
esac
|
||||
|
||||
if command -v apktool &>/dev/null; then
|
||||
ok "apktool installed"
|
||||
else
|
||||
fail "apktool installation may have failed."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_adb() {
|
||||
if command -v adb &>/dev/null; then
|
||||
ok "adb already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$PKG_MANAGER" in
|
||||
brew) info "Installing adb via Homebrew..."; brew install android-platform-tools ;;
|
||||
apt) pkg_install "adb" ;;
|
||||
dnf) pkg_install "android-tools" ;;
|
||||
pacman) pkg_install "android-tools" ;;
|
||||
*) manual "Install Android SDK Platform Tools from https://developer.android.com/tools/releases/platform-tools" ;;
|
||||
esac
|
||||
|
||||
if command -v adb &>/dev/null; then
|
||||
ok "adb installed"
|
||||
else
|
||||
fail "adb installation may have failed."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# Dispatch
|
||||
# =====================================================================
|
||||
|
||||
case "$DEP" in
|
||||
java) install_java ;;
|
||||
jadx) install_jadx ;;
|
||||
vineflower|fernflower) install_vineflower ;;
|
||||
dex2jar) install_dex2jar ;;
|
||||
apktool) install_apktool ;;
|
||||
adb) install_adb ;;
|
||||
*)
|
||||
echo "Error: Unknown dependency '$DEP'" >&2
|
||||
echo "Available: java, jadx, vineflower, dex2jar, apktool, adb" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user