Challenge / Mobile

Saw

Saw is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

MediumPublished 2025-02-14Sanitized local writeup

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.

Saw sanitized attack graph

Walkthrough flow

01

Decompile the APK with jadx and identify the...

02

Trace the activity to a registered JNI method in...

03

Reverse the native input check: two 8-dword tables...

04

Reverse the native dynamic-code generation: the...

05

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.

100% coverage
Evidence verdict

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: contains com.stego.saw.MainActivity.
  • lib/*/libdefault.so: native loader/decoder used by MainActivity.
  • 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:

text
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:

bash
python3 Mobile/Saw/solve/solve.py

The script:

  1. Extracts lib/x86_64/libdefault.so from SAW.apk.
  2. Parses the ELF section and dynamic symbol tables.
  3. Recovers the trigger value from the l and m tables.
  4. Decodes jni_def into the generated DEX by applying XOR 0x64.
  5. Extracts the flag from the generated DEX and writes it to

loot/flag-candidate.txt.

  1. 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

FileSizeSHA256TypeNotes
files/a12c7383-68b4-462f-ad3a-e0f67b27b8c7.zip1392577<hash redacted>Zip archive data, at least v2.0 to extract, compression method=deflatezip entries: 2 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-13T09:46:13Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-13T09:46:26Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-13T09:46:26Zhypothesis recordedhypothesis-board.mdStatically inspect the APK for dynamic code loading, hidden DEX/assets/native payloads, and the final flag-generation path.MediumExtract archive, identify APK contents, run jadx, and search for DexClassLoader/PathClassLoader/loadClass/Base64/AES/reflection/HTB strings.
2026-06-13T09:46:26Zresearch taskanalysis/research/task-20260613T094626457106Z-5f227e0a.mdResearch task created for advisory investigationMediumRecord research output
2026-06-13T09:49:19Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-13T09:49:19Zinstrumentation plananalysis/instrumentation-plan.mdRecover the dynamically generated DEX and flag without installing or executing the APK.HighStop 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:19Zcheckpoint recordedanalysis/checkpoint-analysis-20260613T094919955475Z-08d17b02.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-13T09:49:36ZRAG queryanalysis/rag/rag-query-20260613T094928199657Z-a1b5158a.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-13T09:49:47Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-13T09:49:47Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-13T09:50:09ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-13T09:50:09Zevaluatoranalysis/evaluator-20260613T095009218329Z-1314e054.mdProceedHighCapture flag candidate from loot and complete the challenge.
2026-06-13T09:50:17Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-13T09:50:59Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • README.txt says to install on Android API 29+, but static analysis was

enough; no emulator was required.

  • MainActivity requires an intent extra open=sesame, shows overlay buttons,

asks for input in an "XOR XOR XOR" dialog, then calls native method

a(dataDir, answer) from libdefault.so.

  • libdefault.so validates 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.so decodes jni_def by 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.py reproduces this statically from SAW.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:

bash
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.

  1. Decompile the APK with jadx and identify the app-owned activity.
  2. Trace the activity to a registered JNI method in libdefault.so.
  3. Reverse the native input check: two 8-dword tables produce an 8-byte trigger

value by XOR.

  1. Reverse the native dynamic-code generation: the jni_def dword table is

decoded by XORing each entry with 0x64 and writing the low byte stream.

  1. Confirm the generated output is a Dalvik DEX file.
  2. 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.so was not needed for the final solve; libdefault.so

contained the loader/decoder path.

Tool Quirks

  • jadx was available and sufficient for Java triage.
  • apktool, adb, frida, objection, and ghidra were missing, but the

final solve did not require them.

  • Local objdump could disassemble the x86_64 Android ELF enough to validate

the native logic.

Evidence Paths

  • files/extracted/README.txt
  • files/extracted/SAW.apk
  • analysis/jadx/sources/com/stego/saw/MainActivity.java
  • analysis/native/decode-summary.txt
  • analysis/native/generated-h-redacted-strings.txt
  • analysis/source-audit.md
  • analysis/instrumentation-plan.md
  • solve/solve.py
  • loot/generated-h.dex
  • loot/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.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Statically 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.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit 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.