感谢 @Chieri 提供的网站
话不多说我们直接开始!
首先看入口界面,顺手一不小心打开F12界面。

标准的手机号验证码框框。
那我们就随便乱填一个号看看请求

哎呀,负载(我们发出的内容)居然是加密的,那就转到发起程序去看看。

一眼定真,三段一长选最长,我们直接看与众不同的那行内容。
提示,调用堆栈是从下往上看,最底下那行是最早执行的内容。
哎呀是混淆过的内容,也正常,我们来看看名字叫C函数的内容吧

注意我打断点的位置,await E函数,几个单词好眼熟啊。mobile移动电话,scene场景,country_code国家代码。看不懂没关系,我们有断点了,直接跟进E函数看看。
吐槽(哇咔,captchaVerifyParam验证码验证参数居然直接为空,看起来是为后续开发准mai备lei啊。)
仔细观察发现文件开头有一行
import {d as I, o as i, f as y, w as v, j as k, h as e, a as S, r as $, m, b as U, aP as F, at as L, g as u, av as H, aw as D, br as P, i as V, N as T, au as j, a4 as q, q as f, eh as E} from "./index-19dffaa2.js";
其中内容规定eh as E
所以我们去另一个js文件找eh
嗯?居然改名了,q7 as eh,那就从断点跟进到函数q7
function q7(e) {
return $e({
url: "/sms/send",
data: e
})
}
此时浏览器开发工具提示// e={"mobile": "133号码略","captchaVerifyParam": "","scene": "register","country_code": 86}
那么说明没找错地方, $e 函数 就是一个打包的发送函数,混淆过程将每一次请求都包裹起来了,这里也能从字面上看到url的接口位置,和网络那边的标头一致。
请求 URL:https://xingyuexiezuo.com/api/sms/send
现在再来看下一个函数
function $e({url: e, data: t, method: r="POST", headers: o, onDownloadProgress: n, signal: i, beforeRequest: s, afterRequest: a}) {
return Vy({
url: e,
method: r,
data: t,
headers: o,
onDownloadProgress: n,
signal: i,
beforeRequest: s,
afterRequest: a
})
}
好像就是解释一下而已的嘛,继续追踪!
Vy很长,后面我们会反复用到它
function Vy({url: e, data: t, method: r, headers: o, onDownloadProgress: n, signal: i, beforeRequest: s, afterRequest: a}, l=5) {
zn.defaults.baseURL || (zn.defaults.baseURL = Jd().url);
const c = d => {
const p = oc()
, h = Tf();
if (d.data.status === "Unauthorized" && (p.removeToken(),
h.resetUserInfo(),
Uy()),
d.data.code !== 200)
return Promise.reject(d.data);
const g = d.data.data
, x = Wd(g.encoded);// 注意到3
return x && (d.data.data = x),
d.data.status === "Success" || typeof d.data == "string" ? d.data : Promise.reject(d.data)
}
, u = d => {
throw a == null || a(),
new Error((d == null ? void 0 : d.message) || "Error")
}
, f = d => {
s == null || s();
let p = typeof t == "function" ? t() : t ?? {};
const h = {
headers: o,
signal: i,
onDownloadProgress: n
};
return r === "POST" && (p = {// 注意到1
data: zd(p)
}),// 注意到2
(r === "GET" ? zn.get(e, {
params: p,
...h
}) : zn.post(e, p, h)).then(c).catch(x => x.code > 0 ? u(x) : d < l ? (console.error(`Request failed, retrying... (${l - d} retries left)`),
f(d + 1)) : u(x))
}
;
return f(0)
}
观察注意到1的位置,我们看到熟悉的单词POST,发出验证码的请求就是POST类型,其中data:内容显然就是请求的负载。

zd函数内是什么呢,熟悉加密的朋友应该看出来了,AES.encrypt单词还不懂吗?看我断点处。
同样的,在注意到3的位置 Wd函数 是解码函数。
刚巧我又用过加密库,其原型格式是
CryptoJS.AES.encrypt(message, key, cfg).toString();
因此规律就破解了,输入信息e是一个对象,其值为e={"mobile": "13344445578","captchaVerifyParam": "","scene": "register","country_code": 86},钥匙key的值是VC,配置的cfg初始化按对象格式填写。
刚开始,在这里我还卡了一下。眼瞎没看到就在上两行明确定义了
const fo = NI(xF)
, vF = "chloefuckityoall"
, bF = "9311019310287172"
, VC = fo.enc.Utf8.parse(vF)
, qC = fo.enc.Utf8.parse(bF);
我最初傻傻的直接通过调试器看VC和qC对象的值(变量名肯定是混淆程序重命名了,没意义)。
分别是VC = {"words": [1667787887,1701213539,1802073209,1868655724],"sigBytes": 16}
和qC = {"words": [959656241,808532275,825242168,925972274],"sigBytes": 16}
因为AES 算法通常使用 128 位(16 字节)的密钥,显然4个数字是wordArray 类型,正好合适。
所以我直接在外部const VC = CryptoJS.lib.WordArray.create([1667787887, 1701213539, 1802073209, 1868655724], 16);新建一个值去测试。
我另外自己写了一个程序,然后JSON.stringify(e);序列化一个对象为字符,传值进去看看。

很好,输出的内容和网站的一致(我试了几个号码,所以不同于最上面截图,但是相同电话号码加密后的文本都是对的。)
如果用网页上的硬编码也一样enc.Utf8.parse从字符串转换密钥WordArray对象,比从数字创建省事一点。
那么,整个请求过程就破解了,整理格式削减多余的步骤得到如下
// 假设 CryptoJS 已额外加载
// 定义密钥和IV
const vF = "chloefuckityoall";
const bF = "9311019310287172";
const VC = CryptoJS.enc.Utf8.parse(vF);
const qC = CryptoJS.enc.Utf8.parse(bF);
// 定义配置参数
const cfg = {
iv: qC,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
};
// 定义加密方法
function zd(e) {
const t = JSON.stringify(e);
return CryptoJS.AES.encrypt(t, VC, cfg).toString();
}
// 定义发送请求的方法
async function sendSmsRequest(phone) {
const msgObj = {
"mobile": ""+phone,
"captchaVerifyParam": "",
"scene": "register",
"country_code": 86
};
const encryptedData = zd(msgObj);// 编码后的内容
const body = `{ "data": "${encryptedData}" }`;
try {
const response = await fetch("https://xingyuexiezuo.com/api/sms/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "*/*",
"Referer": "https://xingyuexiezuo.com/"
},
body: body
});
const result = await response.json();
if (result.status === "Success") {
console.log("验证码已发送", result);
} else {
console.error("发送验证码失败", result);
}
} catch (error) {
console.error("请求失败", error);
}
}
// 示例调用
sendSmsRequest("13344445555");