吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1353|回复: 9
上一主题 下一主题
收起左侧

[CrackMe] .Net CrackMe V4!

  [复制链接]
跳转到指定楼层
楼主
gsyifan 发表于 2026-8-14 10:27 回帖奖励
CM是什么?Crackme是什么?这是什么东西?楼主发的什么?
他们都是一些公开给别人尝试破解的小程序,制作 Crackme 的人可能是程序员,想测试一下自己的软件保护技术,也可能是一位 Cracker,想挑战一下其它 Cracker 的破解实力,也可能是一些正在学习破解的人,自己编一些小程序给自己破解,KeyGenMe是要求别人做出它的 keygen (序号产生器), ReverseMe 要求别人把它的算法做出逆向分析, UnpackMe 是要求别人把它成功脱壳,本版块禁止回复非技术无关水贴。

本帖最后由 gsyifan 于 2026-8-14 10:30 编辑

期望目标

  • 解出密码
  • 还原完整算法源码

程序环境

  • .Net 4.8

保护说明

  • 保护选项:混淆+VM虚拟化+反调试
  • V4版本使用了C++虚拟化,难度增加
  • 目前并非最强保护,欢迎持续挑战!

原程序(密码确定可解)

成功截图

可使用AI分析,希望能贴出分析的AI和环境(如Codex+Deepseek Pro)
期望和大家一起探讨对抗AI分析的手段和方法。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

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

推荐
又是馒头 发表于 2026-8-14 12:24

Reasonix+deepseek_flash
花了我2.94元,楼主给不给报销?一行代码没敲,完全AI,跑了44分钟,用了1亿tokens

crackme_solution.py(算法源码+密码推导+走查)
[Python] 纯文本查看 复制代码
# -*- coding: utf-8 -*-
"""
CrackMe.exe 完整逆向
====================
保护: TWProtector (.NET 4.8) — 混淆 + VM虚拟化 + 反调试
虚拟化方法: 5 个 (token 0x06000001/2/5/6/B), 核心为 Verify(0x06000005)

VMBC 解密链:
  global.bin (VMBC v3) -> HMAC-SHA256 校验 + ChaCha20 解密 (master key 由
  "TWProtector_MK_v3.0.0_salt_xyz_9" + nonce + LCG 派生)
  方法体: ChaCha20(per-method key) + per-method S-box(逆)

Verify 算法 (还原自 VM 字节码, 控制流平坦化 + 常量混淆):
  静态数据 (来自 <PrivateImplementationDetails> RVA 字段):
    pattern = A5 5A A5 5A 0F F0 0F F0 33 CC 33 CC 55 AA 55 AA   (0x04000004)
    perm    = [3,11,7,14,1,9,4,12,0,6,10,2,15,5,13,8]           (0x04000003, int[16])
    seq     = 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00   (0x04000005)
    key     = 41 F7 25 EE 25 81 9C 93 47 21 E3 5B 88 12 59 28   (0x04000002)

  Verify(input[16]):
    1. input[i] ^= pattern[i]
    2. input[i] = ROL(input[i], 3)   # 循环左移 3 位
    3. out[i] = input[perm[i]]       # 按排列表重排
    4. out[i] ^= seq[i]
    5. 全部相等返回 true
"""

KEY   = bytes.fromhex('41f725ee25819c934721e35b88125928')
SEQ   = bytes.fromhex('112233445566778899aabbccddeeff00')
PAT   = bytes.fromhex('a55aa55a0ff00ff033cc33cc55aa55aa')
PERM  = [3,11,7,14,1,9,4,12,0,6,10,2,15,5,13,8]

def rol3(x):
    return ((x << 3) | (x >> 5)) & 0xFF

def ror3(x):
    return ((x >> 3) | (x << 5)) & 0xFF

def verify(byte_input):
    assert len(byte_input) == 16
    b = bytearray(byte_input)
    for i in range(16):
        b[i] ^= PAT[i]
    for i in range(16):
        b[i] = rol3(b[i])
    out = bytearray(16)
    for i in range(16):
        out[i] = b[PERM[i]]
    for i in range(16):
        out[i] ^= SEQ[i]
    return bytes(out) == KEY

def derive_password():
    # 逆推: 目标 out == KEY
    # 逆阶段4: out[i] = KEY[i] ^ SEQ[i]
    stage4 = bytes(k ^ s for k, s in zip(KEY, SEQ))
    # 逆阶段3: out[i] = b[PERM[i]]  =>  b[j] = stage4[PERM^-1[j]]
    inv = [0]*16
    for i, p in enumerate(PERM):
        inv[p] = i
    b = bytes(stage4[inv[j]] for j in range(16))
    # 逆阶段2: b = ROL3(x) => x = ROR3(b)
    xored = bytes(ror3(x) for x in b)
    # 逆阶段1: xored = input ^ PAT
    pwd = bytes(x ^ p for x, p in zip(xored, PAT))
    return pwd

if __name__ == '__main__':
    pwd16 = derive_password()
    print('16字节密码: ', pwd16.hex(' '))
    printable = pwd16.rstrip(b'\x00')
    print('可打印部分: ', repr(printable.decode('ascii')))
    # 程序 pad 到 16: 14 字符 + 2 NUL
    padded = printable + b'\x00' * (16 - len(printable))
    print('pad 验证:   ', padded == pwd16)

    # 验证算法
    print()
    print('verify(padded 16B) =', verify(pwd16))
    print('verify("test" pad) =', verify(b'test' + b'\x00'*12))

    # 走查一次: 输入正确密码
    demo = bytearray(padded)
    for i in range(16):
        demo[i] ^= PAT[i]
    print()
    print('演示阶段1 XOR pattern:', bytes(demo).hex(' '))
    for i in range(16):
        demo[i] = rol3(demo[i])
    print('演示阶段2 ROL3       :', bytes(demo).hex(' '))
    out = bytearray(16)
    for i in range(16):
        out[i] = demo[PERM[i]]
    print('演示阶段3 perm 重排  :', bytes(out).hex(' '))
    for i in range(16):
        out[i] ^= SEQ[i]
    print('演示阶段4 XOR seq    :', bytes(out).hex(' '))
    print('目标 key             :', KEY.hex(' '))

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
gsyifan + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

推荐
ma15803216102 发表于 2026-8-14 20:17
我的笨蛋AI脱壳脱了半个小时   脱完壳10分钟干完了   

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x
3#
sfl4800 发表于 2026-8-14 14:48
也差不多30~40分钟,5千万TOKEN...现在AI是真强大。直接扔给他就完事 了
[backcolor=oklab(0.999994 0.0000455678 0.0000200868 / 0.5)]完整还原算法 C# 源码


// ==============================================================================

// CrackMe.exe — Program.Verify(byte[]) 完整算法还原

//

// 原始方法受 TWProtector VM 保护,IL 字节码经 ChaCha20 + S-box 加密,

// 运行时由 native VM 引擎解密解释执行。以下为从 VM 执行跟踪逆向还原的

// 等效 C# 源码。

//

// 密码:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// ==============================================================================



using System;

using System.Runtime.CompilerServices;



class Program

{

    // ====== 静态字段(来自 PE .sdata 段,RVA 0x2050-0x20BF)======



    /// <summary>RID2 (0x04000002) @ RVA 0x2050 — 比较目标值(16 字节)</summary>

    static byte[] s_key = new byte[16] {

        0x41, 0xF7, 0x25, 0xEE, 0x25, 0x81, 0x9C, 0x93,

        0x47, 0x21, 0xE3, 0x5B, 0x88, 0x12, 0x59, 0x28

    };



    /// <summary>RID3 (0x04000003) @ RVA 0x2060 — 置换表(int32[16])</summary>

    static int[] s_perm = new int[16] {

        3, 11,  7, 14,  1,  9,  4, 12,

        0,  6, 10,  2, 15,  5, 13,  8

    };



    /// <summary>RID4 (0x04000004) @ RVA 0x20A0 — XOR 掩码 1(16 字节)</summary>

    static byte[] s_mask1 = new byte[16] {

        0xA5, 0x5A, 0xA5, 0x5A, 0x0F, 0xF0, 0x0F, 0xF0,

        0x33, 0xCC, 0x33, 0xCC, 0x55, 0xAA, 0x55, 0xAA

    };



    /// <summary>RID5 (0x04000005) @ RVA 0x20B0 — XOR 掩码 2(16 字节)</summary>

    static byte[] s_mask2 = new byte[16] {

        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,

        0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00

    };



    // ====== Verify 方法 ======

    // 原始签名: private static bool Verify(byte[] input)

    // IL token: 0x06000005

    // IL 长度: 1525 字节(经 VM 解密后为标准 CIL)

    // 局部变量: 11 个(loc0-loc10)

    //   loc0  = mask2 (byte[])

    //   loc1  = perm  (int[])

    //   loc2  = mask1 (byte[])

    //   loc3  = output (byte[])

    //   loc4  = key   (byte[])

    //   loc5  = 循环索引 i(NOT/XOR 变换阶段)

    //   loc6  = 循环索引 j(比较阶段)

    //   loc7  = 循环索引 k(XOR mask1 阶段)

    //   loc8  = 循环索引 l(ROL 阶段)

    //   loc9  = 循环索引 m(置换阶段)

    //   loc10 = 状态机状态值



    private static bool Verify(byte[] input)

    {

        // ====== 阶段 1: 初始化静态字段 ======

        // IL_0000-IL_001F: 加载 RID4 (mask1) → loc2

        // IL_0428-IL_0443: 加载 RID3 (perm)  → loc1

        // IL_01F1-IL_020C: 加载 RID5 (mask2) → loc0

        // IL_0498-IL_04B3: 加载 RID2 (key)   → loc4

        // IL_04B5:         调用 u.g() 反调试检查



        byte[] mask1  = s_mask1;   // loc2

        int[]  perm   = s_perm;    // loc1

        byte[] mask2  = s_mask2;   // loc0

        byte[] key    = s_key;     // loc4



        // ====== 阶段 2: XOR mask1(16 轮)======

        // 状态机 case: IL_045B(XOR)、IL_031B(递增 loc7)、IL_03D8(检查 loc7 < 16)

        // IL_045B: input ^= mask1

        // IL_02F7: loc7 = 0  (初始化计数器)

        // 循环 16 次 (i = 0..15)

        for (int i = 0; i < 16; i++)

        {

            input ^= mask1;

        }



        // ====== 阶段 3: 循环左移 3 位(16 轮)======

        // 状态机 case: IL_04FE(ROL3)、IL_01A1(递增 loc8)、IL_055E(检查 loc8 < 16)

        // IL_04FE: input = (input << 3) | (input >> 5)

        // IL_0280: loc8 = 0  (初始化计数器)

        // 循环 16 次 (i = 0..15)

        for (int i = 0; i < 16; i++)

        {

            input = (byte)((input << 3) | (input >> 5));

        }



        // ====== 阶段 4: 置换(16 轮)======

        // 状态机 case: IL_0407(置换)、IL_058D(递增 loc9)、IL_0239(检查 loc9 < 16)

        // IL_02C2: new byte[16] → loc3 (output)

        // IL_0407: output = input[perm]

        // 循环 16 次 (i = 0..15)

        byte[] output = new byte[16];

        for (int i = 0; i < 16; i++)

        {

            output = input[perm];

        }



        // ====== 阶段 5: XOR mask2 变换(16 轮)======

        // 状态机 case: IL_053A(NOT/XOR)、IL_05B3(递减 loc5)、IL_0380(检查 loc5 >= 0)

        // IL_00BD: loc5 = 15 (初始化计数器,从 15 递减到 0)

        // IL_053A: output = ~(output ^ ~mask2) = output ^ mask2

        //   (因为 ~(a ^ ~b) = a ^ b)

        // 循环 16 次 (i = 15..0,递减)

        for (int i = 15; i >= 0; i--)

        {

            output = (byte)(~(output ^ (byte)~mask2));

            // 等效于: output ^= mask2;

        }



        // ====== 阶段 6: 逐字节比较(16 轮)======

        // 状态机 case: IL_0352(比较)、IL_0103(递增 loc6 + 检查 loc6 < 16)

        // IL_014B: loc6 = 0  (初始化计数器)

        // IL_0352: if (output[j] != key[j]) return false

        // 循环 16 次 (j = 0..15)

        for (int j = 0; j < 16; j++)

        {

            if (output[j] != key[j])

                return false;

        }



        return true;

    }



    // ====== 逆向求解 ======

    static byte[] Solve()

    {

        // 逆阶段 6: output = key

        byte[] output = (byte[])s_key.Clone();



        // 逆阶段 5: output ^= mask2 (XOR 自反)

        for (int i = 0; i < 16; i++)

            output ^= s_mask2;



        // 逆阶段 4: 逆置换 input[perm] = output

        byte[] input = new byte[16];

        for (int i = 0; i < 16; i++)

            input[s_perm] = output;



        // 逆阶段 3: ROR3 (逆 ROL3)

        for (int i = 0; i < 16; i++)

            input = (byte)((input >> 3) | (input << 5));



        // 逆阶段 2: XOR mask1 (XOR 自反)

        for (int i = 0; i < 16; i++)

            input ^= s_mask1;



        return input;

    }



    static void Main()

    {

        byte[] password = Solve();

        Console.WriteLine("Password (hex): " + BitConverter.ToString(password));

        Console.WriteLine("Password (ASCII): " + (password[14] == 0 && password[15] == 0

            ? System.Text.Encoding.ASCII.GetString(password, 0, 14)

            : System.Text.Encoding.ASCII.GetString(password)));

        Console.WriteLine("Verify result: " + Verify(password));

    }

}

--------------------------------

密码计算+VM 验证脚本


using System;
using System.Reflection;
using System.Runtime.CompilerServices;


class Solver
{
    // Constants from static fields (RID2-RID5)
    static byte[] key   = { 0x41, 0xF7, 0x25, 0xEE, 0x25, 0x81, 0x9C, 0x93, 0x47, 0x21, 0xE3, 0x5B, 0x88, 0x12, 0x59, 0x28 };
    static int[]  perm  = { 3, 11, 7, 14, 1, 9, 4, 12, 0, 6, 10, 2, 15, 5, 13, 8 };
    static byte[] mask1 = { 0xA5, 0x5A, 0xA5, 0x5A, 0x0F, 0xF0, 0x0F, 0xF0, 0x33, 0xCC, 0x33, 0xCC, 0x55, 0xAA, 0x55, 0xAA };
    static byte[] mask2 = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00 };


    static byte ROL(byte b, int n) { return (byte)((b << n) | (b >> (8 - n))); }
    static byte ROR(byte b, int n) { return (byte)((b >> n) | (b << (8 - n))); }


    static void Main()
    {
        // ===== FORWARD ALGORITHM (verify understanding) =====
        // Test with all-zero input
        byte[] test = new byte[16];
        byte[] fwd = (byte[])test.Clone();


        // Step 1: XOR with mask1
        for (int i = 0; i < 16; i++) fwd ^= mask1;
        // Step 2: ROL 3
        for (int i = 0; i < 16; i++) fwd = ROL(fwd, 3);
        // Step 3: Permute
        byte[] out1 = new byte[16];
        for (int i = 0; i < 16; i++) out1 = fwd[perm];
        // Step 4: XOR with mask2
        for (int i = 0; i < 16; i++) out1 ^= mask2;
        // Step 5: Compare with key
        bool allMatch = true;
        for (int i = 0; i < 16; i++)
        {
            if (out1 != key) { allMatch = false; }
        }
        Console.WriteLine("[Forward] all-zero input -> output: " + BitConverter.ToString(out1));
        Console.WriteLine("[Forward] matches key? " + allMatch);


        // ===== REVERSE ALGORITHM (compute password) =====
        // Step R5: output = key
        byte[] output = (byte[])key.Clone();
        // Step R4: XOR with mask2 (XOR is its own inverse)
        for (int i = 0; i < 16; i++) output ^= mask2;
        Console.WriteLine("[Reverse] after un-XOR mask2: " + BitConverter.ToString(output));


        // Step R3: Inverse permutation: output = input[perm] => input[perm] = output
        byte[] input = new byte[16];
        for (int i = 0; i < 16; i++) input[perm] = output;
        Console.WriteLine("[Reverse] after un-permute: " + BitConverter.ToString(input));


        // Step R2: ROR 3 (reverse of ROL 3)
        for (int i = 0; i < 16; i++) input = ROR(input, 3);
        Console.WriteLine("[Reverse] after un-ROL: " + BitConverter.ToString(input));


        // Step R1: XOR with mask1 (XOR is its own inverse)
        for (int i = 0; i < 16; i++) input ^= mask1;
        Console.WriteLine("[Reverse] after un-XOR mask1: " + BitConverter.ToString(input));


        // Print as ASCII if possible
        Console.Write("[Password] ASCII: ");
        foreach (byte b in input)
        {
            if (b >= 0x20 && b <= 0x7E) Console.Write((char)b);
            else Console.Write("\\x" + b.ToString("X2"));
        }
        Console.WriteLine();


        // ===== VERIFY BY CALLING Verify() =====
        Console.WriteLine("\n===== VM Verification =====");
        try
        {
            Assembly asm = Assembly.LoadFrom(@"D:\CrackMe\CrackMe.exe");
            Type progType = asm.GetType("Program");
            if (progType == null)
            {
                // Try to find the type containing Verify
                foreach (var t in asm.GetTypes())
                {
                    var m = t.GetMethod("Verify", BindingFlags.NonPublic | BindingFlags.Static);
                    if (m != null) { progType = t; break; }
                }
            }


            if (progType != null)
            {
                MethodInfo verify = progType.GetMethod("Verify", BindingFlags.NonPublic | BindingFlags.Static);
                if (verify != null)
                {
                    object result = verify.Invoke(null, new object[] { input });
                    Console.WriteLine("Verify(password) = " + result);
                    Console.WriteLine("Password bytes: " + BitConverter.ToString(input));
                }
                else
                {
                    Console.WriteLine("Verify method not found!");
                }
            }
            else
            {
                Console.WriteLine("Program type not found!");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
            if (ex.InnerException != null)
                Console.WriteLine("Inner: " + ex.InnerException.Message);
        }
    }
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x
4#
又是馒头 发表于 2026-8-14 17:01
sfl4800 发表于 2026-8-14 14:48
也差不多30~40分钟,5千万TOKEN...现在AI是真强大。直接扔给他就完事 了
完整还原算法 C# 源码

你用的什么AI环境
6#
xiaolia 发表于 2026-8-15 10:24
感谢老哥

免费评分

参与人数 1吾爱币 -15 违规 +1 收起 理由
L_Monkey -15 + 1 CM区等技术版块禁止回复与主题无关的非技术内容,违者重罚!

查看全部评分

7#
haoer6238 发表于 2026-8-17 12:08
本帖最后由 haoer6238 于 2026-8-17 16:33 编辑

大神们,我也弄了个Reasonix,也弄了deepseek充了值,把CrackMe.exe拖到软件中,输入要求,这都花了11块钱了,还是失败,人家大神话不到3元就解决问题,是哪里没设置好?求大神给讲解一下,谢谢

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x
8#
lengkong 发表于 2026-8-17 12:56

CrackMe.net V4 专业完整步骤分析

文档类型:逆向分析、动态调试、算法恢复与验证复盘
案件编号:crackme_net_20260817
原始目标:C:\Users\LKROG\Downloads\VMP\Crackme_net\CrackMe.exe
原始 SHA-256:181B7AF7932D472654DA074A26ED8577D4875B8205F0D581DB94F34E68E57C45
目标运行时:.NET Framework 4.8 / Windows x64
工具链:Detect It Easy、ILDasm、IDA 9.4、x64dbg、Frida 17.17.0、PowerShell、Python、.NET SDK
分析原则:不修改原始 EXE,不使用 x64dbg 反反调试插件伪造结果,以静态证据、运行时证据和原始程序验收组成闭环。


1. 结论先行

1.1 最终密码

~TWPro~2608v6~

密码长度为 14 个字符,UTF-8 编码后也是 14 字节。程序通过 Array.Resize(ref input, 16) 把输入扩展为 16 字节,因此进入核心校验的实际数据为:

7E 54 57 50 72 6F 7E 32 36 30 38 76 36 7E 00 00

1.2 原始程序验收

在未修改的原始文件上输入正确密码,实际显示:

恭喜,密码正确!

当前 .NET V4 样本的成功字符串是中文,不是旧样本要求中的字面量 Correct!。报告以实际目标输出为准。

1.3 完成情况

项目 结果
密码求解 PASS
Main 明文 IL 恢复 PASS,796 字节
Verify 明文 IL 恢复 PASS,1,525 字节
完整密码校验算法还原 PASS
.NET Framework 4.8 源码编译 PASS,0 warning / 0 error
x64dbg + Frida 无插件反调试 PASS
原始程序正例验证 PASS
两个原始程序反例验证 PASS
原始文件运行前后完整性 PASS

2. 分析范围与真实边界

本次“完整算法源码”指与密码输入、长度约束、字节变换、反调试分支、数组置换、结果比较和密码逆求解有关的完整业务算法。

下列内容不冒充已经恢复:

  1. TWProtector 的完整商业源码;
  2. 整个 C++ VM 工程的逐字源代码;
  3. 作者原始变量名、注释、项目结构和编译参数;
  4. 未被密码路径调用的全部保护器方法。

交付的 Program.cs 是从受保护方法明文 IL 恢复的语义等价实现。它能够编译、求解并正向验证密码,但不声称是作者逐字源码。


3. 证据分层

为了避免把推测写成事实,本案把证据分为六级:

层级 定义 本案内容
L0 用户提供的说明或截图 DiE、IDA、x64dbg 截图
L1 文件直接事实 哈希、PE 头、CLR 头、节、资源、导入
L2 静态逆向事实 IDA 函数、RVA、结构偏移、交叉引用
L3 动态运行事实 API 调用、VM 映射地址、运行时日志、明文 IL
L4 独立算法复现 Python/C# 正向与逆向变换一致
L5 原始程序验收 正确密码显示成功,错误输入显示失败

只有 L4 和 L5 同时成立时,密码才被标记为最终结果。


4. 整体工作流

flowchart TD
    A["原始 CrackMe.exe 基线"] --> B["备份与只读工作副本"]
    B --> C["PE / CLR / 资源静态分析"]
    C --> D["拆出 VMNX / VMBC / JITH"]
    D --> E["提取 x86 / x64 Native VM"]
    E --> F["IDA 恢复 VM_Initialize / VM_Invoke / vm_interpret"]
    F --> G["x64dbg 硬件断点验证运行环境"]
    G --> H["Frida 无插件反调试与手工映射监控"]
    H --> I["抓取 Main / Verify 明文 IL"]
    I --> J["ECMA-335 IL 解析"]
    J --> K["状态机与不透明谓词去除"]
    K --> L["恢复校验常量和完整算法"]
    L --> M["逆向求解密码"]
    M --> N["正向算法自验证"]
    N --> O["原始 EXE 正例与反例验收"]
    O --> P["报告、源码、脚本与回滚记录"]

5. 第一步:建立文件基线

5.1 基线命令

$target = 'C:\Users\LKROG\Downloads\VMP\Crackme_net\CrackMe.exe'
Get-Item -LiteralPath $target | Select-Object FullName, Length, LastWriteTimeUtc
Get-FileHash -LiteralPath $target -Algorithm SHA256

基线结果:

属性
文件大小 841,728 bytes
SHA-256 181B7AF7932D472654DA074A26ED8577D4875B8205F0D581DB94F34E68E57C45
最后写入 UTC 2026-08-14T02:04:36.0727636Z

5.2 备份策略

备份位置:

C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_net_20260817\backup\source_snapshot_20260817_115658\CrackMe.exe

备份文件与原文件 SHA-256 一致。活动 IDA 数据库的 .id0.id1.nam 当时被 IDA 锁定,没有强制终止 IDA 或复制半写入数据库;.til 成功备份。

完整基线账本:

evidence\runtime\baseline.json

6. 第二步:Detect It Easy 与 PE/CLR 初筛

Detect It Easy 给出的方向性判断:

PE32
MSIL / C#
.NET Framework 4.8 / CLR 4.0.30319
Obfuscation
Virtualization
Anti-debug
Strange sections

截图只能说明工具识别结果,不能直接证明算法。随后用脚本和 CLR 工具重新确认。

6.1 CorFlags

ILONLY    = 1
32BITREQ  = 0
32BITPREF = 0

这代表 PE 外壳是 PE32,但程序集为 AnyCPU。目标在 x64 Windows 上进入 x64 CLR,因此保护器最终选用内嵌 x64 VM。用户的 x64dbg 进程也确认实际调试对象是 x64。

6.2 PE 关键字段

字段
ImageBase 0x00400000
EntryPoint RVA 0x000CEC7E
SizeOfImage 0x000D4000
CLR Header RVA 0x00002008
Metadata RVA 0x000C1794
Metadata size 0x0000D494
CLR Entry token 0x06000004

6.3 节表

节名 RVA Raw size 特征
.YYPM 0x2000 0xCCE00 高熵主节,熵约 6.71373
.SWSL 0xD0000 保护附加数据 异常命名
.URQOK 0xD2000 保护附加数据 异常命名

6.4 导入

PE 导入几乎被压缩为:

mscoree.dll!_CorExeMain

这符合托管入口、保护逻辑位于 CLR 元数据与嵌入资源中的特征。


7. 第三步:CLR 元数据与磁盘方法体分析

7.1 元数据流

有效签名:

42 53 4A 42 = BSJB

元数据流:

#-
#Strings
#US
#GUID
#Blob

7.2 Main 调用链

Program.Main token 为 0x06000004,磁盘 IL 为:

call 0x0600000C     // 保护运行时初始化
ldarg.0
call 0x0600000B     // 受保护 Main 业务逻辑
ret

7.3 Verify 磁盘诱饵体

Program.Verify(byte[]) token 为 0x06000005,返回类型是 bool,但磁盘方法体只有:

ldc.i4 0x243a5ccf
pop
ret

ret 前没有 bool 返回值,因此这不是实际业务体。保护器在运行时通过 JIT Hook 或 Native VM 恢复真实实现。

7.4 ILDasm 注意事项

ILDasm 报告:

class 02000008 nested in missing class 02000001

这是混淆元数据造成的异常嵌套提示,不影响完整 IL 输出。完整磁盘 IL 已保存为案件分析文件,不能把这类 warning 误判成提取失败。


8. 第四步:资源拆分

8.1 原始资源

文件 魔数 大小 SHA-256
base.resources VMNX 413,708 4BEAF1AFDC489709E64E1B009949826721A19DD339A8480589600A8B76E5A7B3
global.bin VMBC 13,372 18D71BB3ECDA66DBBBC8AB7248578297E3C810D61834434AF8EAF5992187A034
gqzumbstkrxi JITH 273 68A85B86265EE0B87E12A7E70E25D7DF46AF71CB85C652AA59E8FA4178FEB66B

8.2 VMNX 结构

VMNX 结构可以简化为:

"VMNX"
uint32 x86Length
byte[x86Length] x86 PE
uint32 x64Length
byte[x64Length] x64 PE

容器被两个长度字段精确消费,没有未知尾部。

提取结果:

文件 架构 大小 SHA-256
native_vm_x86.dll x86 186,368 3284E82BBE4856590095FAA324CD163765B534A44D0A158D412E8D101C033487
native_vm_x64.dll x64 227,328 3857AEE2DF5D0BE7DA80F4F8DE9C115550BDA4B31AE536A59AB241D7F1253E9A

复现命令:

python .\scripts\extract_vm_resources.py `
  --vmnx .\evidence\runtime\base.resources `
  --vmbc .\evidence\runtime\global.bin `
  --jith .\evidence\runtime\gqzumbstkrxi `
  --out .\analysis\extracted

交付目录不包含重复的大型原始资源,但保留了拆分清单:

evidence\runtime\resource_manifest.json

9. 第五步:IDA 分析原生 x64 VM

9.1 为什么单独加载 Native VM

原始程序集只显示托管加载器和混淆后的运行时。如果只在原始 IDA 数据库中追踪,会混合 CLR 元数据、手工加载器和 VM 指令。把 native_vm_x64.dll 单独载入第二个 IDA 实例,可以直接恢复:

  1. PE 导出;
  2. C++ VM 诊断字符串;
  3. 解释器主循环;
  4. 方法数据结构;
  5. S-box 解密位置;
  6. VM 到 CLR 的 bridge 调用。

9.2 关键导出和函数

IDA 默认 ImageBase:0x180000000

名称 VA RVA
VM_Initialize 0x18000F470 0xF470
VM_Invoke 0x18000F850 0xF850
VM_SetLogEnabled 0x18000FD40 0xFD40
VM_Shutdown 0x18000FD50 0xFD50
vm_data_load 0x1800032A0 0x32A0
vm_do_call 0x180007040 0x7040
vm_do_newobj 0x180007A10 0x7A10
vm_interpret 0x180007DD0 0x7DD0

9.3 原生导入暴露的保护面

IsDebuggerPresent
CheckRemoteDebuggerPresent
QueryPerformanceCounter
QueryPerformanceFrequency
VirtualAlloc
VirtualProtect
VirtualFree

这些导入表明保护不仅存在托管层的 Program.IsDebuggerPresent,Native VM 自身也进行调试器和时间环境检测。

9.4 VMBC 诊断字符串

IDA 中发现:

vm_data_load: rawLen=%d
master key decrypted
HMAC passed
encryptedLen=%d
methodCount=%d
per-method S-box derived
fullByteSbox applied
vm_interpret

同时发现 SHA-256 K 常量的完整引用,说明 HMAC/master-key 管线不是伪字符串。

9.5 ParsedMethod 结构

IDA 伪代码和 Frida 动态值共同确认:

struct ParsedMethod {
    uint32_t token;           // +0x00
    uint32_t maxStack;        // +0x04
    uint32_t localCount;      // +0x08
    ...
    uint32_t exHandlerLen;    // +0x18
    ...
    uint8_t *il;              // +0x20
    uint32_t ilLen;           // +0x28
};

对应伪代码位于:

evidence\ida_pseudocode\vm_interpret_0x180007DD0.c

10. 第六步:不破坏完整性校验的运行测试

10.1 启用保护器自带诊断

运行副本设置:

$env:TW_VM_DEBUG = '1'
.\CrackMe.exe

VM 自带日志确认:

VMBC magic = 0x43424D56
version = 3
flags = 127
master key decrypted
HMAC passed
encryptedLen = 13288
methodCount = 5
TokenInfo encrypted len = 9895
TokenInfo count = 23
method 0x0600000B offset = 12286
method 0x0600000B ilLen = 796

关键判断:不要直接修改 VMBC 或跳过 HMAC。正确方法是在完整性校验已经通过、方法已经解密后抓取内存明文。

10.2 为什么重定向输入后有异常

程序最终执行 Console.ReadKey()。当 stdin 被管道重定向时,.NET 会在结果输出之后抛出异常。因此自动化日志中出现异常不等于密码判断失败。

验收顺序必须是:

  1. 检查是否已经输出成功或失败标记;
  2. 再判断后续异常是否仅来自 ReadKey 重定向限制;
  3. 不使用进程退出码替代决定性输出。

11. 第七步:x64dbg 动态基线

11.1 初始状态

用户 x64dbg 截图和 MCP 基线显示:

PID = 5620
RIP = ntdll system breakpoint
main base = 0x276805C0000
main entry = 0x2768068EC7E
thread = 38896

11.2 使用硬件执行断点

设置:

kernel32!IsDebuggerPresent
kernel32!CheckRemoteDebuggerPresent
ntdll!NtQueryInformationProcess

选择硬件执行断点的原因:

  1. 不改 API 原始代码;
  2. 不依赖 ScyllaHide、TitanHide、VoidDbg 等插件;
  3. 可以记录真实 API 入参和调用时机;
  4. 便于与 Frida 消息做时间和地址对齐。

11.3 纯 x64dbg 路径的局限

只运行 x64dbg 时,目标在早期保护阶段退出。x64dbg MCP 当时禁用了寄存器/内存写和脚本执行权限,因此不能通过 MCP 强制改写返回寄存器。

这不是失败,而是边界:

  • x64dbg 用于断点、寄存器、内存映射和反汇编证据;
  • Frida 用于无插件 API 返回值标准化;
  • IDA 用于静态函数和 RVA 解释。

12. 第八步:Frida 无插件反调试

12.1 Windows 本机模式

本案使用:

device = frida.get_local_device()

没有启动 Android frida-server。Android-oriented MCP 返回的 running=false 只说明 Android server 未启动,不能据此判断 Windows local-device 不可用。

12.2 Hook 行为

API 原始语义 标准化结果
IsDebuggerPresent() 调试中返回非零 返回 0
CheckRemoteDebuggerPresent 写出调试标志 写 0,函数返回 TRUE
NtQueryInformationProcess, class 7 ProcessDebugPort 写 0
NtQueryInformationProcess, class 0x1E DebugObjectHandle 写 0
NtQueryInformationProcess, class 0x1F DebugFlags 写 1

核心逻辑:

Interceptor.attach(Module.getGlobalExportByName('IsDebuggerPresent'), {
  onLeave(retval) {
    const original = retval.toInt32();
    retval.replace(0);
    send({ type: 'anti_debug', api: 'IsDebuggerPresent', original, forced: 0 });
  }
});

完整代理:

scripts\frida_vm_agent.js

12.3 与 x64dbg 联合验证

x64dbg 占用调试端口后,把 Frida agent 附加到同一 PID。实际消息:

IsDebuggerPresent: original=1, forced=0
IsDebuggerPresent: original=1, forced=0

这里的 original=1 是关键证据:它证明目标确实处于 x64dbg 调试环境,而 Frida 在没有 x64dbg 隐藏插件的条件下把返回值改成 0。


13. 第九步:定位手工映射的 Native VM

13.1 第一次失败原因

最初使用 Process.attachModuleObserver() 等待包含 VM_Invoke 导出的模块,但没有收到 VM 模块事件。

原因:保护器没有用常规 LoadLibrary 注册 Native VM,而是手工映射 PE。手工映射的映像不会可靠出现在 Process.enumerateModules() 中。

13.2 唯一函数签名

从提取的 x64 VM 得到 vm_interpret RVA 0x7DD0 的入口签名:

48 89 4C 24 08 55 53 56 57 41 54 41 55 41 56 41 57 B8 08 20 00 00

13.3 把扫描前移到 VirtualProtect

如果使用定时扫描,第一次 VM 调用可能已经进入解释器。最终策略是在 VirtualProtect 返回时同步扫描刚设为可执行的范围:

VirtualProtect onEnter:
    保存 base / size / newProtect

VirtualProtect onLeave:
    如果成功且目标区域可执行:
        在该范围扫描 vm_interpret 签名
        vmBase = match - 0x7DD0
        验证 vmBase[0:2] == "MZ"
        立即安装 VM Hook

正确密码运行时的一个实例:

manual VM base = 0x1D23A3D0000
vm_interpret   = base + 0x7DD0
VM_Invoke      = base + 0xF850
VM_Initialize  = base + 0xF470

ASLR 每次运行会改变基址,但 RVA 固定。

13.4 x64dbg 交叉验证

x64dbg + Frida 联合会话中另一个映射实例:

base           = 0x1CF313D0000
vm_interpret   = 0x1CF313D7DD0
VM_Invoke      = 0x1CF313DF850

x64dbg 从基址读到:

4D 5A 90 00 ...

说明该地址确实是手工映射 PE。RVA 与 IDA 静态数据库一致。


14. 第十步:选择正确的明文 IL 抓取点

14.1 为什么入口处还是密文

vm_interpret 入口接收 ParsedMethod,但方法 IL 仍经过 per-method full-byte S-box。入口抓取只会得到密文。

14.2 S-box 解密循环

IDA 反汇编:

0x1800081D0  movzx ecx, byte ptr [rdx]
0x1800081D3  mov   rax, [rdi+28h]
0x1800081D7  movzx ecx, byte ptr [rcx+rax]
0x1800081DB  mov   [rdx], cl
0x1800081DD  lea   rdx, [rdx+1]
0x1800081E1  sub   r8, 1
0x1800081E5  jnz   0x1800081D0

循环结束后:

0x1800081FB  mov r8d, r14d

因此模块 RVA 0x81FB 是完整 S-box 解密完成、解释器主循环开始前的稳定抓取点。

14.3 为什么不能只用 onLeave

Main 最后调用 Console.ReadKey。重定向环境中的异常会使解释器不能正常返回,onLeave 不会执行。

最终采用双点抓取:

  1. vm_interpret 入口:保存 ParsedMethod 指针并抓取密文;
  2. base + 0x81FB:抓取明文;
  3. 正常返回时再抓一份,校验明文没有变化。

15. 第十一步:恢复两个核心方法

15.1 Main 业务方法

token  = 0x0600000B
ilLen  = 796
maxStack = 7
localCount = 3
SHA-256 = 13B4A26DCB96615E2C9E8C41ED1D2F5109AE0090B24B7D8E6ADC8235B97340E9

15.2 Verify 方法

token  = 0x06000005
ilLen  = 1525
maxStack = 10
localCount = 11
SHA-256 = 5166122AE8F3182A33E2E4CB76EB0733D24A6B8E1965FBB5B41BA14079B03BF9

15.3 正确密码运行中的调用顺序

Frida 修正 VM_Invoke 原型后记录:

VM_Invoke token=0x0600000B argc=1
VM_Invoke token=0x06000005 argc=1

这与 Main 调用 Verify 的静态 token 关系一致。

明文文件:

evidence\recovered_il\method_0600000B.decrypted.ilbin
evidence\recovered_il\method_06000005.decrypted.ilbin

16. 第十二步:解析原始 ECMA-335 IL

裸 IL 缓冲没有方法头,不能直接交给普通反编译器。为此编写 RawIlDisasm

  1. 通过 System.Reflection.Emit.OpCodes 建立单字节和 0xFE 双字节 opcode 表;
  2. 解析 InlineIInlineI8、branch、switch、token、local、argument 等操作数;
  3. 通过原程序集 ManifestModule.ResolveMethod/Field/Type/String 解析 metadata token;
  4. 同时输出人类可读文本和结构化 JSON。

使用:

dotnet build .\scripts\RawIlDisasm.csproj -c Release

dotnet .\scripts\bin\Release\net8.0\RawIlDisasm.dll `
  <original-assembly> `
  .\evidence\recovered_il\method_06000005.decrypted.ilbin `
  .\analysis\method_06000005

交付结果:

evidence\recovered_il\method_0600000B.il.txt
evidence\recovered_il\method_0600000B.il.json
evidence\recovered_il\method_06000005.il.txt
evidence\recovered_il\method_06000005.il.json

17. 第十三步:Main 控制流去混淆

17.1 分发器结构

Main 使用:

state % 15
switch (... 15 targets ...)

每个业务块结束时把下一个伪随机大整数留在栈上,再跳回:

dup
stloc.2
ldc.i4.s 15
rem.un
switch

17.2 初始状态

115683235 % 15 = 10

沿状态转换和实际条件折叠后得到:

string password = Console.ReadLine();
if (password == null)
    Fail();

if (password.Length != 14)
    Fail();

byte[] bytes = Encoding.UTF8.GetBytes(password);
Array.Resize(ref bytes, 16);

if (Verify(bytes))
    Console.WriteLine("恭喜,密码正确!");
else
    Console.WriteLine("FAILED");

Console.ReadKey();

17.3 长度常量恢复

混淆 IL 使用:

615708912 XOR 615708926

计算结果:

14

因此候选密码必须恰好 14 个 .NET 字符。


18. 第十四步:Verify 控制流去混淆

18.1 分发器

Verify 使用:

state % 31
switch (... 31 targets ...)

初始值:

999431186 % 31 = 21

18.2 局部变量语义

根据 opcode、数组类型和读写方式恢复:

Local 语义
V_0 第二轮 XOR key,byte[16]
V_1 permutation,int[16]
V_2 第一轮 XOR key,byte[16]
V_3 permuted/transformed,byte[16]
V_4 expected,byte[16]
V_5 第二 XOR 循环索引
V_6 最终比较循环索引
V_7 第一 XOR 循环索引
V_8 ROL 循环索引
V_9 permutation 循环索引
V_10 dispatcher state

18.3 三个返回块

结果 IL block
false IL_00E0
false IL_029E
true IL_05E9

18.4 完整业务流程

去除垃圾常量、恒真条件、恒假条件和状态变量后:

bool Verify(byte[] input)
{
    if (IsDebuggerPresent() != 0)
        return false;

    for (int i = 0; i < 16; i++)
        input[i] ^= XorKey1[i];

    for (int i = 0; i < 16; i++)
        input[i] = Rol8(input[i], 3);

    byte[] transformed = new byte[16];
    for (int i = 0; i < 16; i++)
        transformed[i] = input[Permutation[i]];

    for (int i = 0; i < 16; i++)
        transformed[i] ^= XorKey2[i];

    for (int i = 0; i < 16; i++)
        if (transformed[i] != Expected[i])
            return false;

    return true;
}

结构化控制流记录:

evidence\runtime\control_flow_reconstruction.json

19. 第十五步:恢复四个 RVA 数组

受保护 IL 使用:

ldtoken <field>
RuntimeHelpers.InitializeArray

通过 ManifestModule.ResolveField(token)RuntimeHelpers.InitializeArray 直接读取字段数据。

19.1 Expected — 0x04000002

41 F7 25 EE 25 81 9C 93 47 21 E3 5B 88 12 59 28

十进制:

65, 247, 37, 238, 37, 129, 156, 147,
71, 33, 227, 91, 136, 18, 89, 40

19.2 Permutation — 0x04000003

3, 11, 7, 14, 1, 9, 4, 12,
0, 6, 10, 2, 15, 5, 13, 8

19.3 XorKey1 — 0x04000004

A5 5A A5 5A 0F F0 0F F0 33 CC 33 CC 55 AA 55 AA

19.4 XorKey2 — 0x04000005

11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00

复现:

.\scripts\extract_verify_constants.ps1 `
  -AssemblyPath <CrackMe.exe> `
  -OutputJson .\analysis\verify_constants.json

已保存结果:

evidence\runtime\verify_constants.json

20. 第十六步:逆向求解密码

正向算法:

PaddedInput
  XOR XorKey1
  ROL8 3
  Permutation
  XOR XorKey2
  == Expected

逆向顺序:

Expected
  XOR XorKey2
  InversePermutation
  ROR8 3
  XOR XorKey1
  = PaddedInput

20.1 第一步:撤销第二轮 XOR

50 D5 16 AA 70 E7 EB 1B DE 8B 58 97 55 FC A6 28

20.2 第二步:逆置换

DE 70 97 50 EB FC 8B 16 28 E7 58 D5 1B A6 AA 55

20.3 第三步:ROR8(3)

DB 0E F2 0A 7D 9F 71 C2 05 FC 0B BA 63 D4 55 AA

20.4 第四步:撤销第一轮 XOR

7E 54 57 50 72 6F 7E 32 36 30 38 76 36 7E 00 00

ASCII/UTF-8:

~TWPro~2608v6~\0\0

去掉 Main 扩容产生的两个尾零:

~TWPro~2608v6~

20.5 正向回代

求解脚本把结果重新执行完整正向算法,最终得到:

41 F7 25 EE 25 81 9C 93 47 21 E3 5B 88 12 59 28

与 Expected 逐字节完全一致,verification=true

执行:

python .\scripts\solve_password.py

21. 第十七步:还原并编译 .NET 4.8 源码

源码:

source\Program.cs
source\CrackMe.Restored.csproj

目标框架:

<TargetFramework>net48</TargetFramework>

编译:

dotnet build .\source\CrackMe.Restored.csproj -c Release

结果:

0 warnings
0 errors

求解模式:

.\source\CrackMe.Restored.exe --solve

输出:

~TWPro~2608v6~

自检:

.\source\CrackMe.Restored.exe --self-test

输出:

SELFTEST PASS

22. 第十八步:原始程序正例与反例验收

22.1 正例

input  = ~TWPro~2608v6~
output = 恭喜,密码正确!
result = PASS

22.2 错误长度

input  = test
output = FAILED
result = PASS

22.3 错误内容、正确长度

input  = AAAAAAAAAAAAAA
output = FAILED
result = PASS

22.4 完整性

三次运行后的 SHA-256:

181B7AF7932D472654DA074A26ED8577D4875B8205F0D581DB94F34E68E57C45

与基线一致。

完整输出:

evidence\runtime\runtime_regression.json

23. 完整性校验的处理方式

本案没有通过以下方式“解决”完整性校验:

  • 没有 NOP HMAC 分支;
  • 没有改写 VMBC;
  • 没有替换 Expected;
  • 没有强制 Verify 返回 true;
  • 没有修改原始 PE checksum 或节内容。

实际策略:

  1. 允许 VMBC magic、version、master key 和 HMAC 正常验证;
  2. 等待方法密文被合法解析;
  3. 等待 per-method inverse S-box 正常完成;
  4. 在解释器执行前读取明文;
  5. 从真实算法逆向求解输入。

因此完整性链本身没有被破坏,结果也不依赖补丁。


24. 关键失败、原因与修正

24.1 ModuleObserver 看不到 VM

现象: API Hook 生效,但没有 vm_module 事件。
原因: Native VM 是手工映射 PE,不在常规模块列表。
修正:VirtualProtect 返回时扫描 vm_interpret 唯一签名。

24.2 Hook 安装后没有明文 IL

现象: 找到 VM,但第一次只拿到密文。
原因: 定时扫描太晚,Main 已经进入解释器并阻塞在 Console.ReadLine
修正: 扫描前移到保护属性切换瞬间,在手工映射器调用 VM 前安装 Hook。

24.3 onLeave 丢失 Main

现象: Verify 能在返回时抓取,Main 没有 onLeave
原因: 重定向 stdin 后 Console.ReadKey 抛异常,解释器异常离开。
修正: 在 RVA 0x81FB 的解密完成点立即抓取。

24.4 VM_Invoke token 一度记录错误

现象: 日志 token 像随机指针低位。
原因: 初版 Frida 把第二参数当成 token;IDA 原型显示第二参数是 VM 指针,第三参数才是 token。
修正: args[2].toUInt32(),最终记录 0x0600000B0x06000005

24.5 x64dbg 中目标早退

现象: 纯 x64dbg 继续运行后目标停止。
原因: 反调试检查得到真实调试状态;MCP 写权限关闭,不能修改寄存器。
修正: x64dbg 保持观察角色,Frida 完成返回值标准化。联合证据显示 original=1 -> forced=0


25. 可复现命令顺序

以下命令假设当前目录为:

C:\Users\LKROG\Downloads\VMP\Crackme_net\Recovered

25.1 直接查看密码与报告

Get-Content .\PASSWORD.txt
Get-Content .\CrackMe_net_V4_专业完整步骤分析.md

25.2 独立求解

python .\scripts\solve_password.py

25.3 还原程序自检

.\source\CrackMe.Restored.exe --solve
.\source\CrackMe.Restored.exe --self-test

25.4 重新编译源码

dotnet build .\source\CrackMe.Restored.csproj -c Release

25.5 Frida 隔离运行

$python = 'C:\Users\LKROG\frida-mcp\.venv\Scripts\python.exe'

& $python .\scripts\frida_local_runner.py `
  --exe <CrackMe.exe工作副本> `
  --agent .\scripts\frida_vm_agent.js `
  --out .\analysis\frida_recheck `
  --input '~TWPro~2608v6~' `
  --timeout 12

必须使用工作副本或隔离副本,避免把运行时日志写入原始样本目录。


26. 交付文件说明

26.1 结论与报告

PASSWORD.txt
CrackMe_net_V4_full_reverse_report.md
CrackMe_net_V4_专业完整步骤分析.md

26.2 源码

source\Program.cs
source\CrackMe.Restored.csproj
source\CrackMe.Restored.exe
source\ALGORITHM.md

26.3 核心脚本

scripts\frida_vm_agent.js
scripts\frida_local_runner.py
scripts\frida_attach_collector.py
scripts\RawIlDisasm.Program.cs
scripts\RawIlDisasm.csproj
scripts\extract_verify_constants.ps1
scripts\solve_password.py
scripts\extract_vm_resources.py
scripts\triage_pe.py
scripts\triage_native.py
scripts\mcp_http_client.py

26.4 关键证据

evidence\recovered_il\
evidence\ida_pseudocode\
evidence\runtime\evidence_summary.json
evidence\runtime\runtime_regression.json
evidence\runtime\password_solution.json
evidence\runtime\verify_constants.json
evidence\runtime\final_test_results.json
evidence\runtime\rollback_record.json

27. 回滚与恢复

原始 EXE 在分析和验收阶段没有被修改,因此没有二进制补丁需要回滚。

分析备份:

C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_net_20260817\backup\source_snapshot_20260817_115658\CrackMe.exe

SHA-256:

181B7AF7932D472654DA074A26ED8577D4875B8205F0D581DB94F34E68E57C45

报告扩展前的清单和旧报告另有备份:

C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_net_20260817\backup\report_extension_20260817_124904

28. 当前状态补充

本报告扩展时,C:\Users\LKROG\Downloads\VMP\Crackme_net 根目录中只观察到 RecoveredCrackMe.exe.i64,原始 CrackMe.exe 当时不在该根目录。原始文件成功与哈希结论来自此前已经保存的运行验收、基线、回归 JSON 和备份文件;本文没有把当前缺失状态冒充成实时复验。

这是一个外部文件状态变化,不影响已经保存的算法、密码和历史验收结果。如果需要重新现场运行,应先从合法原件或上述备份恢复 CrackMe.exe,并再次核对 SHA-256。


29. 最终审计

检查项 结果
密码是否由算法逆求得 PASS
是否完成正向回代 PASS
是否恢复 Main 输入流程 PASS
是否恢复 Verify 完整业务流程 PASS
是否恢复全部校验数组 PASS
是否提供 net48 可编译源码 PASS
是否在真实 x64dbg 条件下验证反调试返回值修正 PASS
是否避免 x64dbg 隐藏插件 PASS
是否保存 IDA、x64dbg、Frida 分层证据 PASS
是否通过原始程序正例与反例 PASS(历史验收已固化)
是否声明源码真实性边界 PASS
是否记录回滚位置 PASS

最终结论:密码 ~TWPro~2608v6~ 已通过完整静态、动态、算法回代和原始程序验收链确认;密码校验源码已经语义等价恢复,保护完整性链未被破坏。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 1热心值 +1 收起 理由
seekasky + 1 我很赞同!

查看全部评分

9#
叶枫 发表于 2026-8-18 07:14
环境opencode  密码~TWPro~2608v6~
[C#] 纯文本查看 复制代码
// ============================================================================
// CrackMeV4 —— TWProtector VM 保护还原源码(Reconstructed Source)
// ============================================================================
// 说明:本文件是从被 TWProtector 虚拟机化保护的 CrackMe.exe 中还原出来的
//       原始逻辑。可独立编译运行,行为与真实程序一致:
//         - 输入 14 个字符的正确密码 "~TWPro~2608v6~"  -> 打印 "恭喜密码正确"
//         - 其余情况                                  -> 打印 "FAILED"
//
// 还原依据:
//   - 通过 TW_VM_DEBUG=1 让 VM 把解密后的指令写入 vm_diag.log
//   - 挂起进程拿到内存镜像 dump_verify.bin,从 0x2D6439 提取 Verify 解密体
//     verify_plain.bin(1525 字节),反汇编为 verify_il.txt
//   - 反射导出 4 张 <PrivateImplementationDetails> 数据表
//   - 用 in-process 反射调用 Program::Verify 作为判定“神谕”,
//     反复比对正/反向模型,直到与真实 VM 完全一致(正确密码返回 True,
//     已知错误输入返回 False)
// ============================================================================

using System;
using System.Text;

namespace CrackMeReconstructed
{
    // ------------------------------------------------------------------------
    // 被 VM 化的目标方法:Program.???...  (真实方法签名在 IL 中被抹除)
    // 真实 token:0x06000005 (Verify), 0x0600000B (Main)
    // ------------------------------------------------------------------------
    public static class Program
    {
        // ====================================================================
        // Verify(token 0x06000005)
        // 原始代码形如(真实字节码见 verify_il.txt / verify_plain.bin):
        //    arg0[i] ^= key1[i];
        //    arg0[i] = ROL(arg0[i], 3);
        //    buf[i]  = arg0[perm[i]];
        //    buf[i] ^= key2[i];
        //    逐个比较 buf[i] 与 expected[i],全等返回 true,否则 false
        // ====================================================================
        public static bool Verify(byte[] input) // input 必须为 16 字节
        {
            // key1 <-> <PrivateImplementationDetails> 0x04000004
            byte[] key1 = { 0xA5, 0x5A, 0xA5, 0x5A, 0x0F, 0xF0, 0x0F, 0xF0,
                            0x33, 0xCC, 0x33, 0xCC, 0x55, 0xAA, 0x55, 0xAA };
            // perm <-> <PrivateImplementationDetails> 0x04000003(int[16],双射)
            int[] perm = { 3, 11, 7, 14, 1, 9, 4, 12, 0, 6, 10, 2, 15, 5, 13, 8 };
            // key2 <-> <PrivateImplementationDetails> 0x04000005
            byte[] key2 = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
                            0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00 };
            // expected <-> <PrivateImplementationDetails> 0x04000002
            byte[] expected = { 0x41, 0xF7, 0x25, 0xEE, 0x25, 0x81, 0x9C, 0x93,
                                0x47, 0x21, 0xE3, 0x5B, 0x88, 0x12, 0x59, 0x28 };

            byte[] tmp = new byte[16];
            byte[] buf = new byte[16];

            for (int i = 0; i < 16; i++)
                tmp[i] = (byte)(input[i] ^ key1[i]);                // 1. 异或

            for (int i = 0; i < 16; i++)
                tmp[i] = (byte)((tmp[i] << 3) | (tmp[i] >> 5));     // 2. 循环左移 3 位

            for (int i = 0; i < 16; i++)
                buf[i] = tmp[perm[i]];                              // 3. 按表置换

            for (int i = 0; i < 16; i++)
                buf[i] = (byte)(buf[i] ^ key2[i]);                  // 4. 异或

            for (int i = 0; i < 16; i++)                            // 5. 全部比对
                if (buf[i] != expected[i])
                    return false;
            return true;
        }

        // ====================================================================
        // 主流程(token 0x0600000B)
        // 二进制中的字符串均通过垃圾 XOR 常量 + y() 构造,此处已还原为明文。
        // ====================================================================
        public static void Main(string[] args)
        {
            Console.WriteLine("TWProtector 保护测试,请输入正确密码:");

            string input = Console.ReadLine();
            if (input != null && input.Length == 14)
            {
                byte[] buf = Encoding.UTF8.GetBytes(input); // 14 字节
                Array.Resize(ref buf, 16);                  // 补 2 个 0x00

                if (Verify(buf))
                {
                    Console.WriteLine("恭喜密码正确");
                    Console.ReadKey();
                    return;
                }
            }

            Console.WriteLine("FAILED");
            Console.ReadKey();
        }
    }

    // ========================================================================
    // TWProtector 的字符串混淆辅助方法(token 0x06000008)
    // 真实实现:对第 1 个参数做一堆垃圾 XOR 后原样返回。
    // 还原:直接返回参数。消息串在二进制中被拆成若干常量逐段 XOR 拼回。
    // ========================================================================
    public static class DeobfHelper
    {
        public static string y(object arg, uint junk)
        {
            return (string)arg;
        }
    }
}
10#
Richardallanpoe 发表于 2026-8-19 22:12
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-9-6 00:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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