Reverse-Engineering Report · Successor to the v9 Codex

The serpent,
fully uncoiled.

from shipped .exe → Electron main process → a verified exploit chain

Where the v9 pass could only say “the renderer implies nodeIntegration:true but we never read preload.js,” this pass cracks the 100 MB portable binary, extracts the Electron main process, and turns that inference into a confirmed finding — a malicious .oph file that reaches host code execution.

Verdict. Un-obfuscated Electron 39 app. A crafted .oph preset can achieve renderer code execution and, through an unvalidated file-write IPC, code execution on the host. The sign-in gate is still a self-described “fake security camera.”
100 MB
Portable .exe
39.2.4
Electron · ia32
32.5 MB
app.asar
24
src modules
2
Critical
5
Findings
§1 · Executive Summary

What this is, and what we found

Ophis (branded PSYFR, shipped as Ophis v12) is an offline date-projection / cycle-prediction tool — Gann-style cycle work in astronomical dress. You give it two anchor dates (X-Dates); it counts the days between them (Y), runs a table of little formulas that transform Y into offsets, and projects Z-Dates that it scores against resonance number-sets and eclipse tables.

The engine itself is legitimate and interesting. The security posture is not. Because the whole app is un-obfuscated and ships its own source, a full white-box read was possible — and it exposes a clean, verifiable path from “open a shared preset file” to “attacker runs code on your machine.” The rest of this report is the reproducible method, the anatomy, the v9→v12 delta, and the five findings ranked by severity — plus exactly how to build the hardened version.

Critical ×2

new Function() over file-controlled formulas → renderer exec; nodeIntegration:true + unvalidated file-write IPC → host exec.

High ×1

Sign-in is client-side theatre — 5 hard-coded SHA-512 hashes shipped in config; re-enabling it crashes init.

Med / Low

executeJavaScript built by string concat; headless CLI log-forging from file-derived strings.

§2 · Method

From .exe to readable source, step by step

The reproducible spine of the paper. Note the discipline: identify the container from bytes before running anything.

Triage — read bytes, don't execute

MZ header → Windows PE. Machine field 0x14C → 32-bit. String scan finds Nullsoft at offset 81276 → NSIS. package.json → win.target: "portable". No app.asar string in the clear → payload is compressed.

Recognise the NSIS payload as a 7z stream

electron-builder’s NSIS package embeds a 7z archive. A standalone extractor reads it directly — no admin install, no detonation.

Extract app.asar, then unpack it

Pull the app bundle out of the 7z, then use Electron’s own asar tool to expand its tar-like format into a normal file tree.

Read the three files v9 never had

main.js (Electron main process), preload.js (the electronBridge definition), and the real ophis.html bootstrap — the files that convert every “inferred” caveat into a verified one.

# standalone extractor — no admin, no running the app
npm install 7zip-bin
ZA=node_modules/7zip-bin/win/x64/7za.exe

# the NSIS .exe IS a 7z container — list, then extract the app bundle
"$ZA" l Ophis_v12_Windows.exe        # → resources\app.asar (32,524,448 bytes)
"$ZA" e Ophis_v12_Windows.exe "resources/app.asar" -o./extracted

# unpack the asar → full source tree + main.js + preload.js + ophis.html
npx asar extract extracted/app.asar extracted/unpacked_full

Naming trap

The OPHIS.html in this folder is not the bootstrap — it’s the v9 Field Guide (byte-identical to Ophis_v9_Explained.html). The real loader ophis.html lives inside the asar. Every §5 citation refers to that internal file.

§3 · Anatomy

What the package actually contains

LayerWhatNotes
OuterNSIS portable stub (32-bit PE)Self-extracts to %TEMP% and runs Ophis.exe
RuntimeElectron 39.2.4 · Chromium + Node · ia32Ophis.exe 210 MB unpacked; ffmpeg.dll, ICU, 56 locale paks
Appresources/app.asar (32.5 MB)Uncompressed archive; "main": "main.js"
Rendererophis.html + 24 src/*.js + lib/*Hand-rolled serial <script> loader with a cache-buster

The bootstrap injects first-party modules in strict order, appending ?v=<rand> every launch (ophis.html:549–550) — dev-style loading shipped to production. The CSP (ophis.html:72) sets script-src 'self' 'unsafe-inline' 'unsafe-eval' — it explicitly permits the new Function() engine, so it provides no mitigation. Un-obfuscated throughout: real names, comments, TODOs, even debugger; left in.

§4 · The v9 → v12 Delta

What changed since the last pass

Areav9v12
Headless / CLI—New. --headless, --output-path, … runs windowless, writes files (main.js:490–529)
Console→CLI bridge—New ophis_logging.js pipes console.* → logToCli
T-Dates—New second input-date type
File validationsingle strict path3 modes; GUI defaults to LOOSE (most permissive)
Default ops1516 — adds X2+YxOPH_HEP, enabled by default
Astronomy libcommented outLoaded & live (ophis.html:39)
Sign-in gatedisabled, sha512 commented outUnchanged — still both

Net: v12 = v9 + headless CLI + T-Dates + a 16th default op + a validation-mode system whose GUI default (LOOSE) widens the attack surface. Core security posture otherwise unchanged — and marginally worse because hostile files are now more likely to be processed.

§5 · The Verified Exploit Chain

From “open a shared preset” to host compromise

Every step is cited to extracted source. This chain was inferred in v9; with main.js and preload.js in hand it is now confirmed.

▼
▼
▼
▼
▼

Threat model

This is an offline, single-user tool — the realistic attacker is a malicious preset file shared in the community (“seed this .oph”), double-clicked by a victim. Under that model, the two Critical findings chain to full host compromise from opening a shared file, which is exactly how these presets circulate.

§6 · Findings

Ranked by severity

Critical#1 · new Function() over .oph-controlled strings

The engine compiles every operation string loaded from a file to a native function. Reachable on file open / recalc with no UI interaction and no sign-in. The math.js “validation” is bypassable because it inspects a stripped skeleton (prefix removed, oph_* names deleted, Y→10) while new Function compiles the un-stripped body — validator and executor never see the same code. Import does no content validation of the string, and the v12 GUI default LOOSE auto-repairs hostile files rather than rejecting them.

ophis_model__validation.js:158 · strip pass :104–121 · import :620–645 · default mode ophis_main.js:29
Critical#2 · nodeIntegration:true confirmed + unvalidated file-write IPC

v9 could only infer this. main.js:367 sets nodeIntegration:true. contextIsolation is left unset (defaults true in Electron 39), so the page talks to Node through the contextBridge electronBridge API — the real Node boundary. And one bridge method, autoSaveToFile(path, contents), lands in a main-process handler that does fs.mkdirSync(recursive) + fs.writeFile with no path validation. Any renderer code can write arbitrary content to an arbitrary path with the app’s privileges — escalating Finding #1 to persistent host execution. nodeIntegration:true here is also incoherent with isolation-on: it grants the page nothing yet becomes instant full-Node exposure if anyone ever sets contextIsolation:false.

main.js:362–370 (webPreferences) · :113–120 & :267–283 (saveToFile) · preload.js:4
High#3 · Sign-in gate is decorative

Disabled by flag (author’s comment: “a false sense of security… like a fake security camera”). When on, it does a client-side compare of sha512(input) against 5 hard-coded, unsalted, single-round SHA-512 digests shipped in ophis_config.js — offline-crackable and bypassable by editing the flag. Latent crash: sha512.min.js is commented out of the bootstrap, so re-enabling the flag alone throws ReferenceError in init.

ophis_config.js:291, 5–11 · ophis_utils.js:664–666 · ophis.html:67
Medium#4 · executeJavaScript string-building with a 2-char escaper

The main process pushes file data into the renderer by concatenating JS and calling executeJavaScript, with an escaper that only handles \ and ". The obvious break is closed on modern V8 (JSON payload, quotes escaped), but it’s fragile hand-rolled escaping on a privileged sink — any future change reopens injection. Prefer structured argument passing or an ipcRenderer data channel.

main.js:338, 299, 540 · escapeString :26–33
Low#5 · Headless CLI log forging

File-derived strings (event names, paths, error text) reach the CLI stream unsanitised via console.* → logToCli → console.log, so a crafted .oph can inject fake OPH_ERROR: lines, newlines, or terminal escape sequences into whatever consumes headless output. Log-integrity / terminal-injection, not code execution.

ophis_logging.js · main.js:201–203 · ophis_view__export.js:91,125
§7 · Building the Hardened Version

Concrete fixes for “our own modified build”

Paste-ready recommendations. These are proposed, not yet applied to your files — say the word and I’ll implement and verify each.

Portfolio angle

The rewrites (PSYFR / Natori) are already a cohesive, dependency-free, offline single-file UI — genuinely portfolio-grade. Fixing their own new Function + label-innerHTML smells makes the story land: “I found these bug classes in the target, then made sure not to ship them myself.” Commit the un-obfuscated src/, this report, a METHOD.md from §2, and a SECURITY.md from §6 — and keep the 100 MB .exe out of git.