bestV8 使用教程:纯净 V8 环境跑 JS,做补环境调试

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

本文为整理性内容,基于官方开源仓库的公开资料编写

bestV8 是一个开源免费、可以直接跑 JS 的逆向工具,本质是把纯净的 V8 引擎编译成跨平台动态库,供外部(如 Python 通过 ctypes)调用来执行 JavaScript。本文中的代码示例均来自上述两个官方仓库,可直接验证。文末附免责声明,请务必阅读。

为什么要用 bestV8,而不是直接用 Node

做 JS 逆向(尤其是补环境)时,很多人第一反应是拿 Node 跑目标脚本。但 Node 不是一个”干净”的执行环境:

  • Node 里有 processglobal__dirname__filenamerequire 等一堆 Node 特有对象,而真实浏览器里没有这些。
  • 有些反爬 JS 会专门检测这些 Node 特征,一旦发现 process 之类的东西就判定为”非浏览器环境”,直接返回假值或报错。
  • Node 内置的某些属性(比如新版本内置的 navigator)你想删都删不干净,反而干扰补环境。

bestV8 提供的是一个纯净的 V8 环境:里面只有 ECMAScript 标准对象(DateArrayJSON……),没有任何 Node 特征,也没有任何浏览器 BOM/DOM 对象。这正是补环境想要的起点——一张白纸,浏览器里有什么、检测点用到什么,你就往里补什么,补得干净、可控。

一句话:bestV8 = 纯净 V8 执行器;补环境 = 你在这张白纸上手工画出一个”浏览器”。

bestV8 的组成

从官方 Release 仓库 BestToYou/bestV8_release 看,核心就几个文件:

1
2
3
4
5
6
bestV8_win64.dll        # Windows x64 动态库
bestV8_x64.so # Linux x64 动态库
bestV8_mac_intel.dylib # macOS Intel 动态库
bestV8_mac_m.dylib # macOS Apple Silicon (M 系列) 动态库
main.py # Python 调用示例(ctypes 原生调用)
demo.js # 被执行的示例 JS

也就是说,bestV8 本体是编译好的 V8 动态库,对外暴露一个 runJs 导出函数。你用任何能调 C 动态库的语言都能驱动它,官方给的是 Python + ctypes 的方式。

用法一:官方原生调用(Python + ctypes)

这是官方 main.py 的原始代码,直接加载对应平台的动态库、调用 runJs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import sys
from ctypes import *

def get_encrypt_value(data):
if sys.platform == "darwin":
# 这个里面根据 mac 电脑来换,通常一个可以另一个不可以。
cur = cdll.LoadLibrary("./bestV8_mac_m.dylib")
elif sys.platform == "linux":
cur = cdll.LoadLibrary("./bestV8_x64.so")
elif sys.platform == "win32":
cur = cdll.LoadLibrary("./bestV8_win64.dll")
else:
raise Exception("unknown systerm!")

result = bytes(20000)
cur.runJs.argtypes = (c_char_p, c_char_p)
for x in range(1):
cur.runJs(create_string_buffer(data.encode('utf8')), result)
print(result.rstrip(b"\x00"))

data = open("demo.js", "r").read()
get_encrypt_value(data)

配套的 demo.js

1
2
3
4
5
print("hellword bestV8");

var result = "this is return";

result;

运行逻辑很直白:

  1. 按操作系统选择对应的动态库加载(macOS 上 Intel / M 芯片要换对应的 .dylib,官方注释说”通常一个可以另一个不可以”,两个都试一下)。
  2. 声明 runJs 的参数类型:第一个参数是要执行的 JS 源码(c_char_p),第二个参数是一个预分配的接收缓冲区(这里是 20000 字节的 bytes)。
  3. 调用 runJsJS 脚本里最后一个表达式的值会被写回到 result 缓冲区里。
  4. result.rstrip(b"\x00") 去掉尾部填充的空字节,得到真正的返回值。

⚠️ 注意:接收缓冲区大小(示例是 20000 字节)需要 ≥ 你的返回值长度,返回结果很大时要调大,否则会被截断。用封装库 PyBestV8 时对应的是 recv_size 参数。

用法二:PyBestV8(pip 封装,推荐)

每次手写 ctypes 太麻烦。wangluozhe/pybestV8 把上面这套动态库封装成了 pip 包 PyBestV8,动态库已经内置在包里,装完直接 import 即可。

安装

1
pip install PyBestV8

1. eval:直接执行一段 JS

最简单的用法——把 JS 字符串丢进去,拿最后一个表达式的值:

1
2
3
4
5
import bestv8

js_string = """a = 1;a"""
result = bestv8.eval(js_string, recv_size=200)
print("result:", result, type(result))

recv_size 就是接收缓冲区大小(对应原生调用里的那个 20000 字节缓冲区),返回值大就调大。

2. compile + call:编译后按函数名调用(支持上下文)

这是逆向里最常用的模式:先把整段 JS 编译成一个上下文 ctx,之后可以多次调用里面的函数,而且上下文里的全局变量状态会保留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import bestv8

js_string = """
a = 1;
b = 2;
function abc(){
a += 1;
b += 2;
return {
name: "lisa",
age: 18
}
}
function bcd(){
return [a, b];
}
"""
ctx = bestv8.compile(js_string)

result = ctx.call("bcd")
print("bcd->result:", result, type(result)) # [1, 2]

result = ctx.call("abc")
print("abc->result:", result, type(result)) # {'name': 'lisa', 'age': 18}

result = ctx.call("bcd")
print("bcd->result:", result, type(result)) # [2, 4] <- 注意状态被保留了

留意最后一次 bcd 的结果:因为前面调过一次 abc(),全局的 ab 已经被改过,所以第二次 bcd() 返回的是 [2, 4] 而不是 [1, 2]这个”上下文状态保持”的特性,正是补环境跑加密函数时需要的——很多加密逻辑依赖初始化时设置的全局状态。

3. bestv8_import:把补好的环境拆成独立文件导入

补环境代码往往很长(windowdocumentnavigatorlocation……一大堆)。PyBestV8 支持用 ctx.bestv8_import() 把这些环境代码放到单独的 .js 文件里导入,让主逻辑更清爽:

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

js_string = """
function get_location(){
return location;
}
"""

ctx = bestv8.compile(js_string)
ctx.bestv8_import("import_test.js") # 导入补好的环境文件
result = ctx.call("get_location")
print("import->result:", result, type(result))

配套的 import_test.js(一个手工补的 location 对象示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var location = {
ancestorOrigins: {length: 0},
assign: function assign(){console.log("调用了assign")},
hash: "",
host: "www.xxx.com",
hostname: "www.xxx.com",
href: "https://www.xxx.com/",
origin: "https://www.xxx.com",
pathname: "/",
port: "",
protocol: "https:",
reload: function reload(){console.log("调用了reload")},
replace: function replace(){console.log("调用了replace")},
search: "",
}

这样你就能把”环境”和”业务逻辑”分离:import_test.js 专门放补好的 BOM/DOM 对象,主脚本只管调用加密函数。

结合补环境的典型工作流

bestV8 只是”纯净执行器”,真正过检测靠的是你补的环境。完整流程一般是:

  1. 抠出加密 JS:从目标站点定位到加密函数(比如 window.get_signature() 之类),把相关代码抠下来。

  2. 丢进 bestV8 直接跑bestv8.eval / compile 跑一遍,看报什么错——通常是 xxx is not defined(缺 windowdocumentnavigator 等)。

  3. 用 Proxy 自吐环境:给 window 等对象套一层 Proxy,打印所有被 get/set 的属性,摸清脚本到底用了哪些浏览器 API。参考补环境通用写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    window = new Proxy(window, {
    get(target, property, receiver) {
    console.log("get window", property, typeof target[property]);
    return target[property];
    },
    set(target, property, value, receiver) {
    console.log("set window", property, typeof value);
    return Reflect.set(...arguments);
    }
    });
  4. 针对性补:根据自吐日志,把缺的对象/属性一个个补上(放到 import_test.js 里用 bestv8_import 导入)。

  5. 补到不报错、结果和浏览器一致:反复迭代,直到 bestV8 里跑出的加密结果和真实浏览器里一致,补环境就成功了。

小结与选型

  • bestV8:纯净 V8 执行器,无 Node 特征、无浏览器对象,是补环境的干净起点;跨平台动态库,Python 通过 ctypes 或 PyBestV8 驱动。
  • PyBestV8:把 bestV8 封装成 pip 包,提供 eval / compile / ctx.call / bestv8_import,动态库已内置,开箱即用。
  • jsdom / happy-dom 的区别:后两者自带一套现成的 DOM 模拟(省事但特征明显、偏测试向),bestV8 则是完全干净、全靠自己补(更可控、更贴近真实浏览器、也更费手工)。实战里两条路线常按站点检测强度取舍。

免责声明

引用自官方仓库声明:该调试工具仅供技术人员进行内部测试和分析使用。请勿将其用于非法用途或以任何方式窃取第三方数据。使用该工具产生的所有风险均由用户自行承担,作者不对用户因使用该工具而造成的任何损失或损害承担任何责任。

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

参考


bestV8 使用教程:纯净 V8 环境跑 JS,做补环境调试
https://kingjem.github.io/2026/08/14/逆向/bestV8使用教程-纯净V8补环境跑JS/
作者
Ruhai
发布于
2026年8月14日
许可协议