An in-house Android APK hardening / reinforcement toolkit — the same class of tool as 360 Jiagu. It takes a built APK, pulls out and encrypts its DEX behind a native stub loader, repoints the manifest at that loader, applies runtime self-protection (RASP), and re-signs the result — so the shipped APK is resistant to decompilation, repackaging, and runtime tampering.
Runtime-validated on a real device (Redmi, Android 14, arm64): a hardened app boots through the stub, decrypts its DEX in memory, and runs identically to the original.
- Per-feature documentation:
docs/ - Feature status matrix:
FEATURES.md - 360 Jiagu analysis & sample-APK audit:
JIAGU-ANALYSIS.md - Roadmap: see below
- Architecture
- Prerequisites
- Installation & setup
- Quick start
- CLI reference
- Protection profiles
- REST service
- How it works
- Verifying on a device
- Roadmap
- Limitations & caveats
- Legal
The build-time packer is JVM (Kotlin). The on-device protection shell must be native — it runs before the JVM app code, does anti-debug, decrypts the DEX in memory, and hooks the runtime. So the project is polyglot by necessity.
| Module | Language | Runs | Role |
|---|---|---|---|
packer-core |
Kotlin | server | the hardening pipeline (library) — DEX crypto, binary-AXML manifest patch, zip repackage, sign |
packer-service |
Kotlin + Spring Boot 4 | server | REST API: upload → harden → download |
packer-cli |
Kotlin + Clikt | server | command-line packer |
shell-native |
C++ / NDK | phone | libcyberarmor.so — anti-debug, anti-ptrace, RASP detectors, PLT hooker, transparent asset decryption |
shell-stub |
Java → DEX | phone | StubApp bootstrap loader + CyberArmorAssets API |
flowchart LR
A([Input APK]) --> B[Extract DEX]
B --> C[AES-256-GCM<br/>encrypt]
C --> D[Patch manifest<br/>→ StubApp]
D --> E[Inject stub +<br/>native shell + payload]
E --> F[zipalign]
F --> G[apksigner<br/>v1+v2+v3]
G --> H([Hardened APK])
subgraph RT [on-device at launch]
direction LR
I[StubApp] --> J[RASP guards]
J --> K[Decrypt DEX<br/>in memory]
K --> L[Inject into<br/>classloader]
L --> M[Run real app]
end
H -.-> I
| Tool | Version used | Notes |
|---|---|---|
| JDK | 21 | Spring Boot 4 baseline; JAVA_HOME set |
| Android SDK build-tools | 36.1.0 | provides zipalign, apksigner, d8, aapt2 |
| Android NDK | 27.x | builds the native shell |
| CMake + Ninja | 3.22 (bundled in SDK) | native build |
| Gradle | 8.14.3 (via wrapper) | ./gradlew — do not use a system Gradle < 8.14 |
The Gradle wrapper pins Gradle 8.14.3 (Spring Boot 4 requires ≥ 8.14). No global Gradle needed.
The pipeline shells out only to SDK zipalign/apksigner/d8; apktool is not required
(manifest editing is done with a built-in binary-AXML editor, resources are never re-encoded).
Create local.properties in the repo root:
sdk.dir=/home/<you>/Android/Sdk
cyberarmor.buildToolsVersion=36.1.0
cyberarmor.ndkVersion=27.1.12297006(Alternatively export ANDROID_HOME. The build-tools/NDK versions auto-detect to the newest
installed if omitted.)
./shell-native/build-native.sh # -> shell-native/build/<abi>/libcyberarmor.so (4 ABIs)
./shell-stub/build-stub.sh # -> shell-stub/build/classes.dex./gradlew build # compiles + tests packer-core / service / cli
./gradlew :packer-cli:installDist # produces the CLI launcherA hardened APK is always re-signed, so you need a key you control:
keytool -genkeypair -v -keystore keys/cyberarmor.jks -storepass changeit \
-alias cyberarmor -keypass changeit -keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=CyberArmor"Use your real release key for production. Re-signing changes the app's signing identity — coordinate the key with whoever publishes the app.
Harden an APK with the recommended default protection profile:
./packer-cli/build/install/cyberarmor/bin/cyberarmor \
-i app-release.apk \
-o app-hardened.apk \
--stub-dex shell-stub/build/classes.dex \
--shell-arm64 shell-native/build/arm64-v8a/libcyberarmor.so \
--shell-arm32 shell-native/build/armeabi-v7a/libcyberarmor.so \
--ks keys/cyberarmor.jks --ks-pass changeit --ks-alias cyberarmorOutput is a signed (v1+v2+v3), aligned, hardened APK ready to install.
cyberarmor -i <input.apk> -o <output.apk> [options]
| Option | Description |
|---|---|
-i, --input <file> |
(required) raw APK to harden |
-o, --output <file> |
(required) destination for the hardened APK |
--stub-dex <file> |
(required) compiled StubApp dex (shell-stub/build/classes.dex) |
--shell-arm64 <file> |
libcyberarmor.so for arm64-v8a |
--shell-arm32 <file> |
libcyberarmor.so for armeabi-v7a |
--ks <file> |
signing keystore (omit to leave unsigned — debug only) |
--ks-pass <pw> |
keystore password |
--ks-alias <alias> |
key alias |
--protection <profile> |
default (recommended) · strict · none. See below |
--protect-asset <glob> |
encrypt matching assets, transparently decrypted at runtime. Repeatable. e.g. --protect-asset "assets/config/keys.json" |
--protect-so |
encrypt the app's own native libraries; transparently decrypted + loaded in memory, key bound to the signing cert |
--anti-ptrace |
opt-in: block/detect ptrace (ART-safe; disables native crash tombstones) |
--root-detection |
opt-in: refuse to run on rooted/Magisk devices |
--emulator-detection |
opt-in: refuse to run on emulators |
--double-open |
opt-in: detect app-clone / dual-instance containers |
--accessibility |
opt-in: detect enabled accessibility-service automation |
--log-suppress |
opt-in: silence the app's logcat output (PLT hook) |
--keep-work |
keep intermediate build files under work/cli |
Strictest profile + asset protection:
cyberarmor -i app.apk -o app-hardened.apk \
--stub-dex shell-stub/build/classes.dex \
--shell-arm64 shell-native/build/arm64-v8a/libcyberarmor.so \
--shell-arm32 shell-native/build/armeabi-v7a/libcyberarmor.so \
--protection strict \
--protect-asset "assets/*.json" \
--ks keys/cyberarmor.jks --ks-pass changeit --ks-alias cyberarmorPacking only (no runtime guards), useful for debugging a launch:
cyberarmor -i app.apk -o app-packed.apk --protection none \
--stub-dex shell-stub/build/classes.dex \
--shell-arm64 shell-native/build/arm64-v8a/libcyberarmor.so \
--ks keys/cyberarmor.jks --ks-pass changeit --ks-alias cyberarmor| Guard | none |
default |
strict |
|---|---|---|---|
| DEX encryption + in-memory load | ✓ | ✓ | ✓ |
| Re-signing (v1+v2+v3) | ✓ | ✓ | ✓ |
| Signature verification (anti-repackage) | ✓ | ✓ | |
| File-integrity verification | ✓ | ✓ | |
| Anti-debug (TracerPid) | ✓ | ✓ | |
| Anti-Frida / Xposed | ✓ | ✓ | |
| Screen-capture prevention (FLAG_SECURE) | ✓ | ✓ | |
| Anti-hijacking (obscured-touch) | ✓ | ✓ | |
| Anti-ptrace | ✓ | ||
| Root / emulator detection | ✓ | ||
| Double-open / accessibility detection | ✓ | ||
| Log suppression | ✓ |
Any strict-only guard can be added to default with its individual flag.
Asset protection is independent — add --protect-asset to any profile.
The Spring Boot 4 service exposes the same pipeline over HTTP. Configure server-side artifact
paths in packer-service/src/main/resources/application.yml (cyberarmor.stub-dex,
cyberarmor.native-shells, cyberarmor.signing), then:
./gradlew :packer-service:bootRun| Method | Endpoint | Purpose |
|---|---|---|
POST |
/api/harden (multipart apk) |
submit an APK → returns a job id (202) |
GET |
/api/harden/{id} |
job status + step report |
GET |
/api/harden/{id}/download |
download the hardened APK |
curl -F apk=@app-release.apk http://localhost:8080/api/harden # -> {"id": "...", ...}
curl http://localhost:8080/api/harden/<id> # status
curl -OJ http://localhost:8080/api/harden/<id>/download # hardened apkBuild time (packer-core): read the original classes*.dex and binary AndroidManifest.xml
straight from the APK zip → AES-256-GCM encrypt the DEX into assets/cyberarmor.payload (data
key wrapped by a KEK the native shell holds) → surgically patch the manifest's
application android:name to com.cyberarmor.stub.StubApp (resource-id-sorted binary-AXML edit;
resources are never touched) → repackage the zip with the stub dex, native shells, payload and
guard metadata → zipalign → apksigner.
Runtime (shell-stub + shell-native): StubApp.attachBaseContext loads
libcyberarmor.so, runs the enabled guards, verifies the signing cert and payload hash,
decrypts the DEX with the KEK from native, and injects it into the app's classloader
(InMemoryDexClassLoader merged into the base PathClassLoader) so the original code — including
manifest-declared components — resolves normally. Protected assets are decrypted and served
transparently through a native hook of AssetManager.nativeOpenAsset.
Size impact of hardening (default profile), measured on real sample APKs. The DEX is compressed before encryption, so the encrypted payload stays close to the original packaged size; the overhead is essentially the injected native shells (~1.5 MB across arm64 + arm32). Large apps can even end up smaller than the original when our max-compression beats the app's original packaging.
| App | Original | Hardened | Overhead |
|---|---|---|---|
| powrantv | 8.88 MB | 9.76 MB | +0.88 MB (+10.0%) |
| Thogaitv BoxApp | 10.98 MB | 12.03 MB | +1.05 MB (+9.6%) |
| CSM CHANNEL | 28.12 MB | 26.97 MB | −1.15 MB (−4.1%) |
Runtime guards (signature/integrity/anti-debug/anti-Frida/FLAG_SECURE/anti-hijack), SO protection,
and asset protection add negligible size — guards are metadata flags, and encrypted .so/asset
copies replace their originals in place. Reproduce with --protection none|default|strict,
--protect-so, --protect-asset.
Without pre-compression the same apps ballooned +74% to +177% (AES ciphertext is incompressible); compress-then-encrypt is what keeps hardened APKs distributable.
adb install -r -d app-hardened.apk
adb shell monkey -p <your.package> -c android.intent.category.LAUNCHER 1
adb logcat | grep -i CyberArmor # guard/init messages (unless --log-suppress)With --protection default/strict, FLAG_SECURE is on, so screenshots of the app appear
black — that is screen-capture prevention working, not a bug. Build with --protection none
if you need to screenshot the UI.
Detailed status per feature lives in FEATURES.md; each shipped feature has a
page under docs/. The program is organized in phases:
DEX AES-256-GCM encryption + in-memory loading, surgical binary-AXML manifest repoint,
raw-zip repackaging (resources never re-encoded), zipalign + apksigner (v1+v2+v3).
Signature verification (anti-repackaging), file-integrity check, unique per-build fingerprint,
anti-debug, anti-Frida/Xposed, FLAG_SECURE screen-capture prevention, anti-hijacking.
Transparent asset encryption (native AssetManager hook), root/emulator/double-open/accessibility
detection (opt-in), ART-safe anti-ptrace (opt-in), PLT/GOT hooker, log-leak prevention.
- ✅ SO protection / anti-theft (B1/B2) — encrypt the app's own
.so; native in-memory loader (memfd+android_dlopen_exthook), bound to the signing cert. Device-verified (--protect-so). Also landed: a robust custom-Application swap (Hilt/Dagger apps). - ✅ Local files protection (D2) —
CyberArmorStorageAPI: AES-256-GCM encrypted files + preferences under the app's private dir, key bound to the per-build DEX key. Device-verified. - ✅ Per-build KEK + native master-key crypto — unique KEK baked into the shell per build,
DEK unwrapped in native (
ca_crypto.h); the master key never enters the DEX/Java. - ⬜ String encryption (A2) — DEX-instruction rewrite:
const-string→ decrypt call. - ⬜ Transparent asset/SO hook hardening — multi-Android-version validation of the libandroidfw ABI coupling.
DEX VMP, lifecycle/registered-class VMP, Dex2C, QDEX, Dex Shadow, full/custom VMP, and
memory-tamper detection (code-section checksums). These require a custom bytecode VM and/or
native code generation — see docs/roadmap.md.
- Transparent asset protection is ABI-coupled to
libandroidfwsymbols (Asset::createFromFd,AssetManager2::Open) and validated on Android 14. It resolves symbols by name and no-ops safely if they're absent, but re-validate per major Android version. --anti-ptracedisables native crash tombstones (viaPR_SET_DUMPABLE(0)). Leave it off if the app ships an NDK crash reporter.- Master key (KEK): unique per build, patched into the native shell at hardening time and used only in native (it never appears in the DEX/Java). The per-build DEX key is unwrapped in native; the bulk DEX decrypt stays on Java's hardware-accelerated AES to avoid a main-thread stall on large apps.
- The advanced VMP / Dex2C / string-encryption tier is not implemented — see
FEATURES.md.
Only harden APKs your organization owns or is explicitly authorized to repackage and re-sign. Re-signing changes the app's identity and invalidates the original signature.

