这个网站不知道从哪找的加密程序,强行开开发者工具会比较吃内存/CPU,只好想别的办法了。
准备操作
- 安装并配置好 Charles 反代工具,启动并确保能正常抓取流量。
- 安装好编辑器,如 VSCode。
- NodeJS,用来本地运行代码片段。
访问主页并等待加载完毕,将所有 JS 资源文件都保存下来:
- 右键
https://bz.zzzmh.cn/
下的 js
目录
- 选择 “Save All...”,保存到一个目录下。
使用 Map Local 把文件都改为从本地获取:
- 右键
https://bz.zzzmh.cn/
下的 js
目录
- 选择 Map Local
- 点击 Local Path 的 Choose 按钮,选择 Charles 保存资源的 js 目录。
原始网页引入的代码存档:
js_原始.7z
(236.71 KB, 下载次数: 16)
现在可以使用编辑器打开这个 js
目录了。
因为不是很想解决这个反调试功能,只好自己加一些用来帮助调试的“调试器”了:
直接将下述代码随便塞到一个文件的顶部:
(function() {
const container = document.createElement('div');
const console = document.createElement('pre');
const input = document.createElement('textarea');
const exec = document.createElement('button');
const log = (text) => {
console.appendChild(document.createTextNode(text));
};
const clear = () => {
console.textContent = '';
};
input.cols = 60;
input.rows = 8;
exec.textContent = 'exec';
exec.type = 'button';
exec.onclick = () => {
eval(input.value);
};
container.appendChild(input);
container.appendChild(exec);
container.appendChild(console);
document.body.insertAdjacentElement('afterbegin', container);
})();
然后刷新浏览器就可以看到一个输入框和执行按钮,可以直接运行一些简单的代码并获取结果。
比如遇到加密字符串的时候,直接复制过来就能看到解密后的文字了:
随后观察 Charles 抓包到的内容,可以发现这么一则请求:
POST https://api.zzzmh.cn/bz/v3/getData
{
"size": 23,
"current": 0,
"sort": 0,
"category": 0,
"resolution": 0,
"color": 0,
"categoryId": 0
}
{
"msg": "success",
"result": "ak+...省略...wE=",
"code": 0,
"ts": 123456789
}
感觉这个是我们要找的回应,所以直接检索 getData
看看能找到什么:
(VSCode 左侧的第二个图标是全局搜索,搜索到结果后点进去后进行格式化代码处理)
看起来是被 Babel 处理过的代码(async/await 降级),而下面的处理代码却可以直接访问 ["data"]["result"]
对象的成员,怀疑这个接口对返回值进行了处理之后才返回。
“Ctrl-左键” _0x3b1f45
跳转到定义,可以看到很多类似 xxx = yyy("1234")
的代码:
看起来很像是 Webpack 打包后的 require 调用,分别对他们选中、按下 F2
并更名为 httpLib
:
随后进行全局检索关键字 8e44
,进入第一个找到的内容:
看起来第一个函数就是我们想找的,直接改一改变量名看看:
可以看到又调用了一个叫 ed08
的包,暂命名为 decipherLib
,继续重复查找过程…
找到后下拉可以看到一串像是密钥一样的内容。因为我们知道上图中的关键字 decipher
,于是一路折叠这些函数,直到模块底部:
可以看到导出了一些函数,在这里就可以很方便的更名/查找函数了。
直接改名与左侧的名称相同,然后进去看看:
稍微看一看这三个函数,感觉第一层的 4e50b7
只是简单的 base64 解码,4d16fb
是编码,而中间的 58af99
则像是对内容进行解密。没有看到其他依赖,直接复制进去并传入抓包得到的返回值进去看看:
得到结果了,可以试试移植到本地了。
移植
复制粘贴出来,然后进行一点点小清理就能得到类似下述的代码:
decoder.src.7z
(3.56 KB, 下载次数: 16)
function decrypt(input) {
const key = [
0x91, 0x34, 0x5b, 0x41, 0xbf, 0x74, 0x77, 0x6a, 0x87, 0xae, 0xfb, 0x50,
0x33, 0x61, 0x44, 0xad, 0x90, 0xcd, 0x17, 0xd2, 0xde, 0x8e, 0xc9, 0xf5,
0x81, 0x5a, 0x21, 0x16, 0xe1, 0x32, 0xef, 0x14, 0xd4, 0x0f, 0xa2, 0x85,
0x76, 0xe9, 0xc3, 0x72, 0x47, 0x98, 0x82, 0x8b, 0xaf, 0xca, 0xee, 0x92,
0xfc, 0xa1, 0xa5, 0x5e, 0xb0, 0xf2, 0x78, 0x69, 0x55, 0x68, 0xaa, 0x94,
0x43, 0x19, 0x65, 0x6c, 0x10, 0x97, 0x6f, 0xf6, 0x75, 0xb7, 0x4d, 0x59,
0xe3, 0x9e, 0xbc, 0x70, 0x6b, 0xff, 0x56, 0x79, 0x58, 0x9b, 0x84, 0x45,
0xe2, 0xf8, 0x8f, 0xb6, 0x8a, 0x39, 0xe7, 0x0c, 0x8d, 0x96, 0x5f, 0x7f,
0x54, 0x7c, 0x9a, 0xe4, 0x49, 0x2b, 0xc4, 0x1c, 0x2e, 0x73, 0x1e, 0x7a,
0xb5, 0x7d, 0xbd, 0xb3, 0x03, 0xf9, 0xcb, 0xf3, 0x35, 0x4e, 0xb8, 0x01,
0x0b, 0xb9, 0xd9, 0xb1, 0xfd, 0x13, 0x29, 0x7e, 0xd5, 0x83, 0xe5, 0x22,
0x3f, 0x08, 0x48, 0xdd, 0xd7, 0xc1, 0x3c, 0xe8, 0x66, 0x2f, 0x89, 0x99,
0xea, 0x2d, 0x3b, 0x40, 0xa0, 0x31, 0x53, 0x95, 0x88, 0xc7, 0xba, 0x00,
0xda, 0xac, 0xd8, 0x18, 0x0e, 0x30, 0x1d, 0x2c, 0xdc, 0xd1, 0x38, 0xa4,
0x26, 0x25, 0x04, 0xce, 0x67, 0x0a, 0xa7, 0x37, 0x71, 0xe6, 0x6e, 0x36,
0x24, 0xec, 0xb2, 0xf4, 0x8c, 0x46, 0xdb, 0x05, 0xc2, 0xb4, 0xd0, 0xc0,
0x4f, 0x64, 0x28, 0x06, 0xc6, 0xa6, 0xed, 0xf7, 0x27, 0x5d, 0x9d, 0x15,
0x07, 0x1a, 0xfe, 0x1b, 0xd3, 0x51, 0x3a, 0x86, 0x4c, 0xbe, 0x02, 0x5c,
0xd6, 0x62, 0xf0, 0x09, 0x3d, 0x3e, 0xf1, 0x63, 0xeb, 0x1f, 0xc8, 0x57,
0x11, 0xcc, 0xbb, 0xdf, 0xc5, 0xab, 0x42, 0x4a, 0x12, 0xa3, 0x80, 0xa9,
0xe0, 0x2a, 0x20, 0xa8, 0x6d, 0x60, 0x0d, 0xfa, 0x4b, 0x9c, 0xcf, 0x23,
0x9f, 0x52, 0x93, 0x7b
];
let idxA = 0;
let idxB = 0;
let result = [];
for (let i = 0; i < input.length; i++) {
idxA = (idxA + 0x1) & 0xff;
idxB = (key[idxA] + idxB) & 0xff;
let tmp = key[idxA];
key[idxA] = key[idxB];
key[idxB] = tmp;
const realKeyIdx = (key[idxA] + key[idxB]) & 0xff;
result.push(input[i] ^ key[realKeyIdx]);
}
return result;
}
function fromUTF8Array(input) {
return Buffer.from(input).toString("utf-8");
}
function b64Decode(input) {
return new Int8Array(Buffer.from(input, "base64"));
}
function decipherFn(_0x37db15) {
return fromUTF8Array(decrypt(b64Decode(_0x37db15)));
}
module.exports = {
decipherFn,
};
另外这个 decrypt
函数看起来像是预先初始化密钥盒的 rc4,具体就不知道了。
注意:使用到的 Buffer
为 node.js 平台专有 API,浏览器无法正常执行。
如果需要移植到 python,直接拿这个代码改改就好。
拼接链接
这个直接正常点击下载然后观察解密后数据与下载链接的关系即可。
如果你还是想知道怎么生成的话,参考下述整理好的代码:
var urlBase = "https://doge.zzzmh.cn";
function getUrl(imageId, isPng, imgType) {
var end = "";
if (0x0 == imgType) {
end = "/thumbs";
} else if (0x1 == imgType) {
end = "/fhd";
} else if (0x2 == imgType) {
end = "?response-content-disposition=attachment";
}
const ext = isPng ? ".png" : ".jpg";
return urlBase + "/wallpaper/origin/" + imageId + ext + end;
}
后记
现在对抗 devtools 的工具已经越来越多了…
本次实验基本上是绕过了 devtools(更改代码与注入一个 “简陋” devtools 到页面进行调试)。
实际研究的时候走了一些弯路,包括尝试干掉反调试;不知道是 devtools 的问题还是网页做了一些检测,一开就占满 CPU 与内存。
最后:仅供学习交流之用,勿做商业用途。