I could have used CryptStringToBinaryA for the hex->bin conversion, but that would add another import to wineboot.
This patch was created by me on request from Alexandr Oleynikov of the Lutris project back in September of 2020.
-- v3: wineboot: Generate ProductId from host's machine id.
From: Torge Matthies tmatthies@codeweavers.com
Uses a hash of the systemd or dbus machine-id to generate a stable product id. If neither file is found (e.g. on macOS), it will use all zeroes for the hash input.
Signed-off-by: Torge Matthies tmatthies@codeweavers.com --- loader/wine.inf.in | 2 - programs/wineboot/Makefile.in | 2 +- programs/wineboot/wineboot.c | 148 ++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 3 deletions(-)
diff --git a/loader/wine.inf.in b/loader/wine.inf.in index e7b435ed0f0..14a58c9f380 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -343,7 +343,6 @@ HKCU,%CurrentVersion%\Run,,16 HKCU,%CurrentVersionNT%\Winlogon,,16 HKLM,%CurrentVersion%,"CommonFilesDir",,"%16427%" HKLM,%CurrentVersion%,"FirstInstallDateTime",1,21,81,7c,23 -HKLM,%CurrentVersion%,"ProductId",,"12345-oem-0000001-54321" HKLM,%CurrentVersion%,"ProgramFilesDir",,"%16422%" HKLM,%CurrentVersion%,"ProgramFilesPath",0x20000,"%%ProgramFiles%%" HKLM,%CurrentVersion%,"RegisteredOrganization",2,"" @@ -368,7 +367,6 @@ HKLM,%CurrentVersion%\Shell Extensions\Approved,,16 HKLM,%CurrentVersion%\Time Zones,"SymbolicLinkValue",0x60000,"\Registry\Machine%CurrentVersionNT%\Time Zones" HKLM,%CurrentVersion%\Uninstall,,16 HKLM,%CurrentVersionNT%,"InstallDate",0x10003,1273299354 -HKLM,%CurrentVersionNT%,"ProductId",,"12345-oem-0000001-54321" HKLM,%CurrentVersionNT%,"RegisteredOrganization",2,"" HKLM,%CurrentVersionNT%,"RegisteredOwner",2,"" HKLM,%CurrentVersionNT%,"SystemRoot",,"%10%" diff --git a/programs/wineboot/Makefile.in b/programs/wineboot/Makefile.in index 667f8f48702..141c0d560f8 100644 --- a/programs/wineboot/Makefile.in +++ b/programs/wineboot/Makefile.in @@ -1,6 +1,6 @@ MODULE = wineboot.exe IMPORTS = uuid advapi32 ws2_32 kernelbase -DELAYIMPORTS = shell32 shlwapi version user32 setupapi newdev +DELAYIMPORTS = shell32 shlwapi version user32 setupapi newdev bcrypt
EXTRADLLFLAGS = -mconsole
diff --git a/programs/wineboot/wineboot.c b/programs/wineboot/wineboot.c index 4de20705224..8ab9b4c8c91 100644 --- a/programs/wineboot/wineboot.c +++ b/programs/wineboot/wineboot.c @@ -71,6 +71,7 @@ #include <wine/svcctl.h> #include <wine/asm.h> #include <wine/debug.h> +#include <bcrypt.h>
#include <shlobj.h> #include <shobjidl.h> @@ -748,6 +749,152 @@ static void create_hardware_registry_keys(void) HeapFree( GetProcessHeap(), 0, power_info ); }
+static unsigned char decode_hex( char c ) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0xFF; +} + +static void get_machineid( BYTE *buf ) +{ + static const char sd_machineid_path[] = "\??\unix\/etc/machine-id"; + static const char dbus_machineid_path[] = "\??\unix\/var/lib/dbus/machine-id"; + HANDLE file; + char buffer[34]; + BOOL status; + DWORD bytes_read; + int i; + + memset( buf, 0, 16 ); + + file = CreateFileA( sd_machineid_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL ); + if (file == INVALID_HANDLE_VALUE) + file = CreateFileA( dbus_machineid_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL ); + if (file == INVALID_HANDLE_VALUE) + { + WARN( "Could not open machine id file: error %lu\n", GetLastError() ); + return; + } + status = ReadFile( file, buffer, sizeof(buffer), &bytes_read, NULL ); + CloseHandle( file ); + if (!status) + { + WARN( "Could not read machine id file: error %lu\n", GetLastError() ); + return; + } + if (!(bytes_read == 32 || (bytes_read == 33 && buffer[32] == '\n'))) + { + WARN( "Wrong machine id file size\n" ); + return; + } + + for (i = 0; i < 16; i++) + { + unsigned char high_nibble, low_nibble; + high_nibble = decode_hex( buffer[i * 2] ); + low_nibble = decode_hex( buffer[i * 2 + 1] ); + if (high_nibble == 0xFF || low_nibble == 0xFF) + { + WARN( "Failed to decode machine id byte %d\n", i ); + memset( buf, 0, i ); + return; + } + buf[i] = (high_nibble << 4) | low_nibble; + } +} + +#define MACHINEID_HASH_ALG BCRYPT_SHA1_ALGORITHM +#define MACHINEID_HASH_ALG_SIZE 20 + +static void get_machineid_hash( BYTE *buf ) +{ + static const char salt[8] = "WINESALT"; + BYTE input[16 + sizeof(salt)]; + BCRYPT_ALG_HANDLE alg; + NTSTATUS status; + BYTE hash[MACHINEID_HASH_ALG_SIZE]; + int i; + + memset( buf, 0, 8 ); + + get_machineid( input ); + memcpy( &input[sizeof(input) - sizeof(salt)], salt, sizeof(salt) ); + + status = BCryptOpenAlgorithmProvider( &alg, MACHINEID_HASH_ALG, NULL, 0 ); + if (!status) + { + status = BCryptHash( alg, NULL, 0, input, sizeof(input), hash, sizeof(hash) ); + BCryptCloseAlgorithmProvider( alg, 0 ); + } + if (status) + { + WARN( "Couldn't hash machine id: error %lx\n", status ); + return; + } + + for (i = 8; i < ARRAY_SIZE(hash); i++) + hash[i % 8] ^= hash[i]; + memcpy( buf, hash, 8 ); + return; +} + +static void get_productid( WCHAR *buf ) +{ + BYTE machineid[8]; + DWORD mid_lodword, mid_hidword; + unsigned int c, e; + unsigned int tmp, check_digit; + + get_machineid_hash( machineid ); + /* Derived from "Inside Windows Product Activation" by Fully Licensed GmbH, + available at http://www.licenturion.com/xp/ */ + mid_lodword = (machineid[3] << 24U) | (machineid[2] << 16U) | (machineid[1] << 8U) | machineid[0]; + mid_hidword = (machineid[7] << 24U) | (machineid[6] << 16U) | (machineid[5] << 8U) | machineid[4]; + c = (unsigned int)(mid_lodword * 999999ULL / 0xFFFFFFFF); + e = (unsigned int)(mid_hidword * 999ULL / 0xFFFFFFFF); + + tmp = c; + check_digit = tmp % 10; + tmp = tmp / 10; + check_digit += tmp % 10; + tmp = tmp / 10; + check_digit += tmp % 10; + tmp = tmp / 10; + check_digit += tmp % 10; + tmp = tmp / 10; + check_digit += tmp % 10; + tmp = tmp / 10; + check_digit += tmp; + check_digit = 7 - check_digit % 7; + + swprintf( buf, 24, L"55034-OEM-%06u%u-00%03u", c, check_digit, e ); +} + +/* create the volatile software registry keys */ +static void create_software_registry_keys(void) +{ + WCHAR productid[24]; + HKEY key; + + get_productid( productid ); + + if (!RegCreateKeyW( HKEY_LOCAL_MACHINE, L"Software\Microsoft\Windows NT\CurrentVersion", &key )) + { + set_reg_value( key, L"ProductId", productid ); + RegCloseKey( key ); + } + + if (!RegCreateKeyW( HKEY_LOCAL_MACHINE, L"Software\Microsoft\Windows\CurrentVersion", &key )) + { + set_reg_value( key, L"ProductId", productid ); + RegCloseKey( key ); + } +}
/* create the DynData registry keys */ static void create_dynamic_registry_keys(void) @@ -1697,6 +1844,7 @@ int __cdecl main( int argc, char *argv[] )
create_user_shared_data(); create_hardware_registry_keys(); + create_software_registry_keys(); create_dynamic_registry_keys(); create_environment_registry_keys(); create_computer_name_keys();
On Wed Aug 24 13:07:33 2022 +0000, Hans Leidekker wrote:
What problem is fixed by this patch?
Having the same ProductId in every prefix.
Neither me nor Alexandr remember what this was originally for, but since I already have the patch, might as well upstream it.
On Wed Aug 24 13:07:44 2022 +0000, Torge Matthies wrote:
Having the same ProductId in every prefix. Neither me nor Alexandr remember what this was originally for, but since I already have the patch, might as well upstream it.
Possibly it was for SCP: Secret Laboratory, there was some Proton bug reports and feedback from the game developers about people getting banned because the IDs were invalid. See https://github.com/ValveSoftware/Proton/issues/4229#issuecomment-963047595 and https://github.com/ValveSoftware/Proton/issues/4229#issuecomment-974755510
If that is the motivation under this patch, ProductID is not enough. It needs a bit more, most notably unique user SID which is implemented in a hacky way in Proton.
On 24 Aug 2022, at 08:22, Rémi Bernon wine@gitlab.winehq.org wrote:
On Wed Aug 24 13:07:44 2022 +0000, Torge Matthies wrote:
Having the same ProductId in every prefix. Neither me nor Alexandr remember what this was originally for, but since I already have the patch, might as well upstream it.
Possibly it was for SCP: Secret Laboratory, there was some Proton bug reports and feedback from the game developers about people getting banned because the IDs were invalid. See https://github.com/ValveSoftware/Proton/issues/4229#issuecomment-963047595 and https://github.com/ValveSoftware/Proton/issues/4229#issuecomment-974755510
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/701#note_6827
On Wed Aug 24 13:47:48 2022 +0000, **** wrote:
Paul Gofman replied on the mailing list:
If that is the motivation under this patch, ProductID is not enough. It needs a bit more, most notably unique user SID which is implemented in a hacky way in Proton.
I guess there's not enough room in ProductID to make it unique, so it would need to be complemented with other data. It's not clear to me that we need to tie it to the machine id (also note that machine-id on Linux isn't a hardware id, it's a random number generated during OS install).
On 8/24/22 09:25, Hans Leidekker (@hans) wrote:
On Wed Aug 24 13:47:48 2022 +0000, **** wrote:
Paul Gofman replied on the mailing list:
If that is the motivation under this patch, ProductID is not enough. It needs a bit more, most notably unique user SID which is implemented in a hacky way in Proton.
I guess there's not enough room in ProductID to make it unique, so it would need to be complemented with other data. It's not clear to me that we need to tie it to the machine id (also note that machine-id on Linux isn't a hardware id, it's a random number generated during OS install).
There is actually also DigitalProductId and this one should be unique, unlikely so for ProductID.