Skip to content
Home/Blog/Static APK Triage: Reverse-Engineering Android Mal...
Back to blog

Static APK Triage: Reverse-Engineering Android Malware Without Running It

2026-07-08

9 min

androidreverse-engineeringmalware

I built a toolkit called apktriage because I got tired of the same slow first hour with every suspicious Android app. You get an APK, you want to know quickly whether it is boring or dangerous, and you do not want to detonate it on a real device to find out. Static triage answers most of that question in minutes, and it does it without ever executing the sample. This is how I approach it.

The useful mental model is that an APK is just a ZIP file with a strict layout. Rename it to .zip, unpack it, and the whole app is right there: a manifest, the compiled code, resources, native libraries, and the signing block. Almost everything I need for a first verdict is static, sitting in those files, waiting to be read.

The manifest tells you the app's intentions

AndroidManifest.xml is where I always start, because it is a declaration of what the app wants to be able to do. In an installed APK it is stored in a binary format, so I parse it with androguard rather than reading it raw. Three things matter most:

  • Permissions. Not any single permission, but the combination. An app that requests READ_SMS, RECEIVE_SMS, SEND_SMS, and internet access is telling you it can read your one-time passcodes and exfiltrate them. SMS plus accessibility service plus device-admin is the classic banking-trojan silhouette.
  • Exported components. Activities, services, and receivers marked exported are the app's attack surface to other apps on the device. An exported component with no permission guard is worth a closer look, both for what the app exposes and for how it can be triggered.
  • Intent filters. These reveal what the app hooks into: SMS received, boot completed, accessibility events. BOOT_COMPLETED plus a background service is how something quietly persists across reboots.

None of these is malicious on its own. The triage skill is reading the shape they form together.

The signing certificate is an identity clue

Every APK is signed, and the certificate in the signing block is a small piece of provenance. I pull the signer and its hash early because it clusters samples fast. Malware families reuse signing keys across builds, so a certificate that matches a known-bad set, or a self-signed cert impersonating a legitimate developer, is an immediate signal. A test-key or debug certificate on something claiming to be a real banking app is another.

The code: androguard for DEX, LIEF for native

The compiled Java/Kotlin lives in one or more DEX files, and androguard lets me walk it statically: classes, methods, and, most usefully, the cross-references to sensitive Android APIs. I am not reading every method. I am asking targeted questions. What calls DexClassLoader or loadClass? That is dynamic code loading, a way to hide the real payload from static analysis by fetching it at runtime. What uses reflection heavily? What touches the accessibility APIs, the SMS content provider, or the crypto libraries? Those cross-references draw a map of the app's real capabilities regardless of what its UI claims.

Native code gets the same treatment with LIEF. A lot of Android malware pushes its interesting logic into a bundled .so to dodge Java-level analysis, so I parse the ELF: imported functions, exported symbols, and suspicious strings. An app with an unremarkable Java layer and a dense, stripped native library is often hiding its intent one level down.

Strings, URLs, and the obfuscation tell

The cheapest high-value signal is strings. Hardcoded URLs and IP addresses hint at command-and-control or exfiltration endpoints. Base64 blobs, embedded certificates, and shell-command fragments all show up in a plain strings pass. And the absence of readable strings is itself a signal: when class and method names are mangled to a.a.a and the strings are all encrypted, the app is spending real effort to be unreadable, and benign apps rarely bother.

YARA turns judgment into a repeatable rule

Once I have seen a pattern a few times, I do not want to rediscover it by hand every time. This is where YARA comes in, and specifically the DEX-aware variant (yara-python-dex) so rules can match on Dalvik structure, not just raw bytes. A rule can encode "requests SMS permissions AND references the accessibility service AND loads code dynamically" and then flag every future sample that fits. That is the same instinct as writing a detection rule on the defensive side: capture the analyst's reasoning once, in a form a machine can apply at scale.

Why static triage is a defensive tool

I frame apktriage as offense-adjacent, but its whole purpose is defensive: sort the flood, surface the samples that deserve a human and a sandbox, and clear the boring ones fast. The output is meant to feed detection and response, not to build anything. Every signal it extracts (dangerous permission sets, dynamic loading, suspicious signers, hardcoded infrastructure) is also a detection idea. The permission combination that makes me suspicious of a sample is the same combination a mobile EDR or an app-vetting pipeline should score.

Static analysis has honest limits. A determined sample decrypts its real payload at runtime, or gates its behavior behind a check for a real SIM or a specific victim, and none of that shows up until you run it. So static triage is the first filter, not the verdict. But as a first filter it is fast, safe, and cheap to run at volume, and it means the dynamic analysis budget goes to the samples that actually earn it.

What building the toolkit taught me

The lesson that stuck was that reverse engineering, at the triage stage, is mostly disciplined reading. You are not defeating anticheat or cracking crypto. You are asking an app to declare its intentions and then checking whether its code backs up its story. The manifest says what it wants, the certificate says who built it, the DEX and native code say what it can actually do, and the gaps between those three are where the interesting samples live. apktriage just makes that reading fast enough to do at scale.

Resources

  • apktriage (my static APK triage toolkit): https://github.com/gpamarthy/apktriage
  • androguard: static analysis of APK and DEX
  • LIEF: parsing and analysis of native (ELF) libraries
  • yara-python-dex: YARA with Dalvik/DEX awareness for Android rules
  • Android developer docs on permissions and app components (the defender's reference for what each capability means)