逆向目标
- 网址:
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:527var 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 ddddocrocr = ddddocr.DdddOcr()image = open("code.jpg", "rb").read()
result = ocr.classification(image)
print(result)
识别成功
逆向结果
到这里整个逆向过程就全部结束了,整个流程走下来就两点
- 扣代码——CryptoJS加密模块,尤其是后面那个
CryptoJS.mode.ECB
,真的是很有迷惑性; - 验证码识别——这个没有什么可讲的,直接上
ddddocr
硬怼就行了