Patching Smali Files
Markdown--- name: patching-smali-files description: Reads, analyzes, modifies, and validates Android .smali files against a natural-language patch instruction, producing error-free patched smali with full round-trip validation, a detailed patch report, and metadata. Use when the user uploads .smali files and asks to inject code, force a return value, bypass a check, remove logic, or otherwise modify decompiled Android bytecode. Never delivers patched output if any validation error or warning exists. ---
Upload one or more .smali files and describe the patch (e.g. "force isPremium() to always return true"). The skill analyzes the target method, generates a register-safe patch, round-trips it through the assembler/disassembler for validation, and returns patched_output.zip (patched files + originals + report + logs) — only if validation passes with 0 errors and 0 warnings. On failure, only the report and logs are returned, never the ZIP.
Inputs:
smali_files(required) — one or more.smalifilespatch_instruction(required) — natural language description of the desired changepreferred_style(optional, defaultminimal) —minimal(no comments) orverbose(inline comments)max_attempts(optional, default5) — retry budget for validation fixesallow_creative_solution(optional, defaulttrue) — iffalse, pause on any ambiguity instead of choosing
Outputs:
patched_output.zip— delivered ONLY onPASSED:/patched/,/originals/,patch_report.txt,metadata.json,validation_logs/patch_report.txt— always deliveredmetadata.json— always deliveredvalidation_logs/— always delivered
- Round-trip (smali → dex → baksmali → smali → dex) MUST yield 0 errors and 0 warnings, or nothing is delivered except report+logs.
- Only requested files/methods are modified; any extra change is explicitly justified in the report.
patch_report.txtincludes before/after snippets, reasoning, assumptions, and validation summary for every touched method.- Originals preserved verbatim under
/originals/. metadata.jsonincludes SHA-256 checksums for originals, patched files, and the final ZIP.
- MUST update
.localswhenever new registers are introduced. - MUST NOT deliver
patched_output.zipif any error/warning exists anywhere in validation logs. - MUST NOT reuse a register that is live or needed later on any control path.
- MUST NOT insert instructions after an executed
return/throwon the same path (no dead code injection unless explicitly retained as documented unreachable code). - MUST NOT circumvent DRM, licensing, or violate applicable law — refuse instead.
- MUST log exact tool versions and exact shell commands verbatim per step.
- MUST NOT modify method signatures, class headers, or field declarations unless explicitly required by the instruction.
- MUST preserve existing exception handlers unless explicitly targeted.
- MUST prefix injected labels uniquely:
:patch_<job_id>_<n>. - MUST document every assumption when the instruction is ambiguous or underspecified.
Progress:
- Step 1: Parse & normalize instruction
- Step 2: Pre-check & setup (checksums, toolchain versions, working copies)
- Step 3: Static analysis (CFG, registers, exception ranges, insertion point)
- Step 4: Generate & insert patch
- Step 5: Round-trip validation (mandatory, with retry loop)
- Step 6: Smoke tests (if provided)
- Step 7: Package output + write report/metadata
- Step 8: Deliver (ZIP only if PASSED)
Step 1 — Parse & Normalize
Map instruction verbs to canonical ops: inject, replace, remove, change-return, add-hook, replace-conditional. Classify ambiguity:
- LOW — single unambiguous target → proceed.
- MEDIUM — multiple candidates or unclear insertion point → if
allow_creative_solution=true, pick using the candidate resolution priority (exact descriptor match > visibility > param similarity > file order) and document; else pause and ask. - HIGH — sweeping language ("remove all checks", "disable everything") → ALWAYS pause for explicit confirmation, regardless of
allow_creative_solution.
If a clause maps to zero methods, report as NO-OP and list available signatures.
Step 2 — Pre-check & Setup
- Compute SHA-256 for every input file; verify it parses (
.classheader + matched.method/.end method). - Log tool versions:
smali --version,baksmali --version,java -version→validation_logs/00_toolchain.log. - Copy originals verbatim to
/originals/; all edits happen on working copies only.
Step 3 — Static Analysis
- Extract class header, fields, method signatures.
- For each target method: current
.locals, full register map, CFG with basic blocks/branches/returns, exception handler ranges, and live-register set at each candidate insertion point (forward data-flow: a register is live if read on some path before being overwritten). - Determine exact insertion point per action type (inject-at-start, inject-before-return[s], replace, replace-conditional) and document it.
Step 4 — Patch Generation & Insertion
- Generate snippet per
preferred_style. - Compute new registers needed; increment
.localsby exactly that amount, allocating from the highest unused index downward to avoid clobbering live/parameter registers. - Insert at the determined point, matching indentation, with unique
:patch_<job_id>_<n>labels. - Run static sanity checks before assembling: label uniqueness/resolution, valid type descriptors,
.locals≥ max register used, no post-return instructions on the same path. Auto-correct failures before proceeding.
Step 5 — Round-Trip Validation (MANDATORY)
smali assemble -o patched.dex patched_smali/→01_assemble.logbaksmali disassemble -o roundtrip_smali/ patched.dex→02_disassemble.logsmali assemble -o roundtrip.dex roundtrip_smali/→03_reassemble.log- Diff patched regions only (
patched_smali/vsroundtrip_smali/), ignoring cosmetic normalization →04_diff.log - On any error/warning: diagnose (see Troubleshooting), auto-fix, increment attempt counter, re-run 1–4. Log each attempt as
attempt_<N>_*.log. Ifmax_attemptsreached without a clean pass → mark FAILED, skip to Step 7 (report/logs only).
Step 6 — Smoke Tests (if applicable)
Run any user-supplied tests/verifier against the patched dex. Log to 05_smoke_tests.log / 06_verifier.log. Failures here are flagged as WARNING in the report but do not block delivery on their own (only round-trip errors/warnings block delivery).
Step 7 — Packaging & Reporting
Build the ZIP structure exactly as specified in Outputs. Generate patch_report.txt (see Report Template) and metadata.json with job id, timestamp, status, tool versions, per-file checksums/methods/line deltas, zip checksum, attempt count, and allow_creative_solution.
Step 8 — Delivery
- PASSED → deliver ZIP + report + metadata.
- FAILED → deliver report + metadata + logs only, never the ZIP. Report MUST include a numbered
remediation_stepssection and the last unvalidated patched file labeledUNVALIDATED — DO NOT USE IN PRODUCTION.
Example 1 — Inject at method start
Input: "inject a write to debug_log.txt at the start of onCreate" on Lcom/example/MyActivity;->onCreate(Landroid/os/Bundle;)V
Output: .locals raised 2→5, try/catch-wrapped FileWriter write inserted after the locals directive and before invoke-super, unique :patch_abc123_* labels, PASSED after 1 attempt. Assumptions logged: external storage permission assumed present; IOException swallowed silently.
Example 2 — Force return value
Input: "force isPremium() to always return true"
Output: Entire method body replaced with const/4 v0, 0x1 / return v0; .locals reduced 4→1; original validation call removed and documented as an explicit, intentional deletion. PASSED after 1 attempt.
Example 3 — Bypass conditional check
Input: "bypass license check in checkLicense(Ljava/lang/String;)Z"
Output: if-eqz v0, :cond_fail branch removed, unconditional return v1 (true) inserted; :cond_fail block retained as documented dead code to avoid dangling label references. PASSED after 1 attempt.
Example 4 — Remove a guard (risk-flagged)
Input: "remove the null check at the top of processData(Ljava/lang/Object;)V"
Output: if-eqz p1, :cond_null removed; :cond_null retained as dead code; report flags MEDIUM runtime NPE risk and states the user is responsible for call-site safety. PASSED after 1 attempt.
- Always run the full LOW/MEDIUM/HIGH ambiguity check before touching any file — HIGH-risk phrasing always requires confirmation, no exceptions.
- Allocate new registers from the top down; never touch a register proven live by data-flow analysis.
- Keep diffs minimal — the report must justify every line changed beyond the literal instruction.
- Treat round-trip validation as non-negotiable: it is the sole gate for ZIP delivery, independent of how "obviously correct" a patch looks.
- Retain unreachable code (e.g., orphaned
:cond_failblocks) instead of deleting it when other references might exist — safer than risking a dangling label.
- Forgetting to bump
.localsafter adding registers →Verifier error: .locals count too low. - Reusing a live register for injected temporaries → corrupts unrelated program state.
- Inserting code after a
return/throwwithout a reachable label/goto → dead code / unreachable instruction errors. - Colliding injected label names with existing ones →
Duplicate labelerrors. - Treating baksmali's cosmetic re-formatting as a real diff mismatch — compare opcodes/registers, not whitespace.
- Delivering the ZIP "because it looks fine" without completing the full 4-log round-trip — never skip validation steps 5.1–5.4.
- Silently resolving HIGH-risk ambiguous instructions ("remove all checks") instead of pausing for confirmation.