微端参数注入实现(仅供参考)

一、技术背景:为什么 EOF Append 可行?

Windows 可执行文件基于 PE(Portable Executable)格式
PE Loader 只会根据:

  • DOS Header
  • NT Header
  • Section Table
  • 各 Section 指定的 SizeOfRawData / VirtualSize

去加载必要数据。

关键点:

只要不破坏 Header 和 Section 结构,在文件末尾追加任意字节数据,不会影响程序正常运行。

这使得我们可以:

  • 不重新编译
  • 不修改代码段
  • 不改变入口点
  • 不影响签名结构(需注意签名位置)

即可实现“无损参数注入”。


二、整体架构设计

完整流程拆为 4 层:

  1. 广告点击 → 生成 click_id (即 #adwCode# 变量)
  2. 下载请求拦截 → 动态追加参数
  3. 客户端启动 → 读取自身尾部参数
  4. 激活 → S2S 上报

三、服务端实现(核心部分)

1️⃣ 生成唯一 click_id

广告点击时:

用户点击广告
↓
联盟回传 click_id
↓
跳转下载地址
https://download.example.com/launcher.exe?click_id=abc123
 

2️⃣ 下载时动态追加参数

核心思想

在原始 Launcher.exe 文件末尾追加:

[JSON数据]
[长度字段]
[Magic标识]
 

例如:

{"click_id":"abc123","ts":1700000000}
00000034
ADTAG
 

推荐结构:

| 原始EXE |
| JSON数据 |
| uint32 数据长度 |
| 5字节 Magic 标识 |
 

例如:

  • Magic: ADTAG
  • 长度字段:4字节 little endian

3️⃣ 服务端代码示例(Go 实现流式追加)

func ServeLauncher(w http.ResponseWriter, r *http.Request) {
    clickID := r.URL.Query().Get("click_id")

    baseFile, _ := os.Open("Launcher.exe")
    defer baseFile.Close()

    stat, _ := baseFile.Stat()

    // 设置响应头
    w.Header().Set("Content-Type", "application/octet-stream")
    w.Header().Set("Content-Disposition",
        fmt.Sprintf(`attachment; filename="Launcher.exe"`))
    w.Header().Set("Content-Length",
        strconv.FormatInt(stat.Size()+int64(len(clickID)+16), 10))

    // 1️⃣ 发送原始文件
    io.Copy(w, baseFile)

    // 2️⃣ 追加参数
    data := fmt.Sprintf(`{"click_id":"%s"}`, clickID)
    w.Write([]byte(data))

    // 3️⃣ 写入长度
    binary.Write(w, binary.LittleEndian, uint32(len(data)))

    // 4️⃣ 写入Magic
    w.Write([]byte("ADTAG"))
}
 

特点:

  • 无需生成新文件
  • 零磁盘 IO
  • 流式处理
  • 可承载高并发

四、客户端读取自身参数

微端启动时:

  1. 打开自身文件
  2. 从文件尾部读取
  3. 校验 Magic
  4. 读取长度
  5. 解析 JSON

Windows 读取自身路径

char path[MAX_PATH];
GetModuleFileNameA(NULL, path, MAX_PATH);
 

读取尾部数据示例(C++)

std::ifstream file(path, std::ios::binary | std::ios::ate);
auto size = file.tellg();

// 读取Magic
file.seekg(size - 5);
char magic[6] = {0};
file.read(magic, 5);

if (std::string(magic) == "ADTAG") {

    // 读取长度
    file.seekg(size - 9);
    uint32_t dataLen;
    file.read(reinterpret_cast<char*>(&dataLen), 4);

    // 读取JSON
    file.seekg(size - 9 - dataLen);
    std::vector<char> buffer(dataLen);
    file.read(buffer.data(), dataLen);

    std::string json(buffer.begin(), buffer.end());
}
 

五、完整流程时序图

广告点击
   ↓
生成 click_id
   ↓
下载请求带 click_id
   ↓
服务器流式追加
   ↓
用户运行 Launcher.exe
   ↓
读取自身尾部
   ↓
提取 click_id
   ↓
向服务器上报激活
   ↓
下载完整游戏包
 

六、安全与风控设计(关键)

1️⃣ 防伪造

客户端上报时必须:

  • 服务器生成 click_id
  • click_id 存数据库
  • 上报时校验是否存在

2️⃣ 防篡改

推荐改进结构:

{
  "click_id":"abc",
  "sign":"HMAC_SHA256(click_id+secret)"
}
 

客户端只读取,不生成签名。

服务器校验签名。


3️⃣ 防重放

数据库字段:

click_id
activated (bool)
activated_ip
activated_time
 

七、代码签名注意事项

重要

如果 EXE 已做 Authenticode 签名:

  • Windows 允许在签名块之后追加数据
  • 但必须确认签名覆盖范围

建议:

  • 先签名
  • 再做 EOF Append
  • 或使用内嵌资源区方案

八、性能优势

方案 带宽消耗 归因精度 改造成本
EOF Append 极低 极高
重打包
文件名注入 极低

EOF Append 的核心优势:

  • 不需要重编译
  • 不影响 CDN 缓存
  • 支持大规模并发
  • 不污染主程序结构

九、进阶增强方案

1️⃣ AES 加密尾部数据

避免被简单十六进制查看。

2️⃣ 压缩尾部数据

减少体积。

3️⃣ 加入版本字段

{
  "v":1,
  "click_id":"xxx"
}
 

便于后续扩展。


十、企业级推荐架构

高并发场景建议:

  • Nginx 反向代理
  • Go/Node 动态流式服务
  • click_id Redis 缓存
  • 激活上报接口独立服务
  • 异步写库

总结

微端 EOF Append 本质是:

在不破坏 PE 结构的前提下,把 click_id 注入到文件尾部,由客户端自行解析并完成归因。

它是端游行业使用十多年的成熟方案:

  • 技术稳定
  • 归因精准
  • 性能优越
  • 风险可控

让您的广告更简单优雅更有效~