diff --git a/README.md b/README.md index 15288d7..f1ff525 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,63 @@ Examples: ``` -### 2.3. exec子命令 +### 2.3. makecode-high子命令 + +执行子命令`makecode-high`生成cookie,解码两次请求返回的网站代码(功能涵盖makecode子命令),调用示例: + +1. npx方式:`npx rs-reverse makecode-high -u url` +2. 文件方式:`node main.js makecode-high -u url` + +该命令第一次请求生成cookie带入第二次请求,将两次请求返回的加密代码及动态代码解码后保存到`output/makecode-high`目录中,和makecode命令区别为该命令只提供-u方式执行! + +```console + $ npx rs-reverse makecode-high -h +rs-reverse makecode-high + +解码两次请求返回的网站代码(功能涵盖makecode子命令) + +Options: + -h 显示帮助信息 [boolean] + -f + -l, --level 日志打印等级,参考log4js,默认为info [string] + -u, --url 瑞数返回204状态码的请求地址 [string] [required] + -a, --adapt 已经做了适配的网站名称,不传则为cnipa [string] + -v, --version 显示版本号 [boolean] + +Examples: + rs-reverse makecode-high -u http://url/path +``` + +调用示例: + +```bash + $ npx rs-reverse makecode-high -u https://wcjs.sbj.cnipa.gov.cn/sgtmi + +第1次请求: + + url方式提取的ts:/path/to/output/makecode-high/first/ts.json + url方式提取的静态文本:/path/to/output/makecode-high/first/immucfg.json + 程序生成的ts:/path/to/output/makecode-high/first/ts-full.json + url方式提取的javascript代码:/path/to/output/makecode-high/first/cCdzB9ZjDFks.a728b22.js + url方式提取的html代码:/path/to/output/makecode-high/first/sgtmi.html + cCdzB9ZjDFks.a728b22.js生成的动态代码:/path/to/output/makecode-high/first/cCdzB9ZjDFks.a728b22-dynamic.js + +第2次请求: + + url方式提取的ts:/path/to/output/makecode-high/second/ts.json + url方式提取的静态文本:/path/to/output/makecode-high/second/immucfg.json + 程序生成的ts:/path/to/output/makecode-high/second/ts-full.json + url方式提取的javascript代码:/path/to/output/makecode-high/second/cCdzB9ZjDFks.a728b22.js + url方式提取的html代码:/path/to/output/makecode-high/second/sgtmi.html + cCdzB9ZjDFks.a728b22.js生成的动态代码:/path/to/output/makecode-high/second/cCdzB9ZjDFks.a728b22-dynamic.js + url方式提取的javascript代码:/path/to/output/makecode-high/second/chunk-vendors.66e24864.js + url方式提取的javascript代码:/path/to/output/makecode-high/second/app.9f7a91c9.js + chunk-vendors.66e24864.js生成的解密代码:/path/to/output/makecode-high/second/chunk-vendors.66e24864-decrypt.js + app.9f7a91c9.js生成的解密代码:/path/to/output/makecode-high/second/app.9f7a91c9-decrypt.js + +``` + +### 2.4. exec子命令 exec子命令用于开发中或者演示时使用。命令示例: @@ -154,10 +210,10 @@ Examples: 适配文件配置在目录`./src/adapt/`下,已完成兼容配置: -网站 | 名称 | makecode | makecookie | 适配版本 | 是否逆向验证 ----- | ---- | -------- | ---------- | -------- | -------------- -商标网 | cnipa | 👌 | 👌 | - | Y -瑞数官网 | riversecurity | 👌 | 👌 | 版本1 | N +网站 | 名称 | makecode | makecookie | makecode-high | 适配版本 | 是否逆向验证 +---- | ---- | -------- | ---------- | ------------- | -------- | -------------- +商标网 | cnipa | 👌 | 👌 | 👌 | - | Y +瑞数官网 | riversecurity | 👌 | 👌 | N | 版本1 | N 以瑞数官网实例如:`npx rs-reverse makecookie -u https://www.riversecurity.com/resources.shtml -a riversecurity` diff --git a/main.js b/main.js index 98cfa8a..adf507a 100755 --- a/main.js +++ b/main.js @@ -7,6 +7,7 @@ const fs = require('fs'); const makeCode = require('@src/makeCode'); const makeCodeHigh = require('@src/makeCodeHigh'); const makeCookie = require('@src/makeCookie'); +const basearrParse = require('@src/basearrParse'); const utils = require('@utils/'); const { logger, getCode } = utils; const pkg = require(paths.package); @@ -64,6 +65,7 @@ const commandHandler = (command, argv) => { const ts = argv.url?.$_ts || argv.file || require(paths.exampleResolve('codes', `${gv.version}-\$_ts.json`)); logger.trace(`传入的$_ts.nsd: ${ts.nsd}`); logger.trace(`传入的$_ts.cd: ${ts.cd}`); + gv._setAttr('argv', argv); try { if (argv.url) { command(ts, adapt(argv.url, argv.adapt), argv.url); @@ -88,8 +90,15 @@ module.exports = yargs }) .command({ command: 'makecode-high', - describe: '生成动态代码-高级', - builder: commandBuilder, + describe: '解码两次请求返回的网站代码(功能涵盖makecode子命令)', + builder: { + ...commandBuilder, + f: undefined, + u: { + ...commandBuilder.u, + demandOption: true, + } + }, handler: commandHandler.bind(null, makeCodeHigh), }) .command({ @@ -136,6 +145,27 @@ module.exports = yargs console.log([`\n 输入:${argv.code}`, `输出:${output}\n`].join('\n ')); } }) + .command({ + command: 'basearr', + describe: '接收压缩前数字数组的序列化文本并格式化解析', + builder: { + l: { + alias: 'level', + describe: '日志打印等级,参考log4js,默认为info', + type: 'string', + }, + b: { + alias: 'basearr', + describe: '压缩前数字数组的序列化文本', + type: 'array', + demandOption: true, + } + }, + handler: (argv) => { + debugLog(argv.level); + basearrParse(argv.basearr); + } + }) .updateStrings({ 'Show version number': '显示版本号', 'Show help': '显示帮助信息', diff --git a/package.json b/package.json index f70715e..2c8bdfa 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "license": "ISC", "dependencies": { "cheerio": "^1.0.0-rc.12", + "fs-extra": "^11.2.0", "jest": "^29.7.0", "lodash": "^4.17.21", "log4js": "^6.9.1", diff --git a/src/adapt/index.js b/src/adapt/index.js index 90af39a..b9509bc 100644 --- a/src/adapt/index.js +++ b/src/adapt/index.js @@ -21,10 +21,10 @@ module.exports = function({ jscode, url }, name) { gv._setAttr('version', val); return ans; } - const idx = jscode.indexOf(val); + const idx = jscode.code.indexOf(val); if (idx === -1) throw new Error(`${key}值数据未找到,请查看文档:src/adapt/readme.md`); - if (jscode.indexOf(val, idx + val.length) > -1) throw new Error(`${key}对应的值${val}在代码中非唯一,请检查!`); - const fullString = findFullString(jscode, val); + if (jscode.code.indexOf(val, idx + val.length) > -1) throw new Error(`${key}对应的值${val}在代码中非唯一,请检查!`); + const fullString = findFullString(jscode.code, val); return { ...ans, [key]: fullString }; }, {}); } diff --git a/src/basearrParse.js b/src/basearrParse.js new file mode 100644 index 0000000..d77e2a0 --- /dev/null +++ b/src/basearrParse.js @@ -0,0 +1,165 @@ +const logger = require('@utils/logger'); +const error = require('@utils/error'); + +const parseBlock = (item, id) => { + if (typeof id !== 'number') return { ...item, show: id }; + const val = item.val; + let oper = 0; + const next = (val) => { + switch(val.type) { + case 'div': + oper += 1; + break; + case 'val': + case 'arr': + oper = val.idx + val.len; + break; + } + return val; + } + switch(id) { + case 0: + item.child = [ + next(div(val, oper)), + next(value(val, oper, 1, 'window.navigator.maxTouchPoints')), + next(value(val, oper, 1, 'window.eval.toString().length')), + next(div(val, oper)), + next(value(val, oper, 4, 'window.navigator.userAgent')), + value(val, oper, 1, 'length'), + next(block(val, oper, 'window.navigator.platform')), + next(value(val, oper, 4, 'execNumberByTime')), + next(value(val, oper, 2, 'execRandomByNumber')), + next(div(val, oper)), + next(div(val, oper)), + next(value(val, oper, 4, '3136373737323136')), + next(value(val, oper, 4, '0')), + next(value(val, oper, 2, 'window.innerHeight')), + next(value(val, oper, 2, 'window.innerWidth')), + next(value(val, oper, 2, 'window.outerHeight')), + next(value(val, oper, 2, 'window.outerWidth')), + ]; + break; + case 1: + item.child = [ + next(value(val, oper, 1, '0 < +ascii2string(gv.keys[24]) < 8')), + next(div(val, oper)), + next(value(val, oper, 4, 'r2mkaTime + runTime - startTime')), + next(value(val, oper, 4, '+ascii2string(gv.keys[19])')), + next(value(val, oper, 8, 'Math.floor(Math.random() * 1048575) * 4294967296 + (((currentTime + 1) & 4294967295) >>> 0)')), + next(value(val, oper, 1, '+ascii2string(gv.keys[24])')), + ]; + break; + case 2: + item.child = [ + next(value(val, oper, 4, '16777216')), + next(value(val, oper, 4, '0')), + next(value(val, oper, 2, '5900')), + next(value(val, oper, 2, 'codeToStringUid')), + ]; + break; + case 4: + item.child = [ // 编号510方法执行返回 + next(value(val, oper, 1, '1')), + next(value(val, oper, 2, '0')), + next(value(val, oper, 2, '0')), + next(value(val, oper, 1, 'window.document.hidden')), + next(value(val, oper, 8, 'encryptMode2(decrypt(ascii2string(gv.keys[22])), numarrAddTime(gv.keys[16])[0])')), + next(value(val, oper, 2, '+decode(decrypt(ascii2string(gv.keys[22])))')), + ]; + break; + case 5: + item.child = [ + next(value(val, oper, 1, 'taskmap[ascii2string(gv.keys[29])]();')), + next(value(val, oper, 1, 'taskmap[ascii2string(gv.keys[30])]();')), + next(value(val, oper, 1, 'taskmap[ascii2string(gv.keys[31])]();')), + next(value(val, oper, 1, 'taskmap[ascii2string(gv.keys[32])]();')), + ] + break; + case 6: + item.child = [ + next(value(val, oper, 1, 'battery and connection operator')), + next(value(val, oper, 1, 'window.navigator.battery.level * 100')), + next(value(val, oper, 1, 'window.navigator.battery.chargingTime >> 8')), + next(value(val, oper, 1, 'window.navigator.battery.chargingTime & 255')), + next(value(val, oper, 1, 'window.navigator.connection')), + ]; + break; + } + return item; +} + +function value(arr, oper, len, show) { + return { + show, + val: arr.slice(oper, oper + len), + idx: oper, + type: 'val', + len, + } +} + +function block(arr, oper, id) { + const num = arr[oper++] + return parseBlock({ val: arr.slice(oper, num + oper), len: num, idx: oper, type: 'arr' }, id); +} + +function div(arr, oper, show = '-------') { + return { show, val: arr[oper], idx: oper, type: 'div' }; +} + +function print(divarr, deep = 0, parentIdx = 0) { + divarr.forEach((it) => { + const idx = deep ? `(${it.idx},${it.idx + parentIdx})` : `(${it.idx})`; + console.log([new Array(deep * 4).fill(' ').join(''), idx, `[${it.val || '0'}]`, it.show].filter(Boolean).join(' ')); + if (it.child) print(it.child, deep + 1, it.idx); + }) +} + +function parse(basearr) { + let oper = 0; + console.log('\n'); + const next = (val) => { + if (!val) debugger; + switch(val.type) { + case 'div': + oper += 1; + break; + case 'val': + case 'arr': + oper = val.idx + val.len; + break; + } + return val; + } + print(new Array(8).fill('').reduce((ans, it, idx) => { + return [ + ...ans, + next(div(basearr, oper)), + value(basearr, oper, 1, 'length'), + next(block(basearr, oper, idx)), + ]; + }, [])); +} + +module.exports = function (basearrs) { + try { + basearrs = basearrs.map(it => { + basearr = JSON.parse(it); + if (!Array.isArray(basearr)) { + throw new Error(''); + } + return basearr; + }) + } catch (err) { + error('请传入序列化后的数字数组!'); + } + basearrs.forEach(parse); + if (basearrs.length > 1) { + console.log('\n'); + for (let i = 0, j = new Set(); i < Math.max(...basearrs.map(it => it.length)); i++) { + j.clear(); + basearrs.forEach(it => j.add(it[i])); + if (j.size !== 1) console.log(`不同点下标${i}:${basearrs.map(it => it[i]).join(' ')}`); + } + } +} diff --git a/src/handler/AppCode.js b/src/handler/AppCode.js new file mode 100644 index 0000000..c955802 --- /dev/null +++ b/src/handler/AppCode.js @@ -0,0 +1,94 @@ +const gv = require('@src/handler/globalVarible'); + +module.exports = class { + constructor(params, idx) { + this.oper = 0; + this.params = params; + this.idx = idx; + } + + getLength() { + let one, two, three, four, dkey = gv.decryptKeys[5]; + const text = this.params[0]; + const flag = dkey[text.charCodeAt(this.oper++)]; + if (flag <= 80) return flag; + if (flag == 81) { + return dkey[text.charCodeAt(this.oper++)] + 80; + } + if (flag == 82) { + one = dkey[text.charCodeAt(this.oper++)]; + two = dkey[text.charCodeAt(this.oper++)]; + return one + two * 86 + 165; + } + if (flag == 83) { + one = dkey[text.charCodeAt(this.oper++)]; + two = dkey[text.charCodeAt(this.oper++)]; + three = dkey[text.charCodeAt(this.oper++)]; + return one + two * 86 + three * 86 * 86 + 7560; + } + if (flag == 84) { + one = dkey[text.charCodeAt(this.oper++)]; + two = dkey[text.charCodeAt(this.oper++)]; + three = dkey[text.charCodeAt(this.oper++)]; + four = dkey[text.charCodeAt(this.oper++)]; + return one + two * 86 + three * 86 * 86 + four * 86 * 86 * 86 + 643615; + } + } + + getKeys(len) { + const keys = []; + for(let i = 0; i <= len; i++) { + const j = Math.floor((this.random || Math.random()) * 4294967295) % len + 0; + keys[i] = keys[j] || `$_${j}`; + keys[j] = `$_${i}`; + } + return keys; + } + + decrypt() { + const keys = this.getKeys(this.getLength()); + const name = `$$_${this.idx}`; + const num = this.getLength(); + const ret = new Array(num), res = []; + const staticText = this.params[1].split('~'); + for(let i = 0, j; i < num; i++) { + i % 2 == 0 ? j = this.getLength() : j >>= 3; + const next = this.getLength(); + switch(j & 7) { + case 0: + ret[i] = res[next]; + break; + case 1: + ret[i] = keys[next]; + break; + case 2: + ret[i] = gv.ts.cp[1][next]; + break; + case 3: + const val = this.params[0].substr(this.oper, next); + this.oper += next; + res.push(val); + ret[i] = val; + break; + case 4: + // ret[i] = `${name}[${next}]`; + ret[i] = `"${staticText[next]}"`; + break; + case 5: + ret[i] = this.params[2][next] + break; + } + } + // return `window[${name}]=${JSON.stringify(staticText)};${ret.join('')}`; + return ret.join(''); + } + + run() { + const code = this.decrypt(); + if (this.getLength() !== 0) { + debugger; + throw new Error('预期值不符,需要增加额外代码适配!'); + }; + return code; + } +} diff --git a/src/handler/Coder.js b/src/handler/Coder.js index 5f67b80..4e29957 100644 --- a/src/handler/Coder.js +++ b/src/handler/Coder.js @@ -18,6 +18,9 @@ module.exports = class { this.opdata = dataOper(); this.r2mkaText = null; this.immucfg = immucfg || gv.config.immucfg; + this.functionsPushStart = { 1: 938, 2: 0, 3: 0, 4: 0 }; // 生成方法排序数据的开始下标, 938为键值为348所命中的代码中获得 + this.functionsNameSort = []; // 存放vm代码中定义的方法,用于计算代码特征码使用 + this.mainFunctionIdx = null; // 主函数(编号为1)在代码中的开始与结束下标 } run() { @@ -88,6 +91,7 @@ module.exports = class { this.gren(i, codeArr); } codeArr.push('}}}}}}}}}}'.substr(opmate.getMateOri('G_$gG') - 1)); + this.mainFunctionIdx.push(codeArr.join('').length); return codeArr; } @@ -133,15 +137,21 @@ module.exports = class { opmate.setMate('_$$c'); opdata.setData('_$$k', func2(opmate.getMateOri('_$$c'))); if (current) { + if (this.mainFunctionIdx === null) this.mainFunctionIdx = [codeArr.join('').length]; codeArr.push("function ", opmate.getMate('_$jw'), "(", opmate.getMate('_$$6')); opdata.getData('_$_K').forEach(it => codeArr.push(",", keynames[it])); codeArr.push("){"); } else { codeArr.push("(function(", opmate.getMate('G_$dK'), ",", opmate.getMate('G_$kv'), "){var ", opmate.getMate('_$$6'), "=0;"); } - opdata.getData('_$$w').forEach(([key1, key2]) => { - codeArr.push("function ", keynames[key1], "(){var ", opmate.getMate('_$$q'), "=[", key2, "];Array.prototype.push.apply(", opmate.getMate('_$$q'), ",arguments);return ", opmate.getMate('_$$g'), ".apply(this,", opmate.getMate('_$$q'), ");}"); - }); + const functionsNameMap = opdata.getData('_$$w').reduce((ans, [key1, key2], idx) => { + const arr = ["function ", keynames[key1], "(){var ", opmate.getMate('_$$q'), "=[", key2, "];Array.prototype.push.apply(", opmate.getMate('_$$q'), ",arguments);return ", opmate.getMate('_$$g'), ".apply(this,", opmate.getMate('_$$q'), ");}"] + codeArr.push(...arr); + return { + ...ans, + [keynames[key1]]: arr.join(''), + } + }, {}); opdata.getData('_$cS').forEach(item => { for (let i = 0; i < item.length - 1; i += 2) { codeArr.push(keycodes[item[i]], keynames[item[i + 1]]) @@ -157,11 +167,29 @@ module.exports = class { codeArr.push(opmate.getMate('_$$6'), ",", opmate.getMate('_$aw'), "=", opmate.getMate('G_$kv'), "[", current, "];"); codeArr.push("while(1){", opmate.getMate('_$cu'), "=", opmate.getMate('_$aw'), "[", opmate.getMate('_$ku'), "++];"); codeArr.push("if(", opmate.getMate('_$cu'), "<", opmate.getMateOri('_$bf'), "){"); + if ([1, 2, 3, 4].includes(current)) { + this.functionsSort(current, functionsNameMap); + } const codelist = this.grenIfelse(0, opmate.getMateOri('_$bf'), []); codeArr.push(...codelist); codeArr.push("}else ", ';', '}'); } + functionsSort(current, functionsNameMap) { + const start = this.functionsPushStart[current]; + const { opdata, opmate } = this + this.$_ts.aebi[current].slice(start, start + opdata.getData('_$$w').length).forEach(idx => { + const numarr = opdata.getData('_$$k')[idx]; + if (!numarr || numarr.length !== 5) throw new Error(''); + const name = this.keynames[numarr[3]]; + this.functionsNameSort.push({ + name, + current, + code: functionsNameMap[name], + }); + }) + } + grenIfelse(start, end, codeArr) { const { opdata, opmate } = this const arr8 = opdata.getData('arr8') diff --git a/src/handler/CoderHigh.js b/src/handler/CoderHigh.js deleted file mode 100644 index c060d83..0000000 --- a/src/handler/CoderHigh.js +++ /dev/null @@ -1 +0,0 @@ -// 整站代码还原 diff --git a/src/handler/Cookie.js b/src/handler/Cookie.js index 4b16b75..ec6f151 100644 --- a/src/handler/Cookie.js +++ b/src/handler/Cookie.js @@ -43,7 +43,9 @@ const developConfig = { } module.exports = class { - constructor(ts, r2mkaText) { + constructor(ts, r2mkaText, coder, vmcode) { + this.coder = coder; + this.vmcode = vmcode; parser.init(ts, r2mkaText) const current = new Date().getTime() + 1000; this.config = { @@ -151,7 +153,7 @@ module.exports = class { ...numToNumarr4(16777216), // gv.cp2取得 ...numToNumarr4(0), // 任务编号0-0的任务列表取得 ...numToNumarr2(getFixedNumber()), // 固定值5900 - ...numToNumarr2(this.config.formatUid), // 根据方法的toString()计算, 使用了$_ts.aebi[1]作为任务的方法, + ...this.getCodeUid(), ], 0, // 任务编号0>one>63-287的任务列表取得 [0], // 任务编号0>one>63>one>4-290的任务列表取得 @@ -277,4 +279,13 @@ module.exports = class { getTaskNumber(name, idx) { return gv.r2mka(name).taskarr[idx]; } + + getCodeUid() { + const mainFunctionCode = this.vmcode.slice(...this.coder.mainFunctionIdx); + const one = uuid(this.coder.functionsNameSort[ascii2string(gv.keys[33])].code); + const len = parseInt(mainFunctionCode.length / 100); + const start = len * ascii2string(gv.keys[34]); + const two = uuid(mainFunctionCode.substr(start, len)) + return numToNumarr2((one ^ two) & 65535); + } } diff --git a/src/handler/globalVarible.js b/src/handler/globalVarible.js index e1d30e6..a67d45c 100644 --- a/src/handler/globalVarible.js +++ b/src/handler/globalVarible.js @@ -66,6 +66,10 @@ class GlobalVarible { // 返回密钥集合 return cache.keys; } + get argv() { + // 命令调用参数 + return cache.argv; + } _getAttr(attr) { return cache[attr]; } diff --git a/src/handler/parser/common/temp.js b/src/handler/parser/common/temp.js deleted file mode 100644 index 7466571..0000000 --- a/src/handler/parser/common/temp.js +++ /dev/null @@ -1,28 +0,0 @@ -const gv = require('@src/handler/globalVarible'); - -function (text) { - let one, two, three, four, idx = 0, dkey = gv.decryptKeys[5]; - const flag = dkey[text.charCodeAt(idx++)]; - if (flag <= 80) return flag; - if (flag == 81) { - return dkey[text.charCodeAt(idx++)] + 80; - } - if (flag == 82) { - one = dkey[text.charCodeAt(idx++)]; - two = dkey[text.charCodeAt(idx++)]; - return one + two * 86 + 165; - } - if (flag == 83) { - one = dkey[text.charCodeAt(idx++)]; - two = dkey[text.charCodeAt(idx++)]; - three = dkey[text.charCodeAt(idx++)]; - return one + two * 86 + three * 86 * 86 + 7560; - } - if (flag == 84) { - one = dkey[text.charCodeAt(idx++)]; - two = dkey[text.charCodeAt(idx++)]; - three = dkey[text.charCodeAt(idx++)]; - four = dkey[text.charCodeAt(idx++)]; - return one + two * 86 + three * 86 * 86 + four * 86 * 86 * 86 + 643615; - } -} diff --git a/src/makeCode.js b/src/makeCode.js index b0aa591..73defae 100644 --- a/src/makeCode.js +++ b/src/makeCode.js @@ -23,13 +23,13 @@ module.exports = function (ts, immucfg, mate = {}) { mate.jscode ? { name: 'makecode_input_js', desc: 'url方式提取的javascript代码:', - text: mate.jscode, + text: mate.jscode.code, extend: 'js', } : null, mate.html ? { name: 'makecode_input_html', desc: 'url方式提取的html代码:', - text: mate.html, + text: mate.html.code, extend: 'html', newLine: true, } : null, diff --git a/src/makeCodeHigh.js b/src/makeCodeHigh.js index 2c487c5..a7a3385 100644 --- a/src/makeCodeHigh.js +++ b/src/makeCodeHigh.js @@ -1,12 +1,16 @@ -const CoderHigh = require('./handler/CoderHigh'); +const AppCode = require('./handler/AppCode'); const paths = require('@utils/paths'); const fs = require('fs'); +const fse = require('fs-extra'); +const path = require('path'); const logger = require('@utils/logger'); const Coder = require('./handler/Coder'); const Cookie = require('./handler/Cookie'); const unescape = require('@utils/unescape'); const gv = require('@src/handler/globalVarible'); const getCode = require('@utils/getCode'); +const adapt = require('@src/adapt'); +const { getLength } = require('@src/handler/parser/common'); function parseR2mka(text) { const start = text.indexOf('"') + 1; @@ -14,15 +18,91 @@ function parseR2mka(text) { return unescape(text.substr(start, end)); } -module.exports = function (ts, immucfg, mate) { - gv._setAttr('_ts', ts); - const startTime = new Date().getTime(); - const coder = new Coder(ts, immucfg); - const { code, $_ts } = coder.run(); - const r2mkaText = parseR2mka(coder.r2mkaText); - const cookieVal = new Cookie($_ts, r2mkaText).run(); - const cookieKey = gv.utils.ascii2string(gv.keys[7]).split(';')[5] + 'P'; - debugger; - getCode(mate.url, `${cookieKey}=${cookieVal}`); +function mkdirsSync(dirPath) { + if (!fs.existsSync(dirPath)) { + mkdirsSync(path.dirname(dirPath)); + fs.mkdirSync(dirPath); + } +} + +function filenameAddDesc(name, desc) { + const arr = name.split('.'); + if (arr.length < 2) throw new Error(`文件名不正确: ${name}`); + arr[arr.length - 2] += desc; + return arr.join('.'); +} + +function writeFile(step, ts, immucfg, { jscode, html, appcode = [] }, $_ts, code) { + const files = [ + { + name: 'ts.json', + desc: 'url方式提取的ts:', + text: JSON.stringify(ts), + }, + { + name: 'immucfg.json', + desc: 'url方式提取的静态文本:', + text: JSON.stringify(immucfg), + }, + { + name: 'ts-full.json', + desc: '程序生成的ts:', + text: JSON.stringify($_ts), + }, + jscode, + html, + { + name: filenameAddDesc(jscode.name, '-dynamic'), + desc: `${jscode.name}生成的动态代码:`, + text: '// 该行标记来源,非动态代码生成: ' + JSON.stringify(ts) + '\n\n' + code, + }, + ...appcode, + ...appcode.filter(it => it.decryptCode).map(it => ({ + name: filenameAddDesc(it.name, '-decrypt'), + desc: `${it.name}生成的解密代码:`, + text: it.decryptCode, + })) + ].filter(Boolean).map(it => ({ ...it, filepath: paths.outputResolve('makecode-high', step, it.name) })) + if (!fs.existsSync(paths.outputResolve('makecode-high', step))) mkdirsSync(paths.outputResolve('makecode-high', step)); + return files; +} + +function decryptAppCode(appcode, idx) { + const $_ts = { l__: (...params) => params }; + const codeParams = eval(appcode.code); + appcode.decryptCode = new AppCode(codeParams, idx + 1).run(); +} + +function firstStep(ts, immucfg, mate) { + gv._setAttr('_ts', ts); + const coder = new Coder(ts, immucfg); + const { code, $_ts } = coder.run(); + const files = writeFile('first', ts, immucfg, mate, $_ts, code); + const r2mkaText = parseR2mka(coder.r2mkaText); + const cookieVal = new Cookie($_ts, r2mkaText, coder, code).run(); + const cookieKey = gv.utils.ascii2string(gv.keys[7]).split(';')[5] + 'P'; + return [files, `${cookieKey}=${cookieVal}`]; +} + +function secondStep(ts, immucfg, mate) { + gv._setAttr('_ts', ts); + const coder = new Coder(ts, immucfg); + const { code, $_ts } = coder.run(); + mate.appcode.map(decryptAppCode); + return writeFile('second', ts, immucfg, mate, $_ts, code); +} + +module.exports = async function (ts, immucfg, mate) { + fse.moveSync(paths.outputResolve('makecode-high'), paths.outputResolve('makecode-high-old'), { overwrite: true }); + const startTime = new Date().getTime(); + const [files, cookieStr] = firstStep(ts, immucfg, mate); + files.unshift('\n第1次请求:\n'); + const result = await getCode(mate.url, cookieStr); + files.push('\n第2次请求:\n', ...secondStep(result.$_ts, adapt(result, gv.argv.adapt), result)); + files.forEach(({ filepath, text, code }) => filepath && fs.writeFileSync(filepath, text || code)); + logger.info([ + `代码还原成功!用时:${new Date().getTime() - startTime}ms\n`, + ...files.reduce((ans, it, idx) => ([...ans, typeof it === 'string' ? it : `${it.desc}${it.filepath}${idx === files.length - 1 || it.newLine ? '\n' : ''}`]), []), + ].join('\n ')); } diff --git a/src/makeCookie.js b/src/makeCookie.js index 38efe90..a81108b 100644 --- a/src/makeCookie.js +++ b/src/makeCookie.js @@ -39,7 +39,7 @@ module.exports = function (ts, immucfg) { const coder = new Coder(ts, immucfg); const { code, $_ts } = coder.run(); const r2mkaText = parseR2mka(coder.r2mkaText); - const cookie = new Cookie($_ts, r2mkaText).run(); + const cookie = new Cookie($_ts, r2mkaText, coder, code).run(); if (gv.metaContent) { logger.info(`存在meta-content值:${gv.metaContent.content} 解析结果:${gv.metaContent.value}`); } diff --git a/utils/getCode.js b/utils/getCode.js index b4dee72..bebb95e 100644 --- a/utils/getCode.js +++ b/utils/getCode.js @@ -1,4 +1,4 @@ -process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" +// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" const { request, cookieJar } = require('./request'); const cheerio = require('cheerio'); const isValidUrl = require('./isValidUrl'); @@ -7,7 +7,7 @@ const urlresolve = require('url').resolve; function addRequestHead(uri) { return { - proxy: 'http://127.0.0.1:8888', + // proxy: 'http://127.0.0.1:8888', gzip: true, uri, resolveWithFullResponse: true, @@ -22,45 +22,53 @@ function addRequestHead(uri) { } } +function nameHandle(name, extend) { + return name.split('.').pop() === extend ? name : `${name}.${extend}`; +} + module.exports = async function getCode(url, cookieStr) { if (cookieStr) { cookieJar.setCookie(request.cookie(cookieStr), url); - console.log(`当前cookie:${cookieJar.getCookieString(url)}`); } if (!isValidUrl(url)) throw new Error('输入链接不正确'); const res = await request(addRequestHead(url)); const $ = cheerio.load(res.body); - const scripts = [...$('script[r=m]')] + const scripts = [...$('script')] const tsscript = scripts.map(ele => $(ele).text()).filter(text => text.includes('$_ts.nsd') && text.includes('$_ts.cd')); if (!tsscript.length) throw new Error(`${res.body}\n错误:链接返回结果未找到cd或nsd, 请检查!`); const $_ts = Function('window', tsscript[0] + 'return $_ts')({}); $_ts.metaContent = _get($('meta[r=m]'), '0.attribs.content'); - const checkSrc = (src) => src?.split('.').pop() === 'js' ? src : undefined; + const checkSrc = (src) => src?.split('.').pop().split('?')[0] === 'js' ? src : undefined; const remotes = scripts.map(it => checkSrc(it.attribs.src)).filter(Boolean); if (!remotes.length) throw new Error('未找到js外链,无法提取配置文本请检查!'); - for(let src of remotes) { - const jscode = await request(addRequestHead(urlresolve(url, src))); - if (jscode.body.includes('r2mKa')) return { $_ts, jscode: jscode.body, html: res.body, url }; + const ret = { + $_ts, + jscode: null, + html: { + code: res.body, + url, + name: nameHandle(url.split('?')[0].split('/').pop(), 'html'), + desc: 'url方式提取的html代码:' + }, + appcode: [], + url, } + for(let src of remotes) { + const jsurl = urlresolve(url, src); + const name = jsurl.split('?')[0].split('/').pop(); + const jscode = await request(addRequestHead(jsurl)); + const data = { + code: jscode.body, + url: jsurl, + name: nameHandle(name, 'js'), + desc: 'url方式提取的javascript代码:' + }; + if (jscode.body.indexOf('$_ts.l__(') === 0) { + ret.appcode.push(data); + } else if (jscode.body.includes('r2mKa')) { + ret.jscode = data; + } + } + if (ret.jscode) return ret; throw new Error('js外链中没有瑞数的代码文件'); } - -// Host: wcjs.sbj.cnipa.gov.cn -// Connection: keep-alive -// Upgrade-Insecure-Requests: 1 -// sec-ch-ua: "Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123" -// sec-ch-ua-mobile: ?0 -// sec-ch-ua-platform: "macOS" -// User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 -// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 -// Accept-Encoding: gzip, deflate, br, zstd -// Accept-Language: zh-CN,zh;q=0.9 -// Sec-Fetch-Dest: document -// Sec-Fetch-Mode: navigate -// -// Sec-Fetch-Site: none -// Sec-Fetch-User: ?1 -// -// -// Sec-Fetch-Site: same-origin -// Referer: https://wcjs.sbj.cnipa.gov.cn/sgtmi