首页
社区
课程
招聘
[原创]CORE CrackMe v2.0 逆向分析解题报告
发表于: 6小时前 203

[原创]CORE CrackMe v2.0 逆向分析解题报告

6小时前
203

1. 结论

用户名为:

KCTF

对应序列号为:

A86CA89F 77F81C3C FD2620F0

程序验证结果:弹出 Welcome


2. 样本信息

样本:

CRACKME3.EXE

基础信息:

MD5        : 95681bb270413a0632a63ca8a1be44bf
ImageBase  : 00400000
EntryPoint : 00429000

节区特征:

UPX0
UPX1
UPX2
UPX3

说明样本被 UPX 压缩,版本字符串显示为:

UPX 0.72

新版 UPX 直接脱壳会失败:

CantUnpackException: this program is packed with an obsolete version

所以采用运行时脱壳。


3. 脱壳过程

入口点:

00429000 pushad
...
00429197 popad
00429198 jmp 004047CB

真实 OEP:

004047CB

x32dbg 复现步骤

  1. 用 x32dbg 打开样本。
  2. 跳转到:
00429198
  1. 00429198 下断点。
  2. F9 运行。
  3. 断下后 dump 内存:
Base : 00400000
Size : 00038000

保存为:

debug_dump_at_stub.bin

脱壳后 OEP 处代码:

004047CB push ebp
004047CC mov  ebp, esp
004047CE push -1
004047D0 push 004185B8
004047D5 push 00407924

4. 界面资源分析

主对话框资源 ID:

102

标题:

CORE Official CrackMe v2.0 - Written by Egis/CORE

控件 ID:

1000 : 用户名输入框
1001 : 序列号输入框
1002 : OK 按钮
1003 : About 按钮

控件绑定代码:

00401D35 push 3E8h ; 1000
00401D47 push 3E9h ; 1001
00401D7A push 3EAh ; 1002

5. CHECK_FN 定位

搜索 OK 按钮 ID 0x3EA,定位到消息映射表:

00422390 : 000003EA
00422394 : 000003EA
00422398 : 0000000C
0042239C : 004020A0

所以校验函数为:

CHECK_FN = 004020A0

本题是计算序列号,不需要补丁:

OFFSET/PATCH_BYTE = N/A

6. 校验逻辑还原

6.1 输入读取

004020A0 中读取用户名和序列号:

0040212A call 00412706 ; 读取用户名
0040214C call 00412706 ; 读取序列号

之后调用字符串反转函数:

0040216F call 00408D70

00408D70 的作用是 strrev

对于用户名:

KCTF

反转后:

FTCK

程序最终参与 hash 的字符串为:

name + reverse(name) + reverse(name) + reverse(name)

因此:

KCTF + FTCK + FTCK + FTCK

得到:

KCTFFTCKFTCKFTCK

6.2 Hash 部分

004014E0 初始化 MD5 状态:

004014E4 mov [eax],     67452301h
004014F8 mov [eax + 4], EFCDAB89h
004014F1 mov [eax + 8], 98BADCFEh
004014EA mov [eax + C], 10325476h

00401500 是 MD5 compression。

但它不是标准 MD5,padding 长度字段有差异:

标准 MD5     : len(data) * 8
该 CrackMe : (len(data) + 1) * 8

对字符串:

KCTFFTCKFTCKFTCK

计算得到内部 digest:

BAB8150D 57308F2C AEA9D86B FADBC6C0

实际只比较前三个 dword:

BAB8150D
57308F2C
AEA9D86B

6.3 序列号格式

序列号由 sscanf 解析:

0040233B push 00422520 ; "%lx%lx%lx"
00402341 call 00404797
00402349 cmp  eax, 3

格式为:

%lx%lx%lx

所以序列号应为 3 个十六进制整数,例如:

XXXXXXXX XXXXXXXX XXXXXXXX

6.4 线性变换

核心线性变换函数:

00401AD0

使用 4 张表:

0041C2C0
0041C3C0
0041C4C0
0041C5C0

校验关系可抽象为:

L1(serial_1, serial_2) = digest_0, middle
L2(middle, serial_3)  = digest_1, digest_2

因此逆推流程:

1. 由 digest_1, digest_2 逆推出 middle, serial_3
2. 由 digest_0, middle   逆推出 serial_1, serial_2

因为 00401AD0 是 GF(2) 上的线性变换,所以可以用高斯消元求逆。


7. 可复现求解脚本

要求当前目录存在脱壳 dump:

debug_dump_at_stub.bin

保存脚本为:

solve_crackme3.py

代码:

import math
import struct
from pathlib import Path

BASE = 0x400000
mem = Path("debug_dump_at_stub.bin").read_bytes()

def u32(x):
    return x & 0xffffffff

def rol(x, n):
    x &= 0xffffffff
    return ((x << n) | (x >> (32 - n))) & 0xffffffff

def read_table(va, n=64):
    return list(struct.unpack_from(f"<{n}I", mem, va - BASE))

T1_A = read_table(0x41C2C0)
T1_B = read_table(0x41C3C0)
T2_A = read_table(0x41C4C0)
T2_B = read_table(0x41C5C0)

def md5_compress(block, st):
    X = list(struct.unpack("<16I", block))
    a, b, c, d = st

    T = [
        int(abs(math.sin(i + 1)) * (1 << 32)) & 0xffffffff
        for i in range(64)
    ]

    S = (
        [7, 12, 17, 22] * 4 +
        [5, 9, 14, 20] * 4 +
        [4, 11, 16, 23] * 4 +
        [6, 10, 15, 21] * 4
    )

    for i in range(64):
        if i < 16:
            f = (b & c) | (~b & d)
            g = i
        elif i < 32:
            f = (b & d) | (c & ~d)
            g = (5 * i + 1) & 15
        elif i < 48:
            f = b ^ c ^ d
            g = (3 * i + 5) & 15
        else:
            f = c ^ (b | ~d)
            g = (7 * i) & 15

        f = u32(f + a + T[i] + X[g])
        a, d, c, b = d, c, b, u32(b + rol(f, S[i]))

    return [
        u32(st[0] + a),
        u32(st[1] + b),
        u32(st[2] + c),
        u32(st[3] + d),
    ]

def crackme_md5_like(data: bytes):
    n = len(data)

    r = (n + 1) & 0x3f
    zero_pad = (-r) & 0x3f

    if zero_pad <= 7:
        zero_pad += 0x40

    total = n + 1 + zero_pad
    buf = bytearray(data + b"\x80" + b"\x00" * zero_pad)

    struct.pack_into("<I", buf, total - 8, (n + 1) * 8)

    st = [
        0x67452301,
        0xefcdab89,
        0x98badcfe,
        0x10325476,
    ]

    for off in range(0, total, 64):
        st = md5_compress(bytes(buf[off:off + 64]), st)

    return st

def popcnt32(x):
    return (x & 0xffffffff).bit_count()

def transform_pair(x0, x1, A, B):
    out = []

    for off in (0, 32):
        y = 0

        for i in range(32):
            bit = (
                popcnt32(x0 & A[off + i]) ^
                popcnt32(x1 & B[off + i])
            ) & 1

            y = ((y << 1) ^ bit) & 0xffffffff

        out.append(y)

    return out[0], out[1]

def invert_pair(y0, y1, A, B):
    cols = []

    for j in range(64):
        if j < 32:
            x0 = 1 << j
            x1 = 0
        else:
            x0 = 0
            x1 = 1 << (j - 32)

        o0, o1 = transform_pair(x0, x1, A, B)
        cols.append(o0 | (o1 << 32))

    target = y0 | (y1 << 32)
    rows = []

    for out_bit in range(64):
        row = 0

        for var in range(64):
            row |= ((cols[var] >> out_bit) & 1) << var

        rows.append([row, (target >> out_bit) & 1])

    r = 0
    pivots = []

    for col in range(64):
        pivot = None

        for i in range(r, 64):
            if (rows[i][0] >> col) & 1:
                pivot = i
                break

        if pivot is None:
            raise RuntimeError("matrix is not invertible")

        rows[r], rows[pivot] = rows[pivot], rows[r]

        for i in range(64):
            if i != r and ((rows[i][0] >> col) & 1):
                rows[i][0] ^= rows[r][0]
                rows[i][1] ^= rows[r][1]

        pivots.append(col)
        r += 1

    sol = 0

    for i, col in enumerate(pivots):
        sol |= rows[i][1] << col

    return sol & 0xffffffff, (sol >> 32) & 0xffffffff

def make_serial(name: str):
    nb = name.encode("ascii")
    rev = nb[::-1]

    msg = nb + rev + rev + rev
    h0, h1, h2, h3 = crackme_md5_like(msg)

    middle, s3 = invert_pair(h1, h2, T2_A, T2_B)
    s1, s2 = invert_pair(h0, middle, T1_A, T1_B)

    return msg, (h0, h1, h2, h3), (s1, s2, s3)

if __name__ == "__main__":
    msg, digest, serial = make_serial("KCTF")

    print("message:", msg.decode())
    print("digest :", " ".join(f"{x:08X}" for x in digest))
    print("serial :", " ".join(f"{x:08X}" for x in serial))

运行:

python solve_crackme3.py

输出:

message: KCTFFTCKFTCKFTCK
digest : BAB8150D 57308F2C AEA9D86B FADBC6C0
serial : A86CA89F 77F81C3C FD2620F0

8. 验证过程

程序中输入:

Enter your name:
KCTF
Guess your registration code:
A86CA89F 77F81C3C FD2620F0

点击 OK 后弹窗标题为:

Welcome

因此最终答案确认:

A86CA89F 77F81C3C FD2620F0



[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回