dsh-plugin-reverse
A JavaScript reverse-engineering toolkit for DeepSeek Harness: assess the route first, then deliver a result along that route.
English · 简体中文
- Static restoration (the main path): turn extracted obfuscated front-end JS into
computation functions that run standalone in plain Node — strip
window/document/navigator/canvashost dependencies and smooth over environment differences, so you can compute a signature, encrypt a request body or decrypt a response without a browser and without injecting into a page. - Assisted location: generate Hook scripts that capture arguments, return values and call stacks, for debugging, forensics and locating the encryption entry point.
- Verification: built-in AES / RSA / MD5 / SHA / HMAC / cyclic XOR / single-byte brute force / big-integer modular exponentiation, so you can recompute against a known ciphertext, key or IV immediately.
Scope: this plugin is for code you are authorised to analyse — your own projects, authorised security testing, malware analysis, CTF, and interoperability research. The tools are generic debugging and static-analysis techniques and do not implement targeted bypasses for any specific system. Hooks record function arguments and return values and may therefore contain credentials; only use them in environments you are authorised to analyse. Do not point this at other people's accounts or systems.
1. Capability matrix
| Tool | Purpose | Typical output |
|---|---|---|
rev_assess |
Run this before every reverse-engineering task: decides whether hooking is viable and whether static restoration is viable, and gives the blockers and an ordered plan | Markdown assessment report |
rev_hook_generate |
Generate a browser-executable Hook script (WebCrypto / CryptoJS / JSEncrypt / network layer + auto-discovery) | Self-contained .js script |
rev_extract_pure |
Static restoration: dependency-closure extraction plus environment injection, producing host-free computation modules | pure.mjs / env.mjs / demo.mjs |
rev_deobfuscate |
Semantics-preserving safe rewrites (escape restoration, constant folding, member-access normalisation) plus obfuscation feature detection | Rewritten source + analysis report |
rev_crypto_calc |
20 cryptographic verification operations, covering both standard-library and hand-rolled big-integer paths | Structured verification results |
2. Installation
Requires Node.js ≥ 24. The built lib/ output is committed, so installing from GitHub source needs
no build approval.
Option 1 — install directly (recommended; no npm needed on the user side)
dsh plugin --profile web add github:wangyin572/dsh-plugin-reverse
This path needs no npm run and no build approval: pnpm installs the one real dependency,
acorn, straight from the manifest, while@deepseek-ai/*is resolved by the DSH resolution layer to the single instance inside the dsh installation. Measured afterwards, the profile'snode_modulesholds onlyacornand this package — no duplicated@deepseek-ai/*.
Option 2 — clone and install locally (when you want to change the code)
git clone https://github.com/wangyin572/dsh-plugin-reverse.git
cd dsh-plugin-reverse
npm install # runtime dependencies (@deepseek-ai/* and acorn)
dsh plugin --profile web add "$(pwd)"
Then restart that profile. To verify it took effect:
dsh --profile web --dump-config | grep -A3 dsh-plugin-reverse
In Option 2,
npm installis not optional.dsh plugin add "$(pwd)"useslink:: pnpm only creates a symlink and installs nothing for it, and this package does not declareacornas a peer, soacorncan only come from the repository's ownnode_modules. Skipping it fails immediately withCannot find package 'acorn'(measured: a bare copy holding onlypackage.json,cordis.patch.ymlandlib/fails at module import).lib/is already in the repository and needs no build; runnpm run buildand commit the result only after editingsrc/.
After installation the five tools above appear on the model side. The runtime dependencies are
@deepseek-ai/cordis, @deepseek-ai/dsh-tools, @deepseek-ai/schemastery and acorn, all published
publicly on npm. Note the tags differ: dsh-tools@0.2.0-rc.2 is published under the next tag,
whereas cordis ~4.0.4 and schemastery ~3.18.4 resolve from latest. The three
@deepseek-ai/* packages are declared as peerDependencies: the profile resolution layer points them
at the single instance inside the dsh installation (pnpm's autoInstallPeers is off in a profile, so no
duplicate instance is installed).
Prebuilt tarball
npm pack produces a ready-to-install tarball containing the already-built lib/ plus the docs:
npm pack
dsh plugin --profile web add ./dsh-plugin-reverse-0.1.0.tgz
Being listed by community directories
Community plugin directories auto-discover repositories through the dsh-plugin GitHub topic
(Settings → Topics, or gh repo edit --add-topic dsh-plugin). The crawler reads the package.json at
the repository root, and this repository already satisfies it:
dsh.bundle.patchpoints at a relative path inside the repo →./cordis.patch.yml✅- the repository is public and not archived;
descriptionbecomes the one-line summary ✅
Do not add
dsh.profilehere. The official docs are explicit: a bundle manifest declaresdsh.bundle, a profile manifest declaresdsh.profile, and nothing is both.dsh.profilebelongs to the profile directory'spackage.jsonand is written bydsh plugin add. Some installers (such as find-plugin) filter out repositories that mix bundle and profile manifests as invalid candidates.
Configuration
Override by line id in the profile's cordis.patch.yml:
- id: reverse-toolkit
name: 'dsh-plugin-reverse'
config:
maxSourceBytes: 2000000 # maximum source size allowed per analysis, in bytes
hookLogLimit: 2000 # Hook script log ring-buffer size
hookAutoDiscover: true # whether the Hook auto-discovers suspicious functions by default
The config block is optional; every field has a default.
3. Usage flow
3.1 Always assess first
rev_assess(code = <the JS you extracted>)
The report reaches one of four conclusions:
| Conclusion | Meaning |
|---|---|
static-first |
The code is statically complete and lightly obfuscated → restore it directly |
hook-then-static |
Both routes are viable → hook first to obtain a set of real input→output pairs, then restore and compare byte by byte |
hook-first |
eval / new Function / with present, so the real logic is not statically visible → you can only hook first |
blocked |
Insufficient information (e.g. the logic lives in a Worker or WASM); more material is needed |
It also gives an obfuscation score, a feature list, ranked entry-point candidates, the environment values that must be captured, and an ordered execution plan.
3.2 Route ①: hook to locate the entry point
rev_hook_generate(targets = [{ object: "window", method: "makeSign" }])
Save the returned script as a .js file and run it in the target page (DevTools console /
Sources → Snippets / CDP Page.addScriptToEvaluateOnNewDocument). Afterwards:
__revHook.dump() // inspect captured records
__revHook.save() // export as JSON
__revHook.unhook() // restore all replaced methods
Every record carries a call stack — the most effective information for locating who calls the encryption function.
3.3 Route ②: static restoration (the main path)
rev_assess(code = ...) # confirm feasibility first
rev_deobfuscate(code = ...) # preprocessing: escape restoration / constant folding / member-access normalisation
rev_extract_pure(code = ..., entry = ["makeSign"])
Three files are produced; write them to disk and run them directly to verify:
node demo.mjs
The shape of pure.mjs:
export function createRuntime(env) {
// host objects are all injected via env, rather than rewriting every reference site
const { navigator, screen, document, btoa } = env
// ---- from original file line 4: _0x1a2b (function) ----
function _0x1a2b(a, b) { /* verbatim source, not rewritten */ }
// ---- from original file line 17: makeSign (function) ----
function makeSign(payload) { /* verbatim source, not rewritten */ }
return { makeSign }
}
Call site (no browser involved at all):
import { createRuntime } from './pure.mjs'
import { createEnv } from './env.mjs'
// real captured environment values are injected here
const runtime = createRuntime(createEnv({
navigator: { userAgent: 'real UA', platform: 'MacIntel' },
document: { cookie: 'real cookie' },
screen: { width: 1512 },
}))
console.log(runtime.makeSign('business payload'))
3.4 Verification
rev_crypto_calc(operation = "aes-decrypt", data = "<ciphertext>", passphrase = "passphrase", dataEncoding = "base64")
rev_crypto_calc(operation = "hash", algorithm = "md5", data = "abc")
rev_crypto_calc(operation = "rsa-manual-pow", value = "<m>", exponent = "<e>", modulus = "<n>")
rev_crypto_calc(operation = "xor-brute-force", data = "<hex>")
The 20 supported operations: hash, hmac, aes-encrypt, aes-decrypt, openssl-decrypt,
openssl-encrypt, xor, xor-brute-force, xor-recover-key, rsa-encrypt, rsa-decrypt,
rsa-sign, rsa-verify, rsa-public-from-modulus, rsa-manual-pow, rsa-key-info,
mod-pow, mod-inverse, bigint-parse, hex-normalize.
Encoding is always explicit (utf8/hex/base64/base64url/latin1): the same string
interpreted as hex and as utf8 yields completely different results, and silently guessing only
leads to confidently holding a wrong answer. Invalid hex, odd lengths and wrong-length IVs
fail loudly rather than being truncated.
4. The pure computation core is usable on its own
The core is a host-dependency-free pure function layer: it does not import cordis, does not import DSH and performs no I/O. A project that lists this package as a dependency can use it through the exposed subpath:
import { aesDecrypt, opensslDecrypt, hmac, xorBytes, rsaManualPow } from 'dsh-plugin-reverse/core'
You can also copy the core out entirely: the source is src/core/ in this repository, and the compiled
lib/core/index.js is committed (npm run build rebuilds it after you edit src/).
It follows exactly the same constraints as this plugin's static-restoration output: no DOM/BOM, no side effects, runnable standalone.
5. Project structure
src/
├── core/ pure computation core (encoding / hash / aes / rsa / xor / errors)
├── analysis/ static analysis (ast / host / deobfuscate / extract / hook / assess)
├── services/ cordis services (ctx.reverseCrypto / ctx.reverseAnalysis)
├── tools/ the 5 agent tools (defineTool)
└── index.ts root plugin: registers services and tools
test/
├── core.test.mjs cryptographic core (including external OpenSSL vectors)
├── analysis.test.mjs deobfuscation / host detection / closure extraction / Hook / assessment
├── plugin.test.mjs plugin contract and end-to-end tool chain
└── fixtures/ simulated obfuscated samples
Dependencies flow one way only: tools → services → analysis → core. The core layer never depends
on DSH in reverse, so "the plugin's core" and "the pure code the plugin produces" obey the same
host-free constraints.
6. Verification
npm run build # TypeScript strict-mode compilation
npm test # run the test suite
npm run check # the two steps above
npm run verify:integration # real launcher integration check (isolated DSH_HOME, ~1 minute)
npm run verify:all # everything
The integration check creates an isolated DSH_HOME under .dsh-scratch/ in the workspace, builds a
profile from the web template shipped with the product, really runs dsh plugin add, and asserts
that --dump-config contains the line this package contributes and that --dump-config-schema can
import this package's modules. The script has a guard rail that refuses to place the temporary home
outside the repository, and never touches the real ~/.dsh.
A few key assertions in the tests:
- AES/KDF align with external implementations: vectors are generated on the spot by the system
OpenSSL 3.6 (
openssl enc -aes-256-cbc -md md5 -S ... -pass pass:...), not self-generated and self-verified. - Semantic equivalence: the original obfuscated code is run inside a simulated browser in
node:vm, the static-restoration output is run in plain Node, and the two outputs are asserted to be byte-for-byte identical; it is further asserted that changing an environment value changes the result. - Generated artifacts are executable: both Hook scripts and restoration output first pass AST syntax validation, then are really executed in a sandbox, asserting "same return value", "restorable" and "no module loader required".
- Plugin contract: a real cordis mount, asserting that all 5 tools register, both services are available, and the parameter validation chain is intact.
7. Honest boundaries
These are capability boundaries, not a to-do list. In these situations the tools explicitly report a blocker instead of producing code that "runs but computes the wrong thing":
| Situation | Tool behaviour |
|---|---|
eval / new Function / with |
Judged as blocking static restoration; hooking is recommended; reports a blocker and generates no code |
| Control-flow flattening | No automatic reordering (wrong reordering silently changes semantics); reports the case order for manual reconstruction and lowers confidence to low |
| String arrays + decoder functions | Detected and reported only, not automatically inlined (inlining before the rotation offset is fully restored amounts to guessing) |
| canvas / WebGL / Audio fingerprints | Cannot be reproduced in Node; env.mjs leaves explicit placeholders and lists them under unmockable, requiring external capture |
| Environment values that depend on a real browser instance | Produces MUST_CAPTURE placeholders and never fabricates a "plausible" value |
| The entry point depends on an outer scope that cannot be extracted | Reports an unresolved-reference blocker and states which code needs to be supplied |
Only (n, e, d) given, without CRT parameters |
A PEM private key cannot be constructed (mathematically requires p and q); use rsa-manual-pow to do the modular exponentiation directly instead |
MUST_CAPTURE is an inherent boundary of static restoration, not an implementation defect: if a
signature mixes in navigator.userAgent or a canvas fingerprint, any attempt to "simulate a value"
only yields a signature that looks right but is wrong. The correct approach is to capture it once in
the browser and then inject it.
8. License
MIT
No comments yet. Be the first to write one.