From 1dcb360cdfd06f28c3782e770809c9bbdae33075 Mon Sep 17 00:00:00 2001 From: henry Date: Sun, 20 Sep 2026 23:29:56 +0200 Subject: [PATCH] Drop the pycryptodome hard-dep: built-in pure-Python AES fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A friend on Python 3.14 couldn't get pycryptodome to import (no cp314 wheel, and building it — like minilzo — needs MSVC). orchestrate.py only uses one AES-128 ECB block for the Dirty-Frag keystream, so it now has a self-contained pure-Python AES-128 fallback and uses pycryptodome only if already importable (a small speedup). - orchestrate.py: try pycryptodome -> `_aes128`; except ModuleNotFoundError -> embedded pure AES-128 (hardcoded S-box, key expansion, encrypt). Verified vs pycryptodome (3000 random blocks) and FIPS-197; fast-path and fallback produce identical keystreams; full inject-stub replay still reconstructs the carrier. - requirements.txt: running now needs NO pip packages; pycryptodome demoted to an optional speedup (so `pip install -r requirements.txt` can't fail on 3.14). - SETUP.md/README: pycryptodome marked optional; verify snippet reports it as such. Net: `python orchestrate.py ...` runs on any Python, no pip install, no C compiler. Co-Authored-By: Claude Opus 4.8 --- README.md | 4 ++-- SETUP.md | 18 ++++++++++-------- orchestrate.py | 49 +++++++++++++++++++++++++++++++++++++++++++----- requirements.txt | 19 +++++++++---------- 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index ef3813f..17cbc8d 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,8 @@ so only ~48 bytes need poisoning and the whole IV table fits libeva's existing g - `orchestrate.py` — the whole pipeline (build + stage + poison + trigger + verify + post-ex/cleanup); dispatches two-carrier (`run`) vs merged (`run_merged`) on `kernel.merged` - `SETUP.md` — external-dependency install for Linux & Windows + environment self-check -- `requirements.txt` — Python dep to RUN (`python -m pip install -r requirements.txt`); add-target - extra in `requirements-port.txt` +- `requirements.txt` — running needs NO pip packages (built-in pure-Python AES; pycryptodome + optional speedup); add-target extra in `requirements-port.txt` - `toolconf.py` — ONE place for all external tool paths (clang/objcopy/readelf/nm, NDK, payload-dumper, debugfs, vmlinux-to-elf); env vars override. Imported by the scripts below. - `port.py` — gather a new firmware's values → draft `targets/.json` (+ binaries); diff --git a/SETUP.md b/SETUP.md index e130dcb..9a0a4e5 100644 --- a/SETUP.md +++ b/SETUP.md @@ -10,7 +10,7 @@ the ones for what you'll run, then point `toolconf.py` (or env vars) at them. | Tool | Needed by | `toolconf.py` / env var | Notes | |------|-----------|-------------------------|-------| | **Python 3.8+** | everything | — | run scripts with `python` / `python3` | -| **pycryptodome** | `orchestrate.py` | — (pip) | provides `Crypto.Cipher.AES` (keystream) | +| **pycryptodome** | `orchestrate.py` (OPTIONAL) | — (pip) | speedup only; a pure-Python AES fallback is built in, so it's not required | | **adb** (platform-tools) | `orchestrate.py`, `port.py --device` | on PATH | Google platform-tools | | **LLVM/clang** (`clang`, `llvm-objcopy`, `llvm-readelf`, `llvm-nm`) | `orchestrate.py`, `build_credmod.py`, `port.py` | `AOSP_CLANG_BIN` (the `bin/` dir) | ≥ v14; AOSP clang **or** stock LLVM — only assembles AArch64 asm | | **payload-dumper-go** | `port.py` | `PAYLOAD_DUMPER` (full path to the binary) | extracts OTA partitions | @@ -18,7 +18,8 @@ the ones for what you'll run, then point `toolconf.py` (or env vars) at them. | **vmlinux-to-elf** | `port.py` | `VMLINUX_TO_ELF` (default: `vmlinux-to-elf` on PATH) | boot.img → vmlinux with symbols | | Android **NDK** | *only* rebuilding `e2e.dex`/native (optional) | `NDK_BIN` | `e2e.dex` is prebuilt & committed — skip unless changing the Java | -**Minimum to run the exploit** (`orchestrate.py`): Python + pycryptodome + adb + LLVM/clang. +**Minimum to run the exploit** (`orchestrate.py`): Python + adb + LLVM/clang. (pycryptodome is an +optional speedup — orchestrate.py falls back to a built-in pure-Python AES.) **To add a new firmware target** (`port.py`): also payload-dumper-go + debugfs + vmlinux-to-elf. --- @@ -27,7 +28,8 @@ the ones for what you'll run, then point `toolconf.py` (or env vars) at them. ```bash # Python libs -python3 -m pip install --user -r requirements.txt # RUN the exploit (pycryptodome) +# RUN the exploit needs NO pip packages (built-in pure-Python AES). Optional speedup: +python3 -m pip install --user pycryptodome # optional python3 -m pip install --user -r requirements-port.txt # only to ADD targets (vmlinux-to-elf) # distro packages @@ -59,9 +61,9 @@ Install (PowerShell; `winget` or manual downloads): ```powershell winget install Python.Python.3.12 winget install LLVM.LLVM # -> C:\Program Files\LLVM\bin (clang.exe, llvm-*.exe) -python -m pip install -r requirements.txt # RUN the exploit (pycryptodome only) -# python -m pip install -r requirements-port.txt # ONLY to add targets; its minilzo dep needs MSVC -# # build tools on Windows -> prefer porting under WSL +# RUN the exploit needs NO pip packages (built-in pure-Python AES). +# python -m pip install pycryptodome # OPTIONAL speedup (skip on Python 3.14 / no MSVC) +# python -m pip install -r requirements-port.txt # ONLY to add targets; minilzo needs MSVC -> use WSL ``` - **adb**: download *SDK Platform-Tools for Windows* from https://developer.android.com/tools/releases/platform-tools , unzip (e.g. `C:\platform-tools`), @@ -85,7 +87,7 @@ $env:DEBUGFS = "C:\cygwin64\bin\debugfs.exe" # only if not using WSL > `AOSP_CLANG_BIN` just needs to be the `bin` directory. > **Recommended split:** run **`orchestrate.py` natively on Windows** (needs only Python + -> pycryptodome + adb + LLVM — all easy on Windows), and do the occasional **`port.py` under WSL** +> adb + LLVM — no pip packages; pycryptodome optional), and do the occasional **`port.py` under WSL** > (where debugfs/vmlinux-to-elf/payload-dumper are trivial). Targets are just JSON + a few small > files, so build them once (either OS) and commit them. @@ -101,7 +103,7 @@ import toolconf as t def ok(label, path, run=None): found = os.path.isfile(path) or shutil.which(path) print(f" {'OK ' if found else 'MISSING'} {label}: {path}") -print("Crypto (pycryptodome):", "OK" if importlib.util.find_spec("Crypto") else "MISSING (pip install pycryptodome)") +print("pycryptodome (optional speedup):", "OK" if importlib.util.find_spec("Crypto") else "absent — using built-in pure-Python AES (fine)") print("adb:", "OK" if shutil.which("adb") else "MISSING") for lbl, p in [("clang", t.CLANG), ("llvm-objcopy", t.OBJCOPY), ("llvm-readelf", t.READELF), ("llvm-nm", t.NM), ("payload-dumper-go", t.PDG), ("debugfs", t.DEBUGFS), diff --git a/orchestrate.py b/orchestrate.py index bf0a6c2..8a1f718 100644 --- a/orchestrate.py +++ b/orchestrate.py @@ -21,12 +21,51 @@ # targets// and the JSON. No code changes. # ============================================================================================ import argparse, json, os, re, struct, subprocess, sys, time, threading +# AES-128 for the Dirty-Frag keystream. Fast path = pycryptodome; if absent, fall back to a +# self-contained pure-Python AES-128 (verified vs pycryptodome + FIPS-197) so the tool has NO hard +# third-party dependency and runs on any Python (Windows without a C compiler included). try: from Crypto.Cipher import AES + def _aes128(key16): return AES.new(key16, AES.MODE_ECB).encrypt # -> encrypt(block)->16B except ModuleNotFoundError: - sys.exit(f"pycryptodome not found for THIS interpreter:\n {sys.executable}\n" - f"Install it into this exact python (a bare `pip` may target a different one):\n" - f' "{sys.executable}" -m pip install pycryptodome') + _SBOX = bytes.fromhex("637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16") + _RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36] + def _xt(a): return ((a<<1)^0x1b)&0xff if a&0x80 else (a<<1) + def _expand(key): + w=[list(key[i*4:i*4+4]) for i in range(4)] + for i in range(4,44): + t=list(w[i-1]) + if i%4==0: + t=t[1:]+t[:1]; t=[_SBOX[b] for b in t]; t[0]^=_RCON[i//4-1] + w.append([w[i-4][j]^t[j] for j in range(4)]) + return w + def _enc(w, blk): + s=[list(blk[i*4:i*4+4]) for i in range(4)] + for c in range(4): + for j in range(4): s[c][j]^=w[c][j] + for rnd in range(1,10): + s2=[[_SBOX[s[c][j]] for j in range(4)] for c in range(4)] + rows=[[s2[c][j] for c in range(4)] for j in range(4)] + rows=[rows[j][j:]+rows[j][:j] for j in range(4)] + cols=[[rows[j][c] for j in range(4)] for c in range(4)] + s=[] + for a in cols: + s.append([_xt(a[0])^_xt(a[1])^a[1]^a[2]^a[3], + a[0]^_xt(a[1])^_xt(a[2])^a[2]^a[3], + a[0]^a[1]^_xt(a[2])^_xt(a[3])^a[3], + _xt(a[0])^a[0]^a[1]^a[2]^_xt(a[3])]) + for c in range(4): + for j in range(4): s[c][j]^=w[rnd*4+c][j] + s2=[[_SBOX[s[c][j]] for j in range(4)] for c in range(4)] + rows=[[s2[c][j] for c in range(4)] for j in range(4)] + rows=[rows[j][j:]+rows[j][:j] for j in range(4)] + s=[[rows[j][c] for j in range(4)] for c in range(4)] + for c in range(4): + for j in range(4): s[c][j]^=w[40+c][j] + return bytes(s[c][j] for c in range(4) for j in range(4)) + def _aes128(key16): + w = _expand(key16) + return lambda blk: _enc(w, blk) HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) @@ -47,8 +86,8 @@ R_AARCH64_CALL26 = 0x11b # ---- Dirty-Frag keystream (matches Stager/Writer) ---- def keystream0_fn(keymat_base): KEYMAT = bytes((keymat_base + i) & 0xff for i in range(20)) - ecb = AES.new(KEYMAT[:16], AES.MODE_ECB); SALT = KEYMAT[16:20] - return lambda iv: ecb.encrypt(SALT + iv + b"\x00\x00\x00\x02")[0] + enc = _aes128(KEYMAT[:16]); SALT = KEYMAT[16:20] + return lambda iv: enc(SALT + iv + b"\x00\x00\x00\x02")[0] # ============================== logging ============================== C = dict(r="\033[0m", b="\033[1m", dim="\033[2m", red="\033[31m", grn="\033[32m", diff --git a/requirements.txt b/requirements.txt index 25b281e..2cd2314 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,11 @@ -# Python deps to RUN the exploit (orchestrate.py). Install: python -m pip install -r requirements.txt -# (use `python -m pip` so it lands in the same interpreter that runs `python orchestrate.py`) +# RUN the exploit (orchestrate.py): NO required packages. +# orchestrate.py has a built-in, self-contained pure-Python AES-128 fallback (verified vs +# pycryptodome + FIPS-197), so it runs on any Python with zero third-party deps — no pip install, +# no C compiler. `pip install -r requirements.txt` here is intentionally a no-op. # -# Provides Crypto.Cipher.AES (the Dirty-Frag keystream). This is the ONLY Python dep for running. -pycryptodome>=3.15 - -# To ADD a new firmware target (port.py) you also need vmlinux-to-elf — see requirements-port.txt. -# It is intentionally NOT here: its minilzo sub-dep needs a C compiler on Windows, which would break -# `pip install -r requirements.txt` for people who only want to run the exploit. +# OPTIONAL speedup (uses the C-accelerated AES instead of the pure fallback — a few seconds faster +# per run). Only install if you already have wheels/a compiler; skip it on bleeding-edge Pythons: +# python -m pip install pycryptodome # -# Non-pip tools (see SETUP.md): adb (platform-tools), LLVM/clang, and for port.py also -# payload-dumper-go + debugfs. +# To ADD a new firmware target (port.py) you need vmlinux-to-elf — see requirements-port.txt. +# Non-pip tools (SETUP.md): adb, LLVM/clang, and for port.py also payload-dumper-go + debugfs.