JS逆向解密教程1:某吧用户关注功能jt参数分析与解密

JS逆向解密教程1:某吧用户关注功能jt参数分析与解密

之前我们成功破解了某头条的 a_bogusmsToken_signature 参数,今天来挑战一个新平台——某吧的用户关注功能jt参数!这个参数设计相当巧妙,通过二级接口生成,是某吧反爬体系中的重要环节。掌握了它的JS逆向解密方法,就能实现自动化关注功能。本章将完整分享jt参数的逆向全过程,重点讲解如何通过数据抓包实战找到加密入口。

本文目录

  1. 数据抓包实战:锁定关注接口
  2. 参数溯源:发现二级加密机制
  3. XHR断点定位加密入口
  4. 堆栈分析与加密逻辑追踪
  5. 扣取加密代码
  6. 参数分析与调试技巧
  7. 本地运行与结果验证
  8. 常见问题与解决方案(FAQ)
  9. 获取完整课件
1. 数据抓包实战:锁定关注接口

首先按F12打开浏览器开发者工具,切换到Network(网络)面板,并确保录制状态为开启(建议勾选”Preserve log”防止请求被清除)。在某吧中任意选择一个用户,点击“关注”按钮,此时网络面板中会出现一系列请求。

通过筛选XHR请求,很快就能发现目标接口:/home/post/follow。仔细查看这个POST请求的载荷(Payload),其中包含了我们要破解的关键参数——jt,同时还包含其他参数如id(用户ID)、un(用户昵称)等。

关注抓包
图1: 关注接口请求详情,可见jt参数在Form Data中
2. jt参数溯源:发现二级加密机制

通过全局搜索jt参数的值,发现jt参数并不是直接生成的,而是来自另一个接口的响应!在/home/post/follow请求之前,可以看到一个关键接口:

/abot/api/v1/tpl/commit

commit
图2: commit接口在关注接口之前调用

这个接口的响应数据中包含了一个t参数,而这个t参数的值正是/home/post/follow接口中使用的jt值!更重要的是,/abot/api/v1/tpl/commit接口的请求主体是一个加密字段,格式为:

CODED--v20ezLvhHO=QsO>Zau4^)gFbY/Kg]/2h`h7dDK9MoK:V]q0ZwcB^U+GcY+........

abot接口请求和响应-显示加密请求体和返回的t参数
图3: commit接口的加密请求体和响应

这就是本次JS逆向解密的关键发现:jt参数实际上是一个二级加密的结果!我们需要先破解/abot/api/v1/tpl/commit接口的加密逻辑,才能最终获得可用的jt参数。这种设计增加了逆向难度,是典型的企业级反爬策略。

3. XHR断点定位加密入口

现在我们需要找到CODED-开头的加密字符串是如何生成的。由于js文件中没有CODED-明码关键词,我们采用XHR断点的方式进行定位。这是JS逆向解密常用的手段。

源代码/来源面板中,点击右侧的“+”号添加XHR断点,输入:

/abot/api/v1/tpl/commit

重新点击关注按钮触发操作,代码执行会在请求发出前自动暂停。此时在Call Stack(调用堆栈)中,我们可以看到完整的函数调用链条,通常会有多个匿名函数和异步调用。

XHR断点设置
图4: XHR断点设置界面
JS逆向解密调用堆栈分析
图5: 调用堆栈显示的函数调用关系

通过分析调用堆栈,我们成功定位到了生成CODED-加密字符串的核心代码段:

 var t = JSON[_0x1d3c("0x1b")](x)
                  , n = this[_0x1d3c("0x2be")](_0x422d33[_0x1d3c("0x1b6")](t));
                e[_0x1d3c("0x2c8")](_0x1d3c("0x2c9"), _0x1d3c("0x1b7")),
                e.send(d + n)

通过控制台调试和代码分析,我们可以解析出这些函数的真实功能:

加密函数分析
图6: 在控制台中分析加密函数

使用控制台输入以下命令来解析混淆函数:

// 解析_0x1d3c函数的真实含义 
_0x1d3c("0x1b6"); // 输出: "encode" 
_0x1d3c("0x2be"); // 输出: "encrypt"
_0x1d3c("0x1b");// 输出: "stringify"
d // 输出: "CODED--v20"

这段代码揭示了CODED-加密字符串的生成过程:

var t = JSON.stringify(x), 
n = this.encrypt(_0x422d33.encode(t));
 e.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
 e.send('CODED--v20' + n)
  • JSON.stringify(x) – 将数据对象序列化为JSON字符串
  • _0x422d33.encode(t) – 对JSON字符串进行Base64编码预处理
  • this.encrypt(...) – 核心加密函数,生成最终的加密内容
  • 'CODED--v20' + n – 添加固定前缀形成最终请求体
4. 堆栈分析与加密逻辑追踪

现在我们需要深入分析这二个关键函数:

  • _0x422d33.encode() – 数据预处理函数(Base64编码)
  • this.encrypt() – 核心加密函数(位移加密)

因此,原始代码的实际逻辑为:

n = this.encrypt(_0x422d33.encode(t));

现在我们需要重点分析this.encrypt()_0x422d33.encode()这两个函数。

通过进一步调试分析,我们成功还原了这两个关键函数的实现逻辑:

断点停到n
图7: 在加密关键位置设置断点

this.encrypt() 是一个自定义的位移加密函数,而 _0x422d33.encode() 是一个经过修改的Base64编码器

加密函数详细解析:

// 核心加密函数 - 字符位移加密
 function encrypt(x) 
{
 for (var c = "", d = 0; d < x.length; d++) 
{ var _ = x.charCodeAt(d), e = d % 32;
 // 动态偏移量,基于字符位置计算 // 只对特定ASCII范围的字符进行加密
(41-122) c += _ <= 122 && _ >= 41 ? (_ + e > 122 ? String.fromCharCode(40 + _ - 122 + e) :
 // 溢出处理:循环到41开始 
String.fromCharCode(_ + e) 
// 正常偏移 ) : 
String.fromCharCode(x.charCodeAt(d)) // 保持原样 
} 
return c; }

这个加密算法的特点是:

  • 使用动态偏移量(基于字符位置取模32)
  • 只对ASCII码41-122范围内的字符进行加密
  • 处理溢出情况,实现循环移位
  • 其他字符保持原样,避免破坏数据格式

Base64编码器解析:

// 自定义Base64编码器 var _0x422d33 = { keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
 encode: function(x)
 { var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0;
 // 先进行UTF-8编码 
x = this.utf8Encode(x);
 while (i < x.length) 
{ 
chr1 = x.charCodeAt(i++);
 chr2 = x.charCodeAt(i++);
 chr3 = x.charCodeAt(i++); 
enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63;
 if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; 
} output = output + this.keyStr.charAt(enc1) + this.keyStr.charAt(enc2) + this.keyStr.charAt(enc3) + this.keyStr.charAt(enc4); 
}
 return output;
 }, // UTF-8编码和解码方法 
utf8Encode: function(string) {
 // ... UTF-8编码实现 
}, utf8Decode: function(utftext)
 {
 // ... UTF-8解码实现
 } };

完整加密流程总结:

  1. 原始数据通过 JSON.stringify() 序列化为JSON字符串
  2. 使用自定义Base64编码器进行编码(包含UTF-8预处理)
  3. 对Base64字符串进行位移加密(动态偏移)
  4. 添加 CODED--v20 前缀形成最终请求体
  5. 发送到/abot/api/v1/tpl/commit接口获取jt参数
  6. 在关注接口中使用获取到的jt参数
5. JS逆向解密扣取加密代码

现在我们需要将这两个核心函数完整地扣取出来。以下是完整的加密代码实现:

/** * 某吧jt参数加密完整实现 */
 // 位移加密函数
function encrypt(x) {
	for (var c = "", d = 0; d < x.length; d++) {
		var _ = x.charCodeAt(d),
			e = d % 32;
		c += _ <= 122 && _ >= 41 ? _ + e > 122 ? String.fromCharCode(40 + _ - 122 + e) : String.fromCharCode(_ + e) :
			String.fromCharCode(x.charCodeAt(d))
	}
	return c
}
 // Base64编码工具对象 
var _0x422d33 = {
	keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
	encode: function(x) {
		var c = "",
			d = void 0,
			_ = void 0,
			e = void 0,
			t = void 0,
			n = void 0,
			a = void 0,
			r = void 0,
			i = 0;
		for (x = this['utf8Encode'](x); i < x.length;)
			d = x.charCodeAt(i++),
			_ = x.charCodeAt(i++),
			e = x.charCodeAt(i++),
			t = d >> 2,
			n = (3 & d) << 4 | _ >> 4,
			a = (15 & _) << 2 | e >> 6,
			r = 63 & e,
			isNaN(_) ? a = r = 64 : isNaN(e) && (r = 64),
			c = c + this['keyStr'].charAt(t) + this.keyStr.charAt(n) + this['keyStr'].charAt(a) + this['keyStr']
			.charAt(r);
		return c
	},
	decode: function(x) {
		var c = "",
			d = void 0,
			_ = void 0,
			e = void 0,
			t = void 0,
			n = void 0,
			a = void 0,
			r = void 0,
			i = 0;
		for (x = x.replace(/[^A-Za-z0-9\+\/\=]/g, ""); i < x.length;)
			t = this['keyStr'].indexOf(x.charAt(i++)),
			n = this['keyStr'].indexOf(x.charAt(i++)),
			a = this['keyStr'].indexOf(x.charAt(i++)),
			r = this['keyStr'].indexOf(x.charAt(i++)),
			d = t << 2 | n >> 4,
			_ = (15 & n) << 4 | a >> 2,
			e = (3 & a) << 6 | r,
			c += String.fromCharCode(d),
			64 !== a && (c += String.fromCharCode(_)),
			64 !== r && (c += String.fromCharCode(e));
		return c = this['utf8Decode'](c)
	},
	utf8Encode: function(x) {
		x = x.replace(/\r\n/g, "\n");
		for (var c = "", d = 0; d < x.length; d++) {
			var _ = x.charCodeAt(d);
			_ < 128 ? c += String.fromCharCode(_) : _ > 127 && _ < 2048 ? (c += String.fromCharCode(_ >> 6 |
					192),
				c += String.fromCharCode(63 & _ | 128)) : (c += String.fromCharCode(_ >> 12 | 224),
				c += String.fromCharCode(_ >> 6 & 63 | 128),
				c += String.fromCharCode(63 & _ | 128))
		}
		return c
	},
	utf8Decode: function(x) {
		for (var c = "", d = 0, _ = 0, e = 0, t = 0; d < x.length;)
			_ = x.charCodeAt(d),
			_ < 128 ? (c += String.fromCharCode(_),
				d++) : _ > 191 && _ < 224 ? (e = x.charCodeAt(d + 1),
				c += String.fromCharCode((31 & _) << 6 | 63 & e),
				d += 2) : (e = x.charCodeAt(d + 1),
				t = x.charCodeAt(d + 2),
				c += String.fromCharCode((15 & _) << 12 | (63 & e) << 6 | 63 & t),
				d += 3);
		return c
	}
}

 /** * 生成commit接口请求体的主函数 *
@param {Object} data - 要加密的原始数据对象 *
@returns {string} CODED--v20开头的加密字符串 */

 function getCommit(data) 
 {
    var jsonString = JSON.stringify(data);
    var base64Encoded = _0x422d33.encode(jsonString);
    var encrypted = encrypt(base64Encoded);
    return 'CODED--v20' + encrypted;
 }
6. 参数分析与调试技巧

先来查看一下需要加密的参数结构。在控制台输入 tx,可以看到传入的加密参数是一个复杂的JSON对象:

控制台输入参数t
图8: 加密参数的完整结构

典型参数结构包含:

{ "lt": "1752044129609", // 时间戳 "ct": "1752044129609", // 客户端时间戳 "action": "follow", // 操作类型 "uid": "123456789", // 目标用户ID "from": "profile", // 来源页面 "v": "3.0.16", // 版本号 "ut": "" // 用户token或其他标识 // ... 其他字段 }

调试技巧:

  • 中间值输出: 在加密函数关键位置添加 console.log 打印中间值
  • 变量监控: 使用浏览器调试器的 Watch 功能监控变量变化
  • 逐步执行: 使用F10逐步执行观察每一步的加密结果
  • 对比验证: 对比浏览器生成的结果与本地计算结果
  • 参数捕获: 使用以下代码在控制台捕获完整参数:copy(t)
7. 本地运行与结果验证

将上述代码保存为 jt_encrypt.js 文件,并添加测试代码:

// 测试代码 
var testData = { "lt": "1752044129609", "ct": "1752044129609", "action": "follow", "uid": "123456789", "from": "profile", "v": "3.0.16", "ut": "" };
 console.log("原始数据:", JSON.stringify(testData)); 
console.log("Base64编码后:", _0x422d33.encode(JSON.stringify(testData)));
 console.log("位移加密后:", encrypt(_0x422d33.encode(JSON.stringify(testData)))); 
console.log("最终加密结果:"); console.log(getCommit(testData));

在终端中输入 node jt_encrypt.js 运行:

jt参数运行结果
图9: 本地运行结果验证

运行结果将生成完整的 CODED--v20... 格式的加密字符串,可以直接用于某吧的API请求。

验证方法:

  • 字符串对比: 将本地生成的加密字符串与浏览器中的进行逐字符对比
  • 接口测试: 使用生成的加密字符串实际调用commit接口测试
  • 状态检查: 检查接口返回状态是否为200且包含有效的t参数
  • 端到端测试: 使用获取到的t参数作为jt调用关注接口完成整个流程
8. 常见问题与解决方案(FAQ)

Q1: 为什么本地生成的加密结果与浏览器中的不一致?
A: 首先检查参数是否完全一致,特别是时间戳字段。建议使用copy(t)命令直接从浏览器复制完整参数,确保数据一致性。

Q2: 加密算法在不同环境下表现是否一致?
A: 由于是纯JavaScript逻辑实现,在不同浏览器和Node.js环境中表现应该一致。但要注意字符编码的一致性。

Q3: 如何验证加密结果是否正确?
A: 可以通过与浏览器中生成的加密字符串进行逐字符对比,或者实际调用接口验证返回结果。

Q4: 遇到”Invalid character”错误怎么办?
A: 这通常是UTF-8编码处理不一致导致的。检查utf8Encode函数是否正确处理了中文字符。

Q5: 加密函数会随时间变化吗?
A: 有可能。某吧可能会更新加密算法或版本号(v20)。如果发现接口失效,首先检查加密前缀和算法逻辑是否变化。

9. 获取完整课件

关注公众号:孤狼网络科技,回复:jt,即可获取本文涉及的完整代码课件。包含:

  • 完整的加密函数代码(含详细注释)
  • 参数配置示例和测试用例
  • 详细的调试笔记和技巧文档
  • 常见错误排查指南和解决方案
  • 更新通知和算法变更追踪

总结
通过本文的JS逆向,我们成功破解了某吧jt参数的二级加密机制。从数据抓包到加密逻辑分析,完整展示了逆向工程的思维流程和技术方法。重点掌握了:

  • XHR断点的正确使用方法和技巧
  • 调用堆栈分析和加密入口定位
  • 自定义Base64和位移加密算法的分析方法
  • 本地代码还原和验证的正确流程
下期预览

以上就是本次 “jt参数逆向” 实战的全部内容。希望本篇JS逆向教程对您有所帮助。林石工作室下期将为您带来《JS逆向解密教程2:某吧顶贴神器_BSK参数解密教程》的解析,感谢关注。

Comments

No comments yet. Why don’t you start the discussion?

发表回复

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