
Análisis técnico de CVE-2008-0166 en Bitcoin 2009-2012, examinando las debilidades del PRNG de OpenSSL 0.9.8c, los fallos de entropía de Windows y la reconstrucción del espacio de claves.
Autor: análisis basado en bitcoin-code-r252 (0.1.5 / 0.2.0 / 0.3.24) y el diff original make-OpenSSL-0-9-8c-vulnerable-again.diff (THC) Fecha: 2026-09-10
Bitcoin 0.1.5 (enero de 2009) se ejecutaba exclusivamente en Windows, usando OpenSSL
0.9.8c para la generación de claves EC (EC_KEY_generate_key). En 2008,
se descubrió CVE-2008-0166 en el paquete OpenSSL de Debian, pero en Windows
el problema tenía una causa raíz diferente — no la ausencia de /dev/urandom, sino
la implementación de RandAddSeed() en el propio código fuente de Bitcoin.
En bitcoin-code-r252 (tags-0.1.5) la secuencia de inicialización del RNG
(util.cpp):
CInit::CInit() {
RAND_screen(); // (a) screen bitmap scrape
RandAddSeed(true); // (b) QPC + PerfMon
}
RandAddSeed (util.cpp:57-92):
QueryPerformanceCounter -> RAND_add(&PerformanceCount, 8, 1.5)RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Global", ..., buf=250000)
-> si ERROR_SUCCESS: SHA256(pdata) -> RAND_add(&hash, 32, entropy)Generación de claves (key.h:75-78):
CKey::MakeNewKey() {
EC_KEY_generate_key(pkey); // internally: RAND_bytes(32)
}
Importante: las versiones 0.1.5 a 0.3.24 no tenían keypool. GetRand()
usa RAND_bytes(8) para otros propósitos (nonces de red, apodos de IRC), alterando así
el estado del PRNG entre llamadas sucesivas a GenerateNewKey().
La versión 0.4.0 (septiembre de 2011) introdujo TopUpKeyPool() con un pool
predeterminado de 100 claves (GetArg("-keypool", 100)).
El grupo THC (The Hackers Choice) desarrolló thc-btc-rng-bruteforce,
usando un OpenSSL 0.9.8c modificado. Tres cambios críticos:
THC_hitme() (md_rand.c:133-180)THC_hitme(0): pone a cero state_num, state_index, entropy,
initialized, stirred_pool, md_count[0..1], md[], state[]
— reinicio completo del PRNG.THC_hitme(pid): almacena pid en la variable estática thc_pid.ssleay_rand_add() — contenido del búfer ignorado (línea 344)- MD_Update(&m, buf, j);
+ //MD_Update(&m, buf, j); // commented out!
RAND_add todavía avanza state_index en num e incrementa
md_count[1], pero el contenido del búfer no tiene efecto sobre el estado del PRNG.
ssleay_rand_bytes() — sustitución de PID (líneas 550-561)getpid() eliminada.curr_pid = thc_pid — valor constante de THC_hitme.MD_Update(&m, &curr_pid, sizeof(curr_pid)) adicional inyecta
el PID como la única entrada de variable externa.En el OpenSSL parcheado, el estado del PRNG depende únicamente de:
THC_hitme)RAND_add (parámetro num, no el contenido del búfer)RAND_bytes antes de la generación de clavesEn OpenSSL 0.9.8c, BN_ULONG se define mediante opensslconf.h:
#ifdef SIXTY_FOUR_BIT_LONG -> BN_ULONG = unsigned long (64-bit)
#ifdef THIRTY_TWO_BIT -> BN_ULONG = unsigned long (32-bit)
En Linux de 64 bits, OpenSSL usa por defecto SIXTY_FOUR_BIT_LONG, cambiando
sizeof(BN_ULONG) de 4 a 8 bytes. Esto afecta a:
static long md_count[2] (8 vs 16 bytes)1FkLYqPpfKPAR6EZh2RC6sDwTUA6Axb1XQ1JbxBrkBiSwpJUJN2bXrYJc51pH5rjrzTDBitcoin 2009 se ejecutaba en Windows XP de 32 bits, por lo que la reconstrucción correcta de claves
requiere compilación con THIRTY_TWO_BIT forzado (editar
include/openssl/opensslconf.h antes de compilar).
En una publicación del 27 de septiembre de 2012 (topic=113496), Sergio Lerner describió:
(a) RandAddSeed() llama a QueryPerformanceCounter() — requiere
alineación QWORD de su argumento. El compilador gcc en Windows podría fallar
en alinear a 8 bytes la variable de pila, causando un fallo silencioso.
(b) RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", ...) usa un
búfer fijo de 250.000 bytes. En Windows XP, los datos de rendimiento eran
~280 KB — la función devolvía ERROR_MORE_DATA y nunca se
volvía a llamar con un búfer mayor. debug.log no contenía ninguna advertencia.
Si ambos mecanismos fallaban, la única fuente de entropía era RAND_screen()
(bitmap de pantalla). En el OpenSSL parcheado incluso RAND_screen es irrelevante
porque el contenido del búfer se ignora.
Lerner recomendó registrar estos fallos en debug.log — esta corrección fue
incorporada en versiones posteriores de Bitcoin Core.
Verificado en bitcoin-code-r252 (tags: 0.1.5, 0.2.0, 0.3.0) y el repositorio bitcoin/bitcoin (tags: 0.3.24, 0.4.0, 0.5.0, 0.6.0):
| Versión | Fecha | Init RNG (Windows) | Keypool |
|---|---|---|---|
| 0.1.5 | 2009-01 | RAND_screen + RandAddSeed(true) | ninguno |
QPC->RAND_add(8) + perfmon->SHA256->RAND_add(32) | |||
| 0.2.0 | 2009-12 | RAND_screen (solo __WXMSW__) | ninguno |
QPC->RAND_add(8), perfmon separado cada 10 min | |||
| 0.3.0 | 2010 | igual | ninguno |
| 0.3.24 | 2011-07 | perfmon cada 10 min, RegQueryValueExA | ninguno |
| 0.4.0 | 2011-09 | igual | TopUpKeyPool 100 |
| 0.5.0 | 2011-12 | igual | 100 |
| 0.6.0 | 2012-03 | igual | 100 |
RAND_screen() se usó en todas las versiones de Windows hasta 2012.SHA256(pdata) -> RAND_add(32), las versiones posteriores
pasaban pdata sin procesar a RAND_add (el tamaño num diferente importa en el
OpenSSL parcheado porque solo el tamaño, no el contenido, afecta al estado).El espacio de claves para el ataque (en OpenSSL parcheado):
PID (1..32767)
x poll (count of RAND_add calls in init, modeling Toolhelp32 on WinXP)
x keypool position (1 for pre-0.4.0, 100 for 0.4.0+)
x profile (RAND call sequence for each Bitcoin version)
x architecture (le32 / le64)
Los parámetros tick, screen, cursor, hwnd, queue son
irrelevantes en este modelo porque el contenido del búfer de RAND_add se ignora.
Los autores originales de THC escribieron: "We did not find any." — no encontraron ninguna clave en la blockchain. El análisis confirma su hallazgo: la probabilidad de que exista una clave vulnerable on-chain depende enteramente de si alguna cartera Bitcoin fue realmente generada en un sistema Windows XP con el OpenSSL roto y perfmon/QPC fallando silenciosamente.
[1] Sergio Lerner, "Possible new vulnerability: poor entropy in Windows generated keypairs", Bitcointalk 2012-09-27: link
[2] Bitcoin StackExchange — "Was Satoshi using Windows or Linux?": link
[3] THC, thc-btc-rng-bruteforce: link
[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): link
[5] OpenSSL 0.9.8c + patch make-OpenSSL-0-9-8c-vulnerable-again.diff
[6] Analiza_entropy_win.txt — technical report, 2026-09-10