feat: immutext automatic extraction

This commit is contained in:
rnet 2024-01-03 00:37:02 +08:00
parent 45588d148f
commit 96068ddc28
12 changed files with 142 additions and 34 deletions

47
main.js
View File

@ -11,6 +11,8 @@ const pkg = require(paths.package);
const request = require('request-promise'); const request = require('request-promise');
const cheerio = require('cheerio'); const cheerio = require('cheerio');
const log4js = require('log4js'); const log4js = require('log4js');
const urlresolve = require('url').resolve;
const adapt = require('@src/adapt');
function debugLog(level) { function debugLog(level) {
if (level) { if (level) {
@ -25,6 +27,24 @@ function debugLog(level) {
logger.trace('paths:\n', JSON.stringify(paths, null, 2)); logger.trace('paths:\n', JSON.stringify(paths, null, 2));
} }
const getCode = async (url) => {
if (!isValidUrl(url)) throw new Error('输入链接不正确');
const res = await request(url)
const $ = cheerio.load(res);
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('链接返回结果未找到cd或nsd');
const $_ts = Function('window', tsscript[0] + 'return $_ts')({});
const checkSrc = (src) => src?.split('.').pop() === '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(urlresolve(url, src));
if (jscode.includes('r2mKa')) return { $_ts, jscode };
}
throw new Error('js外链中没有瑞数的代码文件');
}
const commandBuilder = { const commandBuilder = {
f: { f: {
alias: 'file', alias: 'file',
@ -45,28 +65,25 @@ const commandBuilder = {
alias: 'url', alias: 'url',
describe: '瑞数返回204状态码的请求地址', describe: '瑞数返回204状态码的请求地址',
type: 'string', type: 'string',
coerce: async (input) => { coerce: getCode,
if (!isValidUrl(input)) throw new Error('输入链接不正确'); },
try { a: {
const res = await request(input) alias: 'adapt',
const $ = cheerio.load(res); describe: '已经做了适配的网站名称不传则为cnipa',
const scripts = [...$('script')].map(ele => $(ele).text()) type: 'string',
.filter(text => text.includes('$_ts.nsd') && text.includes('$_ts.cd'));
if (!scripts.length) throw new Error('链接返回结果未找到cd或nsd');
return Function('window', scripts[0] + 'return $_ts')({});
} catch(err) {
throw new Error('输入链接无效');
}
}
} }
} }
const commandHandler = (command, argv) => { const commandHandler = (command, argv) => {
debugLog(argv.level); debugLog(argv.level);
const ts = argv.file || argv.url || require(paths.exampleResolve('codes/1-\$_ts.json')); const ts = argv.url?.$_ts || argv.file || require(paths.exampleResolve('codes/1-\$_ts.json'));
logger.trace(`传入的$_ts.nsd: ${ts.nsd}`); logger.trace(`传入的$_ts.nsd: ${ts.nsd}`);
logger.trace(`传入的$_ts.cd: ${ts.cd}`); logger.trace(`传入的$_ts.cd: ${ts.cd}`);
command(ts); if (argv.url) {
command(ts, adapt(argv.url.jscode, argv.adapt));
} else {
command(ts);
}
} }
module.exports = yargs module.exports = yargs

6
src/adapt/cnipa/index.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
cp0: 'gl}y|sMnyn}',
cp2: 'g+*`-+`+0`.1`',
globalText1: "'r2mKa'.length",
globalText2: '&Ţfunction',
}

38
src/adapt/index.js Normal file
View File

@ -0,0 +1,38 @@
const fs = require('fs');
const unescape = require('@utils/unescape');
const adapts = fs.readdirSync(__dirname, { withFileTypes: true })
.filter(file => file.isDirectory())
.map(folder => folder.name)
.reduce((ans, it) => {
ans[it] = require(`./${it}/`);
return ans;
}, {});
function findFullString(text, start, end) {
let startIdx, endIdx;
do {
if (startIdx <= 0 || endIdx >= text.length - 1) return;
startIdx === undefined && start--;
endIdx === undefined && end++;
if (startIdx === undefined && text[start] === '"' && text[start - 1] !== '\\') {
startIdx = start;
}
if (endIdx === undefined && text[end] === '"' && text[end - 1] !== '\\') {
endIdx = end;
}
} while (startIdx === undefined || endIdx === undefined);
return unescape(text.slice(startIdx + 1, endIdx));
}
module.exports = function(code, name) {
const config = adapts[name] ? adapts[name] : adapts.cnipa;
const idx = code.indexOf(config.cp2);
return Object.entries(config).reduce((ans, [key, val]) => {
const idx = code.indexOf(val);
if (code.indexOf(val, idx + val.length) > -1) throw new Error(`${key}对应的值${val}在代码中非唯一,请检查!`);
const fullString = findFullString(code, idx, idx + val.length);
return { ...ans, [key]: fullString };
}, {});
}

24
src/adapt/readme.md Normal file
View File

@ -0,0 +1,24 @@
**该目录用于适配瑞数网站使用**
需要手动给出每个网站的静态加密串的特征值(值内部取一段不会重复的文本,尽量不要二进制文本,容易复制黏贴过程中被编辑器转译)
将对应的特征串放入相应变量名下:
1. cp0: `$_ts.cp[0]`对应的加密文本;
2. cp2: `$_ts.cp[2]`对应的加密文本;
3. globalText1: 生成动态代码过程中第1段用到的加密文本;
4. globalText2: 生成动态代码过程中第2段用到的加密文本;
**一定要保证复制的串是唯一的,可以先搜索确认只搜索到一处!!!**
示例:
```javascript
module.exports = {
cp0: 'gl}y|sMnyn}',
cp2: 'g+*`-+`+0`.1`',
globalText1: "'r2mKa'.length",
globalText2: '&Ţfunction',
}
```

View File

@ -6,15 +6,17 @@ const immutext = require('@src/immutext/');
const initTs = require('./initTs'); const initTs = require('./initTs');
module.exports = class { module.exports = class {
constructor(ts) { constructor(ts, immucfg) {
this.startTime = new Date().getTime(); this.startTime = new Date().getTime();
this.$_ts = initTs(ts); this.$_ts = initTs(ts, immucfg);
this.scd = getScd(this.$_ts.nsd); this.scd = getScd(this.$_ts.nsd);
this.keynames = this.$_ts.cp[1]; this.keynames = this.$_ts.cp[1];
this.keycodes = [] this.keycodes = []
this.optext = globaltext(); this.optext = globaltext();
this.opmate = this.mateOper(); this.opmate = this.mateOper();
this.opdata = dataOper(); this.opdata = dataOper();
this.r2mkaText = null;
this.immucfg = immucfg || immutext;
} }
run() { run() {
@ -43,7 +45,7 @@ module.exports = class {
parseGlobalText2() { parseGlobalText2() {
const { opmate, opdata, optext, keynames, getCurr } = this; const { opmate, opdata, optext, keynames, getCurr } = this;
optext.init(0, immutext.globalText2); optext.init(0, this.immucfg.globalText2);
opdata.init(); opdata.init();
opmate.init(); opmate.init();
opmate.setMate('G_$ht', true); opmate.setMate('G_$ht', true);
@ -62,7 +64,7 @@ module.exports = class {
parseGlobalText1(codeArr = []) { parseGlobalText1(codeArr = []) {
const { opmate, opdata, optext, keynames, getCurr } = this; const { opmate, opdata, optext, keynames, getCurr } = this;
optext.init(0, immutext.globalText1); optext.init(0, this.immucfg.globalText1);
opdata.init({ arr8: [4, 16, 64, 256, 1024, 4096, 16384, 65536] }); opdata.init({ arr8: [4, 16, 64, 256, 1024, 4096, 16384, 65536] });
opmate.init(); opmate.init();
opmate.setMate('G_$e4', true); opmate.setMate('G_$e4', true);
@ -73,7 +75,8 @@ module.exports = class {
opmate.setMate(); opmate.setMate();
this.keycodes.push(...optext.getLine(optext.getCode() * 55295 + optext.getCode()).split(String.fromCharCode(257))); this.keycodes.push(...optext.getLine(optext.getCode() * 55295 + optext.getCode()).split(String.fromCharCode(257)));
opmate.setMate(); opmate.setMate();
this.keycodes.push(optext.getLine(optext.getCode() * 55295 + optext.getCode())); this.r2mkaText = optext.getLine(optext.getCode() * 55295 + optext.getCode())
this.keycodes.push(this.r2mkaText);
opmate.setMate('G_$gG', true); opmate.setMate('G_$gG', true);
for (let i = 0; i < opmate.getMateOri('G_$gG'); i++) { for (let i = 0; i < opmate.getMateOri('G_$gG'); i++) {
this.gren(i, codeArr); this.gren(i, codeArr);

View File

@ -27,8 +27,8 @@ const {
} = parser; } = parser;
module.exports = class { module.exports = class {
constructor(ts) { constructor(ts, r2mkaText) {
parser.init(ts) parser.init(ts, r2mkaText)
this.config = { this.config = {
'window.navigator.maxTouchPoints': 0, 'window.navigator.maxTouchPoints': 0,
'window.eval.toString().length': 33, 'window.eval.toString().length': 33,

View File

@ -9,11 +9,11 @@ function grenJf () {
return !flag; return !flag;
} }
module.exports = function(defdata) { module.exports = function(defdata, immucfg = immutext.$_ts) {
const cp = []; const cp = [];
cp[0] = immutext.$_ts.cp0; cp[0] = immucfg.cp0;
cp[1] = grenKeys(806, defdata.nsd); cp[1] = grenKeys(806, defdata.nsd);
cp[2] = immutext.$_ts.cp2; cp[2] = immucfg.cp2;
cp[6] = ''; cp[6] = '';
return { return {
nsd: 0, nsd: 0,

View File

@ -2,11 +2,11 @@ const gv = require('../globalVarible');
const common = require('./common'); const common = require('./common');
const logger = require('@utils/logger'); const logger = require('@utils/logger');
function init(ts) { function init(ts, r2mkaText) {
const startTime = new Date().getTime(); const startTime = new Date().getTime();
gv.setAttr('utils', common); gv.setAttr('utils', common);
gv.setAttr('ts', ts); gv.setAttr('ts', ts);
require('./r2mka').init(); require('./r2mka').init(r2mkaText);
require('./tscp').init(); require('./tscp').init();
require('./tscd').init(); require('./tscd').init();
require('./bignum').init(); require('./bignum').init();

View File

@ -84,7 +84,7 @@ exports.parse = function(str = gt3) {
return parse(deepParse()); return parse(deepParse());
}; };
exports.init = function() { exports.init = function(r2mkaText) {
gv.setAttr('r2mka', exports.parse(gt3)); gv.setAttr('r2mka', exports.parse(r2mkaText || gt3));
logger.debug('头r2mka标识字符串完成解析!') logger.debug('头r2mka标识字符串完成解析!')
} }

View File

@ -3,9 +3,9 @@ const paths = require('@utils/paths');
const logger = require('@utils/logger'); const logger = require('@utils/logger');
const fs = require('fs'); const fs = require('fs');
module.exports = function (ts) { module.exports = function (ts, immucfg) {
const startTime = new Date().getTime(); const startTime = new Date().getTime();
const coder = new Coder(ts); const coder = new Coder(ts, immucfg);
const { code, $_ts } = coder.run(); const { code, $_ts } = coder.run();
const files = [ const files = [
{ {

View File

@ -1,12 +1,20 @@
const logger = require('@utils/logger'); const logger = require('@utils/logger');
const Coder = require('./handler/Coder'); const Coder = require('./handler/Coder');
const Cookie = require('./handler/Cookie'); const Cookie = require('./handler/Cookie');
const unescape = require('@utils/unescape');
module.exports = function (ts) { function parseR2mka(text) {
const start = text.indexOf('"') + 1;
const end = text.lastIndexOf('"') - 2;
return unescape(text.substr(start, end));
}
module.exports = function (ts, immucfg) {
const startTime = new Date().getTime(); const startTime = new Date().getTime();
const coder = new Coder(ts); const coder = new Coder(ts, immucfg);
const { code, $_ts } = coder.run(); const { code, $_ts } = coder.run();
const cookie = new Cookie($_ts).run(); const r2mkaText = parseR2mka(coder.r2mkaText);
const cookie = new Cookie($_ts, r2mkaText).run();
logger.info([`生成动态cookie成功用时${new Date().getTime() - startTime}ms\n`, `Cookie值: ${cookie}`, `Cookie长: ${cookie.length}\n`].join('\n ')) logger.info([`生成动态cookie成功用时${new Date().getTime() - startTime}ms\n`, `Cookie值: ${cookie}`, `Cookie长: ${cookie.length}\n`].join('\n '))
} }

12
utils/unescape.js Normal file
View File

@ -0,0 +1,12 @@
// 转译还原
module.exports = function(text) {
try {
try {
return JSON.parse('"' + text + '"')
} catch (e) {
return eval('("' + text + '")')
}
} catch (e) {
return text
}
}