Reverse engineering

How USB Malware Works

Sudeep Singh
June 14, 2013 by
Sudeep Singh

Introduction

In this article we will look at malware that propagate to other machines using USB removable devices. Unlike most malware which make use of vulnerable Network Services to spread to other machines in the network, these malware are specifically designed to infect USB removable devices.

Become a certified reverse engineer!

Become a certified reverse engineer!

Get live, hands-on malware analysis training from anywhere, and become a Certified Reverse Engineering Analyst.

We will discuss in depth the methods used by a malware to automatically detect any USB removable device connected to the machine and then infect it. The infection routine is a topic for another article; here, we will just analyze the techniques used for detection of removable USB devices.

Such techniques have been used in malware like Stuxnet and Conficker. These malware make extensive use of Windows Messages and the Win32 APIs related to them. A good understanding of these APIs would be helpful while reading this article.

Threads Creation

Malware creates 3 threads as shown below:

Thread #1: This is used to create the Registry Keys used by the malware to make it persistent even after the OS is rebooted.

Thread #2: This thread has two main subroutines as shown below.

The subroutine at 0x00C18CE5 is used to check if any USB removable device is already connected to the machine. If it finds such a device then it infects it.

The subroutine at 0x00C18DBB is used to create a Window that will monitor the WM_DEVICECHANGE message to detect any removable storage device connected to the machine. If it finds such a device then it infects it.

The infection routine used is the same in both the cases (infecting already connected and newly connected removable storage devices).

Thread #3: This thread will create a backdoor on the machine using the Named Pipe, .pipeumadngr.

Monitor Windows Messages

First, let us check the second Thread:

[c]

CMP BYTE PTR DS:[C20941],0

JNZ SHORT 00C18CDA

CMP BYTE PTR DS:[C20942],0

JNZ SHORT 00C18CDA

CALL 00C18CE5 ; To find if any removable device is already connected to the machine and then infect it

CALL 00C18DBB ; To set up a handler that will detect if any removable device is connected to the machine

[/c]

We will analyze the subroutine at 00C18DBB which is used to create a Window for monitoring the WM_DEVICECHANGE message.

Here is the subroutine at 00C18DBB:

The lines of code below are used to initialize the Local Variables with the GUID (Globally Unique Identifier) of a removable USB device: {53f5630d-b6bf-11d0-94f2-00a0c91efb8b}

[cpp]

MOV DWORD PTR SS:[EBP-10],53F56307

MOV WORD PTR SS:[EBP-C],0B6BF

MOV WORD PTR SS:[EBP-A],11D0

MOV BYTE PTR SS:[EBP-8],94

MOV BYTE PTR SS:[EBP-7],0F2

MOV BYTE PTR SS:[EBP-6],BL

MOV BYTE PTR SS:[EBP-5],0A0

MOV BYTE PTR SS:[EBP-4],0C9

MOV BYTE PTR SS:[EBP-3],1E

MOV BYTE PTR SS:[EBP-2],0FB

MOV BYTE PTR SS:[EBP-1],8B

[/cpp]

In the memory dump, it will look like as shown below:

0148FFA0 07 63 F5 53 BF B6 D0 11 94 F2 00 A0 C9 1E FB 8B cõS¿¶Ð"ò. Éû‹

The next 2 lines of code are used to initialize the values that correspond to the NotificationFilter's DEV_BROADCAST_HDR structure. We will look into this in more depth when we analyze the call to RegisterDeviceNotificationA API.

[cpp]

MOV DWORD PTR SS:[EBP-3C],20

MOV DWORD PTR SS:[EBP-38],5

[/cpp]

After this we have:

The lines of code below are used to register a new Class with the name, gdkWindowTopClass and the WindowProc routine at address, 0C18EC7.

[cpp]

MOV DWORD PTR SS:[EBP-6C],30

MOV DWORD PTR SS:[EBP-68],EBX

MOV DWORD PTR SS:[EBP-64],0C18EC7 ; Pointer to WindowProc routine

MOV DWORD PTR SS:[EBP-60],EBX

MOV DWORD PTR SS:[EBP-5C],EBX

MOV DWORD PTR SS:[EBP-58],EBX

MOV DWORD PTR SS:[EBP-54],EBX

MOV DWORD PTR SS:[EBP-50],EBX

MOV DWORD PTR SS:[EBP-4C],EBX

MOV DWORD PTR SS:[EBP-48],EBX

MOV DWORD PTR SS:[EBP-44],0C1F8D4 ; ASCII "gdkWindowTopClass"

MOV DWORD PTR SS:[EBP-40],EBX

MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

CALL DWORD PTR DS:[C1C2B0] ; USER32.RegisterClassExA

[/cpp]

Once the class is registered, it will create a Window with that class name:

[cpp]

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH EBX

PUSH 0C1F8D4 ; ASCII "gdkWindowTopClass"

PUSH EBX

CALL DWORD PTR DS:[C1C2B4] ; USER32.CreateWindowExA

[/cpp]

CreateWindowExA will create a window with the class name gdkWindowTopClass and return a handle to the newly created window.

However, before returning, CreateWindow will send WM_CREATE, WM_GETMINMAXINFO, WM_NCCREATE and some more messages to the Window Procedure corresponding to the Class Name.

Here is a list of window messages which will help us in analyzing the Window Procedure:

http://wiki.winehq.org/List_Of_Windows_Messages

Window Procedure

We need to set a breakpoint at the WindowProc address (in this case, 0C18EC7) and we break at this subroutine once we execute the call to CreateWindowExA as shown below:

Once we break at this Window Procedure, the stack looks like as shown below:

Here the value 0x24 corresponds to the Window Message, WM_GETMINMAXINFO.

As mentioned above, CreateWindowExA will pass a few Window Messages which need to be processed by the Window Procedure before the Window is created.

Here is a description of the Window Procedure code:

[cpp]

PUSH EBP

MOV EBP,ESP

SUB ESP,13C

AND DWORD PTR SS:[EBP-38],0

PUSH ESI

PUSH EDI

PUSH 0A

MOV ESI,DWORD PTR SS:[EBP+8]

POP ECX

XOR EAX,EAX

LEA EDI,DWORD PTR SS:[EBP-34]

REP STOS DWORD PTR ES:[EDI]

MOV EDI,DWORD PTR SS:[EBP+C] ; EDI will hold the code of the Window Message

CMP EDI,1 ; Check if it is a WM_CREATE message

JNZ SHORT 00C18F02

PUSH EAX

MOV DWORD PTR SS:[EBP-C],0C ; Size of the Structure passed to RegisterDeviceNotificationA

MOV DWORD PTR SS:[EBP-8],5 ; Corresponds to the device type (in this case, DBT_DEVTYP_DEVICEINTERFACE)

LEA EAX,DWORD PTR SS:[EBP-C]

JMP 00C18FA9

CMP EDI,219 ; Check if it is a WM_DEVICECHANGE message

JNZ 00C18FD0

CMP DWORD PTR SS:[EBP+10],8000 ; Check if the Device Event is DBT_DEVICEARRIVAL

JNZ 00C18FB3

MOV EAX,DWORD PTR SS:[EBP+14]

CMP DWORD PTR DS:[EAX+4],2 ; Check if the device type is a Logical Volume DBT_DEVTYP_VOLUME

JNZ 00C18FEB

PUSH DWORD PTR DS:[EAX+C] ; dbcv_unitmask corresponding to the Drive Letter of the Logical Volume

CALL 00C18FFF

MOVSX EAX,AL ; AL will hold the ASCII value of the Drive Letter assigned to the removable device

PUSH EAX

LEA EAX,DWORD PTR SS:[EBP-13C]

PUSH 0C1F8E8 ; ASCII "%c:"

PUSH EAX

CALL 00C1B89C ; JMP to msvcrt.sprintf

[/cpp]

If the Window Message is not equal to either of the values defined above then it will call the default Window Proc, DefWindowProc, with the same parameters that were passed to the WindowProc routine.

As can be seen from the code above, the Window Procedure can handle the WM_CREATE and WM_DEVICECHANGE messages. If there is any other Window Message then it will be passed to the default window procedure, DefWindowProc.

The return value of DefWindowProc will depend on the message that was processed by the DefWindowProc routine.

Stack arguments for the call to DefWindowProcA:

For example, the stack arguments when we return from DefWindowProc after processing the message WM_GETMINMAXINFO are:

Here, 0x81 corresponds to the WM_NCCALCSIZE message.

This message is again processed by DefWindowProc since the Window Procedure does not handle this Window Message.

Stack arguments after the WM_NCCALCSIZE message is processed:

0148F7DC 7E418734 RETURN to USER32.7E418734

0148F7E0 00530280

0148F7E4 00000001 // WM_CREATE

0148F7E8 00000000

0148F7EC 0148F928

WM_CREATE is the last Window Message processed by WindowProc before creating the Window and returning.

The section of code below will be executed when the window message is WM_CREATE:

[cpp]

CMP EDI,1

JNZ SHORT 00C18F02

PUSH EAX

MOV DWORD PTR SS:[EBP-C],0C ; size of the structure

MOV DWORD PTR SS:[EBP-8],5 ; device type

LEA EAX,DWORD PTR SS:[EBP-C]

JMP 00C18FA9

[/cpp]

This will set the values for the standard header used in a device event.

[cpp]

PUSH EAX

PUSH ESI

CALL DWORD PTR DS:[C1C2B8] ; USER32.RegisterDeviceNotificationA

[/cpp]

RegisterDeviceNotificationA will return the Device Notification Handle if the call succeeds, or else it returns a NULL value.

The stack arguments:

The second argument on the stack above is a pointer to a data structure that specifies the type of Device for which notifications will be sent to the Window Procedure.

This data structure in our case looks like:

If we compare this with the DEV_BROADCAST_HDR structure:

[cpp]

typedef struct _DEV_BROADCAST_HDR {

DWORD dbch_size;

DWORD dbch_devicetype;

DWORD dbch_reserved;

} DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;

[/cpp]

We see that in our case, the size of the structure is 0xC bytes and the type of the device is DBT_DEVTYP_DEVICEINTERFACE (corresponding to the value 0x5).

Once we return from the RegisterDeviceNotificationA routine, we see that the value of the EAX register is 0 (the return value) which means that no Device Notification handle was returned.

After this, the Window Procedure processes the WM_CREATE message as shown below:

And returns the handle of the newly created Window:

The handle to the Window in our case is: 0x002D032C.

Device Notification Handler

Now, we have another call to the RegisterDeviceNotificationA API as shown below:

The stack arguments are:

The second argument on the stack points to the following data structure:

If we again compare the above data structure with _DEV_BROADCAST_HDR,we can see that the size of the structure is 0x20 bytes and the device type for which notifications will be sent is: DBT_DEVTYP_DEVICEINTERFACE.

Also, we can see the device GUID present in this structure: 53f5630d-b6bf-11d0-94f2-00a0c91efb8b.

Once we return from the call to RegisterDeviceNotificationA API, the Device Notification Handle is stored in the EAX register as shown below:

In our case, the handle is 0x000E3738. This confirms that the call to RegisterDeviceNotificationA was successful.

WM_DEVICECHANGE Handler

After this, the code will use GetMessage API to pop one message at a time from the Thread's message queue.

The stack arguments are:

GetMessage takes only one argument which is a pointer to the MSG Structure that will be populated with the information about the Window Message retrieved from the Thread's message queue.

If there are no messages in the Message Queue of the current Thread, then the execution will be passed to the other Thread.

For instance, at present I did not connect any USB removable device to the machine in between the execution of the Thread, and there are no messages in the Thread's queue. As a result of this, after we execute the call to GetMessageA, we break at the new Thread (in our case, Thread #3) as shown below:

In order to analyze further, I connected a USB removable storage device to the machine while the second Thread was active.

Now, when the call to GetMessage is executed it will populate the MSG structure at, 0148FF94 with the WM_DEVICECHANGE message information (since I connected a USB removable device):

This is how the MSG structure looks like:

[cpp]

typedef struct tagMSG {

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt;

} MSG, *PMSG, *LPMSG;

[/cpp]

In our case, 0x0036027C is the handle to the Window created before (this handle is different from the one mentioned previously because I started another debug session).

0x0219 is the code corresponding to the Window Message retrieved by GetMessage from the Thread's queue.

The next parameters depend on the type of Message retrieved from the Queue.

In our case, the message is WM_DEVICECHANGE which has the following structure:

[cpp]

LRESULT CALLBACK WindowProc(HWND hwnd, // handle to window

UINT uMsg, // WM_DEVICECHANGE

WPARAM wParam, // device-change event

LPARAM lParam ); // event-specific data

[/cpp]

In our case the device change event is 0x7, which corresponds to DBT_DEVNODES_CHANGED meaning a device has been added or removed from the system.

It is important to note that the GUID of the USB removable device I have connected to the machine is not the same as the GUID registered by RegisterDeviceNotificationA (that is, {53f5630d-b6bf-11d0-94f2-00a0c91efb8b}).

You can check the Device GUID for the removable device connected to the machine by looking up the registry key:

HKEY_LOCAL_MACHINESYSTEMCurrentControlSetEnumUSBSTOR

As a result of this, WM_DEVICECHANGE structure is not the same as what the code expects it to be (we will look into this further).

After this, TranslateMessage and DispatchMessage are called which will dispatch this message to the Window Procedure corresponding to the Window as shown below:

The arguments on the stack:

It passes a pointer to the WM_DEVICECHANGE structure to the Window Procedure.

Once the call to DispatchMessage is executed, we break at the WindowProc routine again:

Stack arguments are as shown below:

We will perform static code analysis of the section of code that is executed when the type of message is WM_DEVICECHANGE:

Let us understand these in depth:

CMP EDI, 219

EDI points to the value at EBP+C which is the message code. In our case, it is WM_DEVICECHANGE (0x219).

CMP DWORD PTR SS:[EBP+10],8000

This is the next value on the stack. If we compare this with the WM_DEVICECHANGE structure, we see that it corresponds to the device event.

0x8000 is the device event for: DBT_DEVICEARRIVAL (a device or piece of media has been inserted and is now available).

MOV EAX, DWORD PTR SS:[EBP+14]

It is moving the pointer to event specific data in EAX (referred to as lparam in the WM_DEVICECHANGE structure above).

This is the DEV_BROADCAST_HDR structure:

[cpp]

typedef struct _DEV_BROADCAST_HDR {

DWORD dbch_size;

DWORD dbch_devicetype;

DWORD dbch_reserved;

} DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;

[/cpp]

CMP DWORD PTR DS:[EAX+4], 2

The second DWORD in the structure above corresponds to dbch_devicetype. It is comparing this with 2 (DBT_DEVTYP_VOLUME).

If the device type connected is a Logical Volume, then it parses the DEV_BROADCAST_VOLUME structure to find the unitmask corresponding to the Logical Volume drive letter.

PUSH DWORD PTR DS:[EAX+C]

[cpp]

typedef struct _DEV_BROADCAST_VOLUME {

DWORD dbcv_size;

DWORD dbcv_devicetype;

DWORD dbcv_reserved;

DWORD dbcv_unitmask;

WORD dbcv_flags;

} DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;

[/cpp]

Unitmask is the bit sequence corresponding to the drive letter.

Next, it calls the subroutine at 00C18FFF which is used to convert the unitmask to the corresponding Drive Letter's ASCII value.

Once we have the Drive Letter of the USB removable device connected to the machine that triggered the WM_DEVICECHANGE message, we find the infection routine at 00C190D3.

It is important to note that this infection routine is the same for both the cases, infecting an already connected removable device and a newly connected removable device. Below you can see the first few lines of code of the infection subroutine:

Enumeration of Logical Drives

Now, let us analyze the first subroutine of the second thread at address 0x00C18CE5. This subroutine will help us understand how the malware finds out if any removable device is already connected to the machine before it proceeds to infect it.

The code first calls GetLogicalDriveStringsA to find out all the Logical Volumes already connected and accessible to the machine:

Stack arguments are:

Once the call to GetLogicalDriveStringsA has executed, all the Drive Letters will be populated in the buffer at address, 00148FEA4.

It then checks each drive letter in the list above to find any USB removable device already connected to the machine:

[cpp]

CALL DWORD PTR DS:[C1C0DC] ; kernel32.GetLogicalDriveStringsA

TEST EAX,EAX

JE SHORT 00C18DB7

PUSH EBX

LEA EBX,DWORD PTR SS:[EBP-10C] ; pointer to output buffer of GetLogicalDriveStringsA

MOV AL,BYTE PTR DS:[EBX] ; AL will hold the ASCII value of the drive letter

MOV BYTE PTR SS:[EBP-8],AL

LEA EAX,DWORD PTR SS:[EBP-8]

PUSH EAX ; pointer to the Drive Letter

CALL 00C19016

[/cpp]

USB Device Detection

Let us analyze the subroutine at 00C19016:

It opens a handle to the device name corresponding to the Drive Letter. A device name must be in the format, .Device Name, in order to open a handle to it using CreateFileA API.

So, it allocates memory using calloc and then uses sprintf to prepare the device name as shown below (in this case, .C:).

Below is the call to CreateFileA which will open a handle to the device:

Once this call is executed successfully, we have the handle to the device name: .C:.

Next, it calls DeviceIOControl to query the device handle retrieved above:

Stack arguments are:

Here, 0x164 is the handle corresponding to the device: .C:

Here is an explanation of the code:

[cpp]

PUSH 0C ; size of the Input Buffer

PUSH EAX

PUSH 2D1400 ; IO Control Code corresponding to MASS_STORAGE

PUSH ESI

MOV DWORD PTR SS:[EBP+8],EBX

MOV DWORD PTR SS:[EBP-10],EBX

MOV DWORD PTR SS:[EBP-C],EBX

CALL DWORD PTR DS:[C1C15C] ; kernel32.DeviceIoControl

PUSH ESI

MOV EDI,EAX

CALL DWORD PTR DS:[C1C158] ; kernel32.CloseHandle

[/cpp]

The control code passed to the DeviceIOControl subroutine above is 0x2D1400. To understand the meaning of this control code, we can use the following site:

http://www.osronline.com/article.cfm?article=229

It allows you to enter any IOCTL value in hex format and then tells you the corresponding decoded value.

In our case, 0x2D1400 corresponds to MASS_STORAGE or the mnemonic IOCTL_STORAGE_QUERY_PROPERTY.

If we look up the above IOCTL code on MSDN here:

http://msdn.microsoft.com/en-us/library/windows/desktop/ff800830%28v=vs.85%29.aspx

We see that the value of Output Buffer depends on the Input Buffer. In our case, the Input Buffer is:

And it has a size of 0xC bytes.

The input buffer points to the STORAGE_QUERY_PROPERTY structure as shown below:

[cpp]

typedef struct _STORAGE_PROPERTY_QUERY {

STORAGE_PROPERTY_ID PropertyId;

STORAGE_QUERY_TYPE QueryType;

BYTE AdditionalParameters[1];

} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;

[/cpp]

If we compare this structure with the Input Buffer above, then we can see that the value of the PropertyID is 0 which corresponds to StorageDeviceProperty.

More details here:

http://msdn.microsoft.com/en-us/library/windows/desktop/ff800840%28v=vs.85%29.aspx

The output buffer corresponding to StorageDeviceProperty is STORAGE_DEVICE_DESCRIPTOR as documented here.

Now, we have the format of the Output Buffer:

[cpp]

typedef struct _STORAGE_DEVICE_DESCRIPTOR {

DWORD Version;

DWORD Size;

BYTE DeviceType;

BYTE DeviceTypeModifier;

BOOLEAN RemovableMedia;

BOOLEAN CommandQueueing;

DWORD VendorIdOffset;

DWORD ProductIdOffset;

DWORD ProductRevisionOffset;

DWORD SerialNumberOffset;

STORAGE_BUS_TYPE BusType;

DWORD RawPropertiesLength;

BYTE RawDeviceProperties[1];

} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;

[/cpp]

Once the call to DeviceIOControl has been executed, the output buffer at 0148FA7C looks like as shown below:

Comparing the output buffer with the structure of STORAGE_DEVICE_DESCRIPTOR, we have:

Version: 0x28

Size: 0x9C

STORAGE_BUS_TYPE: 0x3

At offset, 0x1C in the Output Buffer, we have the STORAGE_BUS_TYPE.

After returning from the call to DeviceIOControl we have the following section of code:

[cpp]

PUSH ESI

MOV EDI,EAX

CALL DWORD PTR DS:[C1C158] ; kernel32.CloseHandle

CMP EDI,EBX

POP EDI

JE SHORT 00C190C3

CMP DWORD PTR SS:[EBP-3F4],7

JNZ SHORT 00C190C3

MOV BL,1

PUSH DWORD PTR SS:[EBP-4]

CALL 00C1B94C ; JMP to msvcrt.free

POP ECX

POP ESI

MOVZX EAX,BL

POP EBX

LEAVE

RETN

[/cpp]

It closes the Device Handle and then compares the STORAGE_BUS_TYPE with 0x7.

The STORAGE_BUS_TYPE enum is defined here:

http://msdn.microsoft.com/en-us/library/windows/desktop/ff800833%28v=vs.85%29.aspx

The value corresponding to 0x7 is BusTypeUSB.

It means, that the code is checking whether the Drive Letter corresponds to a USB removable device or not.

In the code above, we can see that it will set the value of EAX to 1 if it finds a USB removable device. After returning from this subroutine, it checks the value of EAX and if it is not 0, it then calls the infection subroutine at 0x00C190D3.

It is important to note that this is the same infection routine that was called above when a USB removable device is connected to the machine and triggers the WM_DEVICECHANGE event.

Conclusion

After reading this article, you should be able to identify malware which make use of similar techniques to detect and infect USB removable devices.

It will also help in writing signatures to detect malware which make use of the techniques discussed in this article.

References

http://msdn.microsoft.com/

Sudeep Singh
Sudeep Singh

Sudeep Singh is an Information Security Professional and has a strong interest in various areas of Information Security ranging from Reverse Engineering, Cryptography, Malware Analysis, latest online threats to Web Application Security, Password Security. His other interests include working in GPGPU related tasks like Cryptographic Hashing Algorithm Cracking and Crypto Currencies.