授权校验逻辑隐匿与抗静态/动态定位加固
防御目标与威胁面
在 02-validation-function-location.md 中,攻击者往往能在 5~30 分钟内精准提取核心校验函数 CheckKey,依赖的是程序留下的四类强信号灯:
- 明文字符串 Xrefs:
"Invalid license key"、"Thank you"、"License expired"等提示直接把反查链路缩短到 0~2 层调用。 - 集中式导入特征:在校验入口直接调用
GetDlgItemTextW、lstrcmpW、memcmp、RegGetValueW。 - 直观的 GUI 调用栈:按钮
WM_COMMAND回调同步直接调用CheckKey(key),对输入 API 下断并回溯即可直达算法核心。 - 单点布尔返回值(
EAX = 0/1):输入正确与错误 key 后观察同一函数返回值跳变(探针分化),极易被确认并直接打补丁跳过。
蓝队加固目标就是熄灭这些定位信标,抬高静态定位与动态栈回溯的分析成本。
核心防御架构
| 定位信标 | 加固措施 | 关键实现约束 |
|---|---|---|
| 明文错误/成功提示字符串 | 编译期字符串加密(Compile-Time XOR/AES 模板),仅在使用前按需栈上解密并在渲染后立即清零 | 严禁在 .rdata 留下 Invalid key、Registration successful、ProLicense 等明文标签;使用错误码映射通用提示 |
| 符号与导出暴露 | Stripped Release 构建禁止导出内部校验符号,禁用 PDB 随包分发与 RTTI 敏感类名暴露 | 仅导出必要接口,启用 LTO/内联将独立小函数打散 |
GetDlgItemTextW 直接栈回溯至校验器 | 异步队列解耦:UI 线程仅将输入拷贝至加密消息队列,由独立延迟/事件驱动工作线程消费 | 切断 GetDlgItemTextW 断点回溯栈与核心验签逻辑的直接调用关系 |
CheckKey(key) -> 0/1 单点布尔返回 | 去布尔化与多点分散内联:验签输出业务功能所需的解密密钥/配置结构,并将分散校验嵌入多个业务路径 | 不存在单个返回 0 或 1 的核心布尔判定点,破坏探针分化与 mov eax, 1; ret 劫持 |
lstrcmpW / memcmp 逐字节早退比较 | 使用恒定时间比较(ConstantTimeEquals)与基于哈希摘要的整块核对 | 杜绝逐字符比对造成的时间/断点旁路信息泄露 |
加固实现与检测代码
以下 C++ 示例演示编译期字符串混淆、恒定时间摘要比较以及用数据派生结果替代单点布尔返值:
cpp
// validation_hardening.cpp: 字符串编译期混淆、恒定时间比较与非布尔状态派生
#include <array>
#include <cstdint>
#include <cstddef>
#include <string>
template <size_t N, uint8_t Key>
struct ObfString {
std::array<char, N> data{};
constexpr ObfString(const char (&str)[N]) {
for (size_t i = 0; i < N; ++i) {
data[i] = static_cast<char>(str[i] ^ static_cast<char>(Key + i * 13));
}
}
std::string Decrypt() const {
std::string out(N - 1, '\0');
for (size_t i = 0; i < N - 1; ++i) {
out[i] = static_cast<char>(data[i] ^ static_cast<char>(Key + i * 13));
}
return out;
}
};
bool ConstantTimeEquals(const uint8_t* a, const uint8_t* b, size_t len) {
volatile uint8_t diff = 0;
for (size_t i = 0; i < len; ++i) {
diff |= static_cast<uint8_t>(a[i] ^ b[i]);
}
return diff == 0;
}
// 非布尔返值示范: 若凭证有效,输出正确的派生会话掩码;无效则产出伪随机废值,不显式返回 0/1
uint64_t DeriveFeatureSeed(const uint8_t* digest, const uint8_t* expected, uint64_t validSeed) {
uint64_t mismatchMask = 0;
for (size_t i = 0; i < 32; ++i) {
mismatchMask |= static_cast<uint64_t>(digest[i] ^ expected[i]);
}
// 无分支掩码: 匹配时 mask=0xFFFFFFFFFFFFFFFF, 失配时 mask=0
uint64_t allOneIfMatch = static_cast<uint64_t>(mismatchMask == 0) * ~0ULL;
return (validSeed & allOneIfMatch) ^ (0x5A5A5A5A12345678ULL & ~allOneIfMatch);
}以下 Python 发布前检查脚本用于扫描目标二进制中的明文授权定位字符串与高危调试符号暴露情况,确保发布产物不残留低成本 Xref 入口:
python
"""scan_beacon_exposure.py: 检查发布 PE 是否残留授权定位明文字符串或未剥离符号"""
from __future__ import annotations
import re
SENSITIVE_STRINGS = (
b"Invalid license",
b"Invalid serial",
b"Registration successful",
b"Thank you for registering",
b"CheckKey",
b"VerifyLicense",
b"IsRegistered",
)
def audit_exposed_beacons(binary_blob: bytes) -> list[dict]:
findings = []
lower_blob = binary_blob.lower()
for s in SENSITIVE_STRINGS:
idx = lower_blob.find(s.lower())
if idx != -1:
findings.append({"beacon": s.decode("ascii"), "offset": hex(idx)})
pdb_match = re.search(rb"[A-Za-z]:\\[^\x00]{4,120}\.pdb", binary_blob)
if pdb_match:
findings.append({"beacon": "exposed_pdb_path", "offset": hex(pdb_match.start())})
return findings
if __name__ == "__main__":
leaky_bin = b"\x00" * 32 + b"Invalid license key!\x00" + b"D:\\Build\\Release\\App.pdb\x00"
res = audit_exposed_beacons(leaky_bin)
assert len(res) == 2
clean_bin = b"\x48\x83\xEC\x28\x33\xC0\xC3"
assert audit_exposed_beacons(clean_bin) == []
print("Validation beacon exposure self-check passed.")防御工作流
text
1. 清理构建暴露面:Release 配置关闭导出符号与 PDB 路径嵌入,去除调试与日志中的授权语义描述
2. 字符串全量加密:使用编译期混淆模板替换所有激活成功/失败提示及配置键名字符串,禁用明文落盘在 .rdata
3. 异步隔离输入栈:UI 读取组件仅投递输入载荷至内部异步管道,打破 GetDlgItemTextW 到校验函数的直接调用栈
4. 消除单点布尔口:将核心授权校验内联并分散到多个业务初始化阶段,输出数据派生密钥而非单一 BOOL 返值
5. 发布门禁扫描:在 CI 中运行 rizin_strings 与扫描脚本,确认高危信标字符串暴露数为零验证与判定标准
- 静态信标清零:对加固后产物运行
rizin_strings与audit_exposed_beacons,不得检出CheckKey、Invalid license、Thank you或.pdb路径明文。 - 调用栈隔离验证:在
GetDlgItemTextW设置断点并触发激活后,当前调用栈不得出现核心密码学验签函数帧。 - 非布尔校验验证:向
DeriveFeatureSeed传入匹配与失配摘要,观察输出均为 64 位非零整数,避免简单0/1探针分化。
MCP 工具映射
| 防御步骤 | MCP 工具 | 说明 |
|---|---|---|
| 扫描发布包残留字符串与导出符号 | rizin_strings、rizin_imports | 排查明文激活提示语及直接暴露的比较函数 |
| 核查字符串交叉引用深度 | rizin_xrefs | 验证任何 UI 提示串均无法反查直达核心验签逻辑 |
| 反编译检查布尔瓶颈 | ghidra_headless_analyze、ghidra_summary_function_detail | 确认已消除单点 return 0/1 校验函数 |
| 检索红蓝成对参考 | kb_router | 对照 02-validation-function-location.md 检查所有定位路径 |