Jump to content
Mark-

Windows product ID (from the registry)...

Recommended Posts

Also posted on Embarcadero site.

 

Hello,

 

Working on creating a better method to fingerprint a computer without the issues of MAC ID, memory size etc.. I was looking at the "ProductID" in the registry and found that it is only for show and can be changed to any value without altering the OS operation.

 

Several other product ID type values are present as binary keys. I found some C# code (http://www.mrpear.net/en/blog/1207/how-to-get-windows-product-key-from-digitalproductid-exported-out-of-registry) that could convert the binary to the product key code but no code in Delphi. I converted it to Delphi and it appears to work, somewhat. It returned the correct product code on a W10 64-bit Pro and a W7 32-bit Pro install.

 

Two places in the code I am not sure are correct.

 

Other test returned a code but, I could not verify it was correct. On a W7 64-bit, I have the code, from the CD and the returned code was not correct.

I am posting the code for anyone that might need it and check if others have a better solution or comments.

 

Cheers,

 

Mark

 

program GetProdIDs;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Winapi.Windows,
  System.SysUtils,
  System.Win.Registry;

procedure ReadRegisteryBinary(rKey:nativeUInt; const path,keyName:string; var buf; var bufSize:integer);
var
 r:TRegistry;
begin
 r:=nil;
 try
  r:=TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
  r.RootKey:=rKey;
  if not r.OpenKey(path,false) then
   Exit;
  if (r.GetDataSize(keyName) > bufSize) then
   begin
    bufSize:=0;
    Exit;
   end;
  try
   bufSize:=r.ReadBinaryData(keyName,buf,bufSize);
  except
   on ERegistryException do
    bufSize:=0;
  end;
 finally
  r.Free;
 end;
end;

function DecodeDigitalProductId(var data:array of byte; dataSize:integer):string;
const
 keyOffset = 52;
 digits = ANSIString('BCDFGHJKMPQRTVWXY2346789');
var
// isWin8:byte;
 last,i,current,j:integer;
 key,keypart1,keypart2:string;
begin
 result:='';
// isWin8:=((data[66] div 6) and 1);                    //not sure about this
 data[66]:=(data[66] and $F7) {or ((isWin8 and $02) * 4)};

 for i:=24 downto 0 do
  begin
   current:=0;
   for j:=14 downto 0 do
    begin
     current:=current * 256;
     current:=data[j + keyOffset] + current;
     data[j + keyOffset]:=(current div 24);
     current:=current mod 24;
     last:=current;
    end;

   if (((current + 1) > 0) and ((current + 1) <= length(digits))) then
    key:=digits[current + 1] + key;
  end;

 keypart1:=key.Substring(1, last);
 keypart2:=key.Substring(last + 1, key.Length - (last + 1));
 key:=keypart1 + 'N' + keypart2;                //not sure if this is correct, any keys without 'N'

 i:=5;
 while (i < key.Length) do
  begin
   key:=key.Insert(i, '-');
   Inc(i,6);
  end;

 result:=key;
end;

function GetProductID(const idStr:string):string;
var
 dataSize:integer;
 data:array[0..$FFF] of byte;
begin
 dataSize:=length(data);
 ReadRegisteryBinary(HKEY_LOCAL_MACHINE,'SOFTWARE\Microsoft\Windows NT\CurrentVersion',idStr,data,dataSize);
 if (dataSize > 0) then
  result:=DecodeDigitalProductId(data,dataSize)
 else
  result:='';
end;

function ReadProductIDString:string;
var
 r:TRegistry;
begin
 result:='';
 r:=nil;
 try
  r:=TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
  r.RootKey:=HKEY_LOCAL_MACHINE;
  if not r.OpenKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion',false) then
   Exit;
  try
   result:=r.ReadString('ProductId');
  except
   on ERegistryException do
    result:='error';
  end;
 finally
  r.Free;
 end;
end;

begin
 try
  Writeln('ProductId: ' + ReadProductIDString);
  Writeln('');
  Writeln('DigitalProductId:  ' + GetProductID('DigitalProductId'));
  Writeln('DigitalProductId1: ' + GetProductID('DigitalProductId1'));
  Writeln('DigitalProductId2: ' + GetProductID('DigitalProductId2'));
  Writeln('DigitalProductId3: ' + GetProductID('DigitalProductId3'));
  Writeln('DigitalProductId4: ' + GetProductID('DigitalProductId4'));
  Writeln('DigitalProductId5: ' + GetProductID('DigitalProductId5'));

  Writeln('');
  Writeln('Press enter/return');
  Readln;
  except
   on E: Exception do
    Writeln(E.ClassName, ': ', E.Message);
  end;

//if the OS was updated using the free version one of these might be the product ID code
//    Windows 10 Home - YTMG3-N6DKC-DKB77-7M9GH-8HVX7
//    Windows 10 Pro - VK7JG-NPHTM-C97JM-9MPGT-3V66T
//    Windows 10 Home SL- BT79Q-G7N6G-PGBYW-4YWX6-6F4BT
//    Windows 10 Pro VL-MAK - QJNXR-7D97Q-K7WH4-RYWQ8-6MT6Y

end.

 

Share this post


Link to post

The line with 'isWin8' is required and correct and the line following with 'data[66]' should only be executed if isWin8=true.

Share this post


Link to post
1 hour ago, FredS said:

The line with 'isWin8' is required and correct and the line following with 'data[66]' should only be executed if isWin8=true.

Thanks.

Changed to:

 isWin8:=((data[66] div 6) and 1);                   
 if (isWin8 <> 0) then
  data[66]:=(data[66] and $F7) or ((isWin8 and $02) * 4);

Did you see:

key:=keypart1 + 'N' + keypart2; //not sure if this is correct, any keys without 'N'

 

Share this post


Link to post

Don't rely on the registry, it easily can be overwritten! Even if it's temporary, it can confuse your application.

I found this code in the Internet, which I extended to generate an MD5 hash with DCPCrypt. Remove it if you don't need / have it and use it if you want.

 

HWID.7z

Share this post


Link to post
5 hours ago, Mark- said:

any keys without 'N'

Works here.. but that code should only run if 'IsWin8'

Edited by FredS

Share this post


Link to post

You can also validate your findings through PowerShell
 

PS C:\> Get-ComputerInfo | select WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer

WindowsProductName    WindowsVersion OsHardwareAbstractionLayer
------------------    -------------- --------------------------
Windows 10 Enterprise 1809           10.0.17763.737 

 

Share this post


Link to post

Thanks

 

The result:

WindowsProductName WindowsVersion OsHardwareAbstractionLayer
------------------ -------------- --------------------------
Windows 10 Pro     1809

 

I ran it with no input parameters and most of the results are blank.

 

Edited by Mark-

Share this post


Link to post

Which version of PowerShell?

 

I recommend installing PS Core 6 while waiting for 7.
Faster and richer.

 

From your Windows Powershell command line, run:

iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI"

Note that PS Core 6 runs on Linux and MacOS too.

Share this post


Link to post

Never used PowerShell much.

The product version seems to be the same as the OS.

The path has V1.0 at the end.

OK installed it and much more data.

Thanks,

  • Like 1

Share this post


Link to post
21 hours ago, aehimself said:

Don't rely on the registry, it easily can be overwritten!

Thanks very much for the code. It is very interesting.

Am I wrong to think that some values, while using WMI to fetch the values, WMI retrieves the values from the registry?

Edited by Mark-

Share this post


Link to post
3 hours ago, Mark- said:

Am I wrong to think that some values, while using WMI to fetch the values, WMI retrieves the values from the registry?

To be honest I am not 100% sure about it, but I think no. WMI is storing information in it's own database, which is a piece of junk. Back in the days when I was a sysadmin we had to rebuild countless corrupted WMI databases 🙂

Wikipedia also seems to confirm that it's separate from Registry: "Windows Management Instrumentation (WMI) consists of a set of extensions to the Windows Driver Model that provides an operating system interface through which instrumented components provide information and notification."

 

https://en.wikipedia.org/wiki/Windows_Management_Instrumentation

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×