詭鋒的JS逆向?qū)崙?zhàn)記錄:藏頭詩(shī)生成器
簡(jiǎn)介
本次逆向主要是為我的機(jī)器人添加一個(gè)功能,但是又不想自己造(老白票怪了2333)
本次逆向歷時(shí)10小時(shí)左右,也算是第一次真正的搞逆向,太好玩了,就是有點(diǎn)費(fèi)腦子。。。。。呵呵哈哈哈或哈哈哈哈O(∩_∩)O
網(wǎng)站:https://cts.mofans.net/
后端php接口:https://api.mofans.net/maker.php
逆向成果:本接口內(nèi)容在前端使用CryptoJS解密,算法為AES,明文填充padding為Pkcs7, 具體算法的破解在后面部分展示

對(duì)接口響應(yīng)的說明
以下響應(yīng)的content就是密文,random是用于混淆密鑰的字符串
{
? ?"code":1,
? ?"msg":"請(qǐng)求成功",
? ?"time":1664095527,
? ?"data":{
"content":"THr+5WtoqA9Mjge9uoMizu0go0Jfl1TViT6SwbHz5BWFTaDzgb+ydjQmxJBl3O4+BfmPw\/l9LhulCtiSjP0HzBtDO+SMLS+BKdmzUzQkMQU=",
? ? ? ?"random":"9760129745683138"
? ?}
}
實(shí)操部分
準(zhǔn)備對(duì)接口的爬蟲
目標(biāo):
url: https://api.mofans.net/maker.php
需要的請(qǐng)求頭:
headers = {
? ? ? ?"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
? ? ? ?"sec-ch-ua": '"Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"',
? ? ? ?"referer": "https://cts.mofans.net/"
? ?}
需要的參數(shù):根據(jù)需求自己改
# 參數(shù):
# word: 內(nèi)容
# length: 幾言詩(shī) 5或7
# type:生成位置,藏頭或藏尾 0 或 1
# mode: 生成模式 0:偶句押韻 1:一二四押 2:一韻到底
params = {
? ? "word": “”
? ? "length": 5
? ? "type": 0,
? ? "mode": 2
}
拿到前端解密的源碼
關(guān)鍵代碼在web.js里面,此js使用十六進(jìn)制混淆,所以第一步是對(duì)代碼的解讀
以下這個(gè)數(shù)組就是破解的關(guān)鍵,里面放著unicode的字符串,記住這個(gè)數(shù)組
var __Oxe5e9c = ["\x61\x6A\x61\x78", "\x73\x75\x62\x6D\x69\x74", "\x2F\x61\x73\x73\x65\x74\x73\x2F\x6C\x69\x62\x2F\x6C\x61\x79\x65\x72\x2F\x6C\x61\x79\x65\x72\x2E\x6A\x73", "\x73\x74\x61\x74\x75\x73", "\x6C\x6F\x61\x64", "\x73\x65\x72\x69\x61\x6C\x69\x7A\x65", "\x23\x69\x6E\x70\x75\x74\x46\x6F\x72\x6D", "\x50\x4F\x53\x54", "\x2F\x2F\x61\x70\x69\x2E\x6D\x6F\x66\x61\x6E\x73\x2E\x6E\x65\x74\x2F\x6D\x61\x6B\x65\x72\x2E\x70\x68\x70", "\x63\x6F\x64\x65", "\x68\x74\x74\x70\x73\x3A\x2F\x2F\x63\x64\x6E\x2E\x62\x6F\x6F\x74\x63\x64\x6E\x2E\x6E\x65\x74\x2F\x61\x6A\x61\x78\x2F\x6C\x69\x62\x73\x2F\x63\x72\x79\x70\x74\x6F\x2D\x6A\x73\x2F\x34\x2E\x30\x2E\x30\x2F\x63\x72\x79\x70\x74\x6F\x2D\x6A\x73\x2E\x6A\x73", "\x68\x6F\x73\x74", "\x6C\x6F\x63\x61\x74\x69\x6F\x6E", "\x63\x74\x73\x2E\x6D\x6F\x66\x61\x6E\x73\x2E\x6E\x65\x74", "\x67\x65\x74\x46\x75\x6C\x6C\x59\x65\x61\x72", "\x67\x65\x74\x4D\x6F\x6E\x74\x68", "\x30", "\x67\x65\x74\x44\x61\x74\x65", "\x2D", "\x57\x44\x45\x6A\x37\x66", "\x74", "\x6B", "\x70", "\x69", "\x6A", "\x6D", "\x64", "\x6E", "\x72", "\x66", "\x61", "\x63", "\x68", "\x77", "\x79", "\x62", "\x78", "\x65", "\x67", "\x7A", "", "\x73\x70\x6C\x69\x74", "\x72\x61\x6E\x64\x6F\x6D", "\x64\x61\x74\x61", "\x6C\x65\x6E\x67\x74\x68", "\x70\x61\x72\x73\x65", "\x55\x74\x66\x38", "\x65\x6E\x63", "\x63\x6F\x6E\x74\x65\x6E\x74", "\x43\x42\x43", "\x6D\x6F\x64\x65", "\x50\x6B\x63\x73\x37", "\x70\x61\x64", "\x64\x65\x63\x72\x79\x70\x74", "\x41\x45\x53", "\x42\x61\x73\x65\x36\x34", "\x73\x74\x72\x69\x6E\x67\x69\x66\x79", "\x34\x30\x30\x70\x78", "\x61\x75\x74\x6F", "\x6F\x70\x65\x6E", "\x6D\x73\x67", "\x63\x6C\x6F\x73\x65", "\x6C\x6F\x67\x69\x6E", "\x72\x65\x73\x70\x6F\x6E\x73\x65\x4A\x53\x4F\x4E", "\x66\x75\x6E", "\x69\x6E\x64\x65\x78\x4F\x66", "\x73\x72\x63", "\x65\x61\x63\x68", "\x73\x63\x72\x69\x70\x74", "\x63\x72\x65\x61\x74\x65\x45\x6C\x65\x6D\x65\x6E\x74", "\x74\x79\x70\x65", "\x74\x65\x78\x74\x2F\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74", "\x72\x65\x61\x64\x79\x53\x74\x61\x74\x65", "\x6F\x6E\x72\x65\x61\x64\x79\x73\x74\x61\x74\x65\x63\x68\x61\x6E\x67\x65", "\x6C\x6F\x61\x64\x65\x64", "\x63\x6F\x6D\x70\x6C\x65\x74\x65", "\x6F\x6E\x6C\x6F\x61\x64", "\x61\x70\x70\x65\x6E\x64\x43\x68\x69\x6C\x64", "\x6D\x65\x74\x61", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x73\x42\x79\x54\x61\x67\x4E\x61\x6D\x65", "\x66\x65\x65\x64\x62\x61\x63\x6B", "\x64\x6F\x6D\x61\x69\x6E", "\x6D\x6F\x66\x61\x6E\x73\x2E\x6E\x65\x74", "\x36\x30\x30\x70\x78", "\x2F\x2F\x77\x77\x77\x2E\x6D\x6F\x66\x61\x6E\x73\x2E\x6E\x65\x74\x2F\x66\x65\x65\x64\x62\x61\x63\x6B\x2E\x70\x68\x70", "\x73\x65\x72\x76\x69\x63\x65", "\x23\x73\x65\x72\x76\x69\x63\x65", "\x68\x69\x64\x65", "\x2E\x62\x74\x6E\x5F\x74\x6F\x70", "\x61\x6E\x69\x6D\x61\x74\x65", "\x68\x74\x6D\x6C\x2C\x20\x62\x6F\x64\x79", "\x63\x6C\x69\x63\x6B", "\x73\x63\x72\x6F\x6C\x6C\x20\x72\x65\x73\x69\x7A\x65", "\x73\x63\x72\x6F\x6C\x6C\x54\x6F\x70", "\x73\x68\x6F\x77", "\x62\x69\x6E\x64", "\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x72\x65\x73\x75\x6C\x74\x22\x3E\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x68\x64\x22\x3E\x3C\x2F\x64\x69\x76\x3E\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x62\x64\x22\x3E", "\x3C\x2F\x64\x69\x76\x3E\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x62\x74\x20\x46\x31\x32\x20\x22\x3E\u7531\x20\x4D\x4F\x46\x41\x4E\x53\x2E\x4E\x45\x54\x20\u85CF\u5934\u8BD7\u751F\u6210\u5668\u667A\u80FD\u521B\u4F5C\x3C\x2F\x64\x69\x76\x3E\x3C\x2F\x64\x69\x76\x3E", "\x23\x70\x61\x72\x74\x69\x63\x6C\x65\x73", "\x2F\x61\x73\x73\x65\x74\x73\x2F\x6A\x73\x2F\x6A\x71\x75\x65\x72\x79\x2E\x70\x61\x72\x74\x69\x63\x6C\x65\x67\x72\x6F\x75\x6E\x64\x2E\x6D\x69\x6E\x2E\x6A\x73", "\x70\x61\x72\x74\x69\x63\x6C\x65\x73", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64", "\x23\x37\x35\x63\x64\x38\x65", "\x68\x74\x74\x70\x73\x3A\x2F\x2F\x68\x6D\x2E\x62\x61\x69\x64\x75\x2E\x63\x6F\x6D\x2F\x68\x6D\x2E\x6A\x73\x3F\x66\x32\x37\x34\x35\x61\x63\x65\x35\x65\x65\x31\x32\x62\x66\x30\x39\x35\x31\x63\x38\x35\x31\x64\x34\x37\x31\x38\x66\x32\x32\x64", "\x69\x6E\x73\x65\x72\x74\x42\x65\x66\x6F\x72\x65", "\x70\x61\x72\x65\x6E\x74\x4E\x6F\x64\x65", "\x75\x6E\x64\x65\x66\x69\x6E\x65\x64", "\x6C\x6F\x67", "\u5220\u9664", "\u7248\u672C\u53F7\uFF0C\x6A\x73\u4F1A\u5B9A", "\u671F\u5F39\u7A97\uFF0C", "\u8FD8\u8BF7\u652F\u6301\u6211\u4EEC\u7684\u5DE5\u4F5C", "\x6A\x73\x6A\x69\x61", "\x6D\x69\x2E\x63\x6F\x6D"];
源代碼的關(guān)鍵解密方法是以下這個(gè)函數(shù):
success: function(_0xe04cx6) {
? ?if (_0xe04cx6[__Oxe5e9c[0x9]] == 1) {
? ? ? ?fun[__Oxe5e9c[0x4]](__Oxe5e9c[0xa], function() {
? ? ? ? ? ?if (window[__Oxe5e9c[0xc]][__Oxe5e9c[0xb]] !== __Oxe5e9c[0xd]) {
? ? ? ? ? ? ? ?return
? ? ? ? ? ?}
? ? ? ? ? ?;var _0xe04cx7 = new Date();
? ? ? ? ? ?var _0xe04cx8 = _0xe04cx7[__Oxe5e9c[0xe]]();
? ? ? ? ? ?var _0xe04cx9 = _0xe04cx7[__Oxe5e9c[0xf]]() + 1 < 10 ? __Oxe5e9c[0x10] + (_0xe04cx7[__Oxe5e9c[0xf]]() + 1) : _0xe04cx7[__Oxe5e9c[0xf]]() + 1;
? ? ? ? ? ?var _0xe04cxa = _0xe04cx7[__Oxe5e9c[0x11]]() < 10 ? __Oxe5e9c[0x10] + _0xe04cx7[__Oxe5e9c[0x11]]() : _0xe04cx7[__Oxe5e9c[0x11]]();
? ? ? ? ? ?var _0xe04cxb = _0xe04cx8 + __Oxe5e9c[0x12] + _0xe04cx9 + __Oxe5e9c[0x12] + _0xe04cxa;
? ? ? ? ? ?_0xe04cxb = _0xe04cxb + __Oxe5e9c[0x13];
? ? ? ? ? ?var _0xe04cxc = [[__Oxe5e9c[0x14], __Oxe5e9c[0x15], __Oxe5e9c[0x16], __Oxe5e9c[0x17], __Oxe5e9c[0x18], __Oxe5e9c[0x19], __Oxe5e9c[0x1a], __Oxe5e9c[0x1b], __Oxe5e9c[0x1c], __Oxe5e9c[0x1d]], [__Oxe5e9c[0x1e], __Oxe5e9c[0x1f], __Oxe5e9c[0x20], __Oxe5e9c[0x21], __Oxe5e9c[0x22], __Oxe5e9c[0x23], __Oxe5e9c[0x24], __Oxe5e9c[0x25], __Oxe5e9c[0x26], __Oxe5e9c[0x27]]];
? ? ? ? ? ?var _0xe04cxd = _0xe04cx6[__Oxe5e9c[0x2b]][__Oxe5e9c[0x2a]][__Oxe5e9c[0x29]](__Oxe5e9c[0x28]);
? ? ? ? ? ?_0xe04cxc = _0xe04cxc[_0xe04cxd[0x0] % 2];
? ? ? ? ? ?var _0xe04cxe = __Oxe5e9c[0x28];
? ? ? ? ? ?for (var _0xe04cxf = 0, _0xe04cx10 = _0xe04cxd[__Oxe5e9c[0x2c]]; _0xe04cxf < _0xe04cx10; _0xe04cxf++) {
? ? ? ? ? ? ? ?_0xe04cxe += _0xe04cxc[_0xe04cxd[_0xe04cxf]]
? ? ? ? ? ?}
? ? ? ? ? ?;_0xe04cxe = CryptoJS[__Oxe5e9c[0x2f]][__Oxe5e9c[0x2e]][__Oxe5e9c[0x2d]](_0xe04cxe);
? ? ? ? ? ?let _0xe04cx11 = CryptoJS[__Oxe5e9c[0x2f]][__Oxe5e9c[0x2e]][__Oxe5e9c[0x2d]](_0xe04cxb);
? ? ? ? ? ?let _0xe04cx12 = CryptoJS[__Oxe5e9c[0x36]][__Oxe5e9c[0x35]](_0xe04cx6[__Oxe5e9c[0x2b]][__Oxe5e9c[0x30]], _0xe04cxe, {
? ? ? ? ? ? ? ?iv: _0xe04cx11,
? ? ? ? ? ? ? ?mode: CryptoJS[__Oxe5e9c[0x32]][__Oxe5e9c[0x31]],
? ? ? ? ? ? ? ?padding: CryptoJS[__Oxe5e9c[0x34]][__Oxe5e9c[0x33]]
? ? ? ? ? ?}).toString(CryptoJS[__Oxe5e9c[0x2f]].Utf8);
? ? ? ? ? ?var _0xe04cx13 = CryptoJS[__Oxe5e9c[0x2f]][__Oxe5e9c[0x2e]][__Oxe5e9c[0x38]](CryptoJS[__Oxe5e9c[0x2f]][__Oxe5e9c[0x37]][__Oxe5e9c[0x2d]](_0xe04cx12)).toString();
? ? ? ? ? ?layer[__Oxe5e9c[0x3b]]({
? ? ? ? ? ? ? ?type: 1,
? ? ? ? ? ? ? ?title: false,
? ? ? ? ? ? ? ?area: [__Oxe5e9c[0x39], __Oxe5e9c[0x3a]],
? ? ? ? ? ? ? ?content: msgFormat(_0xe04cx13)
? ? ? ? ? ?})
? ? ? ?})
? ?}
?
通過對(duì)代碼的研究,可以得到以下結(jié)果(不完全翻譯,我只翻譯了需要的部分,拿出重要的函數(shù))
function a(obj) {
? ? ? ? ? ?let date = new Date();
? ? ? ? ? ?let fullYear = date.getFullYear();
? ? ? ? ? ?let month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;
? ? ? ? ? ?let day = date.getDate() < 10 ? date.getDate() + "0" : date.getDate();
? ? ? ? ? ?let iv = fullYear + "-" + month + "-" + day;
? ? ? ? ? ?iv = iv + "WDEj7f";
? ? ? ? ? ?let key = [[__Oxe5e9c[0x14], __Oxe5e9c[0x15], __Oxe5e9c[0x16], __Oxe5e9c[0x17], __Oxe5e9c[0x18], __Oxe5e9c[0x19], __Oxe5e9c[0x1a], __Oxe5e9c[0x1b], __Oxe5e9c[0x1c], __Oxe5e9c[0x1d]], [__Oxe5e9c[0x1e], __Oxe5e9c[0x1f], __Oxe5e9c[0x20], __Oxe5e9c[0x21], __Oxe5e9c[0x22], __Oxe5e9c[0x23], __Oxe5e9c[0x24], __Oxe5e9c[0x25], __Oxe5e9c[0x26], __Oxe5e9c[0x27]]];
? ? ? ? ? ?let random_array= obj.data.random.split("");
? ? ? ? ? ?key = key[random_array[0] % 2];
? ? ? ? ? ?let target_key = __Oxe5e9c[0x28];
? ? ? ? ? ?for (var i = 0; i < random_array.length; i++) {
? ? ? ? ? ? ? ?target_key += key[random_array[i]]
? ? ? ? ? ?}
? ? ? ? ? ? ? console.log(target_key)
? ? ? ? ? ?target_key = CryptoJS[__Oxe5e9c[0x2f]][__Oxe5e9c[0x2e]][__Oxe5e9c[0x2d]](target_key);
? ? ? ? ? ?let _0xe04cx11 = CryptoJS["enc"]["Utf8"]["parse"](iv);
? ? ? ? ? ?// let _0xe04cx12 = CryptoJS.AES.decrypt(obj["data"]["content"], target_key, {
? ? ? ? ? ?// ? ? iv: _0xe04cx11,
? ? ? ? ? ?// ? ? mode: CryptoJS["mode"]["CBC"],
? ? ? ? ? ?// ? ? padding: CryptoJS.pad.Pkcs7
? ? ? ? ? ?// }).toString(CryptoJS.enc.Utf8);
? ? ? ? ? ?// ?var _0xe04cx13 = CryptoJS.enc.Utf8.stringify(
? ? ? ? ? ?// ? ? ?CryptoJS.enc.Base64.parse(_0xe04cx12)
? ? ? ? ? ?// ?).toString();
? ? ? ? ? ? return msgFormat(iv)
}
function msgFormat(_0xe04cx13) {
? ?return __Oxe5e9c[0x60] + _0xe04cx13 + __Oxe5e9c[0x61]
}
獲取加密算法,密鑰,padding明文填充方式
對(duì)以上代碼分析后得到:加密算法是AES,模式是CBC,也就是說有iv偏移值,而且這個(gè)程序里面對(duì)密鑰也混淆了,甚至對(duì)明文進(jìn)行過填充
所以我們需要先對(duì)iv偏移值進(jìn)行獲取
獲取iv偏移值(AES,CBC模式)
根據(jù)對(duì)以上代碼的分析,iv的算法是new Date()的格式化: "xxxx-xx-xx" 加上字符串"WDEj7f"
let date = new Date();
let fullYear = date.getFullYear();
let month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;
let day = date.getDate() < 10 ? date.getDate() + "0" : date.getDate();
let iv = fullYear + "-" + month + "-" + day;
iv = iv + "WDEj7f";
獲取密鑰的明文
從代碼中還可以知道的重要信息就是獲取到了解密出密鑰明文所需的混淆數(shù)組
let key = [[__Oxe5e9c[0x14], __Oxe5e9c[0x15], __Oxe5e9c[0x16], __Oxe5e9c[0x17], __Oxe5e9c[0x18], __Oxe5e9c[0x19], __Oxe5e9c[0x1a], __Oxe5e9c[0x1b], __Oxe5e9c[0x1c], __Oxe5e9c[0x1d]], [__Oxe5e9c[0x1e], __Oxe5e9c[0x1f], __Oxe5e9c[0x20], __Oxe5e9c[0x21], __Oxe5e9c[0x22], __Oxe5e9c[0x23], __Oxe5e9c[0x24], __Oxe5e9c[0x25], __Oxe5e9c[0x26], __Oxe5e9c[0x27]]];
這個(gè)數(shù)組聯(lián)動(dòng)上面的數(shù)組__Oxe5e9c,就可以拿到里面的字符串,當(dāng)然我們不需要知道他具體什么內(nèi)容,我們可以拿到算法:
let random_array= obj.data.random.split("");
key = key[random_array[0] % 2];
let target_key = __Oxe5e9c[0x28];
for (var i = 0; i < random_array.length; i++) {
? ?target_key += key[random_array[i]]
}
這就可以拿到密鑰target_key了
拿到Padding明文填充方式
通過分析,Padding方式是:CryptoJS.pad.Pkcs7
所以我們可以輕松在網(wǎng)上了解到Python中對(duì)填充值的抹去方式
? # 填充padding: pkcs7
? ?# BS = AES.block_size
? ?# 填充
? ?# pad = lambda s: s+(BS - len(s) % BS) * chr(BS - len(s) % BS)
? ?# 解填充
? ?unpad = lambda s: s[0:-ord(s[-1])]
這樣就有了Pkcs7解填充的方法
一切就緒,開始解密
使用Python中的AES庫(kù)對(duì)密鑰,密文,iv偏移量進(jìn)行utf-8的轉(zhuǎn)碼,就可以拿到明文
? # 密鑰(需要bytes類型)
? ?password = get_password(random)
? ?password = password.encode('utf-8')
? ?# 密文為content
? ?# 密文轉(zhuǎn)碼為utf-8 bytes
? ?text = base64.b64decode(content)
? ?# AES解密
? ?iv = get_iv()
? ?aes = AES.new(password, AES.MODE_CBC, iv)
? ? ?# 拿到明文
? ?target_text = aes.decrypt(text).decode("utf-8") ?# 解碼
拿到明文后別忘了解填充
target_text = unpad(str(target_text))
target_text = base64.b64decode(target_text).decode("utf-8")
target_text = target_text.replace("<br/>", "\n")
對(duì)于本網(wǎng)站的明文里包含了html換行標(biāo)簽,所以需要處理一下
這樣我們的結(jié)果就可以獲取了