Skip to content

进程内 Hook 替换、标志位篡改与本地配置伪造防护 ​

防御目标与威胁面 ​

在 05-keygen-frida-verification-loop.md 中,攻击者在验证或激活阶段采用四类进程内与本地文件手段绕过校验:

  1. 输入 API 进程内替换:通过 Frida 挂载 user32!GetDlgItemTextW、ReadFile 或 RegGetValueW,把合法输入或读取内容动态偷换成自生成 key。
  2. 全局注册标志位篡改:定位内存中的单一全局字节/整数标志(如 0=已注册),直接修改或轮询确认激活生效。
  3. 硬件指纹 Hook 欺骗:拦截 RegQueryValueExW(MachineGuid) 或 DeviceIoControl,返回固定伪造指纹以实现“一码通杀”。
  4. 本地配置直接改写:程序关闭后改写 Preferences.json 中的 Name/Email/Key 明文三键实现冷启动持久激活。

核心防御架构 ​

攻击向量防御控制措施关键实现约束
Frida Hook GetDlgItemTextW / RegQueryValueExW启用 CIG(ProcessSignaturePolicy)阻断未签名 frida-agent 注入,并在调用前校验目标 API 入口字节是否含跳转检查 user32!GetDlgItemTextW、advapi32!RegQueryValueExW 首部未被 E9/FF 25 篡改
全局单一内存标志位(u8 g_isRegistered)消除裸全局布尔位:使用动态分配 + 每会话随机密钥 HMAC 密封的状态对象,多处独立重新核验完整凭证任何直接修改状态字段的操作都会导致 HMAC 失配;标志位不再以固定 RVA 暴露
Hook RegQueryValueExW 伪造 MachineGuid多源交叉硬件绑定 + 服务端签发:结合 TPM EK 公钥 / 签名凭证 / 多路径硬件特征,并在关键业务中校验完整签发载荷若仅依赖单一可被 Hook 的用户态注册表读取 API,极易被拦截替换
明文改写 Preferences.json 等配置文件本地持久化状态必须携带**服务端非对称签名(Ed25519)**并用 DPAPI(CryptProtectData) 绑定本机加密封装退出写回配置时不覆盖未通过服务端签名校验的临时状态;启动读取先解封 DPAPI 再验签名

加固实现与检测代码 ​

以下 Python 参考实现演示本地配置与运行时状态的双重保护:使用密封结构体抵抗进程内单字节标志篡改,并用带机器指纹绑定的认证封套阻止离线直接改写 Preferences.json;下方附带 C++ 检测输入与注册表 API 是否被 Frida Interceptor 劫持的代码:

python
"""sealed_state_and_config.py: 运行时 MAC 密封状态与抗改写本地配置校验"""
from __future__ import annotations
import base64
import hashlib
import hmac
import json
import os
from typing import Tuple


class SealedSessionState:
    """用进程内随机密钥与 HMAC-SHA256 保护运行时授权状态,杜绝裸全局标志位。"""

    def __init__(self, machine_binding: str):
        self._ephemeral_key = os.urandom(32)
        self._machine = machine_binding
        self._blob = b""
        self._mac = b""
        self.update(licensed=False, tier="free", seq=0)

    def update(self, licensed: bool, tier: str, seq: int) -> None:
        payload = json.dumps(
            {"licensed": licensed, "tier": tier, "seq": seq, "hw": self._machine},
            sort_keys=True,
            separators=(",", ":"),
        ).encode("utf-8")
        self._blob = payload
        self._mac = hmac.new(self._ephemeral_key, payload, hashlib.sha256).digest()

    def verify_and_read(self) -> Tuple[bool, dict | str]:
        expected = hmac.new(self._ephemeral_key, self._blob, hashlib.sha256).digest()
        if not hmac.compare_digest(self._mac, expected):
            return False, "session_state_tampered"
        return True, json.loads(self._blob.decode("utf-8"))


def seal_preferences(prefs: dict, dpapi_machine_secret: bytes, server_sign_key: bytes) -> str:
    """模拟本地配置持久化: 内层带服务端签名/MAC,外层模拟 DPAPI 本机加密绑定。"""
    body = json.dumps(prefs, sort_keys=True, separators=(",", ":")).encode("utf-8")
    srv_tag = hmac.new(server_sign_key, b"SRV-PREF-V1:" + body, hashlib.sha256).hexdigest()
    envelope = json.dumps({"body": prefs, "srv_tag": srv_tag}, separators=(",", ":")).encode("utf-8")
    # 模拟 DPAPI 机器绑定封装校验标签
    dpapi_tag = hmac.new(dpapi_machine_secret, envelope, hashlib.sha256).hexdigest()
    return base64.b64encode(json.dumps({"env": envelope.decode("utf-8"), "dpapi": dpapi_tag}).encode()).decode()


def unseal_preferences(sealed_b64: str, dpapi_machine_secret: bytes, server_verify_key: bytes) -> Tuple[bool, dict | str]:
    try:
        outer = json.loads(base64.b64decode(sealed_b64).decode("utf-8"))
        env_bytes = outer["env"].encode("utf-8")
        expected_dpapi = hmac.new(dpapi_machine_secret, env_bytes, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(outer["dpapi"], expected_dpapi):
            return False, "dpapi_binding_mismatch"
        env = json.loads(outer["env"])
        body = json.dumps(env["body"], sort_keys=True, separators=(",", ":")).encode("utf-8")
        expected_srv = hmac.new(server_verify_key, b"SRV-PREF-V1:" + body, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(env["srv_tag"], expected_srv):
            return False, "invalid_server_signature"
        return True, env["body"]
    except Exception:
        return False, "malformed_preferences"


if __name__ == "__main__":
    st = SealedSessionState("hw-fp-001")
    assert st.verify_and_read()[1]["licensed"] is False
    # 模拟攻击者直接改写内存状态字节(把 false 改为 true)
    st._blob = st._blob.replace(b"false", b"true ")
    assert st.verify_and_read() == (False, "session_state_tampered")

    dpapi_secret = b"machine-dpapi-master"
    srv_key = b"server-only-signing-key"
    token = seal_preferences({"Name": "Pro", "Email": "u@example.com"}, dpapi_secret, srv_key)
    ok, cfg = unseal_preferences(token, dpapi_secret, srv_key)
    assert ok and cfg["Name"] == "Pro"

    # 模拟攻击者离线篡改或复制到另一台机器
    assert unseal_preferences(token, b"other-machine-dpapi", srv_key)[0] is False
    print("Frida verification loop defense self-check passed.")

以下 C++ 片段检查常见输入与指纹读取 API 入口是否被 Frida Interceptor 写入跳转跳板:

cpp
// api_hook_integrity.cpp: 检测关键输入与硬件指纹 API 是否被植入内联 Hook
#include <windows.h>
#include <cstdint>

bool IsExportHooked(const wchar_t* dllName, const char* funcName) {
    HMODULE hMod = GetModuleHandleW(dllName);
    if (!hMod) return false;
    const auto* p = reinterpret_cast<const uint8_t*>(GetProcAddress(hMod, funcName));
    if (!p) return false;

    // 检查常见 x86/x64 inline hook 入口跳板字节
    if (p[0] == 0xE9 || p[0] == 0xEB || p[0] == 0xE8) return true;
    if (p[0] == 0xFF && p[1] == 0x25) return true;
    if (p[0] == 0x48 && p[1] == 0xB8) return true; // mov rax, addr
    if (p[0] == 0x49 && p[1] == 0xBB) return true; // mov r11, addr (Frida Gum 常见跳板)
    return false;
}

bool CheckLicensingSurfaceClean() {
    return !IsExportHooked(L"user32.dll", "GetDlgItemTextW") &&
           !IsExportHooked(L"advapi32.dll", "RegQueryValueExW") &&
           !IsExportHooked(L"kernel32.dll", "ReadFile");
}

防御工作流 ​

text
1. 进程加载防线:启用 ProcessSignaturePolicy (CIG) 阻止未签名的 Frida Agent 附加进程
2. API 入口完整性检验:调用 GetDlgItemTextW/RegQueryValueExW/ReadFile 前后检查出口首字节是否存在 E9/FF 25/49 BB 跳板
3. 消除裸全局状态位:将全局注册标志迁移为动态内存的 MAC 密封会话对象,读取时强制验真
4. 配置文件加密与签名:所有持久化授权文件必须经过服务端非对称签名并套用 DPAPI 本机封装,禁止信任明文 JSON 三键
5. 多源硬件绑定:结合 TPM 密钥或交叉系统指纹源并由服务端签发绑定租约,防止单一注册表 API Hook 欺骗

验证与判定标准 ​

  • Frida 挂钩拦截:使用 Frida 对 GetDlgItemTextW 或 RegQueryValueExW 安装 Interceptor.attach 后,IsExportHooked 必须返回 true 并中止激活流程。
  • 内存标志位篡改失效:在进程内存中将状态对象里的授权标志字修改后,verify_and_read 必须因 MAC 校验失败而拒绝放行。
  • 离线配置文件篡改与跨机失效:直接手工修改 Preferences.json 或将其复制到另一台不同 DPAPI 上下文的主机,unseal_preferences 必须返回拒绝错误。

MCP 工具映射 ​

防御步骤MCP 工具说明
检查目标进程敏感全局与明文配置读写ghidra_summary_function_detail、ghidra_summary_imports排查是否存在全局单字节状态标志与裸 JSON 写盘路径
生成检测 Frida 跳板的内存扫描规则make_yara_stub捕获 gum-js-loop 与 49 BB 跳板特征
核查二进制缓解策略基线triage_pe验证 CIG/ACG/CFG 启用状态
检索红蓝成对参考kb_router对照 05-keygen-frida-verification-loop.md 验证防御覆盖

Apache-2.0 · 仅供授权环境下的学习与防御性研究使用 · 免责声明