SourceForge.net Logo

USB History

a tool to extract USB Trace Evidence

In this article I present a tool I wrote to extract trace evidence of USB thumb drive activity from the Windows Registry. This tool can be used to gather information such as the last time the thumb drive or mp3 player was connected to the machine as well as the last drive letter.

The windows Registry is the central nervous system to the windows operating system. Almost any activity that occurs on windows is going to involve the Registry and leave traces of that activity within the Registry. This is especially true when you work with a USB Device.

Extracting trace evidence of this activity from the registry is not a new idea. Most of the method I employ is documented by Harlan Carvey in his book entitled, "Windows Forensic Analysis". As with his last book, this one is an excellent resource for anyone interested in Windows Forensics.

Let's briefly review what happens when you plug your thumb drive into windows. First, the PnP Manager recognized the Device and installs it, probably usually using the generic windows USB Driver USBSTOR.SYS. Then the Windows mount manager (MountMgr.sys ) will be alerted to the device and will query the device (via IOCTL_MOUNTDEV_QUERY_UNIQUE_ID) directly to retrieve it's unique identifying information. The mount manager then creates several registry keys that allow it to manage and map the storage volume and assign it a drive letter.

According to the USB specification, USB devices can store a serial number in the device descriptor of the device. Microsoft also requires that this serial number be unique. This serial number along with product and vendor information is part of the information that the mount manager retrieves in the process I described earlier. Using this serial number a device can be traced across machines. This data, with registry timestamps, MRU lists, AutoplayHandlers and .lnk files can all be used to build a clear timeline of events in regard to USB activity.

Ok, that's enough background information. Let's move on to some actual code and the process involved in collecting this data.

The structure of USBSTOR (HKLM\SYSTEM\CurrentControlSet\Enum\USBSTOR) could be visualized as follows.

USBSTOR----
           |
            --[ Device Key ----
           |                   |
Each Key can have more than     --[ Serial Sub Key ---
one serial subkey and each     |                      |
serial subkey might have more  |                       --[ Many Values
than ten values.               |
                                --[ Serial Sub Key ---
                               |                      |
                               |                       --[ Many Values
           |                   .
           |                   .
           |                   .
            --[ Device Key ...

This registry key provides a convenient listing of all USB Storage devices that are used on the computer. Taking this visualization further I developed two structures:

struct usbInfo { /* usb structure info */
	TCHAR		cKeyName[MAX_KEY_LENGTH] ; 
	TCHAR		cDeviceID[MAX_KEY_LENGTH] ; 
	SYSTEMTIME	stStamp ; // should be creation time of first use
	struct instanceInfo * instance;
} ;

struct instanceInfo { /* each instance record from registry */
	TCHAR		cInstanceID[MAX_KEY_LENGTH] ; 
	TCHAR		cFriendlyName[MAX_VALUE] ; 
	TCHAR		cDriver[MAX_VALUE] ; // Diver Key - this maches
	TCHAR		cParentIdPrefix[MAX_VALUE] ; 
	TCHAR		cHardwareID[MAX_VALUE] ; 
	TCHAR		cLastDriveLetter[MAX_VALUE_NAME] ;
	SYSTEMTIME	stDiskStamp ; // Last write time of instance entry 
	SYSTEMTIME  stVolumeStamp ; // in Device Class Key {53f56307-b6bf-11d0-94f2-00a0c91efb8b}
	struct instanceInfo * next ; // pointer to next instanceInfo if there is more than one
} ;

The first holds information regarding each device. cKeyName holds the Registry key of the device we are looking at.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR\Disk&Ven_&Prod_USB_Flash_Memory&Rev_5.00

We use cDeviceID to hold the Device Identifier (the last part of the key: Disk&Ven_&Prod_USB_Flash_Memory&Rev_5.00) and the creation time of each DeviceID Registry key. We collect the creation times of each subkey by calling RegEnumKeyEx and converting the filetime to a usable time with FileTimeToSystemTime and SystemTimetoTzSpecificLocalTime.

if ( RegEnumKeyEx(hKey, i, cSubKeyName, &dwName, NULL, NULL, NULL, &ft ) == ERROR_SUCCESS) {
	TCHAR deviceKeyName[MAX_KEY_LENGTH] = USBSTOR ;
	_tcscat(deviceKeyName, TEXT("\\") ) ;
	_tcscat(deviceKeyName, cSubKeyName) ;
	_tcscpy( usbList[i].cDeviceID, cSubKeyName) ;
	_tcsncpy( usbList[i].cKeyName, deviceKeyName, MAX_KEY_LENGTH - 1 ) ;
	// collect the creation time of this key - should be when they first plugged it in
	FileTimeToSystemTime( &ft, &stGMT) ;
	SystemTimeToTzSpecificLocalTime(NULL, &stGMT, &stLocal);
	usbList[i].stStamp.wMonth = stLocal.wMonth ;
	usbList[i].stStamp.wDay = stLocal.wDay ;
	usbList[i].stStamp.wYear = stLocal.wYear ;
	usbList[i].stStamp.wHour = stLocal.wHour ;
	usbList[i].stStamp.wMinute = stLocal.wMinute ; 
	usbList[i].instance = NULL ;
}

Now, every USB Device might have more than one set of instance information in the registry. This may happen due to a difference in the ParentIDPrefix of the device. The ParentIDPrefix correlates to the mountpoint that is assigned to the device. So let's say thumbdrive A was initially assigned as F:\ but then you remove it and insert thumbdrive B, which is also assigned as F:\. If you insert thumbdrive A while you still have thumbdrive B mounted as F:\ then it will be assigned another ParentIDPrefix and a new Registry Key will be created to hold that instance information.

In order to get the time that the thumb drive was last plugged into the machine we use the ParentIDPrefix and look under

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

Here we find a list of subkeys for reach instance, similar to the one listed below:

\##?#USBSTOR#Disk&Ven_&Prod_USB_Flash_Memory&Rev_5.00#08B0877092601A5B&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

This subkey contains the instanceID that we retrieved earlier (08B0877092601A5B&0). Using this id we know can now correlate the two registry entries. Each of these subkeys may further have a subkey named Control. If this subkey exists, then we query it in the same manner mentioned above to retrieve the time the thumbdrive was last used. If the Control Subkey does not exist we query the main subkey to retrieve an approximate time.

TCHAR cControlKeyName[MAX_PATH +1] ;
_tcscpy(cControlKeyName, cSubKeyName) ;
_tcscat(cControlKeyName, TEXT("\\Control") ) ;

if ( ( RegOpenKeyEx( hKey, cControlKeyName, 0, KEY_READ, &hControlKey) == ERROR_SUCCESS) &&
		( RegQueryInfoKey(hControlKey, NULL, NULL, NULL, NULL, NULL, NULL, 
				NULL, NULL, NULL, NULL, &ft ) == ERROR_SUCCESS) ) {

	FileTimeToSystemTime( &ft, &stGMT) ;
	SystemTimeToTzSpecificLocalTime(NULL, &stGMT, &stLocal);
	current->stVolumeStamp.wMonth = stLocal.wMonth ;
	current->stVolumeStamp.wDay = stLocal.wDay ;
	current->stVolumeStamp.wYear = stLocal.wYear ;
	current->stVolumeStamp.wHour = stLocal.wHour ;
	current->stVolumeStamp.wMinute = stLocal.wMinute ;

	RegCloseKey( hControlKey ) ;
								
	} else {

	FileTimeToSystemTime( &ft, &stGMT) ;
	SystemTimeToTzSpecificLocalTime(NULL, &stGMT, &stLocal);
	current->stVolumeStamp.wMonth = stLocal.wMonth ;
	current->stVolumeStamp.wDay = stLocal.wDay ;
	current->stVolumeStamp.wYear = stLocal.wYear ;
	current->stVolumeStamp.wHour = stLocal.wHour ;
	current->stVolumeStamp.wMinute = stLocal.wMinute ;

}

Next we want to see if we can find the last drive letter that was assigned to our thumbdrive. To do this we are going to compare our ParentIDPrefix with the Binary String that is stored in the mount manager database. If we find our ParentID listed under a mount point then we know that our USB Device was the last volume that was mounted at that location. With no real function to extract text from Binary data contained in the registry that i could find this code is just a little bit hackerish but it works.

// eleminate older volume's and only check for recent DosDevices
if (strncmp(  (LPSTR)ValName, "\\DosDev", 7) == 0) {	

	// build a usable string out of the REG_BINARY data in the registry by
	// skipping the white space in between each character.
	if (ValType == REG_BINARY)
		for (int i = 0; i < ValLen; i++, Val++) 
			valueString[i] = Val[i] ;

	valueString[ValLen+1] = '\0' ; 	//append a null by to the end of our string
									//as a null character isn't guaranteed

	// We only want to look at removable media and ignore fixed devices
	if (strncmp( valueString, "\\??\\STORAGE#RemovableMedia#", 27) == 0) {

		// Now cycle through each and see if the ParentID is in valueString, if it is
		// then copy ValName to lastDriveLetter
		for (int i = 0; i < iSubKeyCount; i++) {

			current = usbList[i].instance ;

			if (  ( _tcsstr( valueString, current->cParentIdPrefix) != NULL)  
					&& (strlen(current->cParentIdPrefix) > 1 ) )
				_tcscpy(current->cLastDriveLetter, (LPTSTR) ValName) ;
			while (current->next != NULL) {
				current = current->next ;
				if ( ( _tcsstr( valueString, current->cParentIdPrefix) != NULL )  
						&& (strlen(current->cParentIdPrefix) > 1 ) )
					_tcscpy(current->cLastDriveLetter, (LPTSTR)ValName) ;
			}
		}
		// Done Cycling Through the list.
	}
}	

I think that pretty much explains the internals of the program. The program is run from the commandline.

PS C:\Documents and Settings\nabiy\src\usbHistory> .\usbHistory.exe

USB History Dump
by nabiy (c)2008

(1) --- USB Flash Memory USB Device

                instanceID: 08B0877092601A5B&0
                ParentIdPrefix: 8&2ecbeef1&0
                Driver:{4D36E967-E325-11CE-BFC1-08002BE10318}\0007
                Disk Stamp: 02/24/2008 16:24
                Volume Stamp: 02/24/2008 16:24

(2) --- Apple iPod USB Device

                instanceID: 000A2700130E6BB4&0
                ParentIdPrefix: 7&1fc7763&0
                Driver:{4D36E967-E325-11CE-BFC1-08002BE10318}\0009
                Disk Stamp: 01/28/2008 19:37
                Volume Stamp: 01/28/2008 19:37

                instanceID: 000A270015C97A7A&0
                ParentIdPrefix: 7&78dde2a&0
                Driver: {4D36E967-E325-11CE-BFC1-08002BE10318}\0013
                Disk Stamp: 02/15/2008 16:28
                Volume Stamp: 02/15/2008 16:28

(3) --- Flash Drive SM_USB20 USB Device

                instanceID: AA04012710221&0
                ParentIdPrefix: 8&27cca69d&0
                Last Mounted As: \DosDevices\H:
                Driver:{4D36E967-E325-11CE-BFC1-08002BE10318}\0002
                Disk Stamp: 03/02/2008 16:07
                Volume Stamp: 03/02/2008 16:07

Everything is pretty self exclamatory. I've explained both the instanceID and ParentIdPrefix. If the ParentIdPrefix was found in the mount manager database the program displays most recent mount point for the thumb drive. It also pulls the time stamps from the registry for both the Disk and Volume deviceClass keys ({53f56307-b6bf-11d0-94f2-00a0c91efb8b} and {53F5630D-B6BF-11D0-94F2-00A0C91EFB8B} respectively).

Download the USB History Dump program from sourceforge here.

--- notes and links of interest:
USB 2.0 Specification
USB in a NutShell
Maybe I Should Drive - Drive Letter Assignment & The Mount Manager
Windows 2000 assigns duplicate drive letters to a single USB floppy drive
Identifiers Generated by USBSTOR.SYS
Supporting Mount Manager Requests in a Storage Class Driver
---

This tool and article were originally published on neworder.box.sk on March 18, 2008.

feedback? comment on livejournal