aHR0cHM6Ly9tLmRvbmdmYW5nLmNvbS8=
特别声明
++注意!!!!实例仅作为学习案例,禁止其他个人以及团体做谋利用途!!!++
技术点
1、sm2
2、sm4
网页分析
第一步:分析请求和响应



通过分析可以知道,请求参数 和响应内容 都被加密,请求headers 中也存在加密参数**code。**通过加密的信息可复制到在线加解密工具验证是否为猜测的base64或者sha256。经过猜测和验证,上述编码加密类型均不是
第二步:找切入口,开始逆向
1、post请求的url, 添加到XHR中,刷新页面,重新请求

2、重新请求后,观察在哪里截断(停止)

分析堆栈,我个人习惯是从上往下看(怎么看看个人习惯),目的是分析请求都经过了哪些函数,图上框出的地方,可以看出,这一步参数已经被加密,且headers中的code也生成了,那么继续向下分析。

在t.exports的上一个函数中,可以看到没有显示参数和headers的code,那初步认为参数加密和headers的code 在d.request到t.exports 这个过程生成。
3、分析d.request处断点

这一步断点停住后,需要F10自己一步一步调试,需要观察t中的url是否为我们分析请求url, 如果不是就F8跳入下一断点,如果是我们分析的url, 观察t 中的headers 和data。 能够看到data未被加密,且headers中没有code,因此d.request 断点是正确的。

F10一步一步调试,会进入到Vr 函数中,这里有个for 循环,持续F10会很痛苦,因此,这个时候可以将下一个断点,打在var n 这一行,这样F8 跳入下一个断点,既不会绕在循环中也不会跳到莫名的断点处(注意:之所以将断点 添加在 var n 这一行,因为循环中并没有可以跳转到其他函数的操作)

上述跳出循环后,继续F10,会遇到Vn 函数,此时又是一个循环,此时断点可以加在if 的上一行,原理和上述差不多
第三步:处理逆向核心------请求参数(加密前)


按照上述F10一步一步调试,很快就跳转到上图位置。经过分析可以看到实际上,未加密的请求参数中新增了一个参数magicaValue。 而magicaValue值来自x, 也就是Object (w.d)(), 通过验证可以了解到Object (w.d)() 是uuid4, magicaValue是由uuid4+时间戳组成。
第四步:处理逆向核心------headers的code


分析可以看到 code 其实是sm2.doEncrypt(front+时间戳,"固定值") 生成。
第五步:处理逆向核心------params加密(请求参数(加密后))




可以看出来 _ 是请求参数的明文,经过h.a.encodeNoKey(明文参数(字符串), front+时间戳)
第六步:处理逆向核心------响应内容

响应内容排查加密方式,可以search 一下responseText,上图框出的地方进行断点,这块不做详细的描述,如果新手可以把认为的可能性都打上断点



可以看出加密内容解析出明文,使用了一个sm4Code ,这个参数在响应headers中出现了,使用sm2.doDecrypt(sm4code, 固定值), Object(x.a) 可以看出是sm4的加密
第七步:结合AI整理关于sm2, sm4 的加密函数
需要将 2dcfb47.js和3c010f5.js 文件保存在本地。将图上在文件中加密的函数贴出来给AI,让AI整理出可以在node环境中运行的代码(这个过程需要反复验证,以及更换提示词,有些模型会认为这个行为不合规拒绝整理)。
// get_code
const { sm2 } = require("sm-crypto");
function buildEncryptedHeader(t) {
const E = "front" + t; //
const publicKey = "04fcd2cf3649a************************dbe5e48df"; // 需要自己找
console.log(E)
return {
encryptionType: "SM",
code: sm2.doEncrypt(E, publicKey),
randomKey: E
};
}
function code(t){
return buildEncryptedHeader(t).code
}
// **************************************************************************************
// get_params
/* =========================
* Base64
* ========================= */
function bytesToBase64(bytes) {
return Buffer.from(bytes).toString("base64");
}
function base64ToBytes(base64) {
return Array.from(Buffer.from(base64, "base64"));
}
/* =========================
* UTF-8 编解码
* ========================= */
function utf8Bytes(str) {
const bytes = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code >= 0x10000 && code <= 0x10ffff) {
bytes.push((code >> 18) | 0xf0);
bytes.push(((code >> 12) & 0x3f) | 0x80);
bytes.push(((code >> 6) & 0x3f) | 0x80);
bytes.push((code & 0x3f) | 0x80);
} else if (code >= 0x800) {
bytes.push((code >> 12) | 0xe0);
bytes.push(((code >> 6) & 0x3f) | 0x80);
bytes.push((code & 0x3f) | 0x80);
} else if (code >= 0x80) {
bytes.push((code >> 6) | 0xc0);
bytes.push((code & 0x3f) | 0x80);
} else {
bytes.push(code & 0xff);
}
}
return bytes;
}
function bytesToUtf8(bytes) {
let out = "";
for (let i = 0; i < bytes.length; i++) {
const bin = bytes[i].toString(2);
const prefix = bin.match(/^1+?(?=0)/);
if (prefix && bin.length === 8) {
const len = prefix[0].length;
let store = bytes[i].toString(2).slice(7 - len);
for (let j = 1; j < len; j++) {
store += bytes[i + j].toString(2).slice(2);
}
out += String.fromCharCode(parseInt(store, 2));
i += len - 1;
} else {
out += String.fromCharCode(bytes[i]);
}
}
return out;
}
/* =========================
* SM4
* ========================= */
function rotl(x, n) {
return ((x << n) | (x >>> (32 - n))) >>> 0;
}
const SBOX = [
0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
];
const FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];
const CK = [
0x00070e15,0x1c232a31,0x383f464d,0x545b6269,
0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,
0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,
0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,
0x10171e25,0x2c333a41,0x484f565d,0x646b7279
];
function tau(a) {
return (
((SBOX[(a >>> 24) & 0xff] << 24) >>> 0) |
(SBOX[(a >>> 16) & 0xff] << 16) |
(SBOX[(a >>> 8) & 0xff] << 8) |
SBOX[a & 0xff]
) >>> 0;
}
function L(x) {
return (x ^ rotl(x, 2) ^ rotl(x, 10) ^ rotl(x, 18) ^ rotl(x, 24)) >>> 0;
}
function LPrime(x) {
return (x ^ rotl(x, 13) ^ rotl(x, 23)) >>> 0;
}
function bytesToWord(bytes, offset) {
return (
((bytes[offset] || 0) << 24) |
((bytes[offset + 1] || 0) << 16) |
((bytes[offset + 2] || 0) << 8) |
(bytes[offset + 3] || 0)
) >>> 0;
}
function wordToBytes(word, out, offset) {
out[offset] = (word >>> 24) & 0xff;
out[offset + 1] = (word >>> 16) & 0xff;
out[offset + 2] = (word >>> 8) & 0xff;
out[offset + 3] = word & 0xff;
}
function expandKey(keyBytes, mode) {
const MK = [
bytesToWord(keyBytes, 0),
bytesToWord(keyBytes, 4),
bytesToWord(keyBytes, 8),
bytesToWord(keyBytes, 12)
];
const K = [
(MK[0] ^ FK[0]) >>> 0,
(MK[1] ^ FK[1]) >>> 0,
(MK[2] ^ FK[2]) >>> 0,
(MK[3] ^ FK[3]) >>> 0
];
const rk = new Array(32);
for (let i = 0; i < 32; i++) {
const tmp = tau((K[1] ^ K[2] ^ K[3] ^ CK[i]) >>> 0);
const next = (K[0] ^ LPrime(tmp)) >>> 0;
rk[i] = next;
K[0] = K[1];
K[1] = K[2];
K[2] = K[3];
K[3] = next;
}
if (mode === 0) {
rk.reverse();
}
return rk;
}
function cryptBlock(blockBytes, roundKeys) {
const X = [
bytesToWord(blockBytes, 0),
bytesToWord(blockBytes, 4),
bytesToWord(blockBytes, 8),
bytesToWord(blockBytes, 12)
];
for (let i = 0; i < 32; i++) {
const tmp = tau((X[1] ^ X[2] ^ X[3] ^ roundKeys[i]) >>> 0);
const next = (X[0] ^ L(tmp)) >>> 0;
X[0] = X[1];
X[1] = X[2];
X[2] = X[3];
X[3] = next;
}
const out = new Array(16);
wordToBytes(X[3], out, 0);
wordToBytes(X[2], out, 4);
wordToBytes(X[1], out, 8);
wordToBytes(X[0], out, 12);
return out;
}
function zeroPad16(bytes) {
const out = bytes.slice();
while (out.length < 16) out.push(0);
return out;
}
/* =========================
* encodeNoKey / decodeNoKey
* ========================= */
const DEFAULT_KEY = "PNb1v6JvtVH3QQHa8a";
function encodeNoKey(e, t) {
if (!e) return "";
if (t == null) {
t = DEFAULT_KEY;
}
const input = utf8Bytes(e);
const keyBytes = utf8Bytes(t).slice(0, 16);
const roundKeys = expandKey(keyBytes, 1);
let encrypted = [];
for (let i = 0; i < input.length; i += 16) {
const block = zeroPad16(input.slice(i, i + 16));
encrypted = encrypted.concat(cryptBlock(block, roundKeys));
}
return bytesToBase64(encrypted);
}
function decodeNoKey(base64Text) {
if (!base64Text) return "";
const input = base64ToBytes(base64Text.replace(/(\r\n|\n|\r)/gm, ""));
const keyBytes = utf8Bytes(DEFAULT_KEY).slice(0, 16);
const roundKeys = expandKey(keyBytes, 0);
let decrypted = [];
for (let i = 0; i < input.length; i += 16) {
decrypted = decrypted.concat(cryptBlock(input.slice(i, i + 16), roundKeys));
}
const text = bytesToUtf8(decrypted);
const lastBrace = text.lastIndexOf("}");
return lastBrace === -1 ? text : text.slice(0, lastBrace + 1);
}
function decodeNoKey2(base64Text, t) {
if (!base64Text) return "";
const input = base64ToBytes(base64Text.replace(/(\r\n|\n|\r)/gm, ""));
const keyBytes = utf8Bytes(t).slice(0, 16);
const roundKeys = expandKey(keyBytes, 0);
let decrypted = [];
for (let i = 0; i < input.length; i += 16) {
decrypted = decrypted.concat(cryptBlock(input.slice(i, i + 16), roundKeys));
}
const text = bytesToUtf8(decrypted);
const lastBrace = text.lastIndexOf("}");
return text.slice(0, lastBrace + 1);
}
/* =========================
* SM2 请求头 code
* ========================= */
const PUBLIC_KEY =
"04fcd2cf3649a************************dbe5e48df"; // 需要自己找
function buildEncryptedRequest(data, t) {
const E = "front" + t;//Date.now();
const raw = typeof data === "string" ? data : JSON.stringify(data);
return {
headers: {
encryptionType: "SM",
code: sm2.doEncrypt(E, PUBLIC_KEY)
},
data: {
params: encodeNoKey(raw, E)
},
debug: {
randomKey: E,
raw
}
};
}
function uuid4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === "x" ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
function params(keyword, pageNo, t){
const payload = {
keyword: keyword,
pageSize: 20,
pageNo: 1,
goodsType: [],
goodSource: "sc",
magicaValue: uuid4()+"-"+Date.now()
};
console.log(payload)
const req = buildEncryptedRequest(payload, t);
return req.data.params
}
//*******************************************************************************
// get_sm4Code 加密的e 也是n
const { sm2 } = require('sm-crypto');
function get_e(defaultCipher) {
// 默认参数
// const defaultCipher = '714b29006b449d028876167a437f2394fe02481a2b1586313a979387d3360065e46de83566fabc0ca4dd549cb6132eb1200839a933e4657631db760a074f8abdb36bb532d3bf9f83f5f779ead6ab6eff00081c404ce2a0e31c3d790f620efaf92bcfaeb4780bdd75c4c9caf378ac7f08';
const defaultPrivateKey = '0c90e64bfc64*************0c1321e20f4'; // 需要自己找
const defaultCipherMode = 1;
const defaultOutput = 'string';
// 从命令行参数获取值
const cipher = process.argv[2] || defaultCipher;
const privateKey = process.argv[3] || defaultPrivateKey;
const cipherMode = process.argv[4] !== undefined ? parseInt(process.argv[4]) : defaultCipherMode;
const output = process.argv[5] || defaultOutput;
return sm2.doDecrypt(cipher, privateKey, cipherMode, {output})
}
//*******************************************************************************
// get_res
const { sm4 } = require("sm-crypto");
function normalizeHexKey(key) {
if (typeof key !== "string") {
throw new Error("key must be a string");
}
if (/^[0-9a-fA-F]{32}$/.test(key)) {
return key.toLowerCase();
}
if (key.length === 16) {
return Buffer.from(key, "utf8").toString("hex");
}
throw new Error(`invalid key: ${key}`);
}
class SM4Compat {
constructor({ key, mode = "ecb", cipherType = "base64" }) {
this.key = normalizeHexKey(key);
this.mode = mode;
this.cipherType = cipherType;
}
decrypt(text) {
let cipherHex;
if (this.cipherType === "base64") {
cipherHex = Buffer.from(text, "base64").toString("hex");
} else {
cipherHex = text;
}
return sm4.decrypt(cipherHex, this.key, {
mode: this.mode,
padding: "pkcs#7",
output: "string"
});
}
encrypt(text) {
const cipherBytes = sm4.encrypt(text, this.key, {
mode: this.mode,
padding: "pkcs#7",
output: "array"
});
if (this.cipherType === "base64") {
return Buffer.from(cipherBytes).toString("base64");
}
return Buffer.from(cipherBytes).toString("hex");
}
}
function r(text, e) {
return new SM4Compat({
key: e,
mode: "ecb",
cipherType: "base64"
}).decrypt(text);
}