Mirai Botnet Series - Part 6 of 6 (Final)
| Part | Title | Status |
|---|---|---|
| 1 | From Zero to Botnet: What Is a Botnet and Why Should You Care? | |
| 2 | How Mirai Actually Infects a Device | |
| 3 | I Opened Miraiβs Source Code β Here Is What I Found | |
| 4 | I Read Miraiβs Scanner Code β Here Is How It Infects Millions | |
| 5 | I Read the Brain of Mirai β How the C2 Server Actually Works | |
| 6 β You are here | Miraiβs Weapons β How 600,000 Cameras Brought Down the Internet |
β Previous: Part 5: The Brain of Mirai β C2 Server
Youβve completed the full Mirai series!
Start the next series: Side-Channel Power Analysis with ChipWhisperer
Blog 6 of 7 in the Mirai Botnet Series
Let me be honest with you.
When I started this series, I thought the scanning part was the clever bit. How Mirai finds victims. How it cracks passwords. That whole story.
I was wrong.
The DDoS engine is where things get genuinely insane.
Because infecting cameras is just building the army. The attacks are where that army actually goes to war. And when I read the source code for how each attack works I literally laughed. Not because it is funny. Because the thinking behind each one is so calculated, so strategic, that it is almost hard to believe one person wrote it.
We have covered a lot of ground together in this series. We know how bots get infected. We know how the C2 commands them. Now we answer the final question.
What exactly do those 600,000 bots DO when they attack?
Let us find out.
Before We Start β Two Ways To Kill a Server
Every attack we cover today falls into one of two categories. Burn these into your memory before reading further.
BANDWIDTH EXHAUSTION
ββββββββββββββββββββ
The road to the server is completely jammed.
Traffic is so heavy that nobody can even reach it.
The server itself is fine. The pipe is full.
CONNECTION EXHAUSTION
βββββββββββββββββββββ
The road is fine. But the server's waiting room
is completely packed with fake visitors.
Real users cannot get a slot.
Think of a chai stall that can serve 10 customers per minute. One thousand customers showing up simultaneously does not break the stall. It just means nobody gets served. The stall is effectively down even though nothing physically broke.
DDoS is exactly that. Volume versus capacity.
Now. The weapons.
The Weapon Menu β attack.h
Before we go into each attack, open this file:
https://github.com/jgamblin/Mirai-Source-Code/blob/master/mirai/bot/attack.h
At the top you will see this:
#define ATK_VEC_UDP 0 /* Straight up UDP flood */
#define ATK_VEC_VSE 1 /* Valve Source Engine query flood */
#define ATK_VEC_DNS 2 /* DNS water torture */
#define ATK_VEC_SYN 3 /* SYN flood with options */
#define ATK_VEC_ACK 4 /* ACK flood */
#define ATK_VEC_STOMP 5 /* ACK flood to bypass mitigation devices */
#define ATK_VEC_GREIP 6 /* GRE IP flood */
#define ATK_VEC_GREETH 7 /* GRE Ethernet flood */
//#define ATK_VEC_PROXY 8 /* Proxy knockback connection */
#define ATK_VEC_UDP_PLAIN 9 /* Plain UDP flood optimized for speed */
#define ATK_VEC_HTTP 10 /* HTTP layer 7 flood */
Ten attack methods. Notice number 8 β commented out. // means disabled. The proxy attack was written but never shipped. Even the author had limits apparently.
This is the menu. Each number is a different weapon. The C2 sends one of these numbers inside that 16-byte attack packet we covered in Blog 5. The bot reads it, picks the right function, and starts attacking.
Let us go through each one.
Weapon 1 - UDP Flood (ATK_VEC_UDP)
The concept:
Remember TCP from Blog 2? Three-way handshake. SYN, SYN-ACK, ACK. A whole conversation before any real data moves.
UDP has none of that. You just throw packets. No handshake. No confirmation. No return address required. You do not even care if the other side receives it.
TCP: "Hey, you there?" β
"Yes I'm here!" β
"Okay, sending now" β
[actual data] β
UDP: [packet] β
[packet] β
[packet] β
[packet] β
(I don't care if you listen. I will just flood.)
That last line is the entire philosophy of UDP flood. I do not care if you listen. I will just flood.
No waiting. No handshaking. Just maximum packets per second, as fast as possible.
The code:
Open attack_udp.c and find attack_udp_generic. Look at this section inside the while(TRUE) loop:
if (sport == 0xffff)
udph->source = rand_next();
if (dport == 0xffff)
udph->dest = rand_next();
// Randomize packet content?
if (data_rand)
rand_str(data, data_len);
Three things randomized every single packet: source port, destination port, packet contents.
Why randomize?
Think from the defenderβs side.
If every packet looks identical same size, same content, same ports a firewall can write one rule. βBlock all packets that look like THIS.β Done. Attack neutralized in seconds.
But if every packet is different? Random content, random ports, random source IP? The firewall cannot write a rule. Every packet looks like it could be legitimate traffic.
This is why data_rand = TRUE by default. The randomization is not accidental. It is specifically designed to defeat rule-based firewalls.
PREDICTABLE FLOOD: MIRAI'S RANDOM FLOOD:
[packet] identical [packet] different every time
[packet] identical β [packet] different every time β
[packet] identical [packet] different every time
Firewall writes one rule. Firewall has no pattern to block.
Attack neutralized. Attack continues.
But what about smart firewalls?
You might be thinking: modern AI-based firewalls do behavioral analysis. They detect that this traffic has no real content and block it.
Fair point. And you are right that against services like Cloudflare or AWS Shield, a simple UDP flood struggles.
But here is the honest answer. Even a smart firewall has a capacity limit.
YOUR PIPE: ββββββββββ 80% full β you can still filter
MIRAI AT PEAK: ββββββββββββββββββββββββ 1.2 Tbps
Your pipe is COMPLETELY FULL before
packets even reach your firewall.
The filtering hardware itself gets overwhelmed. You cannot filter what you cannot receive.
And there is another thing. Even when the firewall successfully rejects a packet β it still had to LOOK at it first. And looking costs CPU, memory, time. Security researchers call this the inspection tax.
FLOOD ATTACK:
[packet] β firewall looks β rejects β
[packet] β firewall looks β rejects β firewall CPU = 100%
[packet] β firewall looks β rejects β
[packet] β firewall looks β rejects
The firewall is busy REJECTING.
It has no capacity left to serve real users.
Target is effectively down.
The attacker does not need to get THROUGH the firewall. He just needs to keep the firewall BUSY.
It is like a security guard at a gate. Even if he rejects every fake visitor, if 10,000 fake visitors show up every second real visitors can never get through. The guard is exhausted just saying no.
Type: Bandwidth exhaustion.
Weapon 2 - SYN Flood (ATK_VEC_SYN)
The concept:
This one is more clever. It does not brute force the pipe. It exploits how TCP was designed.
You know the TCP three-way handshake. SYN β SYN-ACK β ACK. But here is what happens on the SERVER side during that handshake:
NORMAL HANDSHAKE:
Bot sends SYN β
β Server sends SYN-ACK
Server creates a "slot" in memory
and WAITS for ACK to complete
Bot sends ACK β
Connection complete. Slot filled.
SYN FLOOD:
Bot sends SYN β
β Server sends SYN-ACK
Server creates slot. WAITS...
Bot sends SYN β
β Server sends SYN-ACK
Server creates slot. WAITS...
Bot sends SYN β
WAITS... WAITS... WAITS...
All slots are full.
Real users cannot connect.
The server is holding thousands of half-open connections, waiting for an ACK that will never come. Each one occupies memory. When memory is full nobody new can connect.
And look at this line from attack_tcp_syn:
iph->saddr = rand_next();
The source IP is randomized. Fake. The server sends SYN-ACK to an address that does not exist. Nobody ever completes the handshake. The slots fill up forever.
Here is what makes this attack elegant: the server is not broken. It is following the rules perfectly. And following the rules perfectly is what kills it.
You are not finding a bug. You are weaponizing correct behavior.
Type: Connection exhaustion.
Weapon 3 - ACK Flood (ATK_VEC_ACK)
The concept:
After Mirai-era attacks, firewall vendors got smart. They learned to detect SYN floods.
Simple rule: βIf I see a SYN with no completed handshake β suspicious. Block it.β
The attacker has a problem. SYN packets get blocked. What do you do?
You send ACK packets instead.
Here is why that is sneaky.
STATELESS FIREWALL LOGIC:
SYN with no handshake = suspicious β BLOCK
ACK with no handshake = looks legitimate β PASS
ACK means βI am acknowledging something.β ACK implies there was a connection before. A stateless firewall one with no memory of past packets sees ACK and thinks it must be part of an existing conversation.
But the server checks. βWait. I have no record of this connection.β Drops the packet.
But here is the damage. The packet already got through the firewall. The server already had to search its records. A million times per second that search exhausts the server.
STATELESS FIREWALL: No memory β fooled by ACK
STATEFUL FIREWALL: Tracks connections β catches this
Stateless = sees each packet in isolation, no history
Stateful = remembers what happened before, detects patterns
The key word is stateless. A firewall that does not track connection state cannot detect that these ACKs have no matching SYN. It sees ACK and waves it through.
Type: Exploits firewall blindness.
Weapon 4 - HTTP Flood (ATK_VEC_HTTP)
The concept:
Every attack so far looks suspicious at some level. No real content. Incomplete handshake. Strange patterns.
HTTP flood looks completely real. Because it IS real.
Mirai actually completes the full TCP handshake. Then sends a legitimate HTTP GET request. With proper browser headers. With a real User-Agent string. The server cannot easily tell Mirai apart from an actual user.
Why is this so hard to defend against?
Because HTTP is always the open port. Whatever service you are running your website, your API, your login page HTTP port 80 or HTTPS port 443 is open. That is the whole point of having a website. You cannot block HTTP. If you do, your website goes offline by your own hand.
BLOCK UDP? Easy. Most websites do not need random UDP.
BLOCK SYN? Possible. Track incomplete handshakes.
BLOCK HTTP? You just shut down your own website.
The attacker did not even need to do anything.
The attacker puts the defender in an impossible position.
The code fake browser identities:
Look at table.h:
#define TABLE_HTTP_ONE 47 /* Mozilla/5.0 (Windows NT 10.0; WOW64) Chrome/51.0... */
#define TABLE_HTTP_TWO 48 /* Mozilla/5.0 (Windows NT 10.0; WOW64) Chrome/52.0... */
#define TABLE_HTTP_THREE 49 /* Mozilla/5.0 (Windows NT 6.1; WOW64) Chrome/51.0... */
#define TABLE_HTTP_FOUR 50 /* Mozilla/5.0 (Windows NT 6.1; WOW64) Chrome/52.0... */
#define TABLE_HTTP_FIVE 51 /* Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)... */
Five different fake browser identities. Each bot randomly picks one. So the server sees traffic that looks like it is coming from Chrome on Windows, Safari on Mac, Firefox all mixed together. Exactly like real users on a real website.
Mirai even detects Cloudflare:
Look at this in attack_app.c:
if (util_stristr(...TABLE_ATK_CLOUDFLARE_NGINX...))
conn->protection_type = HTTP_PROT_CLOUDFLARE;
if (util_stristr(...TABLE_ATK_DOSARREST...))
conn->protection_type = HTTP_PROT_DOSARREST;
It reads the serverβs response headers. If it sees βcloudflare-nginxβ it labels this connection as Cloudflare protected and changes behavior.
Like a burglar checking which lock is on the door before choosing which tool to use.
Honest disclosure: against a properly configured Cloudflare setup, Miraiβs HTTP flood mostly fails. IoT devices cannot solve CAPTCHAs or run JavaScript. But most websites in 2016 were not behind Cloudflare. Regular hosting, small DNS providers, gaming servers wide open.
Type: Layer 7 (application layer) attack. Hardest to defend.
Weapon 5 - DNS Water Torture (ATK_VEC_DNS)
The concept:
This one is completely different from everything above. All previous attacks had Mirai sending traffic directly to the target.
DNS water torture makes other servers do the attacking for it.
First, understand two types of DNS servers:
AUTHORITATIVE DNS:
"I am responsible for target.com"
"The answer is 1.2.3.4"
I know the answer directly.
RECURSIVE DNS (like Google's 8.8.8.8):
"I don't know. Let me ask around."
Goes and finds the answer for you.
Returns it back.
Mirai targets a domain let us say victim.com. It sends thousands of requests to public recursive DNS servers like 8.8.8.8, asking for random subdomains that do not exist:
abc123.victim.com β does not exist
xyz789.victim.com β does not exist
qwerty99.victim.com β does not exist
The recursive DNS servers do not know these subdomains do not exist. They have to ask the authoritative DNS server for victim.com. Every single time. For every random subdomain.
The authoritative DNS server gets crushed with lookups.
NORMAL DDOS:
Mirai β floods Target directly
Target sees attack coming from Mirai bots
DNS WATER TORTURE:
Mirai β asks DNS servers β DNS servers flood Target
Target sees attack coming from LEGITIMATE DNS servers
Target cannot block them - DNS is essential infrastructure
And here is the brilliant part you noticed: even the firewall cannot help. The authoritative DNS server MUST respond to every query. That is its job. Refusing to answer means the entire domain stops working.
The attacker found a server that cannot say no.
Mirai is hiding behind innocent DNS servers. The DNS servers are the weapon. Mirai is just pulling the trigger.
Type: Amplification attack. Uses trusted infrastructure against the target.
Weapons 6 and 7 - GRE Floods (ATK_VEC_GREIP and ATK_VEC_GREETH)
The concept:
GRE stands for Generic Routing Encapsulation. It is a tunneling protocol a way to wrap one packet inside another packet.
Think of it like putting a letter inside another envelope. The outer envelope gets delivered. When it arrives, someone opens it to find another envelope inside.
NORMAL PACKET:
[IP Header][Data]
GRE PACKET:
[IP Header][GRE Header][Inner IP Header][Data]
Why does this help an attacker?
Two reasons.
First, GRE traffic confuses many deep packet inspection systems. They see the outer envelope and process it. The inner contents create extra work.
Second, GRE packets are harder to filter. Blocking GRE entirely can break legitimate VPN traffic and network tunnels things businesses depend on. Defenders are reluctant to block it.
ATK_VEC_GREIP wraps IP packets inside GRE. ATK_VEC_GREETH wraps full Ethernet frames. Two variants. Same concept. Different layers of encapsulation.
Type: Tunneling-based flood. Confuses inspection systems.
Weapon 8 - VSE Flood (ATK_VEC_VSE)
The concept:
VSE stands for Valve Source Engine the game engine behind Counter-Strike, Team Fortress 2, Half-Life. Source Engine game servers respond to a specific query packet with game server information.
Mirai sends those query packets. The game servers respond with large responses. Small packet in, large response out.
This is a specific amplification attack targeting the gaming communityβs own infrastructure against itself.
It also tells you something about who Mirai was initially built to target. The gaming world had a serious DDoS problem in 2016. Competitors would DDoS each otherβs servers during tournaments. Mirai made that much easier and much bigger.
Type: Application-specific amplification.
How attack_start() Actually Fires
When the bot receives an attack command from the C2, it calls one function. Let us look at the real code:
void attack_start(int duration, ATTACK_VECTOR vector,
uint8_t targs_len, struct attack_target *targs,
uint8_t opts_len, struct attack_option *opts)
{
int pid1, pid2;
pid1 = fork();
if (pid1 == -1 || pid1 > 0)
return;
pid2 = fork();
if (pid2 == -1)
exit(0);
else if (pid2 == 0)
{
sleep(duration);
kill(getppid(), 9);
exit(0);
}
else
{
for (i = 0; i < methods_len; i++)
{
if (methods[i]->vector == vector)
{
methods[i]->func(targs_len, targs, opts_len, opts);
break;
}
}
exit(0);
}
}
Two fork() calls. The process splits itself into child processes.
Original Bot Process
β
βββ fork() #1
β β
β βββ Child 1: Timer process
β β Sleeps for [duration] seconds
β β Then kills the attack process
β β
β βββ Child 2: Attack process
β Picks the right attack function
β Runs the attack in an infinite loop
β Exits when killed by Child 1
β
βββ Original: Returns to normal bot operation
Still connected to C2. Still receiving commands.
One infected camera becomes two simultaneous processes. The original continues normal bot duties heartbeat to C2, listening for new commands. The children run the actual attack.
And in attack_init(), all ten attack functions are registered like plugins:
BOOL attack_init(void)
{
add_attack(ATK_VEC_UDP, (ATTACK_FUNC)attack_udp_generic);
add_attack(ATK_VEC_VSE, (ATTACK_FUNC)attack_udp_vse);
add_attack(ATK_VEC_DNS, (ATTACK_FUNC)attack_udp_dns);
add_attack(ATK_VEC_UDP_PLAIN, (ATTACK_FUNC)attack_udp_plain);
add_attack(ATK_VEC_SYN, (ATTACK_FUNC)attack_tcp_syn);
add_attack(ATK_VEC_ACK, (ATTACK_FUNC)attack_tcp_ack);
add_attack(ATK_VEC_STOMP, (ATTACK_FUNC)attack_tcp_stomp);
add_attack(ATK_VEC_GREIP, (ATTACK_FUNC)attack_gre_ip);
add_attack(ATK_VEC_GREETH, (ATTACK_FUNC)attack_gre_eth);
add_attack(ATK_VEC_HTTP, (ATTACK_FUNC)attack_app_http);
return TRUE;
}
The attacker sends one number. The bot looks it up in this list. Calls the matching function. The attack begins.
Ten weapons. One lookup table. Beautifully simple architecture.
The Day Everything Went Down β Dyn DNS, October 21, 2016
Everything we have covered today came together on one date.
October 21, 2016.
Someone pointed Mirai at Dyn DNS. Not a website. Not a game server. The phone book of the internet itself. Dyn was a major DNS provider used by some of the biggest websites on the internet.
Take down Dyn. Take down every website that depends on Dyn.
Dyn goes down
β
βββ Twitter β down
βββ Netflix β down
βββ Reddit β down
βββ GitHub β down
βββ CNN β down
βββ PayPal β down
βββ Spotify β down
βββ hundreds more...
1.2 Terabits per second. The largest DDoS attack in history at that time.
Caused by cameras. DVRs. Baby monitors. Routers. Devices with default passwords that nobody had changed. Devices that their owners never thought about.
The attack lasted most of the day. Millions of people could not access services they relied on. Businesses lost money. Trust was shaken.
The attacker was never conclusively identified. The army was real. The general disappeared.
Here is what hit me when I first understood this fully.
The author of Mirai did not just write malware. He studied the entire internetβs infrastructure. He understood which target would cause maximum damage. He built a system so well-engineered that it infected 600,000 devices before anyone understood what was happening.
Then he released the source code publicly.
Which is why we can sit here today and read every line of how it was done.
Whether that was strategy or recklessness or something else entirely you can decide for yourself.
Why IoT Devices Are Perfect DDoS Weapons
Before we close, let me answer something I kept wondering when I first learned this.
Why cameras and DVRs? Why not hack powerful computers?
Three reasons.
REASON 1: ALWAYS ON
A laptop gets shut down. A phone goes on airplane mode.
A security camera runs 24/7/365. It never sleeps.
Always connected. Always available to attack.
REASON 2: HIGH BANDWIDTH
IoT devices sit on home or business internet connections.
100 Mbps. 500 Mbps. Even 1 Gbps fiber connections.
600,000 devices Γ average bandwidth = terrifying numbers.
REASON 3: ZERO SECURITY
No antivirus. No monitoring. No security team.
The owner never looks at it.
Mirai can live on a device for months undetected.
A regular computer is a difficult target. It has security software, regular updates, an owner who notices strange behavior.
A cheap camera from a manufacturer nobody has heard of, running firmware from 2014, with a default password nobody changed, connected to a 500 Mbps business fiber line?
That is the perfect weapon. And there were hundreds of thousands of them on the internet.
What I Found Confusing (And Now Donβt)
βIf firewalls block SYN floods, why does ACK flood work?β
Because not all firewalls are stateful. A stateless firewall has no memory of previous packets. It sees each packet in isolation. An ACK looks legitimate to it because ACK means βI am acknowledgingβ which sounds like a real connection. A stateful firewall would catch it because it tracks the history and knows no SYN came before this ACK.
βWhy would a DNS server keep answering requests that seem suspicious?β
Because the DNS server cannot tell the difference between Mirai asking and a real user asking. The request looks legitimate. The subdomain might exist on some other day. Refusing to look it up means breaking DNS resolution for real users too. The authoritative server is stuck answering.
βWhy fork() twice instead of just running the attack directly?β
Because fork() separates the attack logic from the botβs main process. The bot needs to stay connected to the C2, send heartbeats, and receive new commands during the attack. If the attack ran in the same process, it would block all of that. Two forks create independent processes that do not interfere with each other.
What We Learned
Bandwidth exhaustion = flood the pipe until no traffic can reach the server
Connection exhaustion = fill the server's waiting room with fake visitors
UDP flood = I don't care if you listen. I will just flood.
data_rand = randomize packet contents to defeat rule-based firewalls
Inspection tax = firewall wastes CPU rejecting packets; that CPU is the target
SYN flood = fill half-open connection slots; weaponize correct TCP behavior
Half-open connection = SYN received, SYN-ACK sent, ACK never comes
ACK flood = fool stateless firewalls that have no memory of past packets
Stateless firewall = sees each packet in isolation; no connection history
Stateful firewall = tracks connection history; detects ACK without SYN
HTTP flood = complete real handshakes with fake browser identities
Layer 7 attack = operates at application level; speaks the server's own language
DNS water torture = make innocent recursive DNS servers attack the target
Amplification attack = use trusted infrastructure to attack; target cannot block it
GRE encapsulation = wrap packets inside packets to confuse inspection systems
fork() = split process into children; attack without stopping bot duties
attack_init() = plugin system registering all ten attack functions
ATK_VEC_* = attack type numbers sent from C2 in the 16-byte command packet
Dyn DNS attack = October 21, 2016; 1.2 Tbps; Twitter, Netflix, Reddit went down
Almost At The Finish Line
Six blogs. Six months of learning from source code one line at a time.
We started with a $15 camera in Vietnam and a botnet concept I did not fully understand. We have now read the actual source code of one of the most significant cyberattacks in internet history. We understand how it infected devices, how it hid from analysis, how the C2 commanded hundreds of thousands of bots, and now β how those bots actually attacked.
In Blog 7 β the final blog β we close the loop.
What happened AFTER Mirai? Its source code was released publicly. And as you might expect, releasing the blueprint of a 600,000-device army to the entire internet did not make the internet safer.
Dozens of variants emerged. Some worse than the original. Some targeting entirely new device types. Some attacking infrastructure the original Mirai never touched.
Modern Mirai variants - what they changed, what they added, and what they tell us about where IoT security is heading.
One more blog. See you there.
Previous: Blog 5 - Inside The C2: How One Attacker Commands 600,000 Devices
Next: Blog 7 - After Mirai: The Variants, The Copycats, and What Comes Next

