一、使用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);

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐