The USB thumb drive has always been one of the favorite gadgets in the arsenal of both pentesters and cybercriminals. USB attacks can have devastating results; the Stuxnet worm in 2009 serves as a prime example.
This is the first of a two-part blog series on developing a weaponized USB device from scratch. The series aims to demonstrate how a USB device is capable of penetrating the most secure networks. Today we will focus on the hardware/software requirements and prepare the payload triggering system.
USB Security Risks
As of today, the Universal Serial Bus (USB) is still the most commonly used connector on computer systems. One of the biggest security risks that it associated to USB is its universality:
• The design principles of the USB protocol and its driver stacks are based on end-user convenience.
• The operating system (OS) implicitly assumes that connected USB devices are trustworthy.
• Peripheral devices can perform their identification and capabilities without providing any form of authentication.
The result is that the OS cannot differentiate a USB hotplug event of a normal device from one generated by a malicious device. In addition, adversaries can rely on generic USB drivers (e.g. HID drivers) that are enabled and installed by default. By altering the firmware of the device connectors, USB devices can be used to conduct a range of attacks. Innocent looking USB flash drives are commonly dropped near corporate parking lots by threat actors in an attempt to manipulate employees into inserting the device in (sensitive) systems. By far the most popular USB attack is HID spoofing. This will automate keystroke injections to drop a payload onto the target system. This attack method does not require exploitation of an existing vulnerability on the system or any user interaction. The payloads, when executed, allow reverse shells to be established, act as a MITM to eavesdrop on communications, redirect network traffic and much more.
Modern USB attack devices are weaponized with payloads that establish a covert channel to propagate across air gapped targets. The adversary can send interactive commands and scripts via raw HID, relaying it through a Wi-Fi hotspot (activated by the USB device). Assuming the target environment does not block electromagnetic fields, a covert Wi-Fi channel can be accessed from insecure, physical zones, such as outside a corporate building.
Requirements
Let’s first define some capabilities and hardware/software requirements for our USB attack device. We want to develop a USB attack solution that is open-source, inexpensive and is straightforward to configure. In addition, we require the ability to easily choose between various payloads and the device must support HID emulation and Wi-Fi.
After some online research, I found an awesome, feature-rich USB attack platform based on a Raspberry Pi Zero W named “P4wnP1”. P4wnP1 is a Kali Linux based programmable tool that can perform various HID and ‘Ducky-script’ based attacks through a live web interface, which can be controlled over Bluetooth or Wi-Fi.
Using the P4wnP1 as our selected USB attack platform, our end goal is to compromise an isolated target system as illustrated in the diagram below:
Hardware shopping list:
• Raspberry Pi Zero-W
• Micro SD-card (at least 8GB)
• USB OTG cable
• Mini HDMI to HDMI converter
• Raspberry Pi Zero W USB-A Addon Board
• Waveshare 1.3 inch OLED display hat add-on
Obviously we need the Pi Zero W and a micro SD-card that will run the P4wnP1 software. The add-on board will provide a full-sized, standard USB Type-A connector, through which the Pi Zero W can be powered. The Waveshare OLED display has a mini joystick and three buttons which we can use as control interface. Before we are able to connect the OLED display, the 40-pin GPIO header needs to be soldered onto the Pi’s board.
After assembly, the front and back of the device will look like this:
OLED Display
Before we dive into P4wnP1 and its capabilities, let’s focus on the OLED display first. It is an essential component as it aims to serve as our payload triggering system. We will build a simple payload selection menu which we can browse through using the physical joystick/buttons.
The Waveshare OLED display is driven by the SH1106
driver. We will use the Luma.oled Python driver to interface with the display. First, we will install the driver as follows:
sudo apt-get install python-dev python-pip libfreetype6-dev libjpeg-dev
sudo -H pip install --upgrade pip
sudo apt-get purge python-pip
sudo -H pip install --upgrade luma.oled
As a starting exercise, let’s display a simple message on the OLED display:
#!/usr/bin/python
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont, Image
serial = spi(device=0, port=0, bus_speed_hz = 8000000, transfer_size = 4096)
device = sh1106(serial, rotate=2)
font= ImageFont.truetype('/root/project/fonts/redalert.ttf', 12)
while True:
with canvas(device) as draw:
draw.rectangle(device.bounding_box, outline="white", fill="black")
draw.text((25, 25), "Hello World!", fill="white")
By using the Python imaging library we can even display small images on the LED display:
#!/usr/bin/python
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont, Image
serial = spi(device=0, port=0, bus_speed_hz = 8000000, transfer_size = 4096)
device = sh1106(serial, rotate=2)
font= ImageFont.truetype('/root/project/fonts/redalert.ttf', 12)
while True:
with canvas(device) as draw:
logo = Image.open('/root/project/images/wolf.png')
draw.bitmap((32, 0), logo, fill=1)
Let’s take it up a notch and write some code to display the system usage information of our USB attack platform:
#!/usr/bin/python
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont, Image
import subprocess
width=128
height=64
serial = spi(device=0, port=0, bus_speed_hz = 8000000, transfer_size = 4096)
device = sh1106(serial, rotate=2)
font= ImageFont.truetype('/root/project/fonts/redalert.ttf', 12)
CPU= "mpstat | awk '$12 ~ /[0-9.]+/ { print \"CPU Usage: \" 100 - $12 \"%\"}'"
MEM= "free | grep Mem | awk '{print \"Mem Usage: \" $3/$2 * 100.0 \"%\"}'"
TIME = "date '+%H:%M:%S'"
IP = "hostname -I | awk '{print \"IP-address: \" $1}'"
DISK= "df -h | awk '$NF==\"/\"{printf \"Disk Usage: %s\", $5}'"
def run_cmd(cmd):
return subprocess.check_output(cmd, shell=True).decode('utf-8')
def write_time():
time = run_cmd(TIME)
draw.text((85,1), time, font=font, fill=255)
def write_load():
load = run_cmd(CPU)
draw.text((5,15), load, font=font, fill=255)
def write_mem():
mem = run_cmd(MEM)
draw.text((5,25), mem, font=font, fill=255)
def write_disk():
disk = run_cmd(DISK)
draw.text((5,35), disk, font=font, fill=255)
def write_ip():
ip = run_cmd(IP)
draw.text((5,45), ip, font=font, fill=255)
while True:
with canvas(device) as draw:
draw.text((1, 1),'System Usage', font=font, fill=255)
draw.rectangle((0,12,width-1,height-1), outline=1, fill=0)
write_load()
write_mem()
write_time()
write_ip()
write_disk()
The photo below displays the output of the three scripts on our OLED display:
Now that we are experienced on how to interface with the OLED display, let’s develop the code to display a simple menu. We can utilize the RPI.GPIO
Python module to control the joystick and the third button (‘key3’). In the menu list, I’ve included the code examples we’ve created earlier, two dummy payloads and added a reboot/shutdown function.
#!/usr/bin/python
from RPi import GPIO
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageDraw, ImageFont, Image
import subprocess
up = 6
down = 19
but3 = 16
counter = 0
serial = spi(device=0, port=0, bus_speed_hz = 8000000, transfer_size = 4096)
device = sh1106(serial, rotate=2)
font= ImageFont.truetype('/root/project/fonts/redalert.ttf', 12)
menu_items = ['1.Hello World', '2.Show image', '3.Payload I', '4.Payload II',
'5.Shutdown system', '6.Reboot system']
GPIO.setmode(GPIO.BCM)
GPIO.setup(up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(but3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def run_cmd(cmd):
return subprocess.check_output(cmd, shell=True).decode('utf-8')
def construct(draw,x,y,text):
font = ImageFont.load_default()
draw.rectangle((x, y, x+120, y+10), outline=255, fill=255)
draw.text((x, y), text, font=font, outline=0,fill="black")
def menu(device, draw, menu_items,index):
global index_menu
font = ImageFont.load_default()
draw.rectangle(device.bounding_box, outline="white", fill="black")
for i in range(len(menu_items)):
if( i == index):
index_menu = i
construct(draw, 2, i*10, menu_items[i])
else:
draw.text((2, i*10), menu_items[i], font=font, fill=255)
def menu_browse(option):
if ( option == "1.Hello World"):
with canvas(device) as draw:
draw.rectangle(device.bounding_box, outline="white", fill="black")
draw.text((25, 25), "Hello World!", fill="white")
if ( option == "2.Show image"):
with canvas(device) as draw:
logo = Image.open('/root/project/images/wolf.png')
draw.bitmap((32, 0), logo, fill=1)
if ( option == "3.Payload I"):
with canvas(device) as draw:
draw.text((0, 26), "Payload I - TBD", fill="white")
if ( option == "4.Payload II"):
with canvas(device) as draw:
draw.text((0, 26), "Payload II - TBD", fill="white")
if ( option == "5.Shutdown system"):
command= "shutdown -h now"
run_cmd(command)
if ( option == "6.Reboot system"):
command = "shutdown -r now"
run_cmd(command)
def push_button3(press):
global index_menu
option = menu_items[index_menu]
menu_browse(option)
def push_updown(press):
global counter
if GPIO.input(up):
if counter >= 5:
counter = -1
counter += 1
elif GPIO.input(down):
if counter <= 0:
counter = 6
counter -= 1
with canvas(device) as draw:
menu(device, draw, menu_items,counter)
with canvas(device) as draw:
menu(device, draw, menu_items,0)
GPIO.add_event_detect(up, GPIO.FALLING, callback=push_updown, bouncetime=100)
GPIO.add_event_detect(down, GPIO.FALLING, callback=push_updown, bouncetime=100)
GPIO.add_event_detect(but3, GPIO.FALLING, callback=push_button3, bouncetime=300)
raw_input("Use joystick up/down to browse through menu and button3 to select your option")
GPIO.cleanup()
In the next blogpost we will take a closer look at P4wnP1 and put our device to the test as we try to compromise a fully patched system.
BONUS - Hunting spoofed USB HID devices using Windows Defender ATP
In the past few weeks I’ve had the pleasure to work with Windows Defender ATP and its “Advanced Hunting” feature. You can query raw events related to process creation, network communication, file creation, the registry and much more. It uses the “Kusto” search/query language, which is also used for Azure Log Analytics.
So how do we detect a spoofed HID device? First of all we want to search for devices that present themselves to the system as being a USB keyboard. After the device is connected, (in most cases) a payload is executed within 15 seconds. The payload commonly consists of the use of Powershell to execute commands or scripts on the system. We can use the following Kusto query to look for inserted USB HID devices, followed by execution of a Powershell process within a timeframe of 15 seconds.
// This query attempts to detect USB HID plugin events followed by execution of Powershell process within a timeframe of 15 seconds.
let hid_spoofing =
MiscEvents
| where ActionType == "PnpDeviceConnected"
| extend ParsedFields=parse_json(AdditionalFields)
| project ClassName=tostring(ParsedFields.ClassName), DeviceDescription=tostring(ParsedFields.DeviceDescription),
DeviceId=tostring(ParsedFields.DeviceId), VendorIds=tostring(ParsedFields.VendorIds), MachineId, ComputerName, PluginTime=EventTime
| where ClassName contains "Keyboard";
ProcessCreationEvents
| where ProcessCommandLine contains "powershell" and InitiatingProcessFileName == "explorer.exe"
| project EventTime, ComputerName, ProcessCommandLine, ProcessTokenElevation, AccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName
| join kind=inner hid_spoofing on ComputerName
| where (EventTime-PluginTime) between (0s..15s)
As shown below, there is plugin event of a USB device with device ID VID_16C0&PID_047C
, which is publicaly known as a Teensy board. After insertion we can see that the endpoint also executed Powershell with the “hidden” flag. This is definitely something to look into!