never-jscore:为逆向而生的 Python 高性能 JS 引擎(Hook 拦截 + 零配置补环境)

本文最后更新于:2026年8月14日 下午

本文为工具使用整理,基于官方仓库、README 与类型存根编写

文中代码与 API 签名均来自官方 README 和 never_jscore.pyi 类型存根,可直接验证。

⚠️ License 提示:仓库 GitHub API 未标注 License,而 README 徽章写的是 MIT——两者不一致,商用前请向作者确认授权。

一、它是什么,解决什么问题

never-jscore 是一个用 Python 高性能执行 JavaScript 的引擎,专为 JS 逆向工程设计,定位是 execjs(PyExecJS)的上位替代

在 Python 里跑 JS,常见几条路线:

方案 底层 特点
PyExecJS(execjs) 外部 Node 等 runtime 每次起子进程,慢、已过时
mini-racer 嵌入式 V8 通用、轻量,跑纯算法最省心
bestV8 纯净 V8 动态库 补环境(白纸全手补)
never-jscore Rust + deno_core(V8) 自带 Deno 全套 API + 逆向专用 Hook

never-jscore 的独特之处在于:它底层用的是 deno_core,因此自带 Deno 那一整套 Web / Node.js API(fetch、localStorage、crypto、require 等),实现”零配置补环境”;同时提供 Hook 拦截确定性随机数这两个逆向专用能力。

一句话对比:bestV8 给你一张纯净白纸让你手工补环境;never-jscore 直接给你一个装好浏览器/Node API 的环境,还额外送你逆向 Hook。

二、核心特点

  • 🚀 极致性能:简单任务 25 万+ ops/s,复杂任务 2 万+ ops/s(Context 复用模式)
  • 🎣 双模式 Hook 拦截$return + $terminate),专为逆向设计
  • 🎲 确定性随机数:固定种子,加密算法调试结果可复现
  • 🌐 完整 Web / Node.js API,零配置补环境
  • v3.0.0 GIL 释放优化,多线程性能提升
  • 🎨 附带 Canvas 2D API(纯 Rust 实现)、V8 堆内存分析等

三、安装与快速上手

1
pip install never-jscore

支持 Windows / Linux / macOS,Python 3.8+(官方推荐 3.14 获得最佳性能)。

1
2
3
4
5
6
7
8
9
10
11
12
import never_jscore

# 方式 1:Context(适合探索、调试)
ctx = never_jscore.Context()
ctx.compile("function add(a, b) { return a + b; }")
print(ctx.call("add", [1, 2])) # 3

# 方式 2:JSEngine(Worker Pool,适合批量、并发)
engine = never_jscore.JSEngine("""
function encrypt(data) { return btoa(data); }
""", workers=4)
results = [engine.call("encrypt", [f"data_{i}"]) for i in range(1000)]

四、两种执行模式:Context vs JSEngine

v3.0.0 起提供双架构,选错模式性能差几十上百倍,这是最需要注意的点。

关键性能真相 ⚠️

场景 Context(复用) JSEngine(Pool) 差距
简单 JS(btoa) 255,969 ops/s 743 ops/s Context 快 344×
复杂计算(循环) 23,675 ops/s 550 ops/s Context 快 43×
冷启动(每次重建) 50 ops/s 607 ops/s JSEngine 快 12×

结论:99% 场景用 Context 复用模式。只有”每次都要跑不同 JS、无法复用 Context”或”避免大型 JS 库重复冷启动”时,JSEngine 才有优势。

最容易踩的坑:不要在循环里反复建 Context

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ 错误:每次都重建 + 重新加载(~50 ops/s)
for data in data_list:
ctx = never_jscore.Context()
ctx.compile(js_code)
result = ctx.call("encrypt", [data])

# ✅ 正确:Context 复用(~255,000 ops/s,快 5000 倍)
ctx = never_jscore.Context()
ctx.compile(js_code)
for data in data_list:
result = ctx.call("encrypt", [data])
del ctx

JSEngine 模式(批量 / 并发)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import never_jscore
from concurrent.futures import ThreadPoolExecutor

engine = never_jscore.JSEngine("""
const CryptoJS = require('crypto-js');
function encrypt(data, key) {
return CryptoJS.AES.encrypt(data, key).toString();
}
""", workers=4, enable_node_compat=True)

# 多线程并发(v3.0.0 自动释放 GIL)
def process(data):
return engine.call("encrypt", [data, "secret_key"])

with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(process, data_list))
del engine

五、逆向专属能力

这三个特性是 never-jscore 比通用 JS 引擎(mini-racer 等)更”懂逆向”的地方。

1. 双模式 Hook 拦截

$return() —— 快速拦截,抠出加密中间值

在 JS 逻辑中间插一句 $return(...),提前返回中间结果,后续代码不再执行:

1
2
3
4
5
6
7
8
9
10
11
12
ctx = never_jscore.Context()
result = ctx.evaluate("""
function encrypt(data) {
const step1 = processData(data);
// Hook:提前返回中间结果
$return({ intercepted: true, step1: step1, timestamp: Date.now() });
// 下面的代码不会执行
return furtherProcess(step1);
}
encrypt("sensitive_data")
""")
print(result) # {'intercepted': True, 'step1': ..., 'timestamp': ...}

$terminate() —— 强制终止,连 try-catch 都拦不住

适合 Hook 那些被 try-catch 包裹的关键函数(比如 XMLHttpRequest.send),拿到加密后的 payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
ctx = never_jscore.Context()

try:
ctx.evaluate("""
try {
XMLHttpRequest.prototype.send = function(data) {
$terminate({ url: this._url, method: this._method, encrypted: data });
};
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/login');
xhr.send(encryptedPayload);
} catch (e) {
console.log("不会执行"); // ❌ try-catch 无法捕获 $terminate
}
""")
except Exception:
print("✓ JS 被强制终止")

# 从 Python 侧取出拦截到的数据
hook_data = json.loads(ctx.get_hook_data())
print(hook_data)

$return$terminate 的区别:前者是”正常提前返回”,后者是”强制终止事件循环、无法被 JS 捕获”。逆向时想拿被保护函数的入参/中间值,$terminate 更可靠。

2. 确定性随机数(调试可复现)

固定种子后,所有随机 API 每次结果都一样——对依赖随机数的签名/加密算法调试极其有用:

1
2
3
4
5
6
7
ctx = never_jscore.Context(random_seed=12345)
r1 = ctx.evaluate("Math.random()") # 0.8831156266...
r2 = ctx.evaluate("Math.random()") # 0.5465919174...

# 换个新 Context,同种子 → 同结果
ctx2 = never_jscore.Context(random_seed=12345)
r3 = ctx2.evaluate("Math.random()") # 0.8831156266...(与 r1 相同)

影响的 API:Math.random()crypto.randomUUID()crypto.getRandomValues()

3. 零配置补环境(Web / Node.js API)

Web API(默认启用)

fetchlocalStorageURLTextEncoder/Decoderatob/btoacryptosetTimeoutReadableStreamstructuredCloneEvent/EventTargetAbortController开箱即用

1
2
3
4
5
6
7
8
9
ctx = never_jscore.Context()

ctx.eval("localStorage.setItem('token', 'abc123')")
print(ctx.evaluate("localStorage.getItem('token')")) # 'abc123'

print(ctx.evaluate("""
const url = new URL('https://example.com/path?foo=bar');
({ origin: url.origin, pathname: url.pathname, search: url.search })
"""))

Node.js 兼容模式

enable_node_compat=True 即可用 require() 和 Node 内置模块,甚至加载 npm 包(需先 npm install):

1
2
3
4
5
6
7
8
9
10
ctx = never_jscore.Context(enable_node_compat=True)
print(ctx.evaluate("""
const path = require('path');
const crypto = require('crypto');
({
joined: path.join('a', 'b', 'c'),
hash: crypto.createHash('md5').update('hello').digest('hex')
})
"""))
# {'joined': 'a\\b\\c', 'hash': '5d41402abc4b2a76b9719d911017c592'}

支持 path/fs/crypto/buffer/stream/url/util/events 等内置模块,以及 jsdomlodashcrypto-jsws 等 npm 包。

六、Context 关键参数(来自类型存根)

Context.__init__ 的真实签名与含义:

1
2
3
4
5
6
7
never_jscore.Context(
enable_extensions=True, # True=加载 Deno Web API;False=纯净 V8(只有 ECMAScript)
enable_logging=False, # True=输出所有扩展操作日志,调试用
random_seed=None, # int=固定随机数种子(确定性);None=系统随机
enable_node_compat=False, # True=启用 require() 与 Node 内置模块
fast_return=False, # True=函数 return 后立即返回,不等 setTimeout/setInterval
)

小提示:如果你想要一个纯净 V8 环境(不自带 Deno API,类似 bestV8 的白纸),可以传 enable_extensions=False

常用方法:

  • compile(code):编译并加载 JS(供后续 call 复用)
  • evaluate(code, auto_await=None):执行并返回最后一个表达式的值,自动 await Promise
  • eval(code, return_value=True, auto_await=None):底层求值
  • call(name, args=[], auto_await=None):按函数名调用
  • get_hook_data():取出 Hook($return/$terminate)保存的数据(JSON 字符串)
  • get_heap_statistics() / take_heap_snapshot(path) / gc():V8 堆分析与内存管理
  • get_stats() / reset_stats():调用计数统计

七、性能对比(官方数据,仅供参考)

测试项 never-jscore PyMiniRacer PyExecJS
简单计算 0.007ms 0.005ms 2.3ms
字符串操作 0.004ms 0.008ms 2.3ms
复杂算法(1000 次) 11ms 38ms 69473ms
Promise / async ✅ 3ms ❌ 不支持 ❌ 不支持

数据来自官方 README,随环境变化,仅供参考。可以看出它和 PyMiniRacer 在纯计算上同量级,但多了 Promise/async 支持,且远快于起子进程的 PyExecJS。

八、选型建议

  • 跑不依赖浏览器环境的纯算法 JS → mini-racer 最省心(轻量、稳定)。
  • 需要补浏览器环境 + 逆向 Hook → never-jscore(自带 Deno API,还有 $return/$terminate)。
  • 需要极致纯净 V8、自己精确控制环境特征 → bestV8,或 never-jscore 传 enable_extensions=False

一个需要留意的权衡:never-jscore 底层是 deno_core,自带 Deno/Node 环境是把双刃剑——补环境省事,但如果目标站点会检测 Deno/Node 特征,反而可能露馅。对抗强检测时要评估这一点,必要时切到纯净模式或 bestV8。

参考


本文仅作技术学习记录。请在合法合规、获得授权的前提下使用相关技术。


never-jscore:为逆向而生的 Python 高性能 JS 引擎(Hook 拦截 + 零配置补环境)
https://kingjem.github.io/2026/08/14/逆向/never-jscore-Python高性能JS引擎逆向利器/
作者
Ruhai
发布于
2026年8月14日
许可协议