逆向目标
- 网址:
https://www.91118.com/Passport/Account/Login
- 接口:
https://www.91118.com/passport/Account/LoginPost
- 参数:
pass
r
逆向过程
输入手机号、密码、验证码
点击登陆,多试几次,然后观察并比较不通请求参数有哪些变化,其中 ckcode
是验证码
逆向分析
先使用关键词 pass
搜索,匹配项太多,直接从启动器入口进去跟栈分析
调试如下
可以发现参数 r
就是一个随机值 Math.random()
,pass
跟栈进去如下
加解密的方法都有了
var _key = 'k1fsa01v';
var _iv = 'k1fsa01v';
function encryptByDES(message) {
var keyHex = CryptoJS.enc.Utf8.parse(_key);
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
iv: CryptoJS.enc.Utf8.parse(_iv),
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
function decryptByDES(ciphertext) {
var keyHex = CryptoJS.enc.Utf8.parse(_key);
var decrypted = CryptoJS.DES.decrypt({
ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
}, keyHex, {
iv: CryptoJS.enc.Utf8.parse(_iv),
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
看着像是 DES
对称加密,使用标准库 crypto-js
来加密看看结果是否一致
var CryptoJS = require("crypto-js");
// 定义加密密钥,使用Utf8编码解析密钥字符串
const SECRET_KEY = CryptoJS.enc.Utf8.parse("k1fsa01v");
// 定义加密向量IV,使用Utf8编码解析IV字符串
const SECRET_IV = CryptoJS.enc.Utf8.parse("k1fsa01v");
/**
* 加密函数,接受一个数据参数,返回加密后的字符串
* @param {string|object} data - 需要加密的数据,可以是字符串或对象
* @returns {string} 加密后的数据字符串
*/
function encrypt(data) {
// 如果数据是对象类型,则尝试将其转换为字符串
if (typeof data === "object") {
try {
data = JSON.stringify(data);
} catch (e) {
// 如果转换过程中发生错误,打印错误信息并返回
console.log(e);
return;
}
}
// 将数据转换为Utf8格式的密文
const dataHex = CryptoJS.enc.Utf8.parse(data);
// 使用AES算法加密数据
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC, // 使用CBC模式
padding: CryptoJS.pad.Pkcs7, // 使用Pkcs7填充
});
// 返回加密后的密文字符串
return encrypted.toString();
}
console.log(encrypt("a123456"));
// 输出如下
>> T6fdfWw2zktEch0jKSJkmA==
我们再看网站加密结果
encryptByDES("a123456")
// 输出如下
>> bb3mlkFBqqo=
很明显网站对加密做了改造,我们跟进去把整个加密扣出来
我们把整个文件的 js code
全部copy出来执行并打印 console.log(CryptoJS)
输出如下
{
lib: {
Base: {
extend: [Function: extend],
create: [Function: create],
init: [Function: init],
mixIn: [Function: mixIn],
clone: [Function: clone]
},
WordArray: {
init: [Function: init],
toString: [Function: toString],
concat: [Function: concat],
clamp: [Function: clamp],
clone: [Function: clone],
random: [Function: random],
'$super': [Object]
},
BufferedBlockAlgorithm: {
reset: [Function: reset],
_append: [Function: _append],
_process: [Function: _process],
clone: [Function: clone],
_minBufferSize: 0,
init: [Function (anonymous)],
'$super': [Object]
},
Hasher: {
cfg: [Object],
init: [Function: init],
reset: [Function: reset],
update: [Function: update],
finalize: [Function: finalize],
blockSize: 16,
_createHelper: [Function: _createHelper],
_createHmacHelper: [Function: _createHmacHelper],
'$super': [Object]
},
Cipher: {
cfg: [Object],
createEncryptor: [Function: createEncryptor],
createDecryptor: [Function: createDecryptor],
init: [Function: init],
reset: [Function: reset],
process: [Function: process],
finalize: [Function: finalize],
keySize: 4,
ivSize: 4,
_ENC_XFORM_MODE: 1,
_DEC_XFORM_MODE: 2,
_createHelper: [Function: _createHelper],
'$super': [Object]
},
StreamCipher: {
_doFinalize: [Function: _doFinalize],
blockSize: 1,
init: [Function (anonymous)],
'$super': [Object]
},
BlockCipherMode: {
createEncryptor: [Function: createEncryptor],
createDecryptor: [Function: createDecryptor],
init: [Function: init],
'$super': [Object]
},
BlockCipher: {
cfg: [Object],
reset: [Function: reset],
_doProcessBlock: [Function: _doProcessBlock],
_doFinalize: [Function: _doFinalize],
blockSize: 4,
init: [Function (anonymous)],
'$super': [Object]
},
CipherParams: {
init: [Function: init],
toString: [Function: toString],
'$super': [Object]
},
SerializableCipher: {
cfg: [Object],
encrypt: [Function: encrypt],
decrypt: [Function: decrypt],
_parse: [Function: _parse],
init: [Function (anonymous)],
'$super': [Object]
},
PasswordBasedCipher: {
cfg: [Object],
encrypt: [Function: encrypt],
decrypt: [Function: decrypt],
init: [Function (anonymous)],
'$super': [Object]
}
},
enc: {
Hex: { stringify: [Function: stringify], parse: [Function: parse] },
Latin1: { stringify: [Function: stringify], parse: [Function: parse] },
Utf8: { stringify: [Function: stringify], parse: [Function: parse] },
Base64: {
stringify: [Function: stringify],
parse: [Function: parse],
_map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
}
},
algo: {
MD5: {
_doReset: [Function: _doReset],
_doProcessBlock: [Function: _doProcessBlock],
_doFinalize: [Function: _doFinalize],
clone: [Function: clone],
init: [Function (anonymous)],
'$super': [Object]
},
EvpKDF: {
cfg: [Object],
init: [Function: init],
compute: [Function: compute],
'$super': [Object]
},
DES: {
_doReset: [Function: _doReset],
encryptBlock: [Function: encryptBlock],
decryptBlock: [Function: decryptBlock],
_doCryptBlock: [Function: _doCryptBlock],
keySize: 2,
ivSize: 2,
blockSize: 2,
init: [Function (anonymous)],
'$super': [Object]
},
TripleDES: {
_doReset: [Function: _doReset],
encryptBlock: [Function: encryptBlock],
decryptBlock: [Function: decryptBlock],
keySize: 6,
ivSize: 2,
blockSize: 2,
init: [Function (anonymous)],
'$super': [Object]
}
},
MD5: [Function (anonymous)],
HmacMD5: [Function (anonymous)],
EvpKDF: [Function (anonymous)],
mode: {
CBC: {
init: [Function (anonymous)],
'$super': [Object],
Encryptor: [Object],
Decryptor: [Object]
}
},
pad: { Pkcs7: { pad: [Function: pad], unpad: [Function: unpad] } },
format: {
OpenSSL: { stringify: [Function: stringify], parse: [Function: parse] }
},
kdf: { OpenSSL: { execute: [Function: execute] } },
DES: { encrypt: [Function: encrypt], decrypt: [Function: decrypt] },
TripleDES: { encrypt: [Function: encrypt], decrypt: [Function: decrypt] }
}
然后把具体加密的那段代码也拷贝过来执行
var _key = 'k1fsa01v';
var _iv = 'k1fsa01v';
function encryptByDES(message) {
var keyHex = CryptoJS.enc.Utf8.parse(_key);
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
iv: CryptoJS.enc.Utf8.parse(_iv),
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
function decryptByDES(ciphertext) {
var keyHex = CryptoJS.enc.Utf8.parse(_key);
var decrypted = CryptoJS.DES.decrypt({
ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
}, keyHex, {
iv: CryptoJS.enc.Utf8.parse(_iv),
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
console.log(encryptByDES('a123456'));
结果报错了:
(base) qin@wuanjuns-MacBook-Pro jsprojects % node test.js
/Users/qin/dev/jsprojects/jsprojects/test.js:527
var b = a.createEncryptor;
^
TypeError: Cannot read properties of undefined (reading 'createEncryptor')
因为我们是全扣的代码,不存在少扣的问题,这个时候把这段代码拷贝到浏览器里执行发现正常输出了
大家回想一下在具体的加密代码那个页面是不是还有一段 html
代码,我们去看看是什么东西
(function () {
document.write("<script language=\"javascript\" type=\"text/javascript\" src=\"//assets.91118.com/js/rollups/tripledes.js\"></script><script language=\"javascript\" type=\"text/javascript\" src=\"//assets.91118.com/js/components/mode-ecb.js\"></script>");
})();
代码比较简单,就是一个自执行函数,引入了两个 js script
,tripledes.js
就是定义 CryptoJS
的文件,我们看另外一个 mode-ecb.js
是什么
CryptoJS.mode.ECB = (function () {
var ECB = CryptoJS.lib.BlockCipherMode.extend();
ECB.Encryptor = ECB.extend({
processBlock: function (words, offset) {
this._cipher.encryptBlock(words, offset);
}
});
ECB.Decryptor = ECB.extend({
processBlock: function (words, offset) {
this._cipher.decryptBlock(words, offset);
}
});
return ECB;
}());
补充了 CryptoJS
的 ECB mode
,我们在刚才代码中补上这段代码再执行结果如下
>> node test.js
>> bb3mlkFBqqo=
返回了正确的加密后的结果
另外,在接口请求参数中还有一个验证码参数 ckcode
,这个验证码是一个 img
元素,直接请求网址https://www.91118.com/Passport/Account/Login
并解析返回的 html
内容即可拿到 img
这里我门就不做赘述了,像这种简单的验证码推荐大家使用免费的验证码识别库 ddddocr(https://github.com/sml2h3/ddddocr)
,这里直接给出识别代码
# -*- coding: utf-8 -*-
import ddddocr
ocr = ddddocr.DdddOcr()
image = open("code.jpg", "rb").read()
result = ocr.classification(image)
print(result)
识别成功
逆向结果
到这里整个逆向过程就全部结束了,整个流程走下来就两点
- 扣代码——CryptoJS加密模块,尤其是后面那个
CryptoJS.mode.ECB
,真的是很有迷惑性; - 验证码识别——这个没有什么可讲的,直接上
ddddocr
硬怼就行了