Blog

Twitter header: part 4

2024-12-06

#Twitter

这是一篇用于补充的文章,用于补充一年以后的变化,前三部分请查看 https://antibot.blog/ (注:我不是前三部分的作者)

前情提要

近期 twitter 开始在很多 web 请求校验 x-client-transaction-id,不符合的会直接返回 404,花了点时间琢磨了前三篇文章的内容

前三篇合起来大概讲的就是,请求的 method(大写)和 path,以及 twitter 网页中的 twitter-site-verification 和来自四个 svg 的 d 属性的值的二维数组经过一系列的变换处理得到一串用于请求 headerx-client-transaction-idbase64 文本

原作者在 blog 留下了 反混淆脚本id生成工具 的仓库链接,但都已经删库,我在社区找到了其他 fork ,链接请看文末 #参考

结合原作者和另一个宣称已经修复好这个问题的项目d60/twikit的源码,我修复好了 golang 版的 id生成工具 并总结出这篇文章

获取动态的值

twitter-site-verification

原作者称之为 key

document.querySelectorAll("[name^=tw]")[0].getAttribute("content")

d属性

原文的 frames,太抽象了,英语不太好的我想了很久才弄明白

[...document.querySelectorAll('[id^=loading-x-anim-]>g>path:nth-child(2)')].map(
    node => node.getAttribute('d')
)

顺带提一句,四张 svg 的形状都是 X Corp. 的标志 𝕏 的样子

ROW_INDEX 和 KEY_BYTES_INDICES

go 版没有这个概念,估计当时没考虑过这个问题,这两来自 python 版,从文件名格式为 ondemand.s.<hex>.js(比如 ondemand.s.fc2aec5a.js) 的 js 文件提取

ON_DEMAND_FILE_REGEX = re.compile(
    r"""['|\"]{1}ondemand\.s['|\"]{1}:\s*['|\"]{1}([\w]*)['|\"]{1}""", flags=(re.VERBOSE | re.MULTILINE))
INDICES_REGEX = re.compile(
    r"""(\(\w{1}\[(\d{1,2})\],\s*16\))+""", flags=(re.VERBOSE | re.MULTILINE))

提取到四个数值,第一个为 ROW_INDEX,后三个组成的数组就是 KEY_BYTES_INDICES

修复问题

不论是 golang 版还是 python 版,都有一些待修正的问题,我按照执行顺序一一列出来

精度问题

Bezier Epsilon

go float64 自身就存在精度问题,所以……接受有损耗的事实吧……尽管这些值遇到别的语言可能会没有任何问题

最开始借鉴的那个 js库 的精度是 1e-5,而我去翻了 FirefoxWebKit 的源码,它们的精度分别是 1e-61e-7

下面这个例子在修改成 1e-6 以后输出就正确了

以下案例计算 rgb 值得到的结果是 [181.80453949578018, 55.27301397059616, 82.49921158086055],四舍五入取整后与浏览器计算结果 rgb(182, 55, 83) 不相符

{
    "twitter_site_verification": [208,199,89,137,6,185,69,209,243,208,119,16,83,58,15,170,216,83,56,143,153,0,87,140,13,50,56,45,49,75,198,181,246,254,156,17,240,127,70,115,251,22,182,217,231,143,26,195],
    "2d_array": [[155,169,92,23,59,95,64,16,131,74,114],[234,145,20,13,70,244,247,15,245,162,37],[244,19,47,147,157,138,119,8,223,84,26],[249,162,66,213,147,244,254,212,85,205,10],[95,141,167,167,103,78,83,136,134,44,61],[92,106,95,65,180,27,159,203,160,12,75],[100,227,195,73,96,95,247,241,165,71,228],[154,77,65,25,47,238,76,245,119,196,83],[45,4,40,240,1,148,191,152,10,221,28],[190,176,29,229,131,241,157,156,188,5,75],[203,46,108,139,74,31,20,93,236,194,244],[236,139,223,98,69,108,100,107,203,69,215],[62,252,4,255,52,230,9,223,91,63,50],[89,128,73,28,225,4,174,196,176,141,149],[203,63,95,137,92,208,250,197,85,13,3],[127,95,199,140,115,172,59,227,83,241,0]],
    "ondemand_s_hex": "9494e08",
    "row_index": 13,
    "key_bytes_indices": [18,23,22]
}

frameTime

算法中有一个很重要的值 frameTime 的精度要求四舍五入到整十,即 frameTime-5 <= frameTime <= frameTime+4 (frameTime%10 === 0) 的结果是一样的,原作者在第二篇都写出来了,结果最后的代码却又没加上去,导致我花了大量时间在研究为什么算出来的结果跟 twitter 的结果不一致

// If the comment is in Chinese, it means the code is generated by chatgpt
func calculateFrameTime(keyBytes []int, indices []int) int {
    // 初始化结果为1(乘法的初始值)
    frameTime := 1
    for _, index := range indices {
        // keyBytes[index] % 16 的结果累乘
        log.Println(frameTime, index, keyBytes[index], keyBytes[index]%16)
        frameTime *= keyBytes[index] % 16
    }
    log.Println(keyBytes, indices, frameTime)
    return frameTime
}
frameTime := int(math.Round(float64(calculateFrameTime(keyBytes, DEFAULT_KEY_BYTES_INDICES))/10) * 10)

curves

只要保留两位小数,不需要后面一大串

curves := [4]float64{}
for i := 0; i < len(row); i++ {
    curves[i] = toFixed(a(float64(row[i]), b(i), 1.0), 2)
}

color hex

实际变换颜色时,最终 rgb 的值会有下面四种情况

  • value<0:由于不会有负数,所以计算得到的负数等同于 0
  • value>=256:同样的道理当值大于等于 256 时,应当为 ff
  • 0<=value<=15:当 colorValue 的值小于等于 f 时,不需要补 0 前缀,只要一位即可
  • 其他情况转十六进制即可
for i := 0; i < len(color)-1; i++ {
    if color[i] > 0 {
        roundedColor := math.Round(color[i])
        if roundedColor >= 256 {
            strArr = append(strArr, "ff")
        } else {
            strArr = append(strArr, strings.TrimPrefix(hex.EncodeToString([]byte{byte(roundedColor)}), "0"))
        }
    } else {
        strArr = append(strArr, "0")
    }
}

角度

文章作者处理后的代码里面有一段

_r = (n, W, t, r) => {
    const o = n * (t - W) / 255 + W;
    return r ? Math.floor(o) : o["toFixed"](2);
}

而后面获取目标角度时也有这个函数出现:_r(numArr[6], 60, 360, !0),所以目标角度的值需要向下取整

matrix

当 matrix 有值等于 0 或者 1 时,转换成字符串会位数不够,所以需要额外判断

if rounded == float64(1) {
    strArr = append(strArr, "1")
} else if rounded == float64(0) {
    strArr = append(strArr, "0")
} else {
    strArr = append(strArr, "0"+strings.ToLower(floatToHex(rounded)[1:]))
}

DEFAULT_KEYWORD

概念来自 python 版,值从 bird 变成了 obfiowerehiring,原 blog 称之为 keyWord

var keyWord = "obfiowerehiring"
hash := sha256.Sum256([]byte(fmt.Sprintf(`%s!%s!%v%s%s`, method, path, timeNow, keyWord, strings.Join(strArr, ""))))

ADDITIONAL_RANDOM_NUMBER

概念来自 python 版,值从 1 变成 3

var ADDITIONAL_RANDOM_NUMBER = 3
bytes = append(bytes, ADDITIONAL_RANDOM_NUMBER)

其他

  • 原作者提供的反混淆脚本偶尔能用,但不是一直可用

参考


评论区