存档:TS 重构前基线(lx 聚合源 = isolated-vm 加载器,签名方案已移除)
This commit is contained in:
commit
f289df4cd7
124 files changed
+14110
No files matched your search
@@ -0,0 +1,139 @@
|
||||
// lx-music-source-v5 复现/追踪脚本(与生产加载器 src/apis/lx-source-ivm.js 相互独立)
|
||||
// 1) 将去混淆脚本末尾的字符串缓存 [] 替换为 LOGCACHE(Proxy),按序记录 JSVMP 解码的全部字符串
|
||||
// 2) 模拟 lx-music-desktop preload.js 的 globalThis.lx 环境,观察脚本的注册与取流行为
|
||||
// 运行:node tmp/reproduce-lx-source-v5.mjs
|
||||
import crypto from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import vm from 'node:vm'
|
||||
import zlib from 'node:zlib'
|
||||
import { promisify } from 'node:util'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const inflate = promisify(zlib.inflate)
|
||||
const deflate = promisify(zlib.deflate)
|
||||
const md5 = (s) => crypto.createHash('md5').update(s).digest('hex')
|
||||
|
||||
const deobPath = fileURLToPath(new URL('../lx-music-source-v5-deobfuscated.js', import.meta.url))
|
||||
const obfPath = fileURLToPath(new URL('../lx-music-source-v5.js', import.meta.url))
|
||||
|
||||
let code = fs.readFileSync(deobPath, 'utf8')
|
||||
|
||||
// 字符串缓存:文件末尾 IIFE 最后一个参数 []
|
||||
const cacheLog = []
|
||||
const LOGCACHE = new Proxy([], {
|
||||
set(t, prop, val) {
|
||||
if (typeof val === 'string') cacheLog.push(val)
|
||||
t[prop] = val
|
||||
return true
|
||||
},
|
||||
})
|
||||
const idx = code.lastIndexOf(', []);')
|
||||
code = code.slice(0, idx) + ', LOGCACHE);'
|
||||
|
||||
// ---- lx 环境(复刻 preload.js) ----
|
||||
const handlers = {}
|
||||
const requestCalls = []
|
||||
let rconfigBody = process.env.RCONFIG_BODY ?? JSON.stringify({ code: 0, msg: 'success', data: 'https://example.test/fake.mp3' })
|
||||
const lx = {
|
||||
EVENT_NAMES: { request: 'request', inited: 'inited', updateAlert: 'updateAlert' },
|
||||
request(url, options = {}, callback) {
|
||||
const rec = { url, options: { method: options.method ?? 'get', timeout: options.timeout, headers: options.headers, body: options.body, form: options.form, formData: options.formData } }
|
||||
requestCalls.push(rec)
|
||||
console.log('[lx.request]', url)
|
||||
console.log(' options:', JSON.stringify(rec.options))
|
||||
const isRconfig = url.includes('/rconfig')
|
||||
const raw = isRconfig ? rconfigBody : JSON.stringify({ code: 0, msg: 'success', data: 'https://example.test/fake.mp3' })
|
||||
const parsed = (() => { try { return JSON.parse(raw) } catch { return raw } })()
|
||||
setTimeout(() => {
|
||||
callback(null, { statusCode: 200, statusMessage: 'OK', headers: { 'content-type': 'application/json' }, raw: Buffer.from(raw), body: parsed }, parsed)
|
||||
}, 5)
|
||||
return () => {}
|
||||
},
|
||||
send(eventName, data) {
|
||||
console.log('[lx.send]', eventName, JSON.stringify(data))
|
||||
return Promise.resolve()
|
||||
},
|
||||
on(eventName, handler) {
|
||||
console.log('[lx.on]', eventName, typeof handler, handler && handler.length)
|
||||
handlers[eventName] = handler
|
||||
return Promise.resolve()
|
||||
},
|
||||
utils: {
|
||||
crypto: {
|
||||
aesEncrypt(b, mode, key, iv) { const c = crypto.createCipheriv(mode, key, iv); return Buffer.concat([c.update(b), c.final()]) },
|
||||
rsaEncrypt(b, key) { b = Buffer.concat([Buffer.alloc(128 - b.length), b]); return crypto.publicEncrypt({ key, padding: crypto.constants.RSA_NO_PADDING }, b) },
|
||||
randomBytes: (n) => crypto.randomBytes(n),
|
||||
md5,
|
||||
},
|
||||
buffer: { from: (...a) => Buffer.from(...a), bufToString: (b, f) => Buffer.from(b, 'binary').toString(f) },
|
||||
zlib: { inflate: (b) => inflate(b), deflate: (d) => deflate(d) },
|
||||
},
|
||||
currentScriptInfo: { name: '独家音源', description: '', version: '5', author: 'w', homepage: '', rawScript: code },
|
||||
version: '2.0.0',
|
||||
env: 'desktop',
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
console, setTimeout, clearTimeout, setInterval, clearInterval,
|
||||
Promise, Error, TypeError, RangeError, SyntaxError, EvalError, URIError,
|
||||
Array, Object, String, Number, Boolean, Date, Math, JSON, RegExp, BigInt, Symbol,
|
||||
Function, parseInt, parseFloat, isNaN, isFinite,
|
||||
encodeURIComponent, decodeURIComponent, escape, unescape,
|
||||
Uint8Array, Uint16Array, Uint32Array, Int8Array, Int16Array, Int32Array, Float32Array, Float64Array, ArrayBuffer, DataView, Map, Set, WeakMap, WeakSet,
|
||||
TextEncoder, TextDecoder, URL, URLSearchParams,
|
||||
Buffer,
|
||||
atob: (s) => Buffer.from(s, 'base64').toString('binary'),
|
||||
btoa: (s) => Buffer.from(s, 'binary').toString('base64'),
|
||||
SCRIPT_MD5: md5(fs.readFileSync(obfPath)),
|
||||
LOGCACHE,
|
||||
lx,
|
||||
}
|
||||
ctx.globalThis = ctx
|
||||
ctx.window = ctx
|
||||
ctx.self = ctx
|
||||
ctx.global = ctx
|
||||
|
||||
process.on('uncaughtException', (e) => console.error('[uncaught]', e.stack?.split('\n').slice(0, 6).join('\n')))
|
||||
process.on('unhandledRejection', (e) => console.error('[unhandledRejection]', e?.stack?.split('\n').slice(0, 6).join('\n') || e))
|
||||
|
||||
const context = vm.createContext(ctx)
|
||||
console.error('[run]')
|
||||
try {
|
||||
vm.runInContext(code, context, { filename: 'lx-source-v5.js', timeout: 15000 })
|
||||
console.error('[run ok]')
|
||||
} catch (e) {
|
||||
console.error('[vm error]', e.stack?.split('\n').slice(0, 8).join('\n'))
|
||||
}
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1500))
|
||||
|
||||
console.error('\n===== decoded strings (%d) =====', cacheLog.length)
|
||||
console.log(cacheLog.map((s, i) => `${i}\t${JSON.stringify(s)}`).join('\n'))
|
||||
|
||||
console.error('\n===== summary =====')
|
||||
console.error('events registered:', Object.keys(handlers))
|
||||
console.error('requests made:', requestCalls.length)
|
||||
|
||||
// 触发 musicUrl 请求
|
||||
const handler = handlers.request
|
||||
if (handler) {
|
||||
console.error('\n[trigger] musicUrl kw/128k')
|
||||
const markBefore = cacheLog.length
|
||||
try {
|
||||
const p = handler.call(lx, { source: 'kw', action: 'musicUrl', info: { type: '128k', musicInfo: { songmid: '28423286', hash: 'fakehash123', id: 'kw_28423286' } } })
|
||||
console.error('[trigger] handler returned:', p && p.constructor?.name)
|
||||
if (p && typeof p.then === 'function') {
|
||||
const res = await Promise.race([p.then((v) => ({ ok: true, v }), (e) => ({ ok: false, msg: e?.message, name: e?.name })), new Promise((r) => setTimeout(() => r({ timeout: true }), 8000))])
|
||||
console.error('[trigger result]', JSON.stringify(res, null, 1))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[trigger sync error]', e?.message)
|
||||
}
|
||||
console.error('\n[strings used during trigger]')
|
||||
console.error(cacheLog.slice(markBefore).map((s) => JSON.stringify(s)).join('\n'))
|
||||
await new Promise((r) => setTimeout(r, 500))
|
||||
console.error('\n[requests during trigger]')
|
||||
for (const rc of requestCalls) console.log(rc.url, JSON.stringify(rc.options))
|
||||
}
|
||||
fs.writeFileSync(fileURLToPath(new URL('./trace-strings.json', import.meta.url)), JSON.stringify(cacheLog, null, 1))
|
||||
process.exit(0)
|
||||
Reference in new issue
Block a user