When working with the Guardant protection key (no matter which model), the developer uses the corresponding APIs, while the mechanism for working with the device is hidden from it, not to mention the exchange protocol. It does not have a valid device handle, using only the gateway address (the so-called GuardantHandle) through which all the work goes. If a key emulator is present in the system (especially for models up to Guardant Stealth II inclusive), using this gateway, the developer cannot determine whether it works with a real physical key, or its emulation.
Having asked at one time the question: “how to determine the presence of a physical key?”, I had to write a little superbly submitted material written by Pavel Agurov in the book “
USB Interface. Practice of Using and Programming ”. After that, spend time analyzing the API function calls from a three megabyte object manager linked to the application, which actually hides the whole “magic” of working with the key.
As a result, there appeared a fairly simple solution to this problem that does not require the use of the original Guardant API.
The only drawback is that all this is terribly undocumented and technical support from Asset will not even consider your questions related to the use of Guardant keys.
And of course, at some point all this code may simply stop working due to changes in the Guardant drivers.
But so far, on April 27, 2013, all this material is relevant and its performance has been tested on drivers from version 5.31.78, up to the current actual 6.00.101.
')
The procedure will be something like this:
- Through SetupDiGetClassDevsA () we will receive the list of all present devices.
- Check if the device is related to Guardant keys by checking the device GUID. (At Guardant, this parameter is {C29CC2E3-BC48-4B74-9043-2C6413FFA784})
- Get a symbolic link to each device by calling SetupDiGetDeviceRegistryPropertyA () with the parameter SPDRP_PHYSICAL_DEVICE_OBJECT_NAME.
- Let's open the device with ZwOpenFile () (CreateFile () here unfortunately will not work, because there will be difficulties when working with symbolic links).
Now that we have a real key handle on our hands, instead of the pseudo-handle (gateway) provided by the Guardant API, we can get a description of its parameters by sending the corresponding IOCTL request. True, there is a small nuance.
Starting with Guardant Stealth III and higher, the key handling protocol has changed, as a result, the IOCTL query constants and the contents of the incoming and outgoing buffer have changed. For normal operation of the algorithm, it is desirable to support the capabilities of both old and new keys, so I will describe the differences:
To begin with, the IOCTL constants look like this:
GetDongleQueryRecordIOCTL = $E1B20008; GetDongleQueryRecordExIOCTL = $E1B20018;
First for keys from Guardant Stealth I / II
Second for Guardant Stealth III and above (Sign / Time / Flash / Code)
By sending the first request to the device, we will expect the driver to return the following buffer to us:
TDongleQueryRecord = packed record dwPublicCode: DWord;
In the case of more new keys and given the fact that the protocol has changed, sending the first request will not give us anything. More precisely, the query will of course be executed, but the buffer will come empty (filled out). Therefore, we send a second request to the new keys, which will return the data in a slightly different format:
TDongleQueryRecordEx = packed record Unknown0: array [0..341] of Byte; wMask: WORD;
Here the block is already returned to 512 bytes containing more detailed information about the key. Unfortunately for some reason I cannot give you a complete description of this structure, but I left the fields necessary for this article.
The general code for obtaining data about installed keys is as follows:
procedure TEnumDonglesEx.Update; var dwRequired: DWord; hAllDevices: H_DEV; dwInfo: DWORD; Data: SP_DEVINFO_DATA; Buff: array [0 .. 99] of AnsiChar; hDeviceHandle: THandle; US: UNICODE_STRING; OA: OBJECT_ATTRIBUTES; IO: IO_STATUS_BLOCK; NTSTAT, dwReturn: DWORD; DongleQueryRecord: TDongleQueryRecord; DongleQueryRecordEx: TDongleQueryRecordEx; begin SetLength(FDongles, 0); DWord(hAllDevices) := INVALID_HANDLE_VALUE; try if not InitSetupAPI then Exit; UpdateUSBDevices; hAllDevices := SetupDiGetClassDevsA(nil, nil, 0, DIGCF_PRESENT or DIGCF_ALLCLASSES); if DWord(hAllDevices) <> INVALID_HANDLE_VALUE then begin FillChar(Data, Sizeof(SP_DEVINFO_DATA), 0); Data.cbSize := Sizeof(SP_DEVINFO_DATA); dwInfo := 0; while SetupDiEnumDeviceInfo(hAllDevices, dwInfo, Data) do begin dwRequired := 0; FillChar(Buff[0], 100, #0); if SetupDiGetDeviceRegistryPropertyA(hAllDevices, @Data, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME, nil, @Buff[0], 100, @dwRequired) then if CompareGuid(Data.ClassGuid, GrdGUID) then begin RtlInitUnicodeString(@US, StringToOleStr(string(Buff))); FillChar(OA, Sizeof(OBJECT_ATTRIBUTES), #0); OA.Length := Sizeof(OBJECT_ATTRIBUTES); OA.ObjectName := @US; OA.Attributes := OBJ_CASE_INSENSITIVE; NTSTAT := ZwOpenFile(@hDeviceHandle, FILE_READ_DATA or SYNCHRONIZE, @OA, @IO, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT); if NTSTAT = STATUS_SUCCESS then try if DeviceIoControl(hDeviceHandle, GetDongleQueryRecordIOCTL, nil, 0, @DongleQueryRecord, SizeOf(TDongleQueryRecord), dwReturn, nil) and (DongleQueryRecord.dwID <> 0) then begin SetLength(FDongles, Count + 1); FDongles[Count - 1].Data := DongleQueryRecord; FDongles[Count - 1].PnPParentPath := GetPnP_ParentPath(Data.DevInst); Inc(dwInfo); Continue; end; Move(FlashBuffer[0], DongleQueryRecordEx.Unknown0[0], 512); if DeviceIoControl(hDeviceHandle, GetDongleQueryRecordExIOCTL, @DongleQueryRecordEx.Unknown0[0], SizeOf(TDongleQueryRecordEx), @DongleQueryRecordEx.Unknown0[0], SizeOf(TDongleQueryRecordEx), dwReturn, nil) then begin DongleQueryRecordEx.wMask := htons(DongleQueryRecordEx.wMask); DongleQueryRecordEx.wSN := htons(DongleQueryRecordEx.wSN); DongleQueryRecordEx.dwID := htonl(DongleQueryRecordEx.dwID); DongleQueryRecordEx.dwPublicCode := htonl(DongleQueryRecordEx.dwPublicCode); DongleQueryRecordEx.wType := htons(DongleQueryRecordEx.wType); SetLength(FDongles, Count + 1); ZeroMemory(@DongleQueryRecord, SizeOf(DongleQueryRecord)); DongleQueryRecord.dwPublicCode := DongleQueryRecordEx.dwPublicCode; DongleQueryRecord.dwID := DongleQueryRecordEx.dwID; DongleQueryRecord.byNProg := DongleQueryRecordEx.byNProg; DongleQueryRecord.byVer := DongleQueryRecordEx.byVer; DongleQueryRecord.wSN := DongleQueryRecordEx.wSN; DongleQueryRecord.wMask := DongleQueryRecordEx.wMask; DongleQueryRecord.wType := DongleQueryRecordEx.wType; FDongles[Count - 1].Data := DongleQueryRecord; FDongles[Count - 1].PnPParentPath := GetPnP_ParentPath(Data.DevInst); end; finally ZwClose(hDeviceHandle); end; end; Inc(dwInfo); end; end; finally if DWord(hAllDevices) <> INVALID_HANDLE_VALUE then SetupDiDestroyDeviceInfoList(hAllDevices); end; end;
This procedure enumerates all keys and enters information about them into an array of TDongleQueryRecord structures, after which you can output this data to the user, or use them in any way directly in your application.

As you can see, everything is quite simple, but in Guardant API object modules this code is placed under a rather serious stack virtual machine and is practically not available for analysis by an ordinary developer. In principle, there is nothing secret, as you can see, the calls do not even use encryption of the transmitted and received buffers, but for some reason the developers of the Guardant SDK did not consider it necessary to publish this information (though I could still get permission to publish this code, because as a result, there are not affected any critical aspects of the key exchange protocol).
But let's not get distracted, you probably noticed in the above procedure a call to the function GetPnP_ParentPath (). This function returns the full path to the device from the root. Its implementation looks like this:
function GetPnP_ParentPath(Value: DWORD): string; var hParent: DWORD; Buffer: array [0..1023] of AnsiChar; Len: ULONG; S: string; begin Result := ''; if CM_Get_Parent(hParent, Value, 0) = 0 then begin Len := Length(Buffer); CM_Get_DevNode_Registry_PropertyA(hParent, 15, nil, @Buffer[0], @Len, 0); S := string(PAnsiChar(@Buffer[0])); while CM_Get_Parent(hParent, hParent, 0) = 0 do begin Len := Length(Buffer); CM_Get_DevNode_Registry_PropertyA(hParent, 15, nil, @Buffer[0], @Len, 0); S := string(PAnsiChar(@Buffer[0])); Result := S + '#' + Result; end; end; if Result = '' then Result := ' '; end;
Actually (you will laugh) the emulator will be detected on the basis of this line.
Usually the device path looks like this:
\ Device \ 00000004 # \ Device \ 00000004 # \ Device \ 00000044 # \ Device \ 00000049 # \ Device \ NTPNP_PCI0005 # \ Device \ USBPDO-3 #
It will at least contain the text NTPNP_PCI or USBPDO.
Those. A PCI bus or HCD hub will at least be one of the ancestors.
Since the emulator is a virtual device, the path to it will look something like this:
\ Device \ 00000040 # \ Device \ 00000040
Accordingly, based on this information, you can implement a simple function:
function IsDonglePresent(const Value: string): Boolean; begin Result := Pos('NTPNP_PCI', Value) > 0; if not Result then Result := Pos('USBPDO', Value) > 0; end;
Well, in conclusion I will describe a few more nuances that can be seen in the de-example attached to the article:
- Relatively recently, new Guardant Flash keys appeared, which are two devices in one. Those. This is both a security key and a regular flash drive. In the UpdateUSBDevices () function, you can see how you can determine which of the DRIVE_REMOVABLE disks in the system are located in the key. In general, nothing new, the general principle was shown in the de-example of safe shutdown of Flash devices .
- An example of obtaining a string representation of a PublicCode key is given (naturally, without the terminating check character, to avoid).
- An example of getting a key release date based on its ID is given.
A small nuance:
The method described in the article will give a false / positive response when a user uses your product of the Anywhere platform:
http://www.digi.com/products/usb/anywhereusb#overviewTake an example
here .