How I Used an LLM to Reverse-Engineer an Android App in 15 Minutes
the whole process — from broken feature to working solution — still saved me days.
The Setup
I'm building an Android app that reads and writes NFC/RFID cards for a specific domain. It's been years since my last Android project, so I leaned heavily on an LLM coding assistant for Kotlin and Jetpack Compose UI.
The LLM did great work — until it didn't.
My app uses MIFARE Classic cards (the cheap, ubiquitous ones you find in access control, loyalty programs, and transit systems). The read feature worked fine. But when I asked the LLM to help me write data to the card, it hit a wall.
Me: "I need to write a text record to this MIFARE Classic card."
LLM: "MIFARE Classic requires manufacturer-specific keys for sector authentication. The Android
MifareClassicAPI doesn't expose write functionality directly. Your device may have hardware limitations that prevent MIFARE Classic write operations."
The LLM was confident. It cited Android documentation. It explained the key authentication mechanism.
I asked the agent to research the NFC community forum. It found a lot of information, including keys, but still couldn't deliver a working write function. The conclusion is clear — it is a device limitation.
Proving It Wrong
To prove the device wasn't the problem, I installed a free NFC tools app from the Play Store — the kind of general-purpose reader/writer that millions of people use. There are several of these available; they're the Swiss Army knives of the NFC world.
Within seconds, it wrote a text record to my MIFARE Classic card. No hardware limitation. No manufacturer lockout. The write worked perfectly.
The issue wasn't my device. The issue was my LLM's understanding of MIFARE Classic — and the custom format it had designed instead of using the standard.
The Wild Card
This is where things got interesting. I asked the LLM:
"Can you pull the APK from this app and figure out how they're writing to MIFARE Classic?"
What followed was one of the most impressive demonstrations of LLM capability I've seen.
Pull and Decompile
The LLM ran these commands on my dev machine:
# Pull the APK from the device
adb shell pm path com.example.nfctools
# → package:/data/app/com.example.nfctools/base.apk
adb pull /data/app/com.example.nfctools/base.apk ./nfctools.apk
# Decompile with JADX
jadx -d ./nfctools_src ./nfctools.apk
Five minutes of command-line work. The APK was now a directory full of .java source files — decompiled from DEX bytecode, with all the class names mangled by ProGuard obfuscation.
Navigating the Obfuscation
The app had hundreds of files with names like uh2.java, yu.java, rv.java, sv.java. No meaningful class names — just ProGuard's output. The LLM spent 12 reasoning steps tracing through this maze:
Finding the NFC classes — Searched for Android NFC imports across the entire codebase:
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.nfc.tech.MifareClassic;
Tracing the write flow — Identified the path through three obfuscated classes:
uh2.java(WriterTask) — orchestrates the write operationyu.java(NFCIOHelper) — handles low-level MIFARE Classic I/Orv.java(Tag) — wraps the NFC tag data model
Reconstructing the logic — Despite obfuscated variable names, the LLM traced the data flow:
// The write flow (reconstructed from decompiled code):
uh2.g() → Builds NdefRecord[] from user-configured records
→ Creates NdefMessage(ndefRecordArr)
→ Calls uh2.f(Ndef, NdefFormatable, needsFormat, ndefMessage)
The uh2.f() method failed to decompile (JADX couldn't handle it), but the LLM inferred its behavior from the parameters and control flow:
- If the tag already supports NDEF →
Ndef.writeNdefMessage(ndefMessage) - If the tag needs formatting (blank MIFARE Classic) →
NdefFormatable.format(ndefMessage)— formats the card AND writes the NDEF message in one step
Finding the keys — The exact authentication keys the app tries:
MifareClassic.KEY_DEFAULT // FF FF FF FF FF FF
MifareClassic.KEY_NFC_FORUM // D3 F7 D3 F7 D3 F7
MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY // A0 A1 A2 A3 A4 A5
The Discovery
This was the breakthrough. The LLM's analysis concluded:
"The app uses standard Android NDEF APIs to write to MIFARE Classic. It does NOT write raw blocks with a custom format. It creates standard
NdefRecordobjects, wraps them inNdefMessage, and writes viaNdefFormatable.format()."
The root cause: My app was writing custom data to raw MIFARE Classic blocks using a magic header — an approach the LLM itself had designed before we decompiled anything. The reference app was using standard NDEF — a well-documented format with TLV encoding (Type-Length-Value) in the user data sectors.
We were reinventing the wheel. And the wheel already existed. Even worse: the LLM had guided us away from the standard and into a dead end.
To be honest: this was an easy problem solved in a hard way. But there was a silver lining. The bit-level insights I gained during this process accidentally gave me the blueprint for a much more advanced feature: blocked card recovery. I'll save the technical breakdown of that recovery process for a different post, — as this story is specifically about the power of using an LLM to navigate the maze of decompiled code.
The capability that made this possible — navigating hundreds of obfuscated files, tracing data flow across renamed classes, inferring behavior from API signatures — that's real. For genuinely hard reverse engineering problems (complex state machines, undocumented protocols, hardware-specific behavior), this same workflow saves days of manual analysis.
Without an LLM
What would this look like without the decompilation? Honestly, not great for this particular case — because the answer was "use the standard," and the standard is well-documented. A few hours of focused web searching would have gotten there.
But for genuinely hard reverse engineering problems — undocumented protocols, complex state machines, hardware-specific behavior — the manual approach looks like this:
- Read the obfuscated code by hand — hundreds of files with names like
uh2.java, tracing method calls across class boundaries with a text editor and mental stack - Use reverse engineering tools — JADX's search, ClassyShark, or Ghidra to map class hierarchies and find cross-references
- Trace the Android framework — Read AOSP source to understand what
Ndef.writeNdefMessage()actually does under the hood - Iterate — Each hypothesis requires re-reading different parts of the codebase, following dead ends, backtracking
For this project, the LLM did all four in 12 reasoning steps. For harder problems — a custom binary protocol, a hardware abstraction layer, a proprietary encryption scheme — the same workflow compresses days into hours.
⚠️ Before You Try This
Safety: Use a Closed Environment
This is critical. When you feed decompiled code to an LLM agent, it has access to:
- The entire source directory — every file, every class
- Your device logs —
adb logcatoutput, system messages - File system contents — anything the agent can read
- Clipboard contents — if the agent has clipboard access
I ran this entire process on a dedicated development phone — a factory-reset device with no personal accounts, no photos, no messages, no banking apps. Only test cards and dummy data.
The rule: Never feed your personal phone to an LLM agent for reverse engineering. The LLM is doing exactly what you asked: reading files. Make sure it's only reading the files you want it to see.
Ethics: Should You Do This?
This is the question I asked myself. Here's what I did and why I believe it's defensible.
What I did:
- Downloaded a free app from the Play Store
- Installed it on my device
- Used
adbto extract the APK - Decomplied it with JADX
- Analyzed the decompiled code with an LLM
- Applied the understanding (not the code) to my own app
The legal framework:
- US — DMCA §1201(f): Permanent exception for reverse engineering when done for interoperability on lawfully obtained software.
- US — Sega v. Accolade (1992): 9th Circuit ruled disassembly is fair use when it provides the only access to unprotected functional elements and the copier has a legitimate reason.
- EU — Directive 2009/24/EC Article 6: Explicitly permits decompilation for interoperability. Contractual provisions contrary to this are null and void.
- 2024 DMCA Triennial Exemptions: Renewed good-faith security research exemption, expanded device repair protections.
Free vs. paid — does it matter?
Legally: copyright applies equally. Price doesn't protect more or less.
Ethically: there's a meaningful distinction. Reverse engineering a free app to understand how it implements a public standard (like NDEF) is the classic interoperability scenario — defensible. Reverse engineering a paid app to clone its features and ship a competing product is infringement.
The line: Are you learning the standard, or copying the expression?
The Catch
The LLM identified the problem in 15 minutes. But implementing the fix still required:
- Restructuring the NFC write flow — Moving from raw block writes to NDEF formatting
- Handling real cards — Physical MIFARE Classic cards behave differently depending on manufacturer, vintage, and whether they've been previously formatted
- Debugging authentication failures — Some sectors use non-standard keys
- Edge cases — Cards that are already NDEF-formatted vs. blank cards vs. cards with custom data
Total implementation time: Several hours over a couple of days.
The analysis was fast. The implementation wasn't — but it was focused. Instead of experimenting blindly, I was implementing a known-correct pattern. That's the difference between "I think this might work" and "I know this works, now I need to make it work on my hardware."
Takeaways
What LLMs Excel At
- Navigating obfuscated code — Tracing data flows through renamed classes like
uh2.javaandyu.java - Pattern recognition — Identifying standard API usage in decompiled code
- Root cause analysis — Comparing your approach against a reference implementation
- Following the developer's framing — Especially dangerous; verify assumptions, not just conclusions
Where Humans Are Still Needed
- Hardware debugging — Physical cards, RF field testing, antenna positioning
- Safety decisions — Which device to use, what data to expose
- Ethics judgment — Free vs paid, learning vs copying, responsible disclosure
- Implementation — The LLM found the problem; you still fix it
The Pattern
- Let the LLM try first — Give it the problem, see what it can do
- Verify its claims — Especially about hardware limitations
- When it hits a wall, pivot — "Can you analyze how this other app does it?"
- Use a safe environment — Dedicated dev phone, no personal data
- Extract understanding, not code — Learn the standard, implement your own version
- Budget time for implementation — The LLM compresses research, not debugging
Appendix: Session Metadata
This blog post was based on analysis of 6 OpenCode sessions:
| Session | Topic | Parts | Duration | Key Contribution |
|---|---|---|---|---|
| Explore codebase | Initial NFC architecture analysis | 41 | ~33s | Mapped existing NFC read-only architecture |
| Search MIFARE keys | Web research on community keys | 47 | ~2min | 30+ documented keys with citations |
| Trace NFC read flow | Cross-screen NFC usage audit | 34 | ~15s | Found antenna-release bug in push-mode screens |
| Analyze decompiled APK | Reverse engineering the reference app | 73 | ~3min | Core decompilation analysis — NDEF write/read flow |
| Fix locked card | MIFARE Classic Access Bits diagnosis | 11 | — | Card lock recovery guidance |
| Full architecture exploration | Comprehensive codebase mapping | 96 | — | Complete NFC architecture + dev menu structure |
This project used two free-tier models via OpenCode: DeepSeek (deepseek-v4-flash-free) for the initial app development and some delegated research tasks, and big-pickle for the APK decompilation analysis and NFC flow debugging.
This post was written collaboratively between a human developer and an LLM assistant. The sessions that produced the technical analysis were conducted in a closed development environment with no personal data present.