吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2559|回复: 24
上一主题 下一主题
收起左侧

[Web逆向] 某Q音乐sign纯算-vmp插桩日志分析

  [复制链接]
跳转到指定楼层
楼主
gfy1129 发表于 2026-4-25 22:45 回帖奖励
本文仅供学习交流,因使用本文内容而产生的任何风险及后果,作者不承担任何责任



一个很好的VMP插桩 纯算入门案例
在ai的时代,一些ai工具能够很容易解决这类的vmp,但是对于想要提升自己能力的各位大佬可以看看。

以下内容主要作为学习分析流程,如果要复现,请重新分析当前的vmp,因为算法隔一段时间会有微小的变化。(因为在写这篇blog的时候,算法微调了一下,在末尾会介绍)

网址:aHR0cHM6Ly95LnFxLmNvbS8=
请求包:aHR0cHM6Ly91Ni55LnFxLmNvbS9jZ2ktYmluL211c2ljcy5mY2c=




一、分析日志前的准备

直接全局搜索sign,定位到加密位置


ie()即为sign的加密函数,跟进去 是c 这个函数


能够识别这个是vmp加载的加密函数,然后 var ie = ne._getSecuritySign,然后直接把这一大坨vmp以及开始的 ne,re 的定义扣到本地进行插桩。


类似这种,可以选择手动插桩、ast自动插桩、ai-skill自动插桩这几种方式,目的就是 把字节码真正执行的逻辑通过日志还原出来


先在控制台执行这个保存日志到本地的脚本
[JavaScript] 纯文本查看 复制代码
(() => {
    'use strict';

    const SUPPORTED_EXPORT_FORMATS = new Set(['jsonl', 'json', 'txt']);
    const CONSOLE_METHODS = ['log', 'warn', 'error', 'info', 'debug'];

    const DEFAULT_CONFIG = {
        maxConsoleLines: 4000,
        exportBatchSize: 2000,
        autoExportInterval: 60000,
        enableAutoExport: false,
        exportFormat: 'txt',
        logPrefix: 'VMP_REVERSE',
        keepInConsole: 80,
        maxHistoryEntries: Number.MAX_SAFE_INTEGER,
        maxDepth: 5,
        maxArrayLength: 50,
        maxObjectKeys: 50,
        maxStringLength: 4000,
        previewStringLength: 320,
        includeStack: true
    };

    class VmpLogExporter {
        constructor(config = {}) {
            this.config = this.normalizeConfig(config);
            this.consoleMethods = CONSOLE_METHODS.slice();
            this.pendingLogs = [];
            this.historyLogs = [];
            this.recentConsoleEntries = [];
            this.originalMethods = {};
            this.previousConsoleHelpers = {
                save: console.save,
                saveAll: console.saveAll,
                saveJson: console.saveJson,
                saveJsonl: console.saveJsonl,
                saveTxt: console.saveTxt,
                status: console.status
            };
            this.fileCounter = 1;
            this.consoleLineCount = 0;
            this.totalCapturedLogs = 0;
            this.droppedHistoryLogs = 0;
            this.entryCounter = 1;
            this.isExporting = false;
            this.autoExportTimer = null;
            this.startedAt = new Date().toISOString();
            this.lastExportInfo = null;

            this.init();
        }

        normalizeConfig(config) {
            return {
                maxConsoleLines: this.toNonNegativeInt(config.maxConsoleLines, DEFAULT_CONFIG.maxConsoleLines),
                exportBatchSize: this.toPositiveInt(config.exportBatchSize, DEFAULT_CONFIG.exportBatchSize),
                autoExportInterval: this.toPositiveInt(config.autoExportInterval, DEFAULT_CONFIG.autoExportInterval),
                enableAutoExport: config.enableAutoExport ?? DEFAULT_CONFIG.enableAutoExport,
                exportFormat: this.normalizeFormat(config.exportFormat ?? DEFAULT_CONFIG.exportFormat),
                logPrefix: String(config.logPrefix ?? DEFAULT_CONFIG.logPrefix),
                keepInConsole: this.toNonNegativeInt(config.keepInConsole, DEFAULT_CONFIG.keepInConsole),
                maxHistoryEntries: this.toNonNegativeInt(config.maxHistoryEntries, DEFAULT_CONFIG.maxHistoryEntries),
                maxDepth: this.toPositiveInt(config.maxDepth, DEFAULT_CONFIG.maxDepth),
                maxArrayLength: this.toPositiveInt(config.maxArrayLength, DEFAULT_CONFIG.maxArrayLength),
                maxObjectKeys: this.toPositiveInt(config.maxObjectKeys, DEFAULT_CONFIG.maxObjectKeys),
                maxStringLength: this.toPositiveInt(config.maxStringLength, DEFAULT_CONFIG.maxStringLength),
                previewStringLength: this.toPositiveInt(config.previewStringLength, DEFAULT_CONFIG.previewStringLength),
                includeStack: config.includeStack ?? DEFAULT_CONFIG.includeStack
            };
        }

        toPositiveInt(value, fallback) {
            const number = Number(value);
            return Number.isInteger(number) && number > 0 ? number : fallback;
        }

        toNonNegativeInt(value, fallback) {
            const number = Number(value);
            return Number.isInteger(number) && number >= 0 ? number : fallback;
        }

        normalizeFormat(format) {
            const value = String(format || '').toLowerCase();
            return SUPPORTED_EXPORT_FORMATS.has(value) ? value : DEFAULT_CONFIG.exportFormat;
        }

        init() {
            this.hijackConsole();
            this.installConsoleHelpers();

            if (this.config.enableAutoExport) {
                this.startAutoExport();
            }

            this.internalPrint(
                'info',
                '[VMP] logger ready. manual export mode enabled, run console.saveAll() when finished.'
            );
        }

        hijackConsole() {
            this.consoleMethods.forEach(method => {
                const originalMethod = typeof console[method] === 'function'
                    ? console[method].bind(console)
                    : console.log.bind(console);

                this.originalMethods[method] = originalMethod;

                console[method] = (...args) => {
                    this.captureLog(method, args);
                    return originalMethod(...args);
                };
            });
        }

        installConsoleHelpers() {
            console.save = formatOrOptions => this.manualExport(formatOrOptions);
            console.saveAll = formatOrOptions => this.exportAllLogs(formatOrOptions);
            console.saveJson = () => this.manualExport({ format: 'json' });
            console.saveJsonl = () => this.manualExport({ format: 'jsonl' });
            console.saveTxt = () => this.exportAllLogs({ format: 'txt' });
            console.status = () => console.table(this.getStatus());
        }

        captureLog(level, args) {
            const entry = this.createEntry(level, args);

            this.pendingLogs.push(entry);
            this.appendHistory(entry);
            this.rememberForConsole(entry.timestamp, level, args);

            this.totalCapturedLogs += 1;
            this.consoleLineCount += 1;

            if (this.config.maxConsoleLines > 0 && this.consoleLineCount >= this.config.maxConsoleLines) {
                void this.manageConsole();
                return;
            }

        }

        createEntry(level, args) {
            const timestamp = new Date().toISOString();
            const serializedArgs = args.map(arg => this.serializeValue(arg, 0, new WeakSet()));
            const message = this.buildMessage(serializedArgs);

            return {
                id: this.entryCounter++,
                timestamp,
                level,
                message,
                args: serializedArgs
            };
        }

        appendHistory(entry) {
            if (this.config.maxHistoryEntries === 0) {
                return;
            }

            this.historyLogs.push(entry);

            const overflow = this.historyLogs.length - this.config.maxHistoryEntries;
            if (overflow > 0) {
                this.historyLogs.splice(0, overflow);
                this.droppedHistoryLogs += overflow;
            }
        }

        rememberForConsole(timestamp, level, rawArgs) {
            if (this.config.keepInConsole === 0) {
                return;
            }

            this.recentConsoleEntries.push({ timestamp, level, rawArgs });

            const overflow = this.recentConsoleEntries.length - this.config.keepInConsole;
            if (overflow > 0) {
                this.recentConsoleEntries.splice(0, overflow);
            }
        }

        serializeValue(value, depth, seen) {
            if (value === null) {
                return null;
            }

            const valueType = typeof value;

            if (valueType === 'string') {
                return this.truncateString(value);
            }

            if (valueType === 'number') {
                return Number.isFinite(value) ? value : String(value);
            }

            if (valueType === 'boolean') {
                return value;
            }

            if (valueType === 'undefined') {
                return '[Undefined]';
            }

            if (valueType === 'bigint') {
                return `${value.toString()}n`;
            }

            if (valueType === 'symbol') {
                return value.toString();
            }

            if (valueType === 'function') {
                return `[Function ${value.name || 'anonymous'}]`;
            }

            if (value instanceof Date) {
                return {
                    __type: 'Date',
                    value: value.toISOString()
                };
            }

            if (value instanceof Error) {
                const errorData = {
                    __type: value.name || 'Error',
                    message: this.truncateString(value.message || '')
                };

                if (this.config.includeStack && value.stack) {
                    errorData.stack = this.truncateString(value.stack);
                }

                return errorData;
            }

            if (typeof Element !== 'undefined' && value instanceof Element) {
                return {
                    __type: 'Element',
                    tagName: value.tagName,
                    id: value.id || undefined,
                    className: typeof value.className === 'string' ? value.className || undefined : undefined
                };
            }

            if (typeof Document !== 'undefined' && value instanceof Document) {
                return '[Document]';
            }

            if (typeof Window !== 'undefined' && value === window) {
                return '[Window]';
            }

            if (depth >= this.config.maxDepth) {
                return '[MaxDepth]';
            }

            if (seen.has(value)) {
                return '[Circular]';
            }

            seen.add(value);

            try {
                if (Array.isArray(value)) {
                    return this.serializeArray(value, depth, seen);
                }

                if (value instanceof Map) {
                    return this.serializeMap(value, depth, seen);
                }

                if (value instanceof Set) {
                    return this.serializeSet(value, depth, seen);
                }

                if (value instanceof URL) {
                    return value.toString();
                }

                if (value instanceof RegExp) {
                    return value.toString();
                }

                if (value instanceof ArrayBuffer) {
                    return {
                        __type: 'ArrayBuffer',
                        byteLength: value.byteLength
                    };
                }

                if (ArrayBuffer.isView(value)) {
                    return this.serializeTypedArray(value);
                }

                return this.serializeObject(value, depth, seen);
            } finally {
                seen.delete(value);
            }
        }

        serializeArray(array, depth, seen) {
            const items = array
                .slice(0, this.config.maxArrayLength)
                .map(item => this.serializeValue(item, depth + 1, seen));

            if (array.length > this.config.maxArrayLength) {
                items.push(`[+${array.length - this.config.maxArrayLength} more items]`);
            }

            return items;
        }

        serializeMap(map, depth, seen) {
            const entries = [];
            let index = 0;

            for (const [key, value] of map.entries()) {
                if (index >= this.config.maxArrayLength) {
                    entries.push(`[+${map.size - this.config.maxArrayLength} more entries]`);
                    break;
                }

                entries.push([
                    this.serializeValue(key, depth + 1, seen),
                    this.serializeValue(value, depth + 1, seen)
                ]);

                index += 1;
            }

            return {
                __type: 'Map',
                entries
            };
        }

        serializeSet(set, depth, seen) {
            const values = [];
            let index = 0;

            for (const value of set.values()) {
                if (index >= this.config.maxArrayLength) {
                    values.push(`[+${set.size - this.config.maxArrayLength} more items]`);
                    break;
                }

                values.push(this.serializeValue(value, depth + 1, seen));
                index += 1;
            }

            return {
                __type: 'Set',
                values
            };
        }

        serializeTypedArray(view) {
            if (typeof view.length !== 'number') {
                return {
                    __type: view.constructor ? view.constructor.name : 'TypedArray'
                };
            }

            const values = Array.from(view.slice(0, this.config.maxArrayLength))
                .map(item => this.serializeValue(item, 0, new WeakSet()));

            if (view.length > this.config.maxArrayLength) {
                values.push(`[+${view.length - this.config.maxArrayLength} more items]`);
            }

            return {
                __type: view.constructor ? view.constructor.name : 'TypedArray',
                values
            };
        }

        serializeObject(object, depth, seen) {
            const output = {};
            const constructorName = object && object.constructor && object.constructor.name;

            if (constructorName && constructorName !== 'Object') {
                output.__type = constructorName;
            }

            let keys = [];

            try {
                keys = Object.keys(object);
            } catch (error) {
                return {
                    __type: constructorName || 'Object',
                    __error: `[ObjectKeysError] ${error && error.message ? error.message : 'failed to inspect object'}`
                };
            }

            const limitedKeys = keys.slice(0, this.config.maxObjectKeys);

            limitedKeys.forEach(key => {
                try {
                    output[key] = this.serializeValue(object[key], depth + 1, seen);
                } catch (error) {
                    output[key] = `[PropertyReadError] ${error && error.message ? error.message : 'failed to read property'}`;
                }
            });

            if (keys.length > this.config.maxObjectKeys) {
                output.__truncatedKeys = keys.length - this.config.maxObjectKeys;
            }

            return output;
        }

        truncateString(value, limit = this.config.maxStringLength) {
            const text = String(value);
            if (text.length <= limit) {
                return text;
            }

            return `${text.slice(0, limit)}...[+${text.length - limit} chars]`;
        }

        buildMessage(serializedArgs) {
            const text = serializedArgs
                .map(item => {
                    if (typeof item === 'string') {
                        return item;
                    }

                    try {
                        return JSON.stringify(item);
                    } catch (error) {
                        return '[UnserializablePreview]';
                    }
                })
                .join(' ');

            return text.replace(/\r?\n+/g, ' ').trim();
        }

        manageConsole() {
            if (this.isExporting) {
                return;
            }

            this.internalPrint(
                'info',
                `[VMP] console limit reached (${this.consoleLineCount}). refreshing console without downloading logs.`
            );
            this.refreshConsole();
        }

        refreshConsole() {
            if (typeof console.clear === 'function') {
                console.clear();
            }

            this.recentConsoleEntries.forEach(entry => {
                const method = this.originalMethods[entry.level] || this.originalMethods.log;
                method(`[${entry.timestamp}]`, ...entry.rawArgs);
            });

            this.consoleLineCount = this.recentConsoleEntries.length;
            this.internalPrint('info', `[VMP] console refreshed. kept=${this.recentConsoleEntries.length}`);
        }

        manualExport(formatOrOptions) {
            const options = this.normalizeExportOptions(formatOrOptions);
            return this.exportLogs({
                ...options,
                reason: options.reason || 'manual'
            });
        }

        async exportLogs(options = {}) {
            if (this.isExporting || this.pendingLogs.length === 0) {
                return null;
            }

            this.isExporting = true;

            try {
                const format = this.normalizeFormat(options.format ?? this.config.exportFormat);
                const logsToExport = this.pendingLogs.slice();
                const filename = this.buildFilename(format, `part${this.fileCounter}`);
                const content = this.buildContent(logsToExport, {
                    format,
                    exportKind: 'pending',
                    reason: options.reason || 'manual'
                });

                this.downloadText(content, filename, this.getMimeType(format));

                this.pendingLogs = [];
                this.lastExportInfo = {
                    at: new Date().toISOString(),
                    filename,
                    count: logsToExport.length,
                    format,
                    kind: 'pending'
                };
                this.fileCounter += 1;

                this.internalPrint('info', `[VMP] exported ${logsToExport.length} pending logs to ${filename}`);

                return this.lastExportInfo;
            } catch (error) {
                this.internalPrint('error', '[VMP] export failed', error);
                return null;
            } finally {
                this.isExporting = false;
            }
        }

        exportAllLogs(formatOrOptions) {
            const options = this.normalizeExportOptions(formatOrOptions);
            const format = this.normalizeFormat(options.format ?? this.config.exportFormat);
            const logsToExport = this.historyLogs.slice();

            if (logsToExport.length === 0) {
                this.internalPrint('info', '[VMP] no history logs to export');
                return null;
            }

            try {
                const filename = this.buildFilename(format, 'history');
                const content = this.buildContent(logsToExport, {
                    format,
                    exportKind: 'history',
                    reason: options.reason || 'manual-history'
                });

                this.downloadText(content, filename, this.getMimeType(format));

                this.lastExportInfo = {
                    at: new Date().toISOString(),
                    filename,
                    count: logsToExport.length,
                    format,
                    kind: 'history'
                };

                this.internalPrint('info', `[VMP] exported ${logsToExport.length} history logs to ${filename}`);

                return this.lastExportInfo;
            } catch (error) {
                this.internalPrint('error', '[VMP] history export failed', error);
                return null;
            }
        }

        normalizeExportOptions(formatOrOptions) {
            if (typeof formatOrOptions === 'string') {
                return { format: formatOrOptions };
            }

            return formatOrOptions && typeof formatOrOptions === 'object'
                ? formatOrOptions
                : {};
        }

        buildFilename(format, suffix) {
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            return `${this.config.logPrefix}_${suffix}_${timestamp}.${format}`;
        }

        buildContent(logs, context) {
            const normalizedLogs = logs.map(log => this.toExportRecord(log));

            if (context.format === 'json') {
                return JSON.stringify(
                    {
                        exportInfo: this.buildExportInfo(context, normalizedLogs.length),
                        logs: normalizedLogs
                    },
                    null,
                    2
                );
            }

            if (context.format === 'txt') {
                return this.buildTextContent(normalizedLogs, context);
            }

            return this.buildJsonlContent(normalizedLogs);
        }

        buildExportInfo(context, totalLogs) {
            return {
                exportedAt: new Date().toISOString(),
                startedAt: this.startedAt,
                exportKind: context.exportKind,
                reason: context.reason,
                format: context.format,
                totalLogs,
                totalCapturedLogs: this.totalCapturedLogs,
                historyLogsInMemory: this.historyLogs.length,
                droppedHistoryLogs: this.droppedHistoryLogs,
                pendingLogsAfterExport: context.exportKind === 'pending'
                    ? Math.max(this.pendingLogs.length - totalLogs, 0)
                    : this.pendingLogs.length,
                fileCounter: this.fileCounter,
                source: 'VmpLogExporter'
            };
        }

        buildJsonlContent(logs) {
            if (logs.length === 0) {
                return '';
            }

            return `${logs.map(log => JSON.stringify(log)).join('\n')}\n`;
        }

        buildTextContent(logs, context) {
            const body = logs.map(log => log.message);

            return `${body.join('\n')}\n`;
        }

        toExportRecord(log) {
            return {
                message: log.message,
                args: log.args
            };
        }

        getMimeType(format) {
            if (format === 'json') {
                return 'application/json;charset=utf-8';
            }

            if (format === 'txt') {
                return 'text/plain;charset=utf-8';
            }

            return 'application/x-ndjson;charset=utf-8';
        }

        downloadText(content, filename, mimeType) {
            const blob = new Blob([content], { type: mimeType });
            const url = URL.createObjectURL(blob);
            const anchor = document.createElement('a');

            anchor.href = url;
            anchor.download = filename;
            anchor.style.display = 'none';

            const container = document.body || document.documentElement;

            if (container) {
                container.appendChild(anchor);
            }

            anchor.click();

            if (container && anchor.parentNode === container) {
                container.removeChild(anchor);
            }

            setTimeout(() => URL.revokeObjectURL(url), 0);
        }

        startAutoExport() {
            this.stopAutoExport();

            this.autoExportTimer = window.setInterval(() => {
                if (this.pendingLogs.length > 0) {
                    void this.exportLogs({ reason: 'interval' });
                }
            }, this.config.autoExportInterval);
        }

        stopAutoExport() {
            if (this.autoExportTimer !== null) {
                clearInterval(this.autoExportTimer);
                this.autoExportTimer = null;
            }
        }

        getStatus() {
            return {
                manualMode: true,
                exportFormat: this.config.exportFormat,
                pendingLogs: this.pendingLogs.length,
                historyLogs: this.historyLogs.length,
                recentConsoleEntries: this.recentConsoleEntries.length,
                totalCapturedLogs: this.totalCapturedLogs,
                droppedHistoryLogs: this.droppedHistoryLogs,
                consoleLines: this.consoleLineCount,
                fileCounter: this.fileCounter,
                isExporting: this.isExporting,
                autoExportEnabled: this.autoExportTimer !== null,
                lastExportFile: this.lastExportInfo ? this.lastExportInfo.filename : null,
                lastExportCount: this.lastExportInfo ? this.lastExportInfo.count : 0
            };
        }

        printUsage() {
            this.internalPrint(
                'info',
                [
                    '[VMP] commands:',
                    '  console.save()          -> export pending logs as plain text',
                    '  console.save("json")    -> export pending logs as JSON without id/timestamp',
                    '  console.save("jsonl")   -> export pending logs as JSONL without id/timestamp',
                    '  console.saveAll()       -> export all captured logs as plain text at the end',
                    '  console.saveTxt()       -> export history as plain text, one line per log',
                    '  console.status()        -> print current logger status',
                    '  auto download is disabled in manual mode'
                ].join('\n')
            );
        }

        internalPrint(level, ...args) {
            const method = this.originalMethods[level] || this.originalMethods.log || console.log.bind(console);
            method(...args);
        }

        restore() {
            this.stopAutoExport();

            this.consoleMethods.forEach(method => {
                if (this.originalMethods[method]) {
                    console[method] = this.originalMethods[method];
                }
            });

            Object.keys(this.previousConsoleHelpers).forEach(key => {
                const previous = this.previousConsoleHelpers[key];
                if (typeof previous === 'undefined') {
                    delete console[key];
                } else {
                    console[key] = previous;
                }
            });
        }
    }

    if (window.vmpLogger && typeof window.vmpLogger.restore === 'function') {
        window.vmpLogger.restore();
    }

    const vmpLogger = new VmpLogExporter({
        maxConsoleLines: 4000,
        exportBatchSize: 2000,
        autoExportInterval: 60000,
        enableAutoExport: false,
        exportFormat: 'txt',
        logPrefix: 'VMP_REVERSE',
        keepInConsole: 80,
        maxHistoryEntries: Number.MAX_SAFE_INTEGER,
        maxDepth: 5,
        maxArrayLength: 50,
        maxObjectKeys: 50,
        maxStringLength: 4000,
        previewStringLength: 320,
        includeStack: true
    });

    window.vmpLogger = vmpLogger;
    vmpLogger.printUsage();
})();


然后执行插桩之后的vmp


控制台输入 console.saveAll() 将日志保存到本地



二、分析vmp日志

我这里的加密函数入参是 “111”  ==> zzc9d76f5b92rufgxweci6nb1lgovkgamx9ef1fbbdfa


很清晰的能看到这个加密长串是由4部分拼接成的:
① "zzc" (固定的拼接前缀,可以通过不同的入参来确定这个zzc是固定的)
② 前缀: 9D76F5B
③ 中段:92RuFGXwEcI6NB1lGoVkGAMX9E
④后缀:F1FBBDFA
最后统一 toLowerCase()
综上:("zzc" + 前缀 + 中段 + 后缀).toLowerCase()


(1)分析前缀和后缀的计算(两者类似,所以统一分析)
       ①前缀 9D76F5B


"9D76F5B" 是由一个数组 [23,14,6,36,16,40,7,19]  (固定数组) ,数组逐位+1 ,然后向 长串 "6216F8A75FD5BB3D5F22B6F9958CDEDE3FC086C2" 取对应索引 ,然后拼接字符得到。

②后缀 F1FBBDFA


"F1FBBDFA" 是由一个数组 [16,1,32,12,19,27,8,5] (固定数组) ,数组逐位+1 ,然后向 长串 "6216F8A75FD5BB3D5F22B6F9958CDEDE3FC086C2" 取对应索引 ,然后拼接字符得到。

③ 长串 "6216F8A75FD5BB3D5F22B6F9958CDEDE3FC086C2" 是怎么来的?


[CALL 138]结果 ==>6216f8a75fd5bb3d5f22b6f9958cdede3fc086c2
[CALL_59] 结果 ==>6216f8a75fd5bb3d5f22b6f9958cdede3fc086c26216f8a75fd5bb3d5f22b6f9958cdede3fc086c2
跟上去看这两个函数调用



发现5个寄存器以及finalize这个方法

又发现 这个长串的是40位16进制字符


继续分析日志找到,sha1的经典常量


至此,可以确定 长串是由 入参"111"经过sha1摘要之后的结果,进行验证


④ 简单归总 前缀/后缀 是通过两个固定的数组,对sha1()对入参处理之后的长串,取对应索引拼接的过程。

(2)分析中段 92RuFGXwEcI6NB1lGoVkGAMX9E
   ① base64
去除 / + 这两个base64的特征字符


这个中段长串是由 一个 20字节的数组 每三个字节为一组(3*8=24 bit 24/6 = 6,这个为标准base64编码的简单描述)3字节 ==> 四个字符


长串是经 20字节数组经过base64进行取每三个字节编码然后拼接的一个过程,日志中的base64位运算特征及其明显


②20字节数组是怎么来的?

搜索到20字节数组第一次出现的地方,发现push的过程,是由 194^19=209 然后把209 push进一个数组


194和19又是哪里来的呢?向上找日志

1. 刚19进行异或生成的是最后一位,这里的数组 19也是处于最后一位,而且又发现 217也在进行异或。经日志对比 确定这是一个固定的数组 取值进行异或 然后得到20字节数组


多处日志都能证明这一点

2. 194:

194 是由 “分析前缀和后缀的计算”中的长串“6216F8A75FD5BB3D5F22B6F9958CDEDE3FC086C2”计算得来的
具体过程是
40hex 每两位为一组 由16进制转换为10进制 即 图中的16进制和10进制的对应表
194 是由最后的一组 “c2” c->12, 即 194 = 12*16的一次方 + 2 * 16的零次方


同样的98也是这样来的
98 = 6*16的一次方 + 2 * 16的零次方


至此,中段长串分析结束



三、简单总结


(1)当前分析的加密纯算
[Python] 纯文本查看 复制代码
import base64
import hashlib
import sys


XOR_KEY = [
    149, 114, 150, 179, 58, 37, 170, 255, 101, 22,
    171, 156, 143, 9, 186, 34, 95, 204, 217, 19,
]

PREFIX_INDICES = [24, 15, 7, 37, 17, 41, 8, 20]
SUFFIX_INDICES = [17, 2, 33, 13, 20, 28, 9, 6]


def sha1_upper(value: str) -> str:
    return hashlib.sha1(str(value).encode("utf-8")).hexdigest().upper()


def pick_chars(source: str, indices: list[int]) -> str:
    return "".join(source[index] for index in indices if 0 <= index < len(source))


def xor_digest_bytes(sha_hex: str) -> bytes:
    digest = bytes.fromhex(sha_hex)
    return bytes(left ^ right for left, right in zip(digest, XOR_KEY))


def encode_middle(data: bytes) -> str:
    return (
        base64.b64encode(data)
        .decode("ascii")
        .rstrip("=")
        .replace("/", "")
        .replace("+", "")
    )


def get_security_sign(value: str) -> str:
    sha_hex = sha1_upper(value)
    prefix = pick_chars(sha_hex, PREFIX_INDICES)
    middle = encode_middle(xor_digest_bytes(sha_hex))
    suffix = pick_chars(sha_hex, SUFFIX_INDICES)
    return f"zzc{prefix}{middle}{suffix}".lower()


if __name__ == "__main__":
    # arg = sys.argv[1] if len(sys.argv) > 1 else "111"
    arg = "111"
    print(get_security_sign(arg))


(2)这篇blog在写的过程中,加密算法发生了变化,具体变化为上述固定数组的变化,纯算如下,自行对比

[Python] 纯文本查看 复制代码
import base64
import hashlib
import sys


XOR_KEY = [89,39,179,150,218,82,58,252,177,52,186,123,120,64,242,133,143,161,121,179]

PREFIX_INDICES = [23, 14, 6, 36, 16, 40, 7, 19]
SUFFIX_INDICES = [16, 1, 32, 12, 19, 27, 8, 5]


def sha1_upper(value: str) -> str:
    return hashlib.sha1(str(value).encode("utf-8")).hexdigest().upper()


def pick_chars(source: str, indices: list[int]) -> str:
    return "".join(source[index] for index in indices if 0 <= index < len(source))


def xor_digest_bytes(sha_hex: str) -> bytes:
    digest = bytes.fromhex(sha_hex)
    return bytes(left ^ right for left, right in zip(digest, XOR_KEY))


def encode_middle(data: bytes) -> str:
    return (
        base64.b64encode(data)
        .decode("ascii")
        .rstrip("=")
        .replace("/", "")
        .replace("+", "")
    )


def get_security_sign(value: str) -> str:
    sha_hex = sha1_upper(value)
    prefix = pick_chars(sha_hex, PREFIX_INDICES)
    middle = encode_middle(xor_digest_bytes(sha_hex))
    suffix = pick_chars(sha_hex, SUFFIX_INDICES)
    return f"zzc{prefix}{middle}{suffix}".lower()


if __name__ == "__main__":
    # arg = sys.argv[1] if len(sys.argv) > 1 else "111"
    arg = "111"
    print(get_security_sign(arg))



qqmusic-vmp.txt (501.45 KB, 下载次数: 32)

免费评分

参与人数 8吾爱币 +8 热心值 +7 收起 理由
yixi + 1 + 1 用心讨论,共获提升!
yppsniper + 1 我很赞同!
suaibiyx163 + 1 + 1 热心回复!
fengbolee + 1 + 1 谢谢@Thanks!
ioyr5995 + 1 + 1 谢谢@Thanks!
buluo533 + 1 + 1 用心讨论,共获提升!
helian147 + 1 + 1 热心回复!
megeson + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| gfy1129 发表于 2026-4-27 15:59 |楼主
悦来客栈的老板 发表于 2026-4-27 14:18
这种不是太难的jsvmp,直接用AI编写插桩插件,然后日志丢给AI分析,很快就给纯算代码了。

推荐
悦来客栈的老板 发表于 2026-4-27 14:18
这种不是太难的jsvmp,直接用AI编写插桩插件,然后日志丢给AI分析,很快就给纯算代码了。
4#
861078848 发表于 2026-4-28 10:14
直接交给AI,就能分析了;

有没有这款混淆器的其他网站,我看看能不能完全自动化

assemblyCode.txt

38.55 KB, 下载次数: 9, 下载积分: 吾爱币 -1 CB

5#
fendouing123 发表于 2026-4-28 11:24
膜拜大佬
6#
wchy056 发表于 2026-4-28 12:43
高深莫测,支持你!!!
7#
Xandor 发表于 2026-4-28 15:19
太厉害了佬儿!学习一下!
8#
someone52 发表于 2026-4-28 17:10
哪怕 AI 工具能解决,也要学习解决问题的思路 !
9#
gxzgk 发表于 2026-4-29 08:39
支持纯人大佬。
10#
xinxiu 发表于 2026-4-29 10:27
我倒想知道AI如何处理
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-11 22:28

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表