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 cheerio = require('cheerio');
const log4js = require('log4js');
const urlresolve = require('url').resolve;
const adapt = require('@src/adapt');
function debugLog(level) {
if (level) {
@ -25,6 +27,24 @@ function debugLog(level) {
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 = {
f: {
alias: 'file',
@ -45,28 +65,25 @@ const commandBuilder = {
alias: 'url',
describe: '瑞数返回204状态码的请求地址',
type: 'string',
coerce: async (input) => {
if (!isValidUrl(input)) throw new Error('输入链接不正确');
try {
const res = await request(input)
const $ = cheerio.load(res);
const scripts = [...$('script')].map(ele => $(ele).text())
.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('输入链接无效');
}
}
coerce: getCode,
},
a: {
alias: 'adapt',
describe: '已经做了适配的网站名称不传则为cnipa',
type: 'string',
}
}
const commandHandler = (command, argv) => {
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.cd: ${ts.cd}`);
command(ts);
if (argv.url) {
command(ts, adapt(argv.url.jscode, argv.adapt));
} else {
command(ts);
}
}
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');
module.exports = class {
constructor(ts) {
constructor(ts, immucfg) {
this.startTime = new Date().getTime();
this.$_ts = initTs(ts);
this.$_ts = initTs(ts, immucfg);
this.scd = getScd(this.$_ts.nsd);
this.keynames = this.$_ts.cp[1];
this.keycodes = []
this.optext = globaltext();
this.opmate = this.mateOper();
this.opdata = dataOper();
this.r2mkaText = null;
this.immucfg = immucfg || immutext;
}
run() {
@ -43,7 +45,7 @@ module.exports = class {
parseGlobalText2() {
const { opmate, opdata, optext, keynames, getCurr } = this;
optext.init(0, immutext.globalText2);
optext.init(0, this.immucfg.globalText2);
opdata.init();
opmate.init();
opmate.setMate('G_$ht', true);
@ -62,7 +64,7 @@ module.exports = class {
parseGlobalText1(codeArr = []) {
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] });
opmate.init();
opmate.setMate('G_$e4', true);
@ -73,7 +75,8 @@ module.exports = class {
opmate.setMate();
this.keycodes.push(...optext.getLine(optext.getCode() * 55295 + optext.getCode()).split(String.fromCharCode(257)));
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);
for (let i = 0; i < opmate.getMateOri('G_$gG'); i++) {
this.gren(i, codeArr);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,20 @@
const logger = require('@utils/logger');
const Coder = require('./handler/Coder');
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 coder = new Coder(ts);
const coder = new Coder(ts, immucfg);
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 '))
}

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
}
}