¿Cómo obtener el número de serie de la ROM BIOS?

Obteniendo el número de serie del BIOS

Copyright © 2000 Ernesto De Spirito

InstallAWARE - MSI sin ciencia espacial

Para un esquema simple de protección de copia necesitamos saber si la máquina en que está ejecutándose nuestra aplicación es la misma donde fue instalada. Podemos guardar los datos de la máquina en el Registro de Windows cuando la aplicación sea instalada o ejecutada por primera vez, y luego cada vez que la aplicación se ejecute comparamos los datos de la máquina con el que hemos guardado para ver si son iguales o no.

Pero, ¿qué datos de la máquina deberíamos usar y cómo los obtenemos? En una edición pasada mostramos cómo obtener el número de serie del volumen de una unidad de disco lógica, pero normalmente esto no es satisfaciente para un desarrollador de software dado que este número puede ser cambiado.

Una solución mejor podría ser utilizar el número de serie del BIOS. BIOS significa Sistema Básico de Entrada/Salida (Basic Input/Output System), y básicamente es un chip en la placa madre de la PC que contiene el programa de inicialización de la PC (todo hasta la carga del sector de arranque del disco u otro dispositivo de arranque) y algunas rutinas básicas de acceso a dispositivos. Desafortunadamente, los distintos fabricantes de BIOS han puesto los números de serie y otra información del BIOS en diferentes posiciones de memoria, así que el código que uno generalmente puede encontrar en la red para acceder a esta información puede que funcione con algunas máquinas pero no con otras. Sin embargo, la mayoría (si no todos) los fabricantes de BIOS han puesto la información en alguna parte en los último 8 Kb del primer Mb de memoria, es decir en el espacio de direcciones de $000FE000 a $000FFFFF. Si asumimos que "s" es una variable string, el código siguiente guardaría esos 8 Kb en ella:

  SetString(s, PChar(Ptr($FE000)), $2000);  // $2000 = 8196

Podemos tomar los últimos 64 Kb para estar seguros que no nos perdemos de nada:

  SetString(s, PChar(Ptr($F0000)), $10000);  // $10000 = 65536

El problema es que no se recomienda guardar "grandes volúmenes" de datos en el Registro de Windows. Sería mejor si pudiéramos restringirnos a 256 bytes o menos usando alguna técnica de hashing/checksum. Por ejemplo podemos usar la unidad SHA1 y opcionalmente la unidad Base64 introducidas en el Boletín Pascal #17. El código se podría parecer al siguiente:

uses SHA1, Base64;

function GetHashedBiosInfo: string;
var
  SHA1Context: TSHA1Context;
  SHA1Digest: TSHA1Digest;
begin
  // Obtener los datos del BIOS
  SetString(Result, PChar(Ptr($F0000)), $10000);
  // Obtener el hash
  SHA1Init(SHA1Context);
  SHA1Update(SHA1Context, PChar(Result), Length(Result));
  SHA1Final(SHA1Context, SHA1Digest);
  SetString(Result, PChar(@SHA1Digest), sizeof(SHA1Digest));
  // Devolver el hash codificado en caracteres imprimibles
  Result := B64Encode(Result);
end;

De esta forma obtenemos una cadena corta que podemos guardar en el Registro de Windows sin problemas. La puede considerar como una especie de "número de serie del BIOS".

Como alternativa al uso de SHA1 y Base64, puede usar el algoritmo de suma de verificación y la función de conversión de binario a cadena que más le guste. En el siguiente ejemplo usamos un simple algoritmo que calcula una suma de verificación de 64 bits y finalmente la convertimos en una cadena de 16 caracteres con dígitos hexadecimales:

function GetBiosCheckSum: string;
var
  s: int64;
  i: longword;
  p: PChar;
begin
  i := 0;
  s := 0;
  p := PChar($F0000);
  repeat
    inc(s, Int64(Ord(p^)) shl i);
    if i < 64 then inc(i) else i := 0;
    inc(p);
  until p > PChar($FFFFF);
  Result := IntToHex(s,16);
end;

Mostrando la información del BIOS

Si quisiéramos mostrar la información del BIOS debemos recorrer los bytes para extraer todas las cadenas terminadas en nulo con caracteres ASCII imprimibles de por lo menos 8 caracteres de longitud, como se hace en la siguiente función:

function GetBiosInfoAsText: string;
var
  p, q: pchar;
begin
  q := nil;
  p := PChar(Ptr($FE000));
  repeat
    if q <> nil then begin
      if not (p^ in [#10, #13, #32..#126, #169, #184]) then begin
        if (p^ = #0) and (p - q >= 8) then begin
          Result := Result + TrimRight(String(q)) + #13#10;
        end;
        q := nil;
      end;
    end else
      if p^ in [#33..#126, #169, #184] then
        q := p;
    inc(p);
  until p > PChar(Ptr($FFFFF));
  Result := TrimRight(Result);
end;

Luego podemos utilizar el valor de retorno para por ejemplo mostrarlo en un memo:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Text := GetBiosInfoAsText;
end;

ATENCION: El código presentado en este artículo no funcionará en Windows NT/2000, aunque alguna información sobre la BIOS y el hardware del sistema se puede encontrar en el Registro de Windows bajo la clave HKEY_LOCAL_MACHINE\Hardware\Description\System, pero no la suficiente como para identificar una máquina hasta donde yo sé...


Puede encontrar el código fuente completo de este artículo en el archivo que acompaña al Boletín Pascal #20.

JfControls Library - para Delphi y C++ Builder