CrPosts: 6 · Reputation: 97
Hey everyone,
I wanted to bring to light a research project I've been working on that looks into the historical random-number generation methods used in Bitcoin 0.1.5 on Windows.
Here’s the path I’m focusing on:
Windows XP SP3 CryptGenRandom
→ OpenSSL 0.9.8h
→ Bitcoin 0.1.5
Just to be clear, this isn’t about launching any kind of attack on Bitcoin or saying that we can figure out old private keys.
I’m not trying to promote or justify any attempts to access old wallets, private keys, or funds. That kind of use would totally miss the point of this research. Instead, I want to highlight the randomness processes from back in the day so we can better understand Bitcoin’s early software and help cut down on uncertainty.
This work is all about reducing risk and clearing up uncertainties around the early days of Satoshi’s work. Instead of vague ideas about how randomness was handled with the old Windows/OpenSSL/Bitcoin setup, I’m providing a detailed breakdown of what’s been observed, what can be replicated perfectly offline, what’s constrained by available data, and what still needs to be figured out.
I’m diving into some of the Windows XP CryptGenRandom processes using WinDbg/KD traces, memory dumps, controlled tests, and offline Python analysis.
CryptGenRandom is an actual cryptographic tool that’s been used for real, not just some theoretical idea. One really cool next step would be to see how diffusion plays out after the VLH step and then after going through the ADVAPI/SystemFunction036 process.
APosts: 33 · Reputation: 218
What is stopping a malicious user from using WinDbg to trace Bitcoin Core syscalls on later versions of Windows in order to extract private keys or the AES encryption state of the wallet password?
I don't think the developers even use WinDbg, they should block it from attaching to Windows version of Core.
CrPosts: 6 · Reputation: 97
Hello,
Thank you for your interest and for your question / comment.
Allow me to respond, but it seems to me that if the individual in question already has access to the machine with all the necessary privileges, then the situation is already, in a sense, quite badly compromised.
WinDbg is not a Bitcoin Core vulnerability by itself.
If an attacker has enough local privileges to attach a debugger to the Bitcoin Core process, or to kernel-debug the Windows machine, then the security boundary has already been crossed. At that point, secrets present in memory may be observable regardless of whether Bitcoin Core tries to block a specific debugger.
Wallet encryption protects private keys at rest. It does not provide a guarantee against a fully compromised live system, especially while the wallet is unlocked, signing is taking place, or sensitive material is resident in process memory.
Bitcoin Core has had external security review, including an audit by Quarkslab:
https://blog.quarkslab.com/bitcoin-core-audit.html
But that does not change the local-compromise threat model. Anti-debugging measures such as blocking WinDbg are generally not a strong security boundary: they are bypassable, can interfere with legitimate debugging and crash analysis, and do not stop an attacker with administrator or kernel-level control.
So the issue is not really that Bitcoin Core should block WinDbg. The more fundamental point is that Bitcoin Core cannot be expected to protect wallet secrets against a malicious local administrator, a kernel debugger, or a compromised operating system. In that threat model, the host itself is no longer trusted.
CrPosts: 6 · Reputation: 97
Hi,
Small status clarification on the Windows XP SP3 `CryptGenRandom` reconstruction work.
For this RNG path, there are two major parts that must both be handled:
transport side
provider side
The transport side is now strongly constrained by several `seed2state` campaigns. This includes the KSecDD / NewGenRandomEx / RC4 / ADVAPI / SystemFunction036 path, including repeated RC4/XOR checks and the ADVAPI round-robin behavior.
The provider side is also identified and partly replayed, but it is not fully closed yet. The final rsaenh block is replayed from captured inputs:
(state20, aux20) → out40 → CryptGenRandom output
The remaining provider-side point is still:
seedbase_after / provider persistent state → state20
So the former opaque `G` component is no longer just opaque. It is now decomposed into transport and provider sub-parts, with the transport side strongly constrained and the provider side narrowed but still under investigation.
There are also still two relevant paths to keep distinct:
KSecDD / ADVAPI / SystemFunction036 → rsaenh aux20
provider persistent state → state20
Both matter for understanding the RNG. Closing one does not automatically close the other.
So the current status is not full CryptGenRandom replay. It is:
transport path: strongly constrained
provider final block: replayed from captured state20 + aux20
provider state20 origin/evolution: still open
More work is still needed: more campaigns, edge cases, repeatability checks, output-size variation, ADVAPI boundary refinement, and provider-state tracking.
As before, this is not presented as a practical attack or as a claim that historical private keys can be recovered. The goal is to reduce uncertainty around the historical Windows XP → OpenSSL 0.9.8h → Bitcoin 0.1.5 randomness path, while also documenting the algorithmic beauty of this legacy RNG machinery.
CrPosts: 6 · Reputation: 97
Small update on the XP CryptGenRandom study.
I added a new replay campaign:
seed2state_v29_g_composed_provider_bridge
It validates the composed provider-side `rsaenh.dll` RNG relation in one captured execution:
G_provider =
G_init
+ G_acquire_bridge_00
+ G_runtime_measured
A useful correction came out of this work: I had initially spent time looking for an external source because of a wrong interpretation of the disassembly. The `out` argument at the FIPS-style block was not an upstream source for `aux20`; it was simply the local `out40` destination buffer.
The real missing piece was different: one additional provider transition occurs during `CryptAcquireContext`, between initialization and the first measured `CryptGenRandom(32)` call.
Once this bridge transition is included, the replay validates:
init_state20_after == bridge00_state20_before
bridge00_state20_after == runtime_state20_before
runtime_out40[:32] == measured CryptGenRandom(32) output
Next step: connect the upstream KSecDD / ADVAPI / SystemFunction036 path to the provider-side G relation closed in V29, aiming for a composed replay from KSecDD-derived material through rsaenh and finally to the measured CryptGenRandom(32) output.