Saw
Saw is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
Saw attack path
Saw is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Objective
Challenge walkthrough focused on Mobile evidence, validation, and reusable operator lessons.
Walkthrough flow
Decompile the APK with jadx and identify the...
Trace the activity to a registered JNI method in...
Reverse the native input check: two 8-dword tables...
Reverse the native dynamic-code generation: the...
Confirm the generated output is a Dalvik DEX file.
Source coverage
High source coverage
Status: complete. This article is generated from 4 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.
High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.
- Mobile/Saw/writeup.md
- htb-challenge/Mobile/Saw/notes.md
- htb-challenge/Mobile/Saw/memory-summary.md
- htb-challenge/Mobile/Saw/hypothesis-board.md
Technical Walkthrough
Writeup
Challenge
- Name: Saw
- Category: Mobile
- Difficulty: Medium
- Mode: file
Summary
The APK hides its real payload behind a native method. The Java activity only
loads libdefault.so, asks for an XOR-themed value, and passes that value plus
the app data directory into native code. Reversing the native library shows it
validates an 8-byte trigger, decodes a dword table into a DEX file, and writes
that DEX as h. Extracting the generated DEX reveals the flag.
Artifact Inventory
The ZIP contains:
SAW.apk: Android application sample.README.txt: runtime note saying API 29+ is expected.
Inside the APK, the relevant artifacts are:
classes.dex: containscom.stego.saw.MainActivity.lib/*/libdefault.so: native loader/decoder used byMainActivity.lib/*/libnative-lib.so: extra native library, not needed for the final
static solve.
Analysis
MainActivity requires the intent extra open=sesame; otherwise it exits.
When triggered, it creates overlay buttons and eventually shows a dialog titled
XOR XOR XOR. The dialog value is passed into native method
a(dataDir, answer).
libdefault.so registers that native method for MainActivity. The x86_64
build is enough to analyze statically. It contains two 8-dword tables named
l and m. The native method checks each byte of the dialog value as:
candidate_byte XOR l[i] == m[i]Solving those eight byte equations gives the required trigger value. Once the
check passes, the function decodes the jni_def dword table by XORing each
dword with 0x64 and writing the low byte stream to a file named h under the
app data directory. The resulting file starts with dex\n035, confirming a
generated Dalvik DEX payload.
The generated DEX contains the final flag string. The raw generated DEX is
stored only in loot/generated-h.dex; redacted strings are stored in
analysis/native/generated-h-redacted-strings.txt.
Solve
Run:
python3 Mobile/Saw/solve/solve.pyThe script:
- Extracts
lib/x86_64/libdefault.sofromSAW.apk. - Parses the ELF section and dynamic symbol tables.
- Recovers the trigger value from the
landmtables. - Decodes
jni_definto the generated DEX by applying XOR0x64. - Extracts the flag from the generated DEX and writes it to
loot/flag-candidate.txt.
- Writes only redacted strings under
analysis/native/.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- Android dynamic code loading challenges can often be solved statically if the
generated DEX is embedded as native data.
- JNI registration and exported native symbols can reveal the app-owned path
even when Java code is minimal.
- Keep decoded DEX files under
loot/if they contain raw flag strings; write
only redacted analysis views outside loot/.
Source-Backed Dossier
The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.
Notes
Scope
- Challenge: Saw
- Category: Mobile
- Difficulty: Medium
- Mode: file
- Remote instance: none
- Start time: 2026-06-13T09:46:13Z
- Operator: harness
- State file:
challenge-state.json
Harness Status
- Current phase: see
challenge-state.json - Next allowed actions: see
next-action.json - Raw flags and sensitive material stay in
loot/only. Do not paste them here.
Artifact Inventory
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c7383-68b4-462f-ad3a-e0f67b27b8c7.zip | 1392577 | <hash redacted> | Zip archive data, at least v2.0 to extract, compression method=deflate | zip entries: 2 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-13T09:46:13Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-13T09:46:26Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-13T09:46:26Z | hypothesis recorded | hypothesis-board.md | Statically inspect the APK for dynamic code loading, hidden DEX/assets/native payloads, and the final flag-generation path. | Medium | Extract archive, identify APK contents, run jadx, and search for DexClassLoader/PathClassLoader/loadClass/Base64/AES/reflection/HTB strings. |
| 2026-06-13T09:46:26Z | research task | analysis/research/task-20260613T094626457106Z-5f227e0a.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-13T09:49:19Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-13T09:49:19Z | instrumentation plan | analysis/instrumentation-plan.md | Recover the dynamically generated DEX and flag without installing or executing the APK. | High | Stop after static decode if generated DEX is invalid or has no flag; request APK/runtime tooling only if static extraction fails. |
| 2026-06-13T09:49:19Z | checkpoint recorded | analysis/checkpoint-analysis-20260613T094919955475Z-08d17b02.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-13T09:49:36Z | RAG query | analysis/rag/rag-query-20260613T094928199657Z-a1b5158a.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-13T09:49:47Z | research record | analysis/research/research-records.md | Research tagged MATCHED | Medium | Validate against current evidence |
| 2026-06-13T09:49:47Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-13T09:50:09Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-13T09:50:09Z | evaluator | analysis/evaluator-20260613T095009218329Z-1314e054.md | Proceed | High | Capture flag candidate from loot and complete the challenge. |
| 2026-06-13T09:50:17Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-13T09:50:59Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
README.txtsays to install on Android API 29+, but static analysis was
enough; no emulator was required.
MainActivityrequires an intent extraopen=sesame, shows overlay buttons,
asks for input in an "XOR XOR XOR" dialog, then calls native method
a(dataDir, answer) from libdefault.so.
libdefault.sovalidates an 8-byte input using two 8-dword tables: each input
byte XORs one table and must match the other. The recovered UI input is
fl0ating.
- After the input check passes,
libdefault.sodecodesjni_defby XORing each
dword with 0x64 and writes the low byte stream as a DEX file named h under
the app data directory.
- The decoded payload is a valid Dalvik DEX (
dex\n035) and contains the final
flag material. Raw decoded DEX and raw flag candidate are stored only under
loot/.
solve/solve.pyreproduces this statically fromSAW.apk, writes
loot/generated-h.dex, writes loot/flag-candidate.txt, and writes only
redacted DEX strings under analysis/native/.
RAG / Advisory Memory
RAG output is advisory only. Record evaluated retrievals with:
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."Secrets/Flags
Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.
Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Mobile
- Challenge: Saw
- Difficulty: Medium
- Source workspace:
<local workspace>
Validated Solve Chain
Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.
- Decompile the APK with jadx and identify the app-owned activity.
- Trace the activity to a registered JNI method in
libdefault.so. - Reverse the native input check: two 8-dword tables produce an 8-byte trigger
value by XOR.
- Reverse the native dynamic-code generation: the
jni_defdword table is
decoded by XORing each entry with 0x64 and writing the low byte stream.
- Confirm the generated output is a Dalvik DEX file.
- Extract the flag from the generated DEX, keeping raw decoded DEX and raw flag
material in loot/ only.
Reusable Lessons
- For mobile challenges with "dynamic code" wording, inspect Java-to-JNI
boundaries before attempting emulator instrumentation.
- Android native libraries can hide generated DEX data as integer tables rather
than normal assets.
- If a generated payload contains flag material, keep it under
loot/and make
redacted analysis artifacts for documentation.
Dead Ends
- Full emulator/runtime instrumentation was not needed.
libnative-lib.sowas not needed for the final solve;libdefault.so
contained the loader/decoder path.
Tool Quirks
jadxwas available and sufficient for Java triage.apktool,adb,frida,objection, andghidrawere missing, but the
final solve did not require them.
- Local
objdumpcould disassemble the x86_64 Android ELF enough to validate
the native logic.
Evidence Paths
files/extracted/README.txtfiles/extracted/SAW.apkanalysis/jadx/sources/com/stego/saw/MainActivity.javaanalysis/native/decode-summary.txtanalysis/native/generated-h-redacted-strings.txtanalysis/source-audit.mdanalysis/instrumentation-plan.mdsolve/solve.pyloot/generated-h.dexloot/flag.txt
Ingestion Decision
- Proposed for LightRAG: yes
- Requires user approval before ingestion: yes
Hypothesis Board
Keep no more than 3 active hypotheses on Easy/Medium and 5 on Hard unless the user explicitly asks for breadth.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | Statically inspect the APK for dynamic code loading, hidden DEX/assets/native payloads, and the final flag-generation path. | Scenario says the sample hides and executes code dynamically; Mobile category ZIP likely contains an APK or Android package. | Extract archive, identify APK contents, run jadx, and search for DexClassLoader/PathClassLoader/loadClass/Base64/AES/reflection/HTB strings. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|
Technical analogy
How to remember this solve
Think of the app like a packed suitcase. You unpack it, inspect the labels and hidden pockets, then trace which local file or network call contains the useful clue.
For Saw, keep the mental model simple: identify the trusted assumption, prove it with the smallest safe test, then automate or repeat only the part that directly leads to the flag.