Guardantキーエミュレーターを簡単に検出する方法

Guardantセキュリティキーを使用する場合(モデルに関係なく)、開発者は適切なAPIを使用しますが、交換プロトコルはもちろん、デバイスを操作するためのメカニズムは隠されます。 彼は、すべての作業が行われるゲートウェイアドレス(いわゆるGuardantHandle)のみを使用して、有効なデバイスハンドルを手に持っていません。 システムにこのゲートウェイを使用するキーエミュレーター(特にGuardant Stealth IIまでのモデルに関連)がある場合、開発者は実際の物理キーまたはそのエミュレーションで動作するかどうかを判断できません。



「物理キーの入手可能性を判断する方法」という質問をやがて、私はPavel Agurovの著書「 USB Interface。The Practice of Use and Programming 」で、非常に優れた資料を研究しなければなりませんでした。 その後、キーを操作するすべての「魔法」が実際に隠されているアプリケーションにリンクしている3メガバイトのオブジェクトからのAPI関数呼び出しの分析に時間を費やします。



その結果、元のGuardant APIを使用する必要のない、この問題に対するかなり単純なソリューションが登場しました。

唯一のマイナス点は、これらすべてがひどく文書化されておらず、企業Assetの技術サポートが、Guardantキーのそのような使用に関連する問題さえ考慮しないことです。

そしてもちろん、ある時点で、このコードはすべて、Guardantドライバーの変更により、単に動作を停止する場合があります。

しかし、これまでのところ、2013年4月27日に、この資料はすべて関連性があり、そのパフォーマンスは、バージョン5.31.78から現在の6.00.101までのドライバーでテストされています。





手順は次のようになります。

  1. SetupDiGetClassDevsA()を介して、存在するすべてのデバイスのリストを取得します。
  2. デバイスのGUIDを確認して、デバイスがGuardantキーに関連しているかどうかを確認します。 (Guardantの場合、このパラメーターは{C29CC2E3-BC48-4B74-9043-2C6413FFA784}です)
  3. パラメーターSPDRP_PHYSICAL_DEVICE_OBJECT_NAMEを指定してSetupDiGetDeviceRegistryPropertyA()を呼び出すことにより、各デバイスへのシンボリックリンクを取得します。
  4. ZwOpenFile()を使用してデバイスを開きましょう(シンボリックリンクを使用すると作業が困難になるため、残念ながらここでは機能しません)。


これで、Guardant APIが提供する擬似ハンドル(ゲートウェイ)の代わりに、実際のキーハンドルを手に入れて、対応するIOCTL要求を送信することにより、パラメーターの説明を取得できます。 確かに、わずかなニュアンスがあります。



Guardant Stealth III以降では、キーを操作するためのプロトコルが変更され、その結果、IOCTL要求の定数と着信および発信バッファーの内容が変更されました。 アルゴリズムの通常の操作では、古いキーと新しいキーの両方の機能をサポートすることが望ましいため、違いについて説明します。



まず、IOCTL定数は次のようになります。



GetDongleQueryRecordIOCTL = $E1B20008; GetDongleQueryRecordExIOCTL = $E1B20018;
      
      





Guardant Stealth I / IIからのキーの最初の

Guardant Stealth III以降では2番目(符号/時間/フラッシュ/コード)



デバイスに最初のリクエストを送信するとき、ドライバーは次のバッファーを返すことを期待します。



  TDongleQueryRecord = packed record dwPublicCode: DWord; // Public code byHrwVersion: Byte; //    byMaxNetRes: Byte; //    wType: WORD; //    dwID: DWord; // ID  byNProg: Byte; //   byVer: Byte; //  wSN: WORD; //   wMask: WORD; //   wGP: WORD; //   GP/  wRealNetRes: WORD; //   , .. <= byMaxNetRes dwIndex: DWord; //     end;
      
      





新しいキーの場合、およびプロトコルが変更されたことを考慮すると、最初のリクエストを送信しても何も得られません。 より正確には、もちろんリクエストは実行されますが、バッファは空になります(フラッシュされます)。 したがって、新しいキーに2番目の要求を送信します。これにより、わずかに異なる形式でデータが返されます。



  TDongleQueryRecordEx = packed record Unknown0: array [0..341] of Byte; wMask: WORD; //   wSN: WORD; //   byVer: Byte; //  byNProg: Byte; //   dwID: DWORD; // ID  wType: WORD; //    Unknown1: array [354..355] of Byte; dwPublicCode: DWORD; Unknown2: array [360..375] of Byte; dwHrwVersion: DWORD; //   dwProgNumber: DWORD; //   Unknown3: array [384..511] of Byte; end;
      
      





ここでは、キーに関するより詳細な情報を含む512バイトのブロックがすでに返されています。 残念ながら、何らかの理由で、この構造の完全な説明はできませんが、この記事に必要なフィールドは残しました。



インストールされたキーに関するデータを受信するための一般的なコードは次のようになります。



 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;
      
      





このプロシージャはすべてのキーを反復処理し、それらに関する情報をTDongleQueryRecord構造体の配列に入力します。その後、このデータをユーザーに表示したり、アプリケーションで直接使用したりできます。



画像



ご覧のとおり、すべてが非常に単純ですが、Guardant APIオブジェクトモジュールでは、このコードはかなり深刻なスタック仮想マシンの下に配置され、通常の開発者による分析には実際には使用できません。 原則として、ここでわかるように、呼び出しには送信バッファと受信バッファの暗号化さえ使用されていませんが、何らかの理由でGuardant SDK開発者はこの情報を公開する必要があるとは考えていません(このコードを公開する許可を得ることはできましたが、結果として、鍵交換プロトコルのいくつかの重要な側面はここでは影響を受けません)。



気を散らさないようにしましょう。おそらく、上記の手順でGetPnP_ParentPath()関数の呼び出しに気付いたでしょう。 この関数は、ルートからデバイスへのフルパスを返します。 その実装は次のようになります。



  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;
      
      





実際に(笑います)、エミュレーターはこの行に基づいて検出されます。

通常、デバイスパスは次のとおりです。

\デバイス\ 00000004#\デバイス\ 00000004#\デバイス\ 00000044#\デバイス\ 00000049#\デバイス\ NTPNP_PCI0005#\デバイス\ USBPDO-3#


少なくともテキストNTPNP_PCIまたはUSBPDOが含まれます。

つまり PCIバスまたはHCDハブは、少なくとも1つの祖先になります。

なぜなら エミュレータはまだ仮想デバイスであり、そのパスは次のようになります。

\デバイス\ 00000040#\デバイス\ 00000040


したがって、この情報に基づいて、単純な関数を実装できます。



  function IsDonglePresent(const Value: string): Boolean; begin Result := Pos('NTPNP_PCI', Value) > 0; if not Result then Result := Pos('USBPDO', Value) > 0; end;
      
      





さて、結論として、この記事に添付されているデモ例で見ることができるいくつかのニュアンスについて説明します。





小さなニュアンス:

記事で説明されている方法は、ユーザーがAnywhereプラットフォーム製品を使用するときに、偽/肯定応答を返します。http//www.digi.com/products/usb/anywhereusb#overview



ここで例をご覧ください



All Articles