Zetta-Ømnis Soluções Tecnológicas
Desenvolvendo hoje a tecnologia do amanhã
Visite Nosso Patrocinador
Você está aqui:
DO NOT UNDERSTAND PORTUGUESE? CLICK ON THE FLAG TO CHANGE THE LANGUAGE!

Obtendo o serial físico de um HD

Imagem meramente ilustrativa

Tenho visto nos fóruns e grupos de Facebook que eu frequento que existe uma demanda crescente por meios de proteger nossos sofwares. Minha observação mostra que todas elas culminam com uma questão: Como obter uma identificação única do hardware? De fato nenhum esquema de proteção de software é eficiente sem algo que consiga associar diretamente este sofware à máquina onde ele está instalado. Meu intuito com esse artigo não é ensinar como realizar a "trava" de um software em um hardware específico, mas sim, como identificar o hardware. Cabe ao desenvolvedor elaborar os meios de como associar a identificação do hardware ao software e assim permitir que esse software execute apenas naquele hardware específico. Hoje estou sem muita paciência pra escrever, então vou ser o mais direto possível (sorte a sua, leitor).

Windows Management Instrumentation (WMI)

Os Sistemas Operacionais mais modernos bloquearam o acesso direto ao Hardware (esse modo é inseguro), mas ainda é possível obter informações sobre o hardware usando WMI. O WMI é um conjunto de extensões para o Windows Driver Model que fornece uma interface de sistema operacional através da qual componentes instrumentados fornecem informação e notificação. Esta é a definição da Wikipédia, mas eu prefiro explicar com minhas palavras, de forma bem simples:

O WMI é como um banco de dados de informações sobre o sistema operacional e todo o hardware que está conectado nele, e como todo bom banco de dados, as informações são obtidas por meio da execução de consultas SQL, as quais no WMI se chamam WQL (WMI Query Language - https://msdn.microsoft.com/en-us/library/aa394606(v=vs.85).aspx)

Isso mesmo! A obtenção de tudo que precisamos é feita por meio de execução de consultas à "Base de Dados do WMI". Como pretendo ser bem direto neste artigo, não vou entrar em detalhes explicando o WMI a fundo.


O WMI Delphi Code Creator

Criar código para executar consultas WQL não é difícil, mas também ninguém é obrigado a saber como fazer isso logo de primeira. Mesmo eu, que já sei um pouco de WMI, não perco tempo decorando o código comum. Eu prefiro o caminho mais simples e rápido e para isso eu uso o WMI Code Creator, que é um programa que automatiza a criação de código WMI para Delphi, Free Pascal, Oxygene, Embarcadero C++, Microsoft C++ e C#. Este programa é gratuito e pode ser baixado em https://github.com/RRUZ/wmi-delphi-code-creator. Lá você encontra mais informações sobre o programa além de screenshots.

Com o WMI Code Creator você pode obter uma infinidade de outras informações sobre seu sistema (incluindo informações sobre o hardware conectado a ele). Não vou explicar como se usa esse programa. Você deve baixá-lo e explorá-lo, exatamente como eu fiz.

Citei esse programa aqui porque 90% do código que vou mostrar a seguir foi feita usando ele. Eu apenas customizei esse código para se adaptar às minhas necessidades, por exemplo, para obter o serial a partir de uma letra de volume, pois não há como fazer isso diretamente usando o WMI.

Obtendo o serial físico de um HD a partir de uma letra de volume

O serial físico do HD é uma identificação única desta peça de hardware e não tem formato definido, isto é, cada fabricante tem seu própio modelo de seriais. Alguns usam apenas números, outros letras e números, mas o formato destes seriais não é importante, o que vale mesmo é que, dentro de um mesmo fabricante, é IMPOSSÍVEL que existam dois seriais iguais e entre fabricantes diferentes, é IMPROVÁVEL que existam dois seriais idênticos. Estas características tornam este serial uma informação extremamente útil quando se quer identificar uma máquina como um todo.

Outra característica importante desses seriais é que eles não podem ser alterados ou clonados de nenhuma forma. Muita gente confunde Serial Físico do HD com Serial do Volume. O serial de um volume é um número hexadecimal com oito caracteres e que muda toda vez que um HD é formatado, portanto, ele NÃO DEVE ser usado como identificador de um hardware pois na verdade ele é um serial aplicado via software. Abaixo está uma imagem que mostra o serial de um volume:


Chega de bla bla bla. Eis abaixo uma unit que exporta uma única função (GetHPI - Get HD Info). Esta função é a responsável por extrair informações de um HD a partir de sua letra de volume, mediante uso de WMI. Um programa de exemplo está anexado ao artigo para que você possa estudá-lo melhor executando-o passo-a-passo a fim de aprender um pouco mais sobre o WMI. Ele funciona obtendo informações do HD onde ele estiver executando.

unit UHPI;

interface

type
  THPI = record
    Caption: String;
    Model: String;
    InterfaceType: String;
    MediaType: String;
    PNPDeviceID: String;
    DeviceID: String;
    Size: String;
    Partitions: String;
    BytesPerSector: String;
    SectorsPerTrack: String;
    TotalCylinders: String;
    TotalHeads: String;
    TotalSectors: String;
    TotalTracks: String;
    TracksPerCylinder: String;
    FirmwareRevision: String;
    SerialNumber: String;
  end;

function GetHPI(aVolume: Char): THPI;

implementation

uses
  Winapi.ActiveX, System.Win.ComObj, System.SysUtils, Winapi.Windows,
  System.Classes, System.Variants;

function GetWMIInfo(const aWMIProperty, aWMIClass: String; const aDeviceId: String = ''): String;
const
  wbemFlagForwardOnly = $00000020;
var
  SWbemLocator: OleVariant;
  SWbemService: OleVariant;
  SWbemObjectSet: OleVariant;
  WbemObject: OleVariant;
  oEnum        : IEnumvariant;
  iValue       : Cardinal;
begin;
  Result := '';

  SWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  SWbemService := SWbemLocator.ConnectServer('.', 'root\CIMV2', '', '');

  if Trim(aDeviceId) <> '' then
    SWbemObjectSet := SWbemService.ExecQuery(Format('Select %s from %s where %s',[aWMIProperty, aWMIClass, 'Tag = ' + aDeviceId]),'WQL',wbemFlagForwardOnly)
  else
    SWbemObjectSet := SWbemService.ExecQuery(Format('Select %s from %s',[aWMIProperty, aWMIClass]),'WQL',wbemFlagForwardOnly);

  oEnum := IUnknown(SWbemObjectSet._NewEnum) as IEnumVariant;

  if oEnum.Next(1, WbemObject, iValue) = 0 then
    Result := WbemObject.Properties_.Item(aWMIProperty).Value;
end;

function GetHPI(aVolume: Char): THPI;
////////////////////////////////////////////////////////////////////////////////
function GetFirmwareRevision(aDiskDrive: OleVariant): String;
begin
  try
    Result := aDiskDrive.FirmwareRevision;
  except
    Result := 'n/a';
  end;
end;

function GetSerialNumber(aDiskDrive: OleVariant; aDeviceId: String): String;
begin
  try
    Result := aDiskDrive.SerialNumber;
  except
    try
      Result := GetWMIInfo('SerialNumber', 'Win32_PhysicalMedia',QuotedStr(aDeviceId));
    except
      Result := 'n/a';
    end;
  end;
end;
////////////////////////////////////////////////////////////////////////////////
const
  wbemFlagForwardOnly = $00000020;
var
  SWbemLocator: OleVariant;
  SWbemServices: OleVariant;

  DiskDrives: IEnumVARIANT;
  DiskDrive: OleVariant;
  DiskDrivesFetched: Cardinal;

  DiskPartitions: IEnumVARIANT;
  DiskPartition: OleVariant;
  DiskPartitionsFetched: Cardinal;

  DiskVolumes: IEnumVARIANT;
  DiskVolume: OleVariant;
  DiskVolumesFetched: Cardinal;

  EscapedDeviceID: String;
  HDDInformation: String;
begin
  ZeroMemory(@Result,SizeOf(THPI));

  CoInitialize(nil);
  with TStringList.Create do
    try
      { Realiza a conexão }
      SWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');

      SWbemServices := SWbemLocator.ConnectServer('.', 'root\CIMV2', '', '');

      { Obtém uma lista de drives físicos e circula por ela }
      DiskDrives := IUnknown(SWbemServices.ExecQuery('SELECT * FROM Win32_DiskDrive','WQL',wbemFlagForwardOnly)._NewEnum) as IEnumVariant;
      while DiskDrives.Next(1, DiskDrive, DiskDrivesFetched) = 0 do
      begin
        EscapedDeviceID := StringReplace(String(DiskDrive.DeviceID),'\','\\',[rfReplaceAll]);

        { Para o drive físico atual, obtém uma lista de partições e circula por ela }
        DiskPartitions := IUnknown(SWbemServices.ExecQuery('ASSOCIATORS OF {Win32_DiskDrive.DeviceID="' + EscapedDeviceID + '"} WHERE AssocClass = Win32_DiskDriveToDiskPartition','WQL',wbemFlagForwardOnly)._NewEnum) as IEnumVariant;
        while DiskPartitions.Next(1, DiskPartition, DiskPartitionsFetched) = 0 do
        begin

          { Para a partição atual, obtém uma lista de volumes (letras) e circula por ela }
          DiskVolumes := IUnknown(SWbemServices.ExecQuery('ASSOCIATORS OF {Win32_DiskPartition.DeviceID="' + String(DiskPartition.DeviceID) + '"} WHERE AssocClass = Win32_LogicalDiskToPartition','WQL',wbemFlagForwardOnly)._NewEnum) as IEnumVariant;
          while DiskVolumes.Next(1, DiskVolume, DiskVolumesFetched) = 0 do
          begin
            { Adiciona ao StringlList uma linha com as informações disponíveis indexada pela letra do volume }
            Add(String(DiskVolume.DeviceID)[1] + '=<' + DiskDrive.Caption + '><'
                                                      + DiskDrive.Model + '><'
                                                      + DiskDrive.InterfaceType + '><'
                                                      + DiskDrive.MediaType + '><'
                                                      + DiskDrive.PNPDeviceID + '><'
                                                      + DiskDrive.DeviceID + '><'
                                                      + String(DiskDrive.Size) + '><'
                                                      + String(DiskDrive.Partitions) + '><'
                                                      + String(DiskDrive.BytesPerSector) + '><'
                                                      + String(DiskDrive.SectorsPerTrack) + '><'
                                                      + String(DiskDrive.TotalCylinders) + '><'
                                                      + String(DiskDrive.TotalHeads) + '><'
                                                      + String(DiskDrive.TotalSectors) + '><'
                                                      + String(DiskDrive.TotalTracks) + '><'
                                                      + String(DiskDrive.TracksPerCylinder) + '><'
                                                      + GetFirmwareRevision(DiskDrive) + '><'
                                                      + GetSerialNumber(DiskDrive,EscapedDeviceID) + '>');

            DiskVolume := Unassigned;
          end;

          DiskPartition := Unassigned;
        end;

        DiskDrive := Unassigned;
      end;

      { Busca no StringList a letra passada como parâmetro }
      HDDInformation := Values[aVolume];

      with TStringList.Create do
        try
          Text := StringReplace(Copy(HDDInformation,2,Length(HDDInformation) - 2),'><',#13#10,[rfReplaceAll]);
          if Count > 0 then
            with Result do
            begin
              Caption := Trim(Strings[0]);
              Model := Trim(Strings[1]);
              InterfaceType := Trim(Strings[2]);
              MediaType := Trim(Strings[3]);
              PNPDeviceID := Trim(Strings[4]);
              DeviceID := Trim(Strings[5]);
              Size := Trim(Strings[6]);
              Partitions := Trim(Strings[7]);
              BytesPerSector := Trim(Strings[8]);
              SectorsPerTrack := Trim(Strings[9]);
              TotalCylinders := Trim(Strings[10]);
              TotalHeads := Trim(Strings[11]);
              TotalSectors := Trim(Strings[12]);
              TotalTracks := Trim(Strings[13]);
              TracksPerCylinder := Trim(Strings[14]);
              FirmwareRevision := Trim(Strings[15]);
              SerialNumber := Trim(Strings[16]);
            end;
        finally
          Free;
        end;

    finally
      Free;
      CoUninitialize;
    end;
end;

end.

Porque usar o serial físico do HD? Não haveria outra forma?

Além das formas pagas de identificação de hardwares (usando dongles), existem pessoas por aí dizendo que existem outras formas de obter um identificador único de um hardware. Alguns falam de serial da placa mãe, outros falam em Endereço MAC. Já ouvi falar até mesmo de serial da BIOS e do Processador. Sobre estes exemplos que eu dei tenho algumas ressalvas a fazer:

  • Nem todas as placas-mãe tem serial
  • Nem todas as BIOS tem serial e algumas delas permitem alterar este serial
  • Pelo que sei, apenas o Pentium III possuía um serial "legível", mas alguém não gostou dessa ideia por conta de privacidade e outros processadores da Intel não tem (ou não expõem) um serial
  • O Endereço MAC pode ser clonado

O serial físico do HD continua sendo a forma mais simples de se identificar um hardware. Se você decidir travar seu software usando o serial físico do HD, ele ficará travado no HD em questão, o que, do meu ponto de vista, é bem razoável


  Arquivos anexados  
Arquivo Descrição Tamanho Modificado em
Download this file (HPI.zip) HPI Pequeno projeto que demonstra o uso da função GetHPI 53 KB 20/11/2016 às 01:45
Acesso Rápido
Não digite mais que o necessário...



Escaneie este QRCode em dispositivos móveis para acessar a página atual rapidamente nestes dispositivos
Conteúdo Verificado!
#BULLSHITFREE #CLICKBAITFREE
#MONEYLESS
Este site é amigo do desenvolvedor do mundo real
Gostou do conteúdo?
Se você gostou do conteúdo que está lendo, você pode ajudar a manter este site no ar doando qualquer quantia. Use os botões abaixo para realizar sua doação.
 
É rápido, fácil e indolor :)
 

Estatísticas em tempo real

Visite Nosso Patrocinador