【贴图】trae数据库解密(2026/7/18)

仅贴图,内存扫描key的方式可参考:https://github.com/Oh-My-Trae/trae-db-decrypt
后续验证密钥固定的话,会公开分析

C:\Users\xxx\AppData\Roaming\Trae CN\ModularData\ai-agent\database.db

image



ai_agent.dll 数据库密钥生成算法分析与数据库解析文档

本文档记录通过 IDA Pro 逆向分析 ai_agent.dll 还原的密钥生成算法、SQLCipher 4 数据库加密参数与页面解密逻辑,供后续版本适配与独立工具开发参考。

1. 总体架构

ai_agent.dll (Rust 编译)
  └─ 数据库初始化主函数 sub_18139BFA9
      ├─ 1. 查找配置键 "mics_sj10gy"
      ├─ 2. 多表 XOR 解密 → 明文密码
      ├─ 3. PBKDF2-HMAC-SHA256 派生 → 32 字节加密密钥
      └─ 4. SQLCipher 4 打开数据库

密钥是从固定密码通过 PBKDF2 派生的,不是随机数。密码本身通过多表 XOR 解密从硬编码加密数据中获得。

2. 密钥生成算法

2.1 配置键查找

  • 配置键名:mics_sj10gy(字符串地址 0x1881CBE08)
  • 加密数据地址:0x1880D98B4,长度 32 字节
  • 加密数据(hex):
    45 2A 17 35 1D 19 1E 13 36 09 14 01 2D 4E 2E 00
    17 5B 24 1D 0F 0A 38 09 1F 21 21 0E 24 18 12 53
    

2.2 多表 XOR 解密

使用三个固定字符串表循环异或:

表序号 内容 长度 周期 DLL 地址
表0 rust 4 字节 4 0x18AC4B962
表1 cpp 3 字节 3 0x18AC2F69B
表2 electron 8 字节 8 0x188942CE8

算法(总周期 = 4 × 3 × 8 = 96 字节):

对每个字节 byte[i]:
    v = byte[i]
    v ^= table0[i % 4]   # "rust"
    v ^= table1[i % 3]   # "cpp"
    v ^= table2[i % 8]   # "electron"
    output[i] = v

解密结果:1CqAknayQsrfH9Byp2QzynTckHGzRom9(32 字节 ASCII)

2.3 PBKDF2 密钥派生

参数 来源
算法 PBKDF2-HMAC-SHA256 sub_1860790DF
密码 1CqAknayQsrfH9Byp2QzynTckHGzRom9 XOR 解密结果
Salt 123456789abcdef01122334455667788(16 字节 hex) 硬编码 0x18ADFB8D0
迭代次数 100000
输出长度 32 字节

最终密钥(hex):

3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773

2.4 其他配置键(sub_1802420DA 键值映射)

键名 加密数据地址 长度 用途
mics_sj10gy 0x1880D98B4 32 ai-agent 数据库密码
mics_sj11ky 0x1880D98D4 64 其他数据库密码
mics_sj3gy 0x1880D9914 64 其他数据库密码
mics_EXAMPLE 0x1880D9879 59 示例配置
agent-dev-builder 0x188030890 17 开发构建配置
agent-reader 0x188030CFC 12 Reader 配置
agent-solo-builder 0x18803143C 27 Solo 构建配置

3. 关键函数地址索引

(版本不同,函数地址不同!)

函数地址 作用 关键细节
sub_18139BFA9 数据库初始化主函数 75KB,加载配置并初始化数据库
sub_180241E08 配置值查找 + XOR 解密 查找键名并 XOR 解密返回 String
sub_1802420DA 键值查找函数 26KB,映射键名到加密数据地址
sub_185E2F3E1 字符串比较 比较两个 &str 是否相等
sub_1854CA010 String::from_slice 从 slice 创建 String
sub_1813B7CA9 数据库初始化 22KB,调用密钥生成
sub_1813E87C5 密钥生成分发器 switch/case 结构
sub_1813E8E59 密钥生成核心 25KB,调用 PBKDF2
sub_1860790DF PBKDF2-HMAC-SHA256 100000 次迭代,32 字节输出

关键内存布局(Rust 结构体)

  • 密码 String: rbx+0x290 = {cap:[rbx+0x290], ptr:[rbx+0x298], len:[rbx+0x2A0]}
  • 配置结构体: [rbx+0x90] → r14,密码 String 存于 r14+0x320
  • PBKDF2 参数: 密码 ptr=[r14+0xF0], 密码 len=[r14+0xF8], 输出=[r14+0x100]

4. SQLCipher 4 数据库参数

参数
加密算法 AES-256-CBC
HMAC 算法 SHA-512
页面大小 4096 字节
Salt 长度 16 字节(每个数据库独立,存储在文件头前 16 字节)
密钥长度 32 字节
IV 长度 16 字节
HMAC 长度 64 字节
预留区域 80 字节(16 IV + 64 HMAC)
密钥派生 PBKDF2-HMAC-SHA256, 100000 次迭代
HMAC 密钥派生 PBKDF2-HMAC-SHA512, 2 次迭代

5. 数据库页面布局

5.1 第 1 页(含 salt)

偏移        内容                      长度
0x0000      Salt                      16 字节
0x0010      加密数据                  4096 - 80 - 16 = 4000 字节
0x0F80      IV(reserve 区域开头)    16 字节
0x0FC0      HMAC-SHA512               64 字节

5.2 其他页

偏移        内容                      长度
0x0000      加密数据                  4096 - 80 = 4016 字节
0x0FB0      IV(reserve 区域开头)    16 字节
0x0FC0      HMAC-SHA512               64 字节

6. HMAC-SHA512 密钥验证

# 1. 派生 HMAC 密钥
mac_salt = bytes(b ^ 0x3A for b in salt)  # salt 每字节 XOR 0x3A
mac_key = hashlib.pbkdf2_hmac("sha512", enc_key, mac_salt, 2, dklen=32)

# 2. 计算 HMAC
hmac_data = page1[16 : 4096-80+16]  # salt 之后到 reserve+IV
stored_hmac = page1[4096-64 : 4096]  # 页面最后 64 字节
computed = hmac.new(mac_key, hmac_data + struct.pack('<I', 1), hashlib.sha512).digest()

# 3. 验证
验证: computed == stored_hmac

7. 页面解密逻辑(AES-256-CBC)

IV = page[4096-80 : 4096-80+16]  # reserve 区域开头 16 字节

# 第 1 页:跳过 salt
encrypted = page[16 : 4096-80]
decrypted = AES.new(enc_key, AES.MODE_CBC, iv).decrypt(encrypted)
# 重建: SQLite 文件头 "SQLite format 3\x00" + 解密数据 + reserve 填充

# 其他页
encrypted = page[0 : 4096-80]
decrypted = AES.new(enc_key, AES.MODE_CBC, iv).decrypt(encrypted)
# 重建: 解密数据 + reserve 填充

8. 数据库路径

产品 数据库路径
Trae CN C:/Users/{user}/AppData/Roaming/Trae CN/ModularData/ai-agent/database.db
Trae Solo CN C:/Users/{user}/AppData/Roaming/TRAE SOLO CN/ModularData/ai-agent/database.db

ai_agent.dll 路径:<安装目录>/resources/app/modules/ai-agent/ai_agent.dll

9. 版本适配策略

不同版本的 ai_agent.dll 可能存在以下差异:

  1. 加密数据变化mics_sj10gy 对应的加密数据内容改变
  2. XOR 表变化rust/cpp/electron 表被替换或修改
  3. Salt 变化:PBKDF2 salt 常量改变
  4. 迭代次数变化:PBKDF2 迭代次数调整
  5. 配置键名变化mics_sj10gy 被重命名

DLL 验证检查项

通过在 DLL 中搜索以下关键数据,判断密钥算法是否变更:

检查项 搜索模式 含义
config_key mics_sj10gy 配置键名字符串
encrypted_password 45 2A 17 35 1D 19 1E 13 加密数据前 8 字节
xor_table_rust rust XOR 表 0
xor_table_cpp cpp XOR 表 1
xor_table_electron electron XOR 表 2
pbkdf2_salt 123456789abcdef01122334455667788 PBKDF2 salt
decrypted_password 1CqAknayQsrfH9Byp2QzynTckHGzRom9 解密后的密码

如果所有检查项都找到,说明密钥算法未变更;如果缺失,说明版本更新后算法可能已改变。

10. 完整调用链

(版本不同,函数地址不同!)

sub_18139BFA9 (数据库初始化主函数, 0x18139BFA9, 75KB)
  ├─ 0x18139F7E0: r14 = [rbx+0x90]  (加载配置结构体指针)
  ├─ 0x18139F809: lea rdx, "mics_sj10gy"  (配置键名)
  ├─ 0x18139F81B: call sub_180241E08  (查找+解密配置值)
  │   ├─ sub_1802420DA(key_ptr, key_len)  (键值查找)
  │   │   └─ 返回 &unk_1880D98B4, 长度 32  (加密数据)
  │   └─ 多表 XOR 解密 (table0="rust", table1="cpp", table2="electron")
  │       └─ 返回解密后的 String 到 rbx+0xC80
  ├─ 0x18139F820: lea rcx, [r14+0x320]  (密码 String 目标地址)
  ├─ 0x18139F827: mov rdx, [rbx+0xC88]  (解密后数据 ptr)
  ├─ 0x18139F82E: mov r8, [rbx+0xC90]   (解密后数据 len)
  ├─ 0x18139F839: call sub_1854CA010    (String::from_slice)
  ├─ 0x18139F880: lea rcx, [rbx+0x290]  (另一个密码 String 目标)
  ├─ 0x18139F887: call sub_1854CA010    (String::from_slice)
  └─ 0x18139FB25: call sub_1813B7CA9    (数据库初始化)
      └─ sub_1813E87C5 (密钥生成分发器, switch/case)
          └─ sub_1813E8E59 (密钥生成核心)
              ├─ 0x1813E958C: mov rdx, [r14+0xF0]   (密码 ptr)
              ├─ 0x1813E9593: mov r8, [r14+0xF8]    (密码 len)
              ├─ 0x1813E959A: lea rcx, [r14+0x100]  (输出缓冲区)
              └─ 0x1813E95B6: call sub_1860790DF    (PBKDF2-HMAC-SHA256)

py

#!/usr/bin/env python3
"""
ai_agent.dll 数据库密钥生成独立脚本

功能:
1. 自动查找 ai_agent.dll
2. 从 DLL 中验证/提取密钥生成所需的关键常量
3. 根据常量计算数据库加密密钥(XOR 解密 → PBKDF2 派生)
4. 可选验证密钥是否匹配指定数据库
5. 输出密钥信息(hex 格式,可直接用于 SQLCipher PRAGMA key)

算法流程(详见 docs/AI_AGENT_DLL_KEY_ANALYSIS.md):
  1. 配置键 "mics_sj10gy" → 加密数据(32 字节)
  2. 多表 XOR 解密("rust"/"cpp"/"electron")→ 明文密码
  3. PBKDF2-HMAC-SHA256(密码, salt, 100000, 32) → 32 字节密钥

使用方式:
  # 自动查找 DLL 并计算密钥
  python tools/ai_agent_keygen.py

  # 指定 DLL 路径
  python tools/ai_agent_keygen.py --dll "D:/tools/Trae CN/resources/app/modules/ai-agent/ai_agent.dll"

  # 指定 DLL 并验证密钥是否匹配数据库
  python tools/ai_agent_keygen.py --dll <path> --db <database.db>

  # 仅使用已知常量计算密钥(不读取 DLL)
  python tools/ai_agent_keygen.py --no-dll

  # 从 DLL 动态提取常量后计算密钥(适配新版本)
  python tools/ai_agent_keygen.py --extract

  # 仅输出 DLL 版本信息(方便测试时确认版本)
  python tools/ai_agent_keygen.py --version

  # 指定 DLL 并输出版本信息
  python tools/ai_agent_keygen.py --version --dll <path>

依赖: Python 3.8+, 无第三方依赖(仅使用标准库 hashlib/hmac/struct/pathlib)
"""

from __future__ import annotations

import argparse
import hashlib
import hmac as hmac_mod
import json
import os
import struct
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple

# ============================================================================
# 已知常量(当前版本 ai_agent.dll 逆向确认,见 docs/AI_AGENT_DLL_KEY_ANALYSIS.md)
# ============================================================================

# 配置键名(用于在 DLL 中查找加密数据)
CONFIG_KEY = b"mics_sj10gy"

# 加密的密码数据(32 字节,位于 DLL 偏移 0x1880D98B4)
ENCRYPTED_PASSWORD_BYTES = bytes([
    0x45, 0x2A, 0x17, 0x35, 0x1D, 0x19, 0x1E, 0x13,
    0x36, 0x09, 0x14, 0x01, 0x2D, 0x4E, 0x2E, 0x00,
    0x17, 0x5B, 0x24, 0x1D, 0x0F, 0x0A, 0x38, 0x09,
    0x1F, 0x21, 0x21, 0x0E, 0x24, 0x18, 0x12, 0x53,
])

# XOR 解密表
XOR_TABLE_0 = b"rust"       # 4 字节, 周期 4
XOR_TABLE_1 = b"cpp"        # 3 字节, 周期 3
XOR_TABLE_2 = b"electron"   # 8 字节, 周期 8

# PBKDF2 参数
PBKDF2_SALT = bytes.fromhex('123456789abcdef01122334455667788')  # 16 字节
PBKDF2_ITERATIONS = 100000
PBKDF2_KEY_LENGTH = 32

# 解密后的密码(XOR 解密结果,用于交叉验证)
DECRYPTED_PASSWORD = b"1CqAknayQsrfH9Byp2QzynTckHGzRom9"

# 已知密钥(PBKDF2 派生结果)
DERIVED_KEY_HEX = "3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773"

# SQLCipher 4 页面布局常量
PAGE_SZ = 4096
SALT_SZ = 16
IV_SZ = 16
HMAC_SZ = 64
RESERVE_SZ = 80


# ============================================================================
# 密钥生成算法
# ============================================================================

def xor_decrypt(encrypted_data: bytes,
                table0: bytes = XOR_TABLE_0,
                table1: bytes = XOR_TABLE_1,
                table2: bytes = XOR_TABLE_2) -> bytes:
    """多表 XOR 解密。

    每字节与 3 个表的当前值异或,计数器循环递增。
    总周期 = 4 × 3 × 8 = 96 字节。

    Args:
        encrypted_data: 加密数据
        table0: XOR 表 0(默认 "rust", 周期 4)
        table1: XOR 表 1(默认 "cpp", 周期 3)
        table2: XOR 表 2(默认 "electron", 周期 8)

    Returns:
        bytes: 解密后的数据
    """
    decrypted = bytearray()
    c0, c1, c2 = 0, 0, 0
    for byte in encrypted_data:
        v = byte
        v ^= table0[c0]
        c0 = (c0 + 1) % len(table0)
        v ^= table1[c1]
        c1 = (c1 + 1) % len(table1)
        v ^= table2[c2]
        c2 = (c2 + 1) % len(table2)
        decrypted.append(v)
    return bytes(decrypted)


def derive_key(password: bytes,
               salt: bytes = PBKDF2_SALT,
               iterations: int = PBKDF2_ITERATIONS,
               key_length: int = PBKDF2_KEY_LENGTH) -> bytes:
    """PBKDF2-HMAC-SHA256 密钥派生。

    Args:
        password: 密码字节
        salt: salt 字节(16 字节)
        iterations: 迭代次数(默认 100000)
        key_length: 输出密钥长度(默认 32 字节)

    Returns:
        bytes: 派生的 32 字节密钥
    """
    return hashlib.pbkdf2_hmac('sha256', password, salt, iterations, key_length)


def generate_key(encrypted_password: bytes = ENCRYPTED_PASSWORD_BYTES,
                 table0: bytes = XOR_TABLE_0,
                 table1: bytes = XOR_TABLE_1,
                 table2: bytes = XOR_TABLE_2,
                 salt: bytes = PBKDF2_SALT,
                 iterations: int = PBKDF2_ITERATIONS) -> Tuple[bytes, bytes]:
    """完整的密钥生成算法。

    算法步骤:
    1. 多表 XOR 解密加密数据 → 明文密码
    2. PBKDF2-HMAC-SHA256(密码, salt, iterations, 32) → 32 字节密钥

    Args:
        encrypted_password: 加密的密码数据
        table0/table1/table2: XOR 解密表
        salt: PBKDF2 salt
        iterations: PBKDF2 迭代次数

    Returns:
        Tuple[password, key]: (明文密码, 32 字节加密密钥)
    """
    # Step 1: XOR 解密
    password = xor_decrypt(encrypted_password, table0, table1, table2)

    # Step 2: PBKDF2 密钥派生
    key = derive_key(password, salt, iterations)

    return password, key


# ============================================================================
# DLL 常量提取(适配不同版本)
# ============================================================================

def find_ai_agent_dll() -> Optional[Path]:
    """自动查找 ai_agent.dll 路径。

    查找策略:
    1. 从运行中的 Trae 进程路径推导
    2. 常见安装目录扫描

    Returns:
        找到的 dll Path,或 None
    """
    import subprocess

    # 策略1: 从运行中的 Trae 进程路径推导
    try:
        result = subprocess.run(
            ["powershell", "-Command",
             "Get-Process | Where-Object { $_.Name -like '*Trae*' } | "
             "Select-Object -ExpandProperty Path -Unique"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0 and result.stdout.strip():
            for line in result.stdout.strip().splitlines():
                exe_path = Path(line.strip())
                if not exe_path.exists():
                    continue
                dll_candidate = (exe_path.parent / "resources" / "app" /
                                 "modules" / "ai-agent" / "ai_agent.dll")
                if dll_candidate.exists():
                    return dll_candidate
    except Exception:
        pass

    # 策略2: 常见安装目录
    common_roots = [
        Path("D:/tools"),
        Path("C:/Program Files"),
        Path("C:/Program Files (x86)"),
        Path(os.environ.get("LOCALAPPDATA", "")) / "Programs",
        Path(os.environ.get("USERPROFILE", "")),
    ]
    product_names = ["Trae CN", "Trae", "TRAE SOLO CN", "TRAE CN"]
    for root in common_roots:
        if not root or not root.exists():
            continue
        for product in product_names:
            dll_candidate = root / product / "resources" / "app" / "modules" / "ai-agent" / "ai_agent.dll"
            if dll_candidate.exists():
                return dll_candidate

    return None


def get_dll_version_info(dll_path: Path) -> Dict[str, str]:
    """获取 DLL 的版本信息。

    ai_agent.dll 是 Rust 编译的,通常没有 PE 版本资源(FileVersion 等字段为空),
    因此从以下途径获取版本标识信息:
    1. PE 头部 TimeDateStamp(编译时间戳)
    2. 文件 SHA256 哈希(唯一标识版本)
    3. PE 版本资源(如果有)
    4. 文件大小和修改时间

    Args:
        dll_path: ai_agent.dll 路径

    Returns:
        Dict: 版本信息字典
    """
    info: Dict[str, str] = {}

    try:
        dll_data = dll_path.read_bytes()
    except Exception as e:
        return {"error": f"读取 DLL 失败: {e}"}

    # 1. 从 PE 头部读取编译时间戳
    try:
        pe_offset = struct.unpack_from("<I", dll_data, 0x3C)[0]
        if pe_offset + 8 < len(dll_data):
            # PE 签名 + COFF 头部: TimeDateStamp 在 PE 签名后偏移 4
            timestamp = struct.unpack_from("<I", dll_data, pe_offset + 8)[0]
            if timestamp > 0:
                from datetime import datetime, timezone
                compile_time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
                info["compile_timestamp"] = str(timestamp)
                info["compile_time_utc"] = compile_time.strftime("%Y-%m-%d %H:%M:%S UTC")
    except Exception:
        pass

    # 2. 计算 SHA256 哈希(用于唯一标识 DLL 版本)
    try:
        info["sha256"] = hashlib.sha256(dll_data).hexdigest()
    except Exception:
        pass

    # 3. 尝试从 PE 版本资源获取信息(Rust DLL 通常没有,但尝试一下)
    try:
        result = subprocess.run(
            ["powershell", "-Command",
             f"(Get-Item -LiteralPath '{dll_path}').VersionInfo | "
             f"Select-Object FileVersion, ProductVersion, FileDescription, "
             f"ProductName, CompanyName | ConvertTo-Json -Compress"],
            capture_output=True, text=True, timeout=10
        )
        if result.returncode == 0 and result.stdout.strip():
            data = json.loads(result.stdout.strip())
            for ps_key, dict_key in [("FileVersion", "file_version"),
                                      ("ProductVersion", "product_version"),
                                      ("FileDescription", "file_description"),
                                      ("ProductName", "product_name"),
                                      ("CompanyName", "company_name")]:
                val = str(data.get(ps_key, "") or "").strip()
                if val:
                    info[dict_key] = val
    except Exception:
        pass

    # 4. 文件大小和修改时间
    try:
        stat = dll_path.stat()
        info["file_size"] = str(stat.st_size)
        from datetime import datetime
        info["last_modified"] = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
    except Exception:
        pass

    return info


def format_version_info(version_info: Dict[str, str]) -> str:
    """格式化版本信息为可读字符串。

    Args:
        version_info: get_dll_version_info 返回的字典

    Returns:
        str: 格式化后的多行字符串
    """
    if not version_info:
        return "  (无法获取版本信息)"

    if "error" in version_info:
        return f"  (获取版本信息失败: {version_info['error']})"

    lines = []
    field_map = [
        ("file_version", "文件版本"),
        ("product_version", "产品版本"),
        ("product_name", "产品名称"),
        ("file_description", "文件描述"),
        ("company_name", "公司名称"),
        ("compile_time_utc", "编译时间"),
        ("compile_timestamp", "编译时间戳"),
        ("sha256", "SHA256"),
        ("file_size", "文件大小"),
        ("last_modified", "最后修改"),
    ]
    for key, label in field_map:
        val = version_info.get(key, "")
        if val:
            # 文件大小转换为可读格式
            if key == "file_size":
                try:
                    size = int(val)
                    val = f"{size:,} 字节 ({size / 1024 / 1024:.1f} MB)"
                except ValueError:
                    pass
            lines.append(f"  {label:12s}: {val}")
    return "\n".join(lines) if lines else "  (版本信息为空)"


def verify_dll_constants(dll_data: bytes) -> Dict[str, Dict]:
    """在 DLL 二进制数据中搜索已知常量,判断算法是否变更。

    Args:
        dll_data: DLL 文件的完整二进制数据

    Returns:
        Dict: 各检查项的详细结果
    """
    checks = [
        ("config_key", CONFIG_KEY, "配置键名字符串"),
        ("encrypted_password", ENCRYPTED_PASSWORD_BYTES[:8], "加密密码前 8 字节"),
        ("xor_table_rust", XOR_TABLE_0, "XOR 表 0 (rust)"),
        ("xor_table_cpp", XOR_TABLE_1, "XOR 表 1 (cpp)"),
        ("xor_table_electron", XOR_TABLE_2, "XOR 表 2 (electron)"),
        ("pbkdf2_salt", PBKDF2_SALT, "PBKDF2 salt"),
        ("decrypted_password", DECRYPTED_PASSWORD, "解密后的密码"),
    ]

    results = {}
    for name, pattern, desc in checks:
        pos = dll_data.find(pattern)
        results[name] = {
            "description": desc,
            "found": pos >= 0,
            "offset": pos if pos >= 0 else -1,
        }
    return results


def extract_encrypted_password_from_dll(dll_data: bytes) -> Optional[bytes]:
    """从 DLL 中动态提取加密的密码数据。

    提取策略:
    1. 搜索配置键 "mics_sj10gy" 的位置
    2. 在其附近搜索已知的加密数据前 8 字节模式
    3. 如果找到,提取完整 32 字节

    Args:
        dll_data: DLL 文件的完整二进制数据

    Returns:
        32 字节加密密码,或 None(提取失败)
    """
    # 方法1: 搜索已知的加密数据前 8 字节
    pattern = ENCRYPTED_PASSWORD_BYTES[:8]
    pos = dll_data.find(pattern)
    if pos >= 0:
        extracted = dll_data[pos:pos + 32]
        if len(extracted) == 32:
            return extracted

    # 方法2: 搜索配置键后查找附近的数据引用(更复杂,暂不实现)
    # 新版本如果更改了加密数据,需要通过 IDA 重新分析

    return None


def extract_constants_from_dll(dll_path: Path) -> Dict:
    """从 DLL 中提取/验证所有密钥生成所需常量。

    Args:
        dll_path: ai_agent.dll 路径

    Returns:
        Dict: 提取结果,包含:
        - dll_path, dll_size
        - constants_verified: 已知常量是否在 DLL 中找到
        - checks: 各检查项详情
        - encrypted_password: 提取的加密密码(32 字节)
        - all_found: 是否全部找到
        - summary: 摘要文本
    """
    try:
        dll_data = dll_path.read_bytes()
    except Exception as e:
        return {
            "dll_path": str(dll_path),
            "dll_size": 0,
            "error": f"读取 DLL 失败: {e}",
            "all_found": False,
        }

    checks = verify_dll_constants(dll_data)
    all_found = all(c["found"] for c in checks.values())

    # 尝试提取加密密码
    extracted_password = extract_encrypted_password_from_dll(dll_data)

    found_count = sum(1 for c in checks.values() if c["found"])
    total = len(checks)
    if all_found:
        summary = f"全部 {total} 项关键数据均在 DLL 中找到,密钥算法未变更"
    else:
        missing = [name for name, c in checks.items() if not c["found"]]
        summary = f"{found_count}/{total} 项找到,缺失: {', '.join(missing)}"

    return {
        "dll_path": str(dll_path),
        "dll_size": len(dll_data),
        "checks": checks,
        "extracted_password": extracted_password,
        "all_found": all_found,
        "summary": summary,
    }


# ============================================================================
# 密钥验证
# ============================================================================

def verify_key_with_db(enc_key: bytes, db_path: Path) -> bool:
    """使用 HMAC-SHA512 验证密钥是否匹配数据库。

    Args:
        enc_key: 32 字节加密密钥
        db_path: 数据库文件路径

    Returns:
        bool: 密钥是否正确
    """
    try:
        with open(db_path, 'rb') as f:
            page1 = f.read(PAGE_SZ)
    except Exception as e:
        print(f"读取数据库失败: {e}")
        return False

    if len(page1) < PAGE_SZ:
        print(f"数据库页面不足 {PAGE_SZ} 字节")
        return False

    salt = page1[:SALT_SZ]

    # 派生 HMAC 密钥
    mac_salt = bytes(b ^ 0x3A for b in salt)
    mac_key = hashlib.pbkdf2_hmac("sha512", enc_key, mac_salt, 2, dklen=32)

    # 计算 HMAC
    hmac_data = page1[SALT_SZ: PAGE_SZ - RESERVE_SZ + IV_SZ]
    stored_hmac = page1[PAGE_SZ - HMAC_SZ: PAGE_SZ]
    hm = hmac_mod.new(mac_key, hmac_data, hashlib.sha512)
    hm.update(struct.pack("<I", 1))  # 页号 1,小端

    return hm.digest() == stored_hmac


# ============================================================================
# 主程序
# ============================================================================

def main():
    parser = argparse.ArgumentParser(
        description="ai_agent.dll 数据库密钥生成工具",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  # 自动查找 DLL 并计算密钥
  python tools/ai_agent_keygen.py

  # 指定 DLL 路径
  python tools/ai_agent_keygen.py --dll "D:/tools/Trae CN/resources/app/modules/ai-agent/ai_agent.dll"

  # 指定 DLL 并验证密钥是否匹配数据库
  python tools/ai_agent_keygen.py --dll <path> --db <database.db>

  # 仅使用已知常量计算密钥(不读取 DLL)
  python tools/ai_agent_keygen.py --no-dll

  # 从 DLL 动态提取常量后计算密钥(适配新版本)
  python tools/ai_agent_keygen.py --extract

  # 仅输出 DLL 版本信息(方便测试时确认版本)
  python tools/ai_agent_keygen.py --version
""")
    parser.add_argument("--dll", type=str, help="ai_agent.dll 路径(不指定则自动查找)")
    parser.add_argument("--db", type=str, help="数据库路径(用于验证密钥)")
    parser.add_argument("--no-dll", action="store_true",
                        help="不读取 DLL,仅使用已知常量计算密钥")
    parser.add_argument("--extract", action="store_true",
                        help="从 DLL 动态提取常量后计算密钥(适配新版本)")
    parser.add_argument("--quiet", action="store_true",
                        help="仅输出密钥 hex,不输出详细信息")
    parser.add_argument("--version", action="store_true",
                        help="仅输出 DLL 版本信息后退出")
    args = parser.parse_args()

    # ==================== 仅输出版本信息模式 ====================
    if args.version:
        dll_path = Path(args.dll) if args.dll else find_ai_agent_dll()
        if dll_path is None or not dll_path.exists():
            print("❌ 未找到 ai_agent.dll")
            sys.exit(1)
        print(f"📁 DLL 路径: {dll_path}")
        version_info = get_dll_version_info(dll_path)
        print(format_version_info(version_info))
        return 0

    # ==================== 计算密钥 ====================
    encrypted_password = ENCRYPTED_PASSWORD_BYTES
    table0, table1, table2 = XOR_TABLE_0, XOR_TABLE_1, XOR_TABLE_2
    salt = PBKDF2_SALT
    iterations = PBKDF2_ITERATIONS

    dll_info = None

    if not args.no_dll:
        # 查找或使用指定的 DLL
        dll_path = Path(args.dll) if args.dll else find_ai_agent_dll()

        if dll_path is None or not dll_path.exists():
            if not args.quiet:
                print("⚠️  未找到 ai_agent.dll,使用已知常量计算密钥")
        else:
            if not args.quiet:
                print(f"📁 DLL 路径: {dll_path}")
                print(f"   大小: {dll_path.stat().st_size / 1024 / 1024:.1f} MB")

                # 输出 DLL 版本信息(方便测试时确认版本)
                version_info = get_dll_version_info(dll_path)
                print(f"\n📋 DLL 版本信息:")
                print(format_version_info(version_info))

            # 读取 DLL 数据
            dll_data = dll_path.read_bytes()

            if args.extract:
                # 动态提取模式:从 DLL 中提取常量
                if not args.quiet:
                    print("\n🔍 从 DLL 动态提取常量...")
                extracted = extract_encrypted_password_from_dll(dll_data)
                if extracted:
                    encrypted_password = extracted
                    if not args.quiet:
                        print(f"   ✅ 提取加密密码: {extracted.hex()}")
                else:
                    if not args.quiet:
                        print("   ⚠️  无法从 DLL 提取加密密码,使用已知常量")
            else:
                # 验证模式:检查 DLL 中的常量是否与已知值匹配
                checks = verify_dll_constants(dll_data)
                all_found = all(c["found"] for c in checks.values())
                dll_info = checks

                if not args.quiet:
                    print("\n🔍 DLL 常量验证:")
                    for name, result in checks.items():
                        status = "✅" if result["found"] else "❌"
                        offset = f"0x{result['offset']:08X}" if result["offset"] >= 0 else "未找到"
                        print(f"   {status} {name:25s} @ {offset}")

                    if all_found:
                        print("\n   ✅ 所有常量匹配,密钥算法未变更")
                    else:
                        print("\n   ⚠️  部分常量缺失,密钥可能已变更")
                        print("   建议使用 --extract 模式尝试动态提取")

    # 生成密钥
    if not args.quiet:
        print("\n" + "=" * 60)
        print("🔐 密钥生成")
        print("=" * 60)

    password, key = generate_key(
        encrypted_password, table0, table1, table2, salt, iterations
    )

    if not args.quiet:
        print(f"\n  Step 1: XOR 解密")
        print(f"    加密数据: {encrypted_password.hex()}")
        print(f"    XOR 表: {table0.decode()} / {table1.decode()} / {table2.decode()}")
        print(f"    解密密码: {password.decode('ascii', errors='replace')}")

        print(f"\n  Step 2: PBKDF2-HMAC-SHA256")
        print(f"    Salt: {salt.hex()}")
        print(f"    迭代次数: {iterations}")
        print(f"    输出长度: {len(key)} 字节")

        print(f"\n  ✅ 加密密钥 (hex):")
        print(f"     {key.hex()}")

        # 交叉验证
        if key.hex() == DERIVED_KEY_HEX:
            print(f"\n  ✅ 密钥与已知值匹配")
        else:
            print(f"\n  ⚠️  密钥与已知值不同")
            print(f"     已知值: {DERIVED_KEY_HEX}")
            print(f"     计算值: {key.hex()}")
    else:
        print(key.hex())

    # ==================== 验证密钥 ====================
    if args.db:
        db_path = Path(args.db)
        if not db_path.exists():
            print(f"\n❌ 数据库文件不存在: {db_path}")
            sys.exit(1)

        if not args.quiet:
            print(f"\n" + "=" * 60)
            print(f"🔎 验证密钥")
            print(f"=" * 60)
            print(f"   数据库: {db_path}")
            print(f"   大小: {db_path.stat().st_size / 1024 / 1024:.1f} MB")

        if verify_key_with_db(key, db_path):
            if not args.quiet:
                print(f"\n   ✅ HMAC 验证通过,密钥正确!")
            else:
                print("VALID")
        else:
            if not args.quiet:
                print(f"\n   ❌ HMAC 验证失败,密钥不匹配")
            else:
                print("INVALID")
            sys.exit(1)

    # ==================== 使用说明 ====================
    if not args.quiet:
        print(f"\n" + "=" * 60)
        print(f"📋 使用说明")
        print(f"=" * 60)
        print(f"""
  密钥已生成,可通过以下方式使用:

  1. SQLCipher 命令行:
     sqlcipher database.db
     PRAGMA key = "x'{key.hex()}'";
     PRAGMA cipher_compatibility = 4;
     SELECT count(*) FROM sqlite_master;

  2. Python (sqlcipher3):
     import sqlcipher3
     conn = sqlcipher3.connect("database.db")
     conn.execute(f"PRAGMA key = \\"x'{key.hex()}'\\"")
     conn.execute("PRAGMA cipher_compatibility = 4")

  3. DB Browser for SQLite (SQLCipher):
     打开数据库时选择 SQLCipher 4,密钥格式: {key.hex()}

  4. 保存到 trae_converter 配置:
     将密钥写入 %APPDATA%/TraeMigrationTool/db_key.txt:
     key_hex={key.hex()}
""")

    return 0


if __name__ == "__main__":
    sys.exit(main())

usage: ai_agent_keygen.py [-h] [--dll DLL] [--db DB] [--no-dll] [--extract] [--quiet] [--version]

ai_agent.dll 数据库密钥生成工具

options:
  -h, --help  show this help message and exit
  --dll DLL   ai_agent.dll 路径(不指定则自动查找)
  --db DB     数据库路径(用于验证密钥)
  --no-dll    不读取 DLL,仅使用已知常量计算密钥
  --extract   从 DLL 动态提取常量后计算密钥(适配新版本)
  --quiet     仅输出密钥 hex,不输出详细信息
  --version   仅输出 DLL 版本信息后退出

示例:
  # 自动查找 DLL 并计算密钥
  python tools/ai_agent_keygen.py

  # 指定 DLL 路径
  python tools/ai_agent_keygen.py --dll "D:/tools/Trae CN/resources/app/modules/ai-agent/ai_agent.dll"

  # 指定 DLL 并验证密钥是否匹配数据库
  python tools/ai_agent_keygen.py --dll <path> --db <database.db>

  # 仅使用已知常量计算密钥(不读取 DLL)
  python tools/ai_agent_keygen.py --no-dll

  # 从 DLL 动态提取常量后计算密钥(适配新版本)
  python tools/ai_agent_keygen.py --extract

  # 仅输出 DLL 版本信息(方便测试时确认版本)
  python tools/ai_agent_keygen.py --version

python ai_agent_keygen.py

📁 DLL 路径: D:\IDE\Trae CN\resources\app\modules\ai-agent\ai_agent.dll
   大小: 224.4 MB

📋 DLL 版本信息:
  编译时间        : 2026-07-14 10:51:19 UTC
  编译时间戳       : 1784026279
  SHA256      : 96febd51092cc92e2dcda982722ec8a7763ce22c4adce791c3c58c233dc80ef3
  文件大小        : 235,337,104 字节 (224.4 MB)
  最后修改        : 2026-07-15 23:17:46

🔍 DLL 常量验证:
   ✅ config_key                @ 0x083534E8
   ✅ encrypted_password        @ 0x08261364
   ✅ xor_table_rust            @ 0x0832A7A6
   ✅ xor_table_cpp             @ 0x083C956F
   ✅ xor_table_electron        @ 0x08A52F80
   ✅ pbkdf2_salt               @ 0x0AFDFDD0
   ✅ decrypted_password        @ 0x08353C68

   ✅ 所有常量匹配,密钥算法未变更

============================================================
🔐 密钥生成
============================================================

  Step 1: XOR 解密
    加密数据: 452a17351d191e13360914012d4e2e00175b241d0f0a38091f21210e24181253
    XOR 表: rust / cpp / electron
    解密密码: 1CqAknayQsrfH9Byp2QzynTckHGzRom9

  Step 2: PBKDF2-HMAC-SHA256
    Salt: 123456789abcdef01122334455667788
    迭代次数: 100000
    输出长度: 32 字节

  ✅ 加密密钥 (hex):
     3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773

  ✅ 密钥与已知值匹配

============================================================
📋 使用说明
============================================================

  密钥已生成,可通过以下方式使用:

  1. SQLCipher 命令行:
     sqlcipher database.db
     PRAGMA key = "x'3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773'";
     PRAGMA cipher_compatibility = 4;
     SELECT count(*) FROM sqlite_master;

  2. Python (sqlcipher3):
     import sqlcipher3
     conn = sqlcipher3.connect("database.db")
     conn.execute(f"PRAGMA key = \"x'3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773'\"")
     conn.execute("PRAGMA cipher_compatibility = 4")

  3. DB Browser for SQLite (SQLCipher):
     打开数据库时选择 SQLCipher 4,密钥格式: 3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773

  4. 保存到 trae_converter 配置:
     将密钥写入 %APPDATA%/TraeMigrationTool/db_key.txt:
     key_hex=3605f6691095a993f03d5009c918352ef5be31ae31e8f000212b81ff058da773

PS

内存扫描可参考:https://forum.trae.cn/t/topic/18248

image

从分析来看,数据库密钥是固定的,各位可以测试一下;

posted @ 2026-07-15 21:48  DirWangK  阅读(17)  评论(0)    收藏  举报