携程网phantom-token逆向 全过程

在逆向补环境时,你是否还在手动补 windowdocumentnavigator 等几十上百个环境变量?每次运行报错就补一个,补到怀疑人生?

之前我写过一篇关于开源插件 v_jstools 的文章《逆向补环境太麻烦?推荐这款免费开源插件 v_jstools,一键生成浏览器环境》,介绍了如何通过插件快速生成补环境代码。今天正好遇到一个需要补环境的网站——携程网,我们借这个机会来实战演示一下,看看这个插件到底能省多少事。

一、抓包分析:锁定本地热搜词接口

打开携程网的酒店搜索功能,进入开发者工具(F12),切换到”网络”面板。在搜索框中输入关键词进行搜索,此时抓包列表中会出现一个 /getCityList 接口,这个接口用于获取本地热搜词数据。

点击这个请求,查看请求头信息,可以看到里面有一个 phantom-token 参数,这就是我们今天要逆向的目标。

携程网phantom-token逆向 全过程
图1:getCityList 接口请求头中的 phantom-token 参数

二、全局搜索:定位 phantom-token 赋值位置

刷新页面,在”源代码”面板中使用 Ctrl+Shift+F 进行全局搜索,输入 phantom-token 作为关键词。搜索结果会指向某个 JavaScript 文件,我们直接在搜索结果中打上断点,然后重新触发搜索请求。

断点触发后,可以看到关键代码:

p = window.signature(null !== (v = e.options.body) && void 0 !== v ? v : {}),
void 0 !== e.options.headers && (e.options.headers["phantom-token"] = p)

这段代码的逻辑很清晰:第一行调用 window.signature 方法,传入请求体 body 作为参数,返回值赋给 p;第二行将 p 的值赋值给请求头中的 phantom-token 字段。

携程网phantom-token逆向 全过程
图2:断点触发,定位到 phantom-token 赋值逻辑

在控制台中打印一下传入 signature 的参数:

null !== (v = e.options.body) && void 0 !== v ? v : {}

{
    "requestType": "5",
    "head": {
        "platform": "PC",
        "cver": "0",
        "cid": "1782371822872.3538ecJ8epZG",
        "bu": "HBU",
        "group": "ctrip",
        "aid": "4897",
        "sid": "130026",
        "ouid": "",
        "locale": "zh-CN",
        "region": "CN",
        "timezone": "8",
        "currency": "CNY",
        "pageId": "10650171192",
        "vid": "1782371822872.3538ecJ8epZG",
        "guid": "",
        "isSSR": false,
        "extension": [
            {
                "name": "cityId",
                "value": ""
            },
            {
                "name": "checkIn",
                "value": "2026-06-25"
            },
            {
                "name": "checkOut",
                "value": "2026-06-26"
            }
        ]
    }
}

这是一个标准的 JSON 对象,包含了请求的类型、平台信息、城市 ID、入住日期等参数。再看看 window.signature 的执行结果:

window.signature(null !== (v = e.options.body) && void 0 !== v ? v : {})

'1006-common-otpxdOyDEpUvP4RA7xObyG4wgMJqgwlNvsqJdLwmDizsvAXiqLJXZi0ajHzvSlY49i3gycTydkyckEdmW5UWMBi5oIzLRbUEsaRZTwQ3wA7YdPW1pYgYH9xgNRF1RcfRdQyG0jOhYa5itXyzGEqnEabWXfv6zYM9i7OYaEsFJ1OrMYZSilSIgwcAKSzjqGjF6ElOyaEDsimhYbEL6YSoytEogYOBycMKdZiXUjoGjkgWZDyd7Io9w9cjLFwU8epgEP7I0gw1DihnRfE9OYqfyQsKqMitUjb3j3MWQDy5ENTYSUykcW6MespytpjaE3qibUyFOvk0WHtjD5ea7wO8IUXEmHj3EZ7igmJG6RfE7nYdUwDgvcbeTqeM8jz9yF8JoQE4qjZ7jSE0hiSLJ0bWGETHY0kwANw7ke9ZjQcImSx4syXE4TiGDJT5YSEMmY9LwqPE6NwNti7kx50eLnYB5wM1j7fxgqyAEzmilSJGTYNEqbYHtwmfw9UimlvkaYB5jOMwZ9Ifpxs3y7EFBi6AJ7GRaENSYbFwf8wFXeGAjOoIZbWZMEn8RXdjzENdi45JXEFHYOqJgZwotEQElSYONJt4iBtjqEbTYUOJB7j1QY5ETnY3kJd9wF6wBEFPYp0JsaiNkJoEhzYkPJonishyqET0Y8kwZpyTNjBcyZQjfNWT6E5ZRNljBEHBiATJdEtqYG5Jlgw7GEOEbDYSgJ7qipPjtEmtYQ7JbUjFdYHEb6YmoJNnw68wXEnFYa7JNpiQ1JFEgDY6hJhZioTyPE4kY6Dw1ZyUSjZsEN4wdUwkDKHMwpDIgXEnbjBEGLiQ7J0NEfQwUE9TYNmw9GwcXK5DjgdwF1r7leNOYZEGsiQGJ0SYghWFDYLpwTZegkWSLwNTeptYZUJXEFmYBdwspwmDKfbj15wNzrTcikDv7sjHEQkiB4JpFYTAWqsYaswUMeBqW7MwSGembKB3e8miHYMXi9mihnJ38R4FyHdjsaYM7iN3ya4RcLjo0YF9YMHjSfy9BYnhIN9iqdJkXif5jd9jzPwA8KZ3jg3jZ1EDde3fjnvA1vmsezYqwoyntwQME96jzFi0PYmaJbBYtnJ4lw9Be19iMHw16wb8j3teg7EPkEflJUlvHoeFqJa0vPnJOBWA6e4fwBaE6cEUGvqtjzGJsMwGLv0kyBXyM7yQajUlYnZytcIMYpORBJfQiUayz5wMgY6swlhESmjH0YUswLPjBkKP7JkYfLyk5effKkZi8fYzXYtDWSzwbYhMEA3WgSe6tvmteF3YmtiUSYlzJPPxl8RQYcFxhlJ6LK1LeBkEGbjXbWlsef8WU0jOYfLebMJG9xShrMtKUge4bE87W0w1MEtSyaYzDIgHIOMiAHR87vPmY8NW8DeLNRqQW7Fj8MWlSE7ted7vPYcnen9wmNRQkRULvBSY40WoAe76RO6Wp5ikQYUgifnw1br4Y1wsSYHfKl1E8lYh0vgyZgE4HK7YqQEt4iAvgnYn5iPSwGAR91E7DW1kj34YbhemYpSRn3WFfw4HJQcjN4Kmle4Yqpx7Mjly5mjAOwZcv9qjBNvbbK8pRDYqqKQaKDMJ5hYpfyqhRhLxSYZSR4AWnAx8Ujnmwodv97jM8YlojoTv9YSMWmXinAy34JXsWF3rGXEL7r4Yanx3meG0JDQezoRfAeOYTMjaoEDmjFHRDfWngwt6JMnWzGRtsyXQRMlRaBvoNYpkiz6rS1y4Yp5Yd5ENGJXOR9pvtUYq4WFpe13Es3wQoJULJgOePw5YA5RhaRlcRQsRonvSZY4OWageoURa5Wm1i8XYlnx9tIBZ'

生成了一个以 1006-common- 开头的长字符串,看起来长度还不短。

三、断点调试:找到加密入口 signature 函数

既然确定了 phantom-token 是由 window.signature 生成的,那我们直接跟进这个函数,看看它的实现逻辑。

在”源代码”面板中,通过调用栈找到 signature 的定义位置。经过跟踪发现,window.signature 实际上是 _unknown_45ed5 的一个别名,所在的文件是 sdt.1006-common.min.js,这是一个经过混淆压缩的 JavaScript 文件。

携程网phantom-token逆向 全过程
图3:signature 函数定义位置

跟进代码结构,可以看到类似这样的混淆代码:

, function _unknown_b51f0(e, f, g, h, i) {
    var j = _unknown_40af6(e, f)
      , a = _unknown_40af6(g, h)
      , b = _unknown_3ec6b.slice(j, a + 1)
      , c = _unknown_2999a;
    return _unknown_45ed5(function() {
        return _unknown_e00bc = {
            _unknown_72041: this || _unknown_36ab6,
            _unknown_69e85: _unknown_e00bc,
            _unknown_0119f: arguments,
            _unknown_60e6f: c
        },
        _unknown_3c349(b),
        _unknown_e00bc = _unknown_e00bc._unknown_69e85,
        _unknown_3b272(_unknown_8a054[0])
    }, _unknown_c451f, _unknown_c451f, 0),
    ++i
}
, function _unknown_58cbe() {
    return _unknown_45ed5(_unknown_c451f, _unknown_c451f, _unknown_c451f, 0, 0),
    _unknown_6306f(),
    _unknown_fb61f(),
    1 / 0
}
, function _unknown_c5f09(a, b, c, d, e) {
    // 后续代码省略......
}

这是一段典型的混淆代码,所有的函数名、变量名都经过了重命名,可读性很差。但好在,我们不需要完全理解它的内部逻辑,只需要将它完整抠出来,在 Node.js 环境中运行即可。

四、算法分析:追踪签名生成逻辑

从上面的代码片段可以看出,整个加密逻辑被分散在多个 _unknown_ 开头的函数中。signature 函数内部调用了 _unknown_45ed5,而这个函数又依赖于 _unknown_40af6_unknown_3ec6b_unknown_3c349 等一系列辅助函数。

这种混淆方式的特点是:

  • 所有的函数名都是动态生成的,每次加载可能都不一样
  • 代码的执行流程通过嵌套函数调用串联
  • 核心的加密逻辑被分散在多个函数中,增加了分析难度

不过,由于 signature 函数的实现全都集中在 sdt.1006-common.min.js 这一个文件中,我们只需将整个文件的内容拷贝下来,不需要做任何修改,就能在本地复现它的功能。

五、代码扣取:拷贝加密函数主体

在”源代码”面板中找到 sdt.1006-common.min.js 文件,右键选择”Save as…”,将整个文件保存到本地。或者直接全选复制,新建一个 sign.js 文件粘贴进去。

需要注意的是,这个文件是混淆压缩后的代码,里面包含了完整的 signature 函数及其所有依赖。由于它本身就是一个独立的模块,我们不需要额外扣取其他文件。

携程网phantom-token逆向 全过程
图4:保存 sdt.1006-common.min.js 到本地

六、初次运行:Node.js 报错环境缺失

现在新建一个 main.js 文件,引入 sign.js 并编写调用代码:

require("./sign")

body = {
    "requestType": "5",
    "head": {
        "platform": "PC",
        "cver": "0",
        "cid": "1782371822872.3538ecJ8epZG",
        "bu": "HBU",
        "group": "ctrip",
        "aid": "4897",
        "sid": "130026",
        "ouid": "",
        "locale": "zh-CN",
        "region": "CN",
        "timezone": "8",
        "currency": "CNY",
        "pageId": "10650171192",
        "vid": "1782371822872.3538ecJ8epZG",
        "guid": "",
        "isSSR": false,
        "extension": [
            {
                "name": "cityId",
                "value": ""
            },
            {
                "name": "checkIn",
                "value": "2026-06-25"
            },
            {
                "name": "checkOut",
                "value": "2026-06-26"
            }
        ]
    }
}

sign = window.signature(body)
console.log("phantom-token:" + sign)
console.log("length:" + sign.length)

运行 node main.js,果然报错了:

ReferenceError: window is not defined

这是因为代码运行在 Node.js 环境中,缺少浏览器环境下的 windowdocumentnavigator 等全局对象。sign.js 中的代码依赖于这些对象,在 Node.js 中无法直接运行。

如果手动去补这些环境变量,一个接一个地补,不知道要补到什么时候。这时候就要请出我们的神器——v_jstools 插件。

七、插件补环境:一键生成 env.js

打开浏览器,确保 v_jstools 插件已经安装并启用。在携程网的页面上刷新一下,插件会自动捕获当前页面的浏览器环境,包括:

  • window 对象及其所有属性
  • document 对象
  • navigator 对象
  • location 对象
  • screen 对象
  • 以及各种内置函数和原型链上的方法
携程网phantom-token逆向 全过程
图5:v_jstools 插件生成的补环境代码

插件生成的环境代码是一段完整的 JavaScript,包含了当前页面所有必要的环境变量和对象的模拟实现。我们把它复制下来,新建一个 env.js 文件保存。

重要提示:保存时需要把下面这几句注释的代码取消注释,否则运行时会一直输出大量日志,导致程序卡死:

v_console_log = function(){} // 关闭日志输出
setTimeout = function(){} // 关闭定时器
setInterval = function(){} // 关闭定时器

这样修改后,所有调试日志和定时器都会被静默处理,程序才能顺畅运行。

八、最终验证:phantom-token逆向成功生成 1803 位 值

在 main.js 文件最开头引入 env.js,然后再引入 sign.js

require("./env")
require("./sign")

body = {
    "requestType": "5",
    "head": {
        "platform": "PC",
        "cver": "0",
        "cid": "1782371822872.3538ecJ8epZG",
        "bu": "HBU",
        "group": "ctrip",
        "aid": "4897",
        "sid": "130026",
        "ouid": "",
        "locale": "zh-CN",
        "region": "CN",
        "timezone": "8",
        "currency": "CNY",
        "pageId": "10650171192",
        "vid": "1782371822872.3538ecJ8epZG",
        "guid": "",
        "isSSR": false,
        "extension": [
            {
                "name": "cityId",
                "value": ""
            },
            {
                "name": "checkIn",
                "value": "2026-06-25"
            },
            {
                "name": "checkOut",
                "value": "2026-06-26"
            }
        ]
    }
}

sign = window.signature(body)
console.log("phantom-token:" + sign)
console.log("length:" + sign.length)

再次运行 node main.js,这次没有任何报错,顺利生成了 phantom-token

phantom-token:1006-common-zTUx35ykTi1aWHGwkhy7ti0Ar1ty7GJU9whFwgpJcZwmZiA8j03iFMvAMjQOEN0yhXWTHvnzwtoyptE4qEh8JqsjGQE4Swnhj3zyhHyLojGgYzkxAjGYmvqmwGtwSdRpzyoDjL6YaciG4yztJaqilXEfHWafwf7ybtY7cebsvnSIZY5lRlZRzQIMOKkTjlOjNkEp7ykEaPi8hYFE8nYqcykE64YGByzLK0dib4jTDjHgW75ylGIlmwOhjdawodegZEMOIUswGFi04RfEQaYDNyBUKNli4pj6gjs8WOby6EhNY8OynqWlke5qyoZjUEh5iZlyMdvPcWQPj4heq3wapI5bEMfjmE8Ti9mJnQR0ES5Y3fw6BvcFeQXesGjM5yM7J8XE5HjO3j8EfniOUJgcW0EzPYMTwQfwXAenLj81Ipoxhcy0EP9izDJ8FYqEsqYlDw9HEfcwQciMcxUmeAHY5Mw7bj1mxM8yFEQgi5nJGnYFEO9YsOwOdwUNi8svHmY1Zj8fwtTIMOx4dyMENciG9JNtRBEhpYmLwT0wFoe9Dj7qIocWNnEULRb7jzEzPikzJcENBYtpJQGwT5EzEt1YZhJs1i7SjfEQMYStJfnj1hY8EtHYmQJl7wGfwME4FY1TJ4Ai69JZE1mY5lJGMipAylEZbY5mwsZyn4jbPyLfjnsWZGELqRZdjmEzni6mJfE1QYMLJZtwglE3EUfY08JXZiGHjaEUhY4sJpgjFmYNEUPYb9Jn8wFPwzE6GYDkJ81il0JBEH7YAXJmQikOyPEnOYt7wUnysBj0bE5nwo5wDkKptw1aIglEnljUEoMimgJTzEGXwkEsHYHmwanwA7KsLjGpwtnrFteoNY3ESpihXJMlYaHWdSYXkw5beGqWHLw04eaZYShJzEQ1Y17wb1w7hK8ZjAfwglrSbiFsvbOjSENXiboJXnYZNWzAYPbwNUed4WOHwT0eD5j88K7hYhYlAYXqjPXxbsRBny35jS7Yhqia7yBTRczjUhY9XYcZjmPyz1YqfIz7iHQJm5ih0jHBjgzwl9K7sjsojk8Epseb0jNBjhnra4rSYTLIspYLkJhBJFmw8GvfmwZdi0lE0Ovcaw7UeFOWaAv4PE0ZJMTefXEzAjmzJnLvPae5oj7cJ0fESzjfDeH4E8OwAbJSXwLtJHtimkvHDjsSRoUJPtimpvqHRhXJlvMYa5j93EFzwgAennx16KsfIB5JdZiQUxThv6SEDFisY50rf0vPAIdziAoYnTjPLJmZW8YLEOSe9prXBvo1ePAYBqiFTYlDJtzx8fyPYfzWD9KOQYDQelfE01j5gWcAv8GrP0RkYsPrUkEbByqfrzfKXqeGLEnZWkdrD3RaDxlYobEgAi6oIt6RfSvnQYaNWPUeZ7R8pW39jq6WDlJf9WDbr6YAdY8cwL6WQORNbvPXY1MWkze7sRaPW5miUpYZnYS6vLNezYTlv9tJkUIdAjOmET0jp9yQXWzYfXE8Txz0wpZYoliLdwNURzBEdfWbGyhhxHsKBYGsiTPiLaEQheSMR6qEGUWpaiZYNkiXBenXIk3eqNRsfx4wp9KlYN4EZ8yf7e94eHORbGeASJNgxcYUZJaaKBgjPmeDcRUUKSv8MeZYQ3Y8MJfgWHbeaARsygNinmR6Y50K4HJ97JUsvTkysfrPYkFRXUjhbKSPeldR9NwGHrhkyQYggx3hxhajlPR3qvs1Yn4WGmeZ1vlUR3qR9Drl7KoqeQYkTr34rh6EgbR8Ov7aYoQWZ1eO6RTzW7aibSYAhesmYk9
length:1803

生成的 phantom-token 长度为 1803 位,以 1006-common- 开头。将它与浏览器抓包获取的 phantom-token 进行对比,二者完全一致,说明我们成功在 Node.js 中复现了携程网的签名生成逻辑。

phantom-token逆向成功生成 1803 位 值
图6:Node.js 运行结果

九、技术总结

  • 目标接口/getCityList 本地热搜词接口
  • 目标参数:请求头中的 phantom-token 字段
  • 定位方式:全局搜索 phantom-token,在赋值处断点调试
  • 加密入口window.signature(body) → 位于 sdt.1006-common.min.js 中的混淆代码
  • 扣取方式:完整拷贝 sdt.1006-common.min.js 文件
  • 补环境方式:使用 v_jstools 插件一键生成 env.js,关闭日志和定时器输出
  • 验证结果:Node.js 生成的 phantom-token 长度为 1803 位,与浏览器环境完全一致

至此,携程网 phantom-token 的补环境逆向工作已全部完成。可以看到,借助 v_jstools 插件,原本需要逐个补几十上百个环境变量的繁琐工作,被简化为”刷新页面→复制代码”两步操作,效率提升了不止一个量级。

如果你也在逆向中遇到过环境补不完的困境,不妨试试这个插件,或许能帮你省下大量时间。

温馨提示:本文技术仅供学习研究,请遵守相关法律法规,不得用于非法用途。

给TA打赏
共{{data.count}}人
人已打赏
技术文档

腾讯音乐sign参数逆向 评论点赞接口签名完整复现

2026-6-25 2:02:15

技术文档

从零搭建Frida环境 超详细攻略

2026-7-3 1:23:11

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
有新私信 私信列表
搜索