JS逆向实战:某音a_bogus参数逆向,从抓包到Python刷播放量脚本全记录

JS逆向实战:某音a_bogus参数逆向,从抓包到Python刷播放量脚本全记录

前段时间有粉丝联系我,叫我做一个某音刷播放量的脚本,我抓包分析了一下某音的接口,发现使用的也是抖系的a_bogus参数加密。

对于这个参数,我们之前在《JS逆向实战:某茄小说a_bogus参数逆向实战与bdms.js算法还原》和《JS逆向实战系列一:某头条a_bogus参数逆向分析与算法还原》中都有接触过,也算是不陌生了。但是某音是抖系的核心产品,相比之前提到的两个产品,难度要略微复杂一些。

本文目录

  1. 抓包分析:锁定视频推荐接口
  2. 启动器追踪:定位bdms.js调用链
  3. 循环断点困境:从普通断点到日志断点
  4. 精准定位:条件断点锁定a_bogus生成
  5. 堆栈回溯:分析函数调用顺序
  6. 代码扣取:改造D函数导出加密入口
  7. 环境补全:适配Node.js运行环境
  8. 本地验证:生成192位a_bogus参数
  9. 实战应用:Python调用实现刷播放量
  10. 常见问题与解决方案

1. 抓包分析:锁定视频推荐接口

既然都是一个公司的产品,根据我们以往的逆向经验,加密算法都是放在bdms.js文件里面的。那我们就不使用XHR断点全局搜索了,直接在某音视频播放页面打开开发者控制台进行抓包,随便找一个带有a_bogus参数的请求接口。

这里就以视频推荐接口/aweme/v1/web/aweme/related/做演示,如下图:

图1:视频推荐接口中的a_bogus参数

2. 启动器追踪:定位bdms.js调用链

然后点击/aweme/v1/web/aweme/related/这个请求接口,查看这个接口的启动器(调用堆栈)。从这里可以观察到JS的一个调用顺序,其中就包含了在bdms.js中调用dXn这几个函数,如下图:

图2:启动器面板中的调用堆栈,可以看到bdms.js的调用链

3. 循环断点困境:从普通断点到日志断点

那我们就按顺序来,先进入函数d,可以看到这里是一个标准压栈调用apply。打下断点后,发现断点一直卡在这里,一直在断,是一个循环调用,如下图:

else {
    var m = n.apply(d, e);
    v[++p] = m
}
图3:断点在循环调用中频繁触发,无法正常调试

那就改换成日志断点,输出一下这些值,看看有没有生成的a_bogus值,如下图:

图4:设置日志断点,避免循环调用导致的卡死

4. 精准定位:条件断点锁定a_bogus生成

切换到控制台,可以看到一直在输出日志,有点卡。清空控制台,随便在界面划拉一下,然后再控制台搜索a_bogus,可以看到搜索出来1条。这个是在e参数里面搜索到的,这是一个数组:

[
    "a_bogus",
    "xX4jkwSiQoWcFdKb8OpZe3NUMqyANPuyvpiKbHoPer/oPqzONmNpuxCWbxzUhlcp7mpTiHKHhxGAannb8zX0ZexkLmkfSTtS10VAV0sL0qq4TMvQLHD8ewuFKw0rUcGql/54iIW6MUJo6fVAkHQm/B-99KLCQb8BPpORk/YcY9BhZzLAEZnaPBSDNXPY0fOR"
]
图5:在控制台日志中搜索a_bogus,定位到目标参数

既然找到了生成的值,那我们就来下一个精准的条件断点。断下来以后从调用堆栈看一下d函数是何时压栈、传递了哪些值。通过观察调用堆栈,调用顺序是Cn → n → X → d,在i.apply(this, t)这段代码进行的压栈调用。

function Cn(i, u) {
    var a = In();
    return function() {
        for (var n, r, o, t = [], e = 0; e < arguments.length; e++)
            t[e] = arguments[e];
        return this._start = $(),
        this._data = null == t ? void 0 : t[0],
        a(this._url) || (n = u([this._method, this._url, this._start, this]),
        o = n,
        s(r = this, "onreadystatechange", function(e) {
            return function() {
                for (var n = [], t = 0; t < arguments.length; t++)
                    n[t] = arguments[t];
                return 4 === this.readyState && o(r),
                e && e.apply(this, n)
            }
        })()),
        i.apply(this, t)
    }
}
图6:设置条件断点,精准过滤只停在a_bogus生成的位置

5. 堆栈回溯:分析函数调用顺序

现在在控制台打印一下i,可以看到i输出的是D函数里面的ƒ (){return X(e,this,arguments,r)},而thista_bogus生成所需要的参数。

图7:调用堆栈面板,清晰展示Cn→n→X→d的调用顺序

6. 代码扣取:改造D函数导出加密入口

现在跟进D函数,我们需要把这个n函数导出去。但是因为这是一个压栈的,我们不知道是何时调用X生成a_bogus的,因此我们需要查看一下e,来判断是何时执行的a_bogus生成。

function D(t, r) {
    var e = z[t];
    Y.has(t) && V.delete(Y.get(t));
    var n = function() {
        return X(e, this, arguments, r)
    };
    return Y.set(t, n),
    V.set(n, [e, r]),
    n
}

在调用堆栈点到n函数,在控制台打印一下e参数。在生成a_bogus值时,X函数e指向的是一个数组。因此我们在扣代码时,需要在这个D函数写个判断,如果e函数等于下面的值,就可以把n函数赋值给我们的全局变量:

图8:在控制台打印e参数,获取生成a_bogus时的配置数组

思路有了,开始扣代码。先把bdms.js函数复制到本地,再改一下D函数,当e参数的数组等于上面的值时,就把n赋值给window.CreateAbogus,这是我们自己创建的一个全局变量。代码如下:

function D(t, r) {
    var e = z[t];
    Y.has(t) && V.delete(Y.get(t));
    var n = function() {
        return X(e, this, arguments, r)
    };
    if(Array.isArray(e))
    {
        if(JSON.stringify(e) ==  '[[34,54,0,3,34,30,214,41,212,34,30,214,30,70,54,0,4,74,0,4,30,218,54,0,5,74,0,5,30,72,54,0,6,33,74,2,33,74,0,6,0,1,54,0,7,74,0,7,41,5,74,0,6,53,11,60,161,74,0,6,60,216,30,178,59,2,54,0,8,74,0,8,30,162,18,30,219,73,165,0,1,29,17,5,74,2,3,30,150,41,18,74,0,8,30,162,18,30,163,73,165,74,2,3,30,150,0,2,26,74,0,8,30,162,18,30,219,73,220,0,1,29,41,45,33,74,3,14,0,0,26,33,74,2,37,74,0,8,30,162,18,30,9,0,0,74,0,2,0,2,54,0,9,74,0,8,30,162,18,30,163,73,220,74,0,9,0,2,26,74,0,7,29,41,10,74,0,5,74,0,8,30,178,20,72,34,30,214,18,30,51,63,108,0,1,26,33,74,2,36,74,0,8,30,215,0,1,41,7,33,74,2,5,0,0,26,34,73,214,25,26,74,1,4,18,30,126,34,74,0,2,39,1,0,2,26,33,76],1,true,[]]')
            window.CreateAbogus = n
    }
    return Y.set(t, n),
    V.set(n, [e, r]),
    n
}

7. 环境补全:适配Node.js运行环境

代码扣好了,开始补充基础环境。创建一个env.js,把下面的代码放进去保存,代码如下:

window = global
window.requestAnimationFrame = function () { }
window.XMLHttpRequest = function () { }
window.outerWidth = 1920
window.outerHeight = 1032
window.innerWidth = 1920
window.innerHeight = 487
window.onwheelx = { "_Ax": "0X21" }
window.EventSource = function(){return X(e,this,arguments,r)}

document = {
    all: {},
    addEventListener: function () { },
    createEvent: function () { },
}
documentElement = {}

setInterval = function () { }
setTimeout = function () { }
navigator =
{
    storage: {},
    platform: 'Win32',
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
}

navigator.permissions = {
  query: function() {
    return Promise.resolve({ state: 'granted' })
  }
}
location =
{
    "ancestorOrigins": {},
    "href": "https://www.douyin.com/video/761890243432432",
    "origin": "https://www.douyin.com",
    "protocol": "https:",
    "host": "www.douyin.com",
    "hostname": "www.douyin.com",
    "port": "",
    "pathname": "/video/7618902948386573594",
    "search": "",
    "hash": ""

}
screen = {
    availHeight: 1032,
    availWidth: 1920,
    colorDepth: 32,
    height: 1080,
    pixelDepth: 32,
    width: 1920,
}

8. 本地验证:生成192位a_bogus参数

全部弄好以后,创建一个test.js用来组装调用。通过window.CreateAbogus.apply来调用我们赋值的函数n(也就是X),下面的xhr就是我们上面Cn函数里面输出的this,另一个参数t传递空就行了。

require('./env');
require('./bdms');

function get_a_bogus(args) {
    xhr = new XMLHttpRequest()
    xhr.bdmsInvokeList = [
        {
            "args": [
                "POST",
                args,
                true
            ],
            "func": function(){}
        },
        {
            "args": [
                "Accept",
                "application/json, text/plain, */*"
            ]
        },
        {
            "args": [
                "uifid",
                "ed3eadd74fe8fd7fe8cc39b2f8425a87324d41d3f6a0cfdc014da4c26c6540515265656e5f7c1bd3f0268b0b0b0607c36830303e84ec90e1e569a1bc6965a19863f80cf3a0ad8e004e3b315fc8eada61d752f8833365fc1f52311829ea2a09971e66d8ec06aa51da144ccafc44c9ea14444ec1b32748606195d5fddfc5b3e1709cdca693c2500bf9ae798d2b3ea17902a0944485451c64a8d38ded336a38e8fdd84ba24a96340c4bd0d2fe83c39f24e2147cc1a7b3cbdfa401e2c7eb3b7a7691"
            ]
        }
    ];
    return window.CreateAbogus.apply(xhr, {"0": null});
}

url = 'https://www.douyin.com/aweme/v1/web/aweme/detail/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=7614467988905934585&request_source=600&origin_type=video_page&update_version_code=170400&pc_client_type=1&pc_libra_divert=Windows&support_h265=1&support_dash=1&cpu_core_num=8&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=146.0.0.0&browser_online=true&engine_name=Blink&engine_version=146.0.0.0&os_name=Windows&os_version=10&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=150&webid=7622670628450141730&uifid=ed3eadd74fe8fd7fe8cc39b2f8425a87324d41d3f6a0cfdc014da4c26c6540515265656e5f7c1bd3f0268b0b0b0607c36830303e84ec90e1e569a1bc6965a19863f80cf3a0ad8e004e3b315fc8eada61d752f8833365fc1f52311829ea2a09971e66d8ec06aa51da144ccafc44c9ea14444ec1b32748606195d5fddfc5b3e1709cdca693c2500bf9ae798d2b3ea17902a0944485451c64a8d38ded336a38e8fdd84ba24a96340c4bd0d2fe83c39f24e2147cc1a7b3cbdfa401e2c7eb3b7a7691&verifyFp=verify_mnbsxfzo_qHLOgkFq_FSkQ_4L97_8AbL_Od6pRv7x0CV9&fp=verify_mnbsxfzo_qHLOgkFq_FSkQ_4L97_8AbL_Od6pRv7x0CV9&msToken=ub0_h-rFSuJTA_PMaW9-r-L2EpNwnIZhbrdTUDc56iJB2gve8cUnkx2HZ5c_row3_6PLc0dzgySaqJrkrgJE--2-AZ33aKu02PPnQozktjZVqpNJ0M5fSsxJFdvEr_Xt62ht2BiNbmJqzCGTWwtEM9QQprRJdXj1THWkoWETGmACGbP2yXEE_6M%3D'
a_bogus = get_a_bogus(url)
console.log('值:', a_bogus, '长度', a_bogus.length)

直接在控制台运行node test.js,可以看到能正常生成a_bogus值,而a_bogus值的长度是192位,和网页生成的长度一致。如果运行时报错误异常,去bdms.js搜索throw l,把这句注释掉就行了。

图9:终端运行结果,成功生成192位a_bogus值

9. 实战应用:Python调用实现刷播放量

最后写个Python代码,直接调用test.js文件里面的get_a_bogus函数生成a_bogus,组装请求去访问一下视频播放接口,可以看到下面的代码能够正常请求,视频播放量成功+1。

import execjs
import requests

# 加载JS文件
with open('bdms.js', 'r', encoding='utf-8') as f:
    js_code = f.read()

with open('env.js', 'r', encoding='utf-8') as f:
    env_code = f.read()

# 编译JS上下文
ctx = execjs.compile(env_code + js_code)

def get_a_bogus(url):
    return ctx.call('get_a_bogus', url)

# 构造请求
video_url = "https://www.douyin.com/aweme/v1/web/aweme/detail/"
params = {
    "device_platform": "webapp",
    "aid": "6383",
    "aweme_id": "7614467988905934585",
}

# 生成加密参数
full_url = video_url + "?" + "&".join([f"{k}={v}" for k, v in params.items()])
a_bogus = get_a_bogus(full_url)
params["a_bogus"] = a_bogus

# 发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://www.douyin.com/",
}
response = requests.get(video_url, params=params, headers=headers)

if response.status_code == 200:
    print("请求成功,播放量+1")
    print(response.json())
else:
    print(f"请求失败:{response.status_code}")
图10:Python脚本运行结果,请求成功返回视频数据

10. 常见问题与解决方案

  • Q:配置数组匹配不上怎么办?
    A:在控制台打印出当前命中的e值,复制完整数组替换判断条件。不同版本的某音配置数组可能不同。
  • Q:运行时报throw l错误如何解决?
    A:在bdms.js中搜索throw l,直接注释掉该行。这是某音的反调试代码,注释后不影响加密结果。
  • Q:生成的a_bogus长度不对?
    A:检查环境补全是否完整,特别是navigator.userAgentscreen对象的值。长度异常通常是环境检测导致的。
  • Q:Python调用execjs报编码错误?
    A:确保JS文件以utf-8编码保存,读取时加上encoding='utf-8'参数。
  • Q:刷播放量有限制吗?
    A:同一个IP、同一个视频,短时间内频繁请求可能被限流。建议合理控制请求频率,配合代理池使用。

11. 技术总结

通过本次逆向,我们完成了某音a_bogus参数的完整分析。与之前两个产品相比,某音的防护强度明显更高,主要体现在参数长度更长(192位)、加密位置更难定位、存在反调试代码等方面。

核心技巧总结

  • 日志断点解决循环调用无法调试的问题
  • 条件断点精准过滤目标参数
  • 配置数组匹配定位加密入口
  • 最小化扣代码,只导出需要的函数

12. 获取完整代码

关注公众号:孤狼网络科技,回复:某音Python代码,获取完整代码课件。

温馨提示:本文仅作技术研究用途,请勿用于非法刷量等违规行为。逆向技术的意义在于学习防护机制,而非破坏平台规则。

2 Comments

  1. oy

    从知乎来的,写的不错,自己定位到的了D函数也打日志断点看到了有生成a_bogus,但是不知道怎么把这个函数暴露给外部使用,你知乎上的文章让我眼前一亮思路豁然开朗。知乎上的文章思路写的很清晰,比这篇写的还好些,是重写了一份吗

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注