In Part 1 of this series, Coreflood lost its botnet because its bots obeyed whoever answered at the controller’s address. The FBI redirected 29 domains, and the bots took orders from a court-appointed server.
Conficker, which appeared in November 2008, made none of those mistakes. Its updates were signed with an RSA key that only its authors held. Its rendezvous domains changed every day, and one variant generated 50,000 of them daily across more than 100 top-level domains. Later versions also talked to each other directly, peer to peer. Nobody could send Conficker a stop command.
So the defenders played a different game. Instead of taking the botnet over, an informal alliance of researchers, registries and companies spent months making sure the authors could never reach it. This post is Part 2 of A Short History of Botnets: how Conficker was built, and how it was held in check without ever being captured.
A note on the exhibits and names. As in Part 1, the log and traffic exhibits are reconstructions built from published analyses, mainly SRI International’s reverse-engineering reports, and each says so. Variant names also need care: SRI’s “Conficker C” is the variant Microsoft calls Conficker.D. I use SRI’s letters, because most technical write-ups do.
Lineage: five variants in five months
Conficker was built on a vulnerability that already had a patch. Microsoft fixed MS08-067, a flaw in the Windows Server service reachable over TCP 445, in an out-of-band update on 23 October 2008. Conficker A appeared four weeks later and found millions of machines that had not installed it.
| Date | Event |
|---|---|
| 7 to 8 April 2009 | Variant E installs the Waledac spambot and SpyProtect 2009 scareware; removes them on 3 May |
| 1 April 2009 | Variant C’s new domain generator switches on; nothing visible happens |
| 4 to 5 March 2009 | Variant C appears: 50,000 domains a day and peer-to-peer updates |
| 16 to 20 February 2009 | Variant B++ appears (Microsoft’s “C”) |
| 12 February 2009 | Conficker Working Group announced; Microsoft offers a $250,000 reward |
| 1 January 2009 | Variant B starts contacting its domains |
| 29 December 2008 | Variant B released: password guessing on shares, USB spread |
| 26 November 2008 | Variant A starts contacting its domains |
| 20 to 21 November 2008 | Variant A first seen, spreading only through MS08-067 |
| 23 October 2008 | Microsoft releases the MS08-067 patch |
Dates are from the Conficker Working Group’s lessons-learned report and SRI International’s analyses; where they differ by a day, both are shown.
The scale was large and hard to pin down. SRI counted about 4.7 million unique IP addresses for A and 6.8 million for B, 10.5 million in total, while warning that dynamic addressing inflates such counts. Its own estimate of the active population at any moment was under 1 million for A and under 3 million for B. The Working Group’s later figure was 5 to 13 million infected computers within a year.
One small detail says something about the authors. Variant A checked the keyboard layout and quit if it found Ukrainian. In SRI’s data Ukraine had about 35,000 A infections but nearly 256,000 B infections, after B dropped the check.
How it spread: one exploit, then three doors
Variant A used a single way in: a crafted RPC request to TCP 445 that exploited MS08-067. The victim then downloaded the worm’s DLL from the attacking machine. Once installed, Conficker patched the vulnerable function in memory. The patch blocked other attackers’ exploits but still accepted Conficker’s own, so the worm could reinfect and update the machine while keeping competitors out.
Variant B added two methods that did not need any unpatched vulnerability (SRI):
- Network shares. B copied itself to the
ADMIN$share of other machines, guessing passwords from a built-in list of more than 240 common ones. Microsoft’s support article told administrators to remove all AT-created scheduled tasks, which the worm used to run its copy on the target. - USB drives. B wrote an
autorun.infto removable drives. Its AutoPlay entry was labelled “Open folder to view files”, so a user who wanted to see their files launched the worm instead.
The password guessing had a side effect that many administrators noticed before they found the worm. Microsoft’s first listed symptom was that “account lockout policies are being tripped”. A single infected laptop could lock out a large share of a domain’s accounts.
On the host, Microsoft listed the persistence traces: a randomly named service added to the netsvcs group under the SvcHost registry key, a ServiceDll pointing to a randomly named DLL in System32, and Automatic Updates, BITS, Windows Defender and Error Reporting switched off.
Exhibit 1 (reconstruction). Variant B’s share-based spread as it would appear in Windows logs today. The event sequence follows the documented behaviour; names, addresses and the DLL name are placeholders.
# Source: WS-0117 (10.40.2.117), infected | Target: WS-0203 | DC: DC-01
[Security 4625] x 214 within 90 s on WS-0203
Logon Type: 3 (Network)
Account Name: jdoe, asmith, administrator, ... <- many accounts
Failure Status: 0xC000006D / Sub Status 0xC000006A (bad password)
Source Address: 10.40.2.117
[Security 4740] x 37 on DC-01
A user account was locked out
Caller Computer: WS-0117 <- the infected host
[Security 4624] on WS-0203
Logon Type: 3 (Network)
Account Name: administrator <- password guessed
Source Address: 10.40.2.117
[Security 5145] on WS-0203
Share Name: \\*\ADMIN$
Relative Target: System32\<random>.dll <- placeholder
Accesses: WriteData (or AddFile)
[Security 4698] on WS-0203
A scheduled task was created
Task Name: \At1
Command: rundll32.exe <random>.dll,<export> <- placeholder
The signal here is the ratio, not any single event. Hundreds of failed network logons for many different accounts from one workstation, followed by lockouts on the domain controller, then a successful logon and a write to ADMIN$. Coreflood waited patiently for one good password. Conficker simply tried hundreds.
Inside the domain generator
Coreflood had a fixed list of controller domains, so taking it over meant seizing that list. Conficker replaced the list with an algorithm. Every bot computed the same set of domain names for the current day, and the authors only had to register one of them, briefly, to deliver an update.
SRI’s reverse engineering laid out how the generator worked:
| Variant A | Variant B | Variant C | |
|---|---|---|---|
| Domains per day | 250 | 250 | 50,000 |
| Domains queried per bot | all 250 | all 250 | 500, once a day |
| Check interval | every 3 hours | every 2 hours | every 24 hours |
| Name length | 5 to 11 letters | 5 to 11 letters | 4 to 10 letters |
| Top-level domains | 5: .com .net .org .info .biz | 8: adds .ws .cn .cc | 110 |
The seed was the current date, and Conficker did not trust the infected machine’s clock to supply it. It fetched the date from the HTTP Date header of a well-known site: w3.org, ask.com, msn.com, yahoo.com, google.com or baidu.com. A machine with a wrong clock would otherwise compute the wrong list and miss the rendezvous.
The design created an asymmetry that shaped everything that followed. To keep variant A from ever receiving an update, defenders had to block 250 domains a day across five registries, every day. For variant C, the number rose to 50,000 a day across 110 top-level domains, most of them country-code domains run by different organisations. The authors needed to win only once.
Exhibit 2 (illustrative). What a variant C bot looked like on the network on 1 April 2009. The sequence and counts follow SRI’s sandbox run, in which all 500 lookups failed; the domain names are invented examples of the format, not real Conficker output.
# 1. Get a trusted date (one of six well-known sites)
GET http://www.google.com/ HTTP/1.1
<- 200 OK Date: Wed, 01 Apr 2009 00:04:31 GMT
# 2. Resolve 500 of today's 50,000 candidate domains
00:04:33 A? qhwkzjru.com.br -> NXDOMAIN
00:04:33 A? mfcpeyt.cl -> NXDOMAIN
00:04:34 A? xbouvlwd.ws -> NXDOMAIN
00:04:34 A? tgrsizn.info -> NXDOMAIN
...
00:09:51 A? ydjqabkx.co.uk -> NXDOMAIN
# 3. Summary for this host, this day
queries=500 unique_tlds=94 nxdomain=500 answered=0
# 4. Nothing answered: sleep 24 hours, generate tomorrow's list
For a DNS analyst the pattern is unmistakable once you look for it: hundreds of lookups for short, random-looking names across dozens of country-code domains, almost all of them failing, from a single host within minutes. The hunting section turns this into queries.
Signed updates and peer-to-peer: no way in
The domain generator decided where a bot looked for updates. Cryptography decided what it would accept. According to SRI, every downloaded binary was encrypted with RC4 and had to carry a valid RSA signature before Conficker would run it. Variant A used a 1,024-bit key. Variant B moved to a key of about 4,096 bits; SRI’s report gives slightly different sizes in different places.
The hash behind the signature was MD6, a function Ron Rivest’s group had submitted to the US SHA-3 competition only weeks earlier. When the MD6 team published a fix for a buffer overflow in its reference code, the Conficker authors adopted the same fix, and variant C ships with it.
Variant C added a second channel that needed no domains at all. Infected machines opened two UDP and two TCP ports, derived from their own IP address, and scanned for other infected machines. Peers exchanged files directly, and every file passed the same signature check before it was stored, run or passed on (SRI addendum C).
Variant C also protected itself on the host. It disabled six Windows services including Security Center, Windows Defender, Windows Update and BITS. It killed processes whose names matched 23 security tools, such as Process Explorer, Wireshark and TCPView. And it patched the Windows DNS client in memory so that lookups containing strings like “symantec”, “kaspersky” or “microsoft” failed.
Put together, this closed the door that the FBI used against Coreflood. A defender who controlled a rendezvous domain could see which machines checked in, but could not tell them to do anything. Without the authors’ private key, there was nothing a sinkhole or a peer could send that the bots would run.
Exhibit 3. Three ways to reach a Conficker bot, one gate.

Whether a file came from the authors’ domain, a defender’s sinkhole or another infected machine, it ran only if it carried the authors’ signature. Compare this with Exhibit 3 in Part 1, where Coreflood ran whatever its controller address sent.
The defense: winning by denial, not by capture
If you cannot take a botnet over, you can still try to keep its authors from ever reaching it. That was the strategy of the Conficker Working Group, and its lessons-learned report is one of the most detailed public case studies of a private-sector response to a botnet.
It started small. In late January 2009 Microsoft asked Neustar, the .biz registry, to block Conficker’s .biz domains, and ICANN agreed to waive registration fees. A DNS security meeting in Atlanta on 3 and 4 February became the core of the group, which went public on 12 February with Microsoft’s $250,000 reward for information on the authors.
The method was straightforward to describe and hard to run:
- Reverse-engineer the domain generator, so every future day’s list is known in advance.
- Have the registries register or block those names before the authors can. Verisign, Neustar and Afilias covered the generic top-level domains, and the registries automated the work.
- Point the names at sinkholes, at first six of them and later a central one at Georgia Tech, and use the check-ins to count and locate infected machines.
Variant C turned this from 250 domains a day across five registries into 50,000 a day across 110 top-level domains. In March 2009 ICANN contacted more than 100 country-code registries, most of which had never dealt with anything like it. China’s .cn registry agreed to block its share and run its own sinkhole. By the group’s count, several million domains were blocked in total.
It was not perfect. On 10 and 17 March two domains were missed, and bots that reached them upgraded to the new variant. On 1 April, when variant C switched to 50,000 domains and many feared the worst, nothing visible happened: every domain was blocked. A week later the authors used the peer-to-peer channel instead, which needed no domains, to push variant E. It installed the Waledac spambot and SpyProtect 2009 scareware, then removed both and reverted on 3 May.
That is roughly where the story ends. The botnet was never used for a major attack, and its authors’ purpose was never confirmed. The report’s harshest lesson was about cleanup: the group contained the botnet but did little to remove it, and its members called the lack of remediation through ISPs the biggest failure. Variant C still had 300,000 to 400,000 unique IP addresses in December 2009, down from over a million in April.
Hunting the pattern today
Conficker’s specific signatures are history, but its two techniques are not. Domain generation algorithms are still used by malware families today, and password guessing against network logons is routine. Both are behaviours, so the rules below count and compare events instead of matching names.
Exhibit 4a: Sigma correlation, NXDOMAIN burst. A host that gets 100 or more failed lookups in ten minutes. This uses the Sigma correlation format (Sigma 2.0) on Zeek DNS logs; tune the threshold to your network, because misconfigured software can also produce bursts.
title: DNS NXDOMAIN Response
name: dns_nxdomain_response
status: experimental
logsource:
product: zeek
service: dns
detection:
selection:
rcode_name: 'NXDOMAIN'
condition: selection
---
title: Possible DGA Activity - NXDOMAIN Burst From One Host
id: 3f8b2c71-5d4e-4a90-b6c2-8e1f0d7a9b35
status: experimental
description: Many failed DNS lookups from one host in a short window, typical of a domain generation algorithm
correlation:
type: event_count
rules:
- dns_nxdomain_response
group-by:
- id.orig_h
timespan: 10m
condition:
gte: 100
falsepositives:
- Misconfigured applications, broken search suffixes, security scanners
level: medium
Exhibit 4b: Sigma correlation, password spraying over the network. Failed network logons for 20 or more different accounts from one source in five minutes, the variant B pattern from Exhibit 1.
title: Failed Network Logon
name: failed_network_logon
status: experimental
logsource:
product: windows
service: security
detection:
selection:
EventID: 4625
LogonType: 3
condition: selection
---
title: Network Logon Failures Across Many Accounts From One Source
id: 9a4e6d10-2b7c-4f58-a1e3-5c0b8d2f7e46
status: experimental
correlation:
type: value_count
rules:
- failed_network_logon
group-by:
- IpAddress
timespan: 5m
condition:
field: TargetUserName
gte: 20
tags:
- attack.credential-access
- attack.t1110.003
level: high
Exhibit 4c: Splunk, DGA profile per host. Counts queries, failures, distinct names and distinct top-level domains per host in ten-minute windows, using Zeek DNS data. Many failures across many top-level domains is the Conficker C shape.
index=zeek sourcetype=zeek:dns
| eval nx=if(rcode_name="NXDOMAIN", 1, 0)
| rex field=query "\.(?<tld>[^.]+)$"
| bin _time span=10m
| stats count AS queries sum(nx) AS nxdomain
dc(query) AS unique_names dc(tld) AS unique_tlds
BY _time, "id.orig_h"
| eval nx_ratio=round(nxdomain/queries, 2)
| where queries>=100 AND nx_ratio>=0.8 AND unique_tlds>=10
| sort - nxdomain
If you have the URL Toolbox app, adding the Shannon entropy of each name (ut_shannon) separates random-looking names from ordinary typos.
Exhibit 4d: KQL (Microsoft Sentinel). The same DNS profile through the ASIM DNS parser, plus the password-spraying check on Windows security events.
// DGA profile per source host, 10-minute windows
_Im_Dns(starttime = ago(1d))
| summarize Queries = count(),
NxDomain = countif(DnsResponseCodeName == "NXDOMAIN"),
UniqueNames = dcount(DnsQuery),
UniqueTlds = dcount(tostring(split(DnsQuery, ".")[-1]))
by SrcIpAddr, bin(TimeGenerated, 10m)
| extend NxRatio = round(todouble(NxDomain) / Queries, 2)
| where Queries >= 100 and NxRatio >= 0.8 and UniqueTlds >= 10
| order by NxDomain desc
// Network logon failures across many accounts from one source
SecurityEvent
| where EventID == 4625 and LogonType == 3
| summarize Failures = count(), Accounts = dcount(TargetUserName),
SampleAccounts = make_set(TargetUserName, 10)
by IpAddress, bin(TimeGenerated, 5m)
| where Accounts >= 20
| order by Accounts desc
The preventive controls are older than the queries and still matter more. Patch quickly; block SMB between workstations with the host firewall; enforce unique, strong local administrator passwords; and keep AutoRun disabled for removable media, which Microsoft later made the default.
What I take away
Signing works, for everyone. Coreflood and Conficker are the same experiment run twice. The botnet without signed commands was taken over in a week; the one with them was never taken over at all. If this is true for criminals’ infrastructure, it is true for ours: any fleet that accepts unsigned commands or updates can be steered by whoever controls its addresses.
The patch existed first. MS08-067 was fixed four weeks before Conficker A appeared. Millions of infections were not a zero-day story but a patch-latency story, and the same is true of many large incidents since.
Hunt the behaviour, not the family. A burst of failed lookups across dozens of top-level domains, or failed logons across dozens of accounts from one workstation, looks the same whatever the malware is called. Those rules outlive any signature.
Containment is not cleanup, again. Part 1 ended with about 19,000 cleaned machines out of millions. Conficker ended with a botnet that was neutralised but still installed on hundreds of thousands of machines a year later. In both cases the hard part was not the clever technique; it was reaching the owners.
Coordination is infrastructure. The Working Group succeeded on goodwill, volunteer nights and ICANN fee waivers, and its own report asks for formal leadership, paid coordinators and a proper channel to government. Those are not glamorous recommendations, but they are what decides whether the next response scales.
Conficker showed that peer-to-peer channels and signed updates could keep defenders out. Part 3 looks at GameOver Zeus, the banking botnet where defenders finally found a way into a peer-to-peer network.
Sources
- Phillip Porras, Hassen Saïdi and Vinod Yegneswaran, SRI International: An Analysis of Conficker’s Logic and Rendezvous Points (2009, covers variants A and B)
- SRI International: Conficker C Analysis (addendum C) (2009)
- SRI International: Conficker C P2P Protocol and Implementation (2009)
- Porras, Saïdi and Yegneswaran: A Foray into Conficker’s Logic and Rendezvous Points, USENIX LEET 2009
- The Rendon Group for the Conficker Working Group: Conficker Working Group: Lessons Learned (June 2010, published January 2011)
- Microsoft: Virus alert about the Win32/Conficker worm (KB962007)
- Part 1 of this series: Coreflood Under the Hood