wangyin572/dsh-plugin-reverse ↗★ 1

dsh-plugin-reverse

DSH plugin: JS reverse-engineering toolkit - host-env stripping for static deobfuscation and crypto verification.

Package
dsh-plugin-reverse
Compatibility
Unverified
Harness peer range
>=0.2.0-rc.2 <0.3.0
Cordis peer range
~4.0.4
Version
0.1.0
License
MIT
Last updated
Oct 7, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:wangyin572/dsh-plugin-reverse

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 report reaches one of four conclusions:

ConclusionMeaning
static-firstThe code is statically complete and lightly obfuscated → restore it directly
hook-then-staticBoth routes are viable → hook first to obtain a set of real input→output pairs, then restore and compare byte by byte
hook-firsteval / new Function / with present, so the real logic is not statically visible → you can only hook first
blockedInsufficient 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 = "", passphrase = "passphrase", dataEncoding = "base64")
rev_crypto_calc(operation = "hash", algorithm = "md5", data = "abc")
rev_crypto_calc(operation = "rsa-manual-pow", value = "", exponent = "", modulus = "")
rev_crypto_calc(operation = "xor-brute-force", data = "")

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.