对2009-2012年比特币中CVE-2008-0166的技术分析,考察OpenSSL 0.9.8c PRNG弱点、Windows熵故障以及密钥空间重建。
作者: 基于 bitcoin-code-r252 (0.1.5 / 0.2.0 / 0.3.24) 以及 原始 make-OpenSSL-0-9-8c-vulnerable-again.diff (THC) 的分析 日期: 2026-09-10
Bitcoin 0.1.5(2009 年 1 月)仅在 Windows 上运行,使用 OpenSSL
0.9.8c 进行 EC 密钥生成(EC_KEY_generate_key)。2008 年,
CVE-2008-0166 在 Debian 的 OpenSSL 软件包中被发现,但在 Windows
上该问题有着不同的根本原因——并非缺少 /dev/urandom,而是
Bitcoin 源代码中 RandAddSeed() 的实现本身。
在 bitcoin-code-r252(tags-0.1.5)中,RNG 初始化序列
(util.cpp):
CInit::CInit() {
RAND_screen(); // (a) 屏幕位图抓取
RandAddSeed(true); // (b) QPC + PerfMon
}
RandAddSeed():
util.cpp:57-92QueryPerformanceCounter -> RAND_add(&PerformanceCount, 8, 1.5)RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Global", ..., buf=250000)
-> 如果 ERROR_SUCCESS:SHA256(pdata) -> RAND_add(&hash, 32, entropy)密钥生成(key.h:75-78):
CKey::MakeNewKey() {
EC_KEY_generate_key(pkey); // 内部:RAND_bytes(32)
}
重要提示:0.1.5 至 0.3.24 版本没有 keypool。GetRand()
将 RAND_bytes(8) 用于其他目的(网络 nonce、IRC 昵称),从而
在连续的 GenerateNewKey() 调用之间扰乱 PRNG 状态。
0.4.0 版本(2011 年 9 月)引入了 TopUpKeyPool(),默认
池大小为 100 个密钥(GetArg("-keypool", 100))。
THC 组织(The Hackers Choice)开发了 thc-btc-rng-bruteforce,
使用修改过的 OpenSSL 0.9.8c。三处关键改动:
THC_hitme()(md_rand.c:133-180)THC_hitme(0):将 state_num、state_index、entropy、
initialized、stirred_pool、md_count[0..1]、md[]、state[]
清零——完全重置 PRNG。THC_hitme(pid):将 pid 存入静态变量 thc_pid。ssleay_rand_add() — 缓冲区内容被忽略(第 344 行)- MD_Update(&m, buf, j);
+ //MD_Update(&m, buf, j); // 已注释掉!
RAND_add 仍会将 state_index 推进 num 并递增
md_count[1],但缓冲区内容对 PRNG 状态没有影响。
ssleay_rand_bytes() — PID 替换(第 550-561 行)getpid() 调用被移除。curr_pid = thc_pid——来自 THC_hitme 的常量值。MD_Update(&m, &curr_pid, sizeof(curr_pid)) 将
PID 作为唯一的外部变量输入注入。在打过补丁的 OpenSSL 中,PRNG 状态仅取决于:
THC_hitme)RAND_add 调用的顺序和大小(num 参数,而非缓冲区内容)RAND_bytes 的调用次数在 OpenSSL 0.9.8c 中,BN_ULONG 由 opensslconf.h 定义:
#ifdef SIXTY_FOUR_BIT_LONG -> BN_ULONG = unsigned long (64 位)
#ifdef THIRTY_TWO_BIT -> BN_ULONG = unsigned long (32 位)
在 64 位 Linux 上,OpenSSL 默认使用 SIXTY_FOUR_BIT_LONG,将
sizeof(BN_ULONG) 从 4 字节变为 8 字节。这会影响:
static long md_count[2] 的大小(8 字节 vs 16 字节)1FkLYqPpfKPAR6EZh2RC6sDwTUA6Axb1XQ1JbxBrkBiSwpJUJN2bXrYJc51pH5rjrzTDBitcoin 2009 运行在 Windows XP 32 位上,因此正确的密钥重建
需要强制使用 THIRTY_TWO_BIT 进行编译(在构建前编辑
include/openssl/opensslconf.h)。
在 2012 年 9 月 27 日的一篇帖子中(topic=113496),Sergio Lerner 描述:
(a) RandAddSeed() 调用 QueryPerformanceCounter()——要求
其参数按 QWORD 对齐。Windows 上的 gcc 编译器可能无法
将栈变量按 8 字节对齐,导致静默失败。
(b) RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", ...) 使用
固定大小的 250,000 字节缓冲区。在 Windows XP 上,性能数据约为
280 KB——该函数返回 ERROR_MORE_DATA,并且再也不会用
更大的缓冲区调用。debug.log 中没有任何警告。
如果这两个机制都失败了,唯一的熵源就是 RAND_screen()
(屏幕位图)。在打过补丁的 OpenSSL 中,即使 RAND_screen 也无关紧要,
因为缓冲区内容被忽略。
Lerner 建议将这些失败记录到 debug.log——该修复被
纳入后来的 Bitcoin Core 版本中。
已在 bitcoin-code-r252(tags:0.1.5、0.2.0、0.3.0)和 bitcoin/bitcoin 仓库(tags:0.3.24、0.4.0、0.5.0、0.6.0)中验证:
| 版本 | 日期 | 初始化 RNG(Windows) | Keypool |
|---|---|---|---|
| 0.1.5 | 2009-01 | RAND_screen + RandAddSeed(true) | 无 |
QPC->RAND_add(8) + perfmon->SHA256->RAND_add(32) | |||
| 0.2.0 | 2009-12 | RAND_screen(仅 __WXMSW__) | 无 |
QPC->RAND_add(8),perfmon 每 10 分钟单独执行 | |||
| 0.3.0 | 2010 | 相同 | 无 |
| 0.3.24 | 2011-07 | perfmon 每 10 分钟,RegQueryValueExA | 无 |
| 0.4.0 | 2011-09 | 相同 | TopUpKeyPool 100 |
| 0.5.0 | 2011-12 | 相同 | 100 |
| 0.6.0 | 2012-03 | 相同 | 100 |
RAND_screen() 在截至 2012 年的所有 Windows 版本上都被使用。SHA256(pdata) -> RAND_add(32),后续版本
将原始 pdata 传给 RAND_add(在打过补丁的 OpenSSL 中,
不同的 num 大小很重要,因为只有大小而非内容会影响状态)。攻击的密钥空间(在打过补丁的 OpenSSL 中):
PID (1..32767)
x poll(init 中 RAND_add 调用次数,模拟 WinXP 上的 Toolhelp32)
x keypool 位置(0.4.0 之前为 1,0.4.0+ 为 100)
x profile(每个 Bitcoin 版本的 RAND 调用序列)
x 架构(le32 / le64)
在此模型中,tick、screen、cursor、hwnd、queue 参数
无关紧要,因为 RAND_add 缓冲区内容被忽略。
THC 原作者写道:“我们没有找到任何。”——他们 没有在区块链上找到任何密钥。分析证实了他们的发现: 链上存在易受攻击密钥的概率完全取决于 是否真的有任何 Bitcoin 钱包是在 Windows XP 系统上使用 有缺陷的 OpenSSL 以及静默失败的 perfmon/QPC 生成的。
[1] Sergio Lerner,“可能的新漏洞:Windows 生成的密钥对中 熵不足”,Bitcointalk 2012-09-27: 链接
[2] Bitcoin StackExchange — “中本聪使用的是 Windows 还是 Linux?”: 链接
[3] THC,thc-btc-rng-bruteforce: 链接
[4] Bitcoin Core tags(0.1.5、0.2.0、0.3.0、0.3.24、0.4.0、0.5.0、0.6.0): 链接
[5] OpenSSL 0.9.8c + 补丁 make-OpenSSL-0-9-8c-vulnerable-again.diff
[6] Analiza_entropy_win.txt — 技术报告,2026-09-10