
تحليل تقني لـ CVE-2008-0166 في Bitcoin 2009-2012، مع فحص نقاط ضعف PRNG في OpenSSL 0.9.8c، وإخفاقات العشوائية في 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) يعمل حصريًا على Windows، باستخدام OpenSSL
0.9.8c لتوليد مفاتيح EC (EC_KEY_generate_key). في عام 2008،
تم اكتشاف CVE-2008-0166 في حزمة OpenSSL الخاصة بـ Debian، لكن على Windows
كانت المشكلة لها سبب جذري مختلف — ليس غياب /dev/urandom، بل
تنفيذ RandAddSeed() في الكود المصدري لـ Bitcoin نفسه.
في bitcoin-code-r252 (tags-0.1.5) تسلسل تهيئة 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)
-> if ERROR_SUCCESS: SHA256(pdata) -> RAND_add(&hash, 32, entropy)توليد المفتاح (key.h:75-78):
CKey::MakeNewKey() {
EC_KEY_generate_key(pkey); // internally: RAND_bytes(32)
}
مهم: الإصدارات من 0.1.5 حتى 0.3.24 لم يكن لديها keypool. يستخدم GetRand()
الدالة RAND_bytes(8) لأغراض أخرى (nonces الشبكة، ألقاب IRC)، مما
يعطّل حالة PRNG بين استدعاءات GenerateNewKey() المتتالية.
قدّم الإصدار 0.4.0 (سبتمبر 2011) الدالة TopUpKeyPool() مع pool افتراضي
يتكون من 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); // commented out!
لا يزال 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-bit)
#ifdef THIRTY_TWO_BIT -> BN_ULONG = unsigned long (32-bit)
على Linux بنظام 64-bit، يستخدم OpenSSL افتراضيًا SIXTY_FOUR_BIT_LONG، مما يغيّر
sizeof(BN_ULONG) من 4 إلى 8 بايت. يؤثر هذا على:
static long md_count[2] (8 مقابل 16 بايت)1FkLYqPpfKPAR6EZh2RC6sDwTUA6Axb1XQ1JbxBrkBiSwpJUJN2bXrYJc51pH5rjrzTDكان Bitcoin 2009 يعمل على Windows XP 32-bit، لذا تتطلب إعادة بناء المفتاح
الصحيحة الترجمة مع فرض THIRTY_TWO_BIT (عدّل
include/openssl/opensslconf.h قبل البناء).
في منشور بتاريخ 27 سبتمبر 2012 (topic=113496)، وصف Sergio Lerner:
(أ) RandAddSeed() يستدعي QueryPerformanceCounter() — يتطلب
محاذاة QWORD لمعامله. قد يفشل مترجم gcc على Windows في
محاذاة متغير المكدس إلى 8 بايت، مما يسبب فشلًا صامتًا.
(ب) RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", ...) يستخدم
مخزنًا مؤقتًا ثابتًا بحجم 250,000 بايت. على Windows XP، كانت بيانات الأداء
~280 KB — أعادت الدالة ERROR_MORE_DATA ولم يتم استدعاؤها مرة أخرى
أبدًا بمخزن مؤقت أكبر. لم يحتوي debug.log على أي تحذير.
إذا فشلت كلتا الآليتين، كان مصدر العشوائية الوحيد هو RAND_screen()
(bitmap الشاشة). في 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() على جميع إصدارات Windows حتى 2012.SHA256(pdata) -> RAND_add(32)، الإصدارات اللاحقة
مرّرت pdata الخام إلى RAND_add (حجم num المختلف مهم في
OpenSSL المُرقّع لأن الحجم فقط، وليس المحتوى، يؤثر على الحالة).فضاء المفاتيح للهجوم (في OpenSSL المُرقّع):
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)
معاملات tick، screen، cursor، hwnd، queue غير ذات صلة
في هذا النموذج لأن محتوى مخزن RAND_add يتم تجاهله.
كتب مؤلفو THC الأصليون: "We did not find any." — لم يجدوا أي مفاتيح على الـ blockchain. يؤكد التحليل اكتشافهم: تعتمد احتمالية وجود مفتاح ضعيف على السلسلة بالكامل على ما إذا كان قد تم فعلاً توليد أي محفظة Bitcoin على نظام Windows XP مع OpenSSL المعطوب وperfmon/QPC الفاشل صامتًا.
[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 — تقرير تقني، 2026-09-10