C# 实现小某书千帆平台登录售后功能的核心逻辑拆解
本文分析了某网站的登录和售后接口加密机制。通过Fiddler抓包发现关键参数X-t(时间戳)和X-s(加密值)。JS分析显示X-s的生成流程:1)拼接时间戳、接口路径和请求体;2)进行MD5哈希;3)通过自定义编码函数转换。研究提供了C#实现代码,包含时间戳处理、MD5计算和Base64-like编码步骤,可用于模拟请求。该加密方案应用于登录、售后列表查询和退款审核等接口,验证了参数生成逻辑的通用
·
一、使用Fiddler抓包找到登录相关接口
curl -X POST 'https://customer.******.com/api/cas/customer/web/service-ticket' \
--header 'X-S-Common: 2UQAPsHC+******sIj2erlH0ijJBSF8aQR' \
--header 'X-s: Ojci1lUB1gT******+12sC16s3' \
--header 'X-t: 1764559193274' \
-- 此处省略部分header头
--data-raw '{"service":"https%3A%2F%2Fark.******.com%2Fapp-system%2Fhome","email":"******","pwd":"******","source":"","type":"emailPwd"}'
response:
{"msg":"成功","data":{"type":"st","ticket":"ST-68c******mxkqylshk8m"},"code":0,"success":true}
curl -X POST 'https://customer.******.com/api/edith/open/ssologin' \
--header 'X-s: 0j5Lsg46sjdk******0gFl1lV6sB93' \
--header 'X-t: 1764559193274' \
-- 此处省略部分header头
--data-raw '{"system":"https://ark.******.com/app-system/home?from=ark-login","ticket":"ST-68c517******mxkqylshk8m"}'
response:
{"code":0,"success":true,"msg":"ok","data":{"seller_infos":[{"remark":"","shopname":"******","state":1,"accountType":"SECONDARY","seller_id":"******","role_infos":[],"userName":"******","userId":"******","sellerTypeName":"******","image":"******"}],"access_token":"AT-68c51******"}}
经验证,需要关注的两个参数X-t(当前时间戳,单位:毫秒)、X-s(需要进一步定位分析js),X-S-Common这个非必要参数可忽略。
二、JS定位分析关键函数代码片段
通过js分析定位找到关键代码如下:
function tf(t, e) {
var n = e.url
, r = e.params
, i = e.paramsSerializer
, o = e.data;
t.configInit;
var a = t.xsIgnore
, u = t.autoReload;
if (!(!a.some(function(t) {
return n.indexOf(t) >= 0
}) && V(n)))
return e;
try {
var s, c, l, f, h, d, p, v = (s = function(t, e, n) {
var r = ["%27"]
, i = t;
if (0 === t.indexOf("//") && (t = "".concat(window.location.protocol).concat(t)),
/^https?:/.test(t))
try {
var o = new URL(t);
i = o.href.replace(o.origin, "")
} catch (e) {
i = t
}
var a = Z.ZP.http.buildURL(i, e, n);
return [/\'/g].forEach(function(t, e) {
a = a.replace(t, r[e])
}),
a
}(n, r, i),
c = o,
l = function(t) {
t = t.replace(/\r\n/g, "\n");
for (var e = "", n = 0; n < t.length; n++) {
var r = t.charCodeAt(n);
r < 128 ? e += String.fromCharCode(r) : (r > 127 && r < 2048 ? e += String.fromCharCode(r >> 6 | 192) : (e += String.fromCharCode(r >> 12 | 224),
e += String.fromCharCode(r >> 6 & 63 | 128)),
e += String.fromCharCode(63 & r | 128))
}
return e
}
,
f = "A4NjFqYu5wPHsO0XTdDgMa2r1ZQocVte9UJBvk6/7=yRnhISGKblCWi+LpfE8xzm3",
h = "iamspam",
d = new Date().getTime(),
p = window,
void 0 !== p && p && p.navigator && p.navigator.userAgent && p.alert && (h = "test"),
{
"X-s": function(t) {
var e, n, r, i, o, a, u, s = "", c = 0;
for (t = l(t); c < t.length; )
e = t.charCodeAt(c++),
n = t.charCodeAt(c++),
r = t.charCodeAt(c++),
i = e >> 2,
o = (3 & e) << 4 | n >> 4,
a = (15 & n) << 2 | r >> 6,
u = 63 & r,
isNaN(n) ? a = u = 64 : isNaN(r) && (u = 64),
s = s + f.charAt(i) + f.charAt(o) + f.charAt(a) + f.charAt(u);
return s
}(R([d, h, s, "[object Object]" === Object.prototype.toString.call(c) || "[object Array]" === Object.prototype.toString.call(c) ? JSON.stringify(c) : ""].join(""))),
"X-t": d
});
e.headers["X-t"] = v["X-t"],
e.headers["X-s"] = v["X-s"]
} catch (t) {}
return e
}
R函数js如下:
R = function(t) {
function e(r) {
if (n[r])
return n[r].exports;
var o = n[r] = {
i: r,
l: !1,
exports: {}
};
return t[r].call(o.exports, o, o.exports, e),
o.l = !0,
o.exports
}
var n = {};
return e.m = t,
e.c = n,
e.i = function(t) {
return t
}
,
e.d = function(t, n, r) {
e.o(t, n) || Object.defineProperty(t, n, {
configurable: !1,
enumerable: !0,
get: r
})
}
,
e.n = function(t) {
var n = t && t.__esModule ? function() {
return t.default
}
: function() {
return t
}
;
return e.d(n, "a", n),
n
}
,
e.o = function(t, e) {
return Object.prototype.hasOwnProperty.call(t, e)
}
,
e.p = "",
e(e.s = 4)
}([function(t, e) {
var n = {
utf8: {
stringToBytes: function(t) {
return n.bin.stringToBytes(unescape(encodeURIComponent(t)))
},
bytesToString: function(t) {
return decodeURIComponent(escape(n.bin.bytesToString(t)))
}
},
bin: {
stringToBytes: function(t) {
for (var e = [], n = 0; n < t.length; n++)
e.push(255 & t.charCodeAt(n));
return e
},
bytesToString: function(t) {
for (var e = [], n = 0; n < t.length; n++)
e.push(String.fromCharCode(t[n]));
return e.join("")
}
}
};
t.exports = n
}
, function(t, e, n) {
var r, o, i, a, u;
r = n(2),
o = n(0).utf8,
i = n(3),
a = n(0).bin,
(u = function(t, e) {
t.constructor == String ? t = e && "binary" === e.encoding ? a.stringToBytes(t) : o.stringToBytes(t) : i(t) ? t = Array.prototype.slice.call(t, 0) : Array.isArray(t) || (t = t.toString());
for (var n = r.bytesToWords(t), c = 8 * t.length, s = 0x67452301, l = -0x10325477, f = -0x67452302, h = 0x10325476, d = 0; d < n.length; d++)
n[d] = 0xff00ff & (n[d] << 8 | n[d] >>> 24) | 0xff00ff00 & (n[d] << 24 | n[d] >>> 8);
n[c >>> 5] |= 128 << c % 32,
n[14 + (c + 64 >>> 9 << 4)] = c;
for (var p = u._ff, v = u._gg, g = u._hh, m = u._ii, d = 0; d < n.length; d += 16) {
var b = s
, y = l
, w = f
, _ = h;
s = p(s, l, f, h, n[d + 0], 7, -0x28955b88),
h = p(h, s, l, f, n[d + 1], 12, -0x173848aa),
f = p(f, h, s, l, n[d + 2], 17, 0x242070db),
l = p(l, f, h, s, n[d + 3], 22, -0x3e423112),
s = p(s, l, f, h, n[d + 4], 7, -0xa83f051),
h = p(h, s, l, f, n[d + 5], 12, 0x4787c62a),
f = p(f, h, s, l, n[d + 6], 17, -0x57cfb9ed),
l = p(l, f, h, s, n[d + 7], 22, -0x2b96aff),
s = p(s, l, f, h, n[d + 8], 7, 0x698098d8),
h = p(h, s, l, f, n[d + 9], 12, -0x74bb0851),
f = p(f, h, s, l, n[d + 10], 17, -42063),
l = p(l, f, h, s, n[d + 11], 22, -0x76a32842),
s = p(s, l, f, h, n[d + 12], 7, 0x6b901122),
h = p(h, s, l, f, n[d + 13], 12, -0x2678e6d),
f = p(f, h, s, l, n[d + 14], 17, -0x5986bc72),
l = p(l, f, h, s, n[d + 15], 22, 0x49b40821),
s = v(s, l, f, h, n[d + 1], 5, -0x9e1da9e),
h = v(h, s, l, f, n[d + 6], 9, -0x3fbf4cc0),
f = v(f, h, s, l, n[d + 11], 14, 0x265e5a51),
l = v(l, f, h, s, n[d + 0], 20, -0x16493856),
s = v(s, l, f, h, n[d + 5], 5, -0x29d0efa3),
h = v(h, s, l, f, n[d + 10], 9, 0x2441453),
f = v(f, h, s, l, n[d + 15], 14, -0x275e197f),
l = v(l, f, h, s, n[d + 4], 20, -0x182c0438),
s = v(s, l, f, h, n[d + 9], 5, 0x21e1cde6),
h = v(h, s, l, f, n[d + 14], 9, -0x3cc8f82a),
f = v(f, h, s, l, n[d + 3], 14, -0xb2af279),
l = v(l, f, h, s, n[d + 8], 20, 0x455a14ed),
s = v(s, l, f, h, n[d + 13], 5, -0x561c16fb),
h = v(h, s, l, f, n[d + 2], 9, -0x3105c08),
f = v(f, h, s, l, n[d + 7], 14, 0x676f02d9),
l = v(l, f, h, s, n[d + 12], 20, -0x72d5b376),
s = g(s, l, f, h, n[d + 5], 4, -378558),
h = g(h, s, l, f, n[d + 8], 11, -0x788e097f),
f = g(f, h, s, l, n[d + 11], 16, 0x6d9d6122),
l = g(l, f, h, s, n[d + 14], 23, -0x21ac7f4),
s = g(s, l, f, h, n[d + 1], 4, -0x5b4115bc),
h = g(h, s, l, f, n[d + 4], 11, 0x4bdecfa9),
f = g(f, h, s, l, n[d + 7], 16, -0x944b4a0),
l = g(l, f, h, s, n[d + 10], 23, -0x41404390),
s = g(s, l, f, h, n[d + 13], 4, 0x289b7ec6),
h = g(h, s, l, f, n[d + 0], 11, -0x155ed806),
f = g(f, h, s, l, n[d + 3], 16, -0x2b10cf7b),
l = g(l, f, h, s, n[d + 6], 23, 0x4881d05),
s = g(s, l, f, h, n[d + 9], 4, -0x262b2fc7),
h = g(h, s, l, f, n[d + 12], 11, -0x1924661b),
f = g(f, h, s, l, n[d + 15], 16, 0x1fa27cf8),
l = g(l, f, h, s, n[d + 2], 23, -0x3b53a99b),
s = m(s, l, f, h, n[d + 0], 6, -0xbd6ddbc),
h = m(h, s, l, f, n[d + 7], 10, 0x432aff97),
f = m(f, h, s, l, n[d + 14], 15, -0x546bdc59),
l = m(l, f, h, s, n[d + 5], 21, -0x36c5fc7),
s = m(s, l, f, h, n[d + 12], 6, 0x655b59c3),
h = m(h, s, l, f, n[d + 3], 10, -0x70f3336e),
f = m(f, h, s, l, n[d + 10], 15, -1051523),
l = m(l, f, h, s, n[d + 1], 21, -0x7a7ba22f),
s = m(s, l, f, h, n[d + 8], 6, 0x6fa87e4f),
h = m(h, s, l, f, n[d + 15], 10, -0x1d31920),
f = m(f, h, s, l, n[d + 6], 15, -0x5cfebcec),
l = m(l, f, h, s, n[d + 13], 21, 0x4e0811a1),
s = m(s, l, f, h, n[d + 4], 6, -0x8ac817e),
h = m(h, s, l, f, n[d + 11], 10, -0x42c50dcb),
f = m(f, h, s, l, n[d + 2], 15, 0x2ad7d2bb),
l = m(l, f, h, s, n[d + 9], 21, -0x14792c6f),
s = s + b >>> 0,
l = l + y >>> 0,
f = f + w >>> 0,
h = h + _ >>> 0
}
return r.endian([s, l, f, h])
}
)._ff = function(t, e, n, r, o, i, a) {
var u = t + (e & n | ~e & r) + (o >>> 0) + a;
return (u << i | u >>> 32 - i) + e
}
,
u._gg = function(t, e, n, r, o, i, a) {
var u = t + (e & r | n & ~r) + (o >>> 0) + a;
return (u << i | u >>> 32 - i) + e
}
,
u._hh = function(t, e, n, r, o, i, a) {
var u = t + (e ^ n ^ r) + (o >>> 0) + a;
return (u << i | u >>> 32 - i) + e
}
,
u._ii = function(t, e, n, r, o, i, a) {
var u = t + (n ^ (e | ~r)) + (o >>> 0) + a;
return (u << i | u >>> 32 - i) + e
}
,
u._blocksize = 16,
u._digestsize = 16,
t.exports = function(t, e) {
if (null == t)
throw Error("Illegal argument " + t);
var n = r.wordsToBytes(u(t, e));
return e && e.asBytes ? n : e && e.asString ? a.bytesToString(n) : r.bytesToHex(n)
}
}
, function(t, e) {
var n, r;
n = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
r = {
rotl: function(t, e) {
return t << e | t >>> 32 - e
},
rotr: function(t, e) {
return t << 32 - e | t >>> e
},
endian: function(t) {
if (t.constructor == Number)
return 0xff00ff & r.rotl(t, 8) | 0xff00ff00 & r.rotl(t, 24);
for (var e = 0; e < t.length; e++)
t[e] = r.endian(t[e]);
return t
},
randomBytes: function(t) {
for (var e = []; t > 0; t--)
e.push(Math.floor(256 * Math.random()));
return e
},
bytesToWords: function(t) {
for (var e = [], n = 0, r = 0; n < t.length; n++,
r += 8)
e[r >>> 5] |= t[n] << 24 - r % 32;
return e
},
wordsToBytes: function(t) {
for (var e = [], n = 0; n < 32 * t.length; n += 8)
e.push(t[n >>> 5] >>> 24 - n % 32 & 255);
return e
},
bytesToHex: function(t) {
for (var e = [], n = 0; n < t.length; n++)
e.push((t[n] >>> 4).toString(16)),
e.push((15 & t[n]).toString(16));
return e.join("")
},
hexToBytes: function(t) {
for (var e = [], n = 0; n < t.length; n += 2)
e.push(parseInt(t.substr(n, 2), 16));
return e
},
bytesToBase64: function(t) {
for (var e = [], r = 0; r < t.length; r += 3)
for (var o = t[r] << 16 | t[r + 1] << 8 | t[r + 2], i = 0; i < 4; i++)
8 * r + 6 * i <= 8 * t.length ? e.push(n.charAt(o >>> 6 * (3 - i) & 63)) : e.push("=");
return e.join("")
},
base64ToBytes: function(t) {
t = t.replace(/[^A-Z0-9+\/]/gi, "");
for (var e = [], r = 0, o = 0; r < t.length; o = ++r % 4)
0 != o && e.push((n.indexOf(t.charAt(r - 1)) & Math.pow(2, -2 * o + 8) - 1) << 2 * o | n.indexOf(t.charAt(r)) >>> 6 - 2 * o);
return e
}
},
t.exports = r
}
, function(t, e) {
function n(t) {
return !!t.constructor && "function" == typeof t.constructor.isBuffer && t.constructor.isBuffer(t)
}
t.exports = function(t) {
return null != t && (n(t) || "function" == typeof t.readFloatLE && "function" == typeof t.slice && n(t.slice(0, 0)) || !!t._isBuffer)
}
}
, function(t, e, n) {
t.exports = n(1)
}
])
三、借助豆包分析JS函数
断点调试可知道,xt为当前时间戳,t值组成:"时间戳"+"接口路径"+"请求体",借助豆包分析如下:


借助豆包可知R函数其实就是MD5,接着再把l = function(t)这个函数片段的代码交给豆包分析如下:

最终让豆包将l函数转为C#代码如下:
public class XhsXsEncoder
{
private const string F = "A4NjFqYu5wPHsO0XTdDgMa2r1ZQocVte9UJBvk6/7=yRnhISGKblCWi+LpfE8xzm3";
public static string EncodeL(string input)
{
if (string.IsNullOrEmpty(input))
return "";
string t = input.Replace("\r\n", "\n");
StringBuilder e = new StringBuilder();
foreach (char c in t)
{
int r = (int)c;
if (r < 128)
{
e.Append((char)r);
}
else
{
if (r > 127 && r < 2048)
{
e.Append((char)(r >> 6 | 192));
}
else
{
e.Append((char)(r >> 12 | 224));
e.Append((char)((r >> 6 & 63) | 128));
}
e.Append((char)((r & 63) | 128));
}
}
return e.ToString();
}
public static string EncodeXs(string input)
{
if (string.IsNullOrEmpty(input))
return "";
string t = EncodeL(input);
StringBuilder c = new StringBuilder();
int s = 0;
while (s < t.Length)
{
// 读取当前位置的字符编码,超出范围则视为“未定义”(对应 JS 中的 NaN)
int e = (s < t.Length) ? (int)t[s++] : int.MinValue; // 用 int.MinValue 标记“不存在”
int n = (s < t.Length) ? (int)t[s++] : int.MinValue;
int r = (s < t.Length) ? (int)t[s++] : int.MinValue;
// 计算 4 个编码索引(默认值为 0,后续会被补位覆盖)
int o = (e != int.MinValue) ? (e >> 2) : 0;
int i = 0;
int a = 0;
int u = 0;
if (e != int.MinValue)
{
i = ((e & 3) << 4);
if (n != int.MinValue)
{
i |= (n >> 4); // 若 n 存在,拼接 n 的高 4 位
a = ((n & 15) << 2);
if (r != int.MinValue)
{
a |= (r >> 6); // 若 r 存在,拼接 r 的高 2 位
u = r & 63;
}
else
{
u = 64; // r 不存在,补位
}
}
else
{
a = 64; // n 不存在,a 和 u 都补位
u = 64;
}
}
// 从编码表取字符(确保索引在有效范围内)
c.Append(F[o % F.Length]);
c.Append(F[i % F.Length]);
c.Append(F[a % F.Length]);
c.Append(F[u % F.Length]);
}
return c.ToString();
}
}
四、登录、售后的加密方式
登录接口
接口:/api/cas/customer/web/service-ticket
对应:
var xt = TimeStamp.DateTimeToUnixTimestampMs(DateTime.Now);
var t = $"{xt}test/api/cas/customer/web/service-ticket{requestBody}";
var c = MD5Utility.Compute(t).ToLower();
var xs = XhsXsEncoder.EncodeXs(c);
接口:/api/edith/open/ssologin
var xt= TimeStamp.DateTimeToUnixTimestampMs(DateTime.Now);
var t = $"{xt}test/api/edith/open/ssologin{requestBody}";
var c = MD5Utility.Compute(t).ToLower();
var xs = XhsXsEncoder.EncodeXs(c);
售后接口
获取售后列表:
var xt = TimeStamp.DateTimeToUnixTimestampMs(DateTime.Now);
var t = $"{xt}test/api/edith/after-sales/returns/v3?page={page}&number={number}&sort=time&order=asc&status_in=1,3,12";
var c = MD5Utility.Compute(t).ToLower();
var xs = XhsXsEncoder.EncodeXs(c);
同意退款接口:
//{"audit_result":200,"returns_id":"******","ship_needed":0,"update_at":1762327051000}
var request = new ArkXhsAfterSaleAuditRequest() { AuditResult = 200, ReturnsId = returnsId, ShipNeeded = 0, UpdateAt = updateAt };
var requestBody = JsonConvert.Serialize(request);
var ts = TimeStamp.DateTimeToUnixTimestampMs(DateTime.Now);
var t = $"{ts}test/api/edith/after-sales/audit/{returnsId}{requestBody}";
var c = MD5Utility.Compute(t).ToLower();
var xs = XhsXsEncoder.EncodeXs(c);
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐

所有评论(0)