[C#] Decrypt Office 2010 Digital Product ID

Discussion in 'Mixed Languages' started by Josh Cell, Jul 11, 2011.

  1. DrCoolZic

    DrCoolZic MDL Member

    Jan 15, 2011
    247
    326
    10
    Thanks for your answer. Yes in WMI you find the partial key as usual but I also found that the office key information is located somewhere else (in C2R) and it is no more encrypted.
    But I need to further investigate. There are so many different way to install O 2019 ...
     
  2. Muerto

    Muerto MDL Debugger

    Mar 7, 2012
    1,865
    2,143
    60
    #42 Muerto, Jan 5, 2019
    Last edited: Jan 14, 2021
    ...
     
  3. DrCoolZic

    DrCoolZic MDL Member

    Jan 15, 2011
    247
    326
    10
    For reference it seems that with Office 2019 the key is no more encrypted in DigitalProductID
    It is now provided not encrypted in ProductKeys. If you have several office product installed the keys are concatenated separated by comma.
    This key is located here: SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL
    The code to use looks like:
    Code:
                List<string> DetectedKeys = new List<string>();
                string RegistrationPathName, DigitalIdPath;
                RegistryKey RegistryBase, registry;
                // if office is 32-bits we want the to use the 32-bit registry
                if (GetOfficeArch() == "x86")
                    RegistryBase = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
                else
                    RegistryBase = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
    
                // get keys for office 2010 (14.0), 2013 (15.0), 2016 (16.0)
    ...
                // get keys for office 2019 (placed inClickToRun ...)
                RegistrationPathName = @"SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL";
                using (registry = RegistryBase.OpenSubKey(RegistrationPathName, false))
                {
                    if (registry != null)
                    {
                        // keys for all product are concatenated (separated by comma)
                        string keys = (string)registry.GetValue("ProductKeys");
                        foreach (string key in keys.Split(',').ToList<string>())
                            DetectedKeys.Add(key);
                    }
                }
     
  4. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    I don't know if this thread still alive or no..

    But I'm trying to get the key for office 2013 Pro using the below powershell script I found over the internet.

    Though its mentioned that the code is for 2016 but I guess its only a game of proper offset selection.

    Script:
    Code:
    function Get-MSOfficeProductKey {
        param(
        [string[]]$computerName = "."
        )
    
        $product = @()
        $hklm = 2147483650
        $path = "SOFTWARE\Microsoft\Office"
    
        foreach ($computer in $computerName) {
    
            $wmi = [WMIClass]"\\$computer\root\default:stdRegProv"
    
            $subkeys1 = $wmi.EnumKey($hklm,$path)
            foreach ($subkey1 in $subkeys1.snames) {
                $subkeys2 = $wmi.EnumKey($hklm,"$path\$subkey1")
                foreach ($subkey2 in $subkeys2.snames) {
                    $subkeys3 = $wmi.EnumKey($hklm,"$path\$subkey1\$subkey2")
                    foreach ($subkey3 in $subkeys3.snames) {
                        $subkeys4 = $wmi.EnumValues($hklm,"$path\$subkey1\$subkey2\$subkey3")
                        foreach ($subkey4 in $subkeys4.snames) {
                            if ($subkey4 -eq "digitalproductid") {
    
                                $temp = "" | select ComputerName,ProductName,ProductKey
                                $temp.ComputerName = $computer
                                $productName = $wmi.GetStringValue($hklm,"$path\$subkey1\$subkey2\$subkey3","productname")
                                $temp.ProductName = $productName.sValue
                                $data = $wmi.GetBinaryValue($hklm,"$path\$subkey1\$subkey2\$subkey3","digitalproductid")
                                $valueData = ($data.uValue)[52..67]
                                # decrypt base24 encoded binary data
                                $productKey = ""
                                $chars = "BCDFGHJKMPQRTVWXY2346789"
                                for ($i = 24; $i -ge 0; $i–-) {
                                    $r = 0
                                    for ($j = 14; $j -ge 0; $j–-) {
                                        $r = ($r * 256) -bxor $valueData[$j]
                                        $valueData[$j] = [math]::Truncate($r / 24)
                                        $r = $r % 24
                                    }
                                    $productKey = $chars[$r] + $productKey
                                    if (($i % 5) -eq 0 -and $i -ne 0) {
                                        $productKey = "-" + $productKey
                                    }
                                }
                                $temp.ProductKey = $productKey
                                $product += $temp
                            }
                        }
                    }
                }
            }
        }
    $product
    }
    I've trying all the different offset discussed in this thread but they all are returning wrong key(when verifying using OSPP.vbs script).

    Any Suggestions or help?
     
  5. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,186
    90,642
    340
    #45 abbodi1406, Aug 21, 2021
    Last edited: Aug 22, 2021
    @abhinavp649
    Code:
    function Get-MSOfficeProductKey {
        param(
        [string[]]$computerName = "."
        )
    
        $product = @()
        $hklm = 2147483650
        $path = "SOFTWARE\Microsoft\Office"
        $keyOffset = 808
    
        foreach ($computer in $computerName) {
    
            $wmi = [WMIClass]"\\$computer\root\default:stdRegProv"
    
            $subkeys1 = $wmi.EnumKey($hklm,$path)
            foreach ($subkey1 in $subkeys1.snames) {
                $subkeys2 = $wmi.EnumKey($hklm,"$path\$subkey1")
                foreach ($subkey2 in $subkeys2.snames) {
                    $subkeys3 = $wmi.EnumKey($hklm,"$path\$subkey1\$subkey2")
                    foreach ($subkey3 in $subkeys3.snames) {
                        $subkeys4 = $wmi.EnumValues($hklm,"$path\$subkey1\$subkey2\$subkey3")
                        foreach ($subkey4 in $subkeys4.snames) {
                            if ($subkey4 -eq "digitalproductid") {
    
                                $temp = "" | select ComputerName,ProductName,ProductKey
                                $temp.ComputerName = $computer
                                $productName = $wmi.GetStringValue($hklm,"$path\$subkey1\$subkey2\$subkey3","productname")
                                $temp.ProductName = $productName.sValue
                                $data = $wmi.GetBinaryValue($hklm,"$path\$subkey1\$subkey2\$subkey3","digitalproductid")
                                $endOffset = ($data.uValue)[($keyOffset + 14)]
                                $isWin8 = ($endOffset / 6) -and 1
                                $endOffset = ($endOffset -band 247) -bor (($isWin8 -band 2) * 4)
                                $valueData = ($data.uValue)[($keyOffset)..($keyOffset + 13)] + $endOffset
    
                                $productKey = ""
                                $chars = "BCDFGHJKMPQRTVWXY2346789"
                                for ($i = 24; $i -ge 0; $i–-) {
                                    $r = 0
                                    for ($j = 14; $j -ge 0; $j–-) {
                                        $r = ($r * 256) -bxor $valueData[$j]
                                        $valueData[$j] = [math]::Truncate($r / 24)
                                        $r = $r % 24
                                    }
                                    $productKey = $chars[$r] + $productKey
                                    $l = $r
                                }
                                if ($isWin8) {
                                    $productKey = $productKey.substring(1,$l) + 'N' + $productKey.substring(($l + 1), $productKey.length - ($l + 1))
                                }
                                $productKey = $productKey.substring(0,5) + '-' + $productKey.substring(5,5) + '-' + $productKey.substring(10,5) + '-' + $productKey.substring(15,5) + '-' + $productKey.substring(20,5)
                                $temp.ProductKey = $productKey
                                $product += $temp
                            }
                        }
                    }
                }
            }
        }
    $product
    }
     
  6. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0

    Amazing! The code you provided is returning the correct key.. Thanks
     
  7. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    @abbodi1406

    I guess your code is not properly working as the key I got from your code is almost correct but not fully.

    This is what I'm getting from a third party key finding software:
    [​IMG]

    And this is what your code is calculating:
    [​IMG]

    Order of first group is different while in the second group there is a wrong word. Why is this happening?
     
  8. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,186
    90,642
    340
    #48 abbodi1406, Aug 22, 2021
    Last edited: Aug 22, 2021
    @abhinavp649
    Edit:
    fixed

    the N letter must be inserted manually
     
  9. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    This quick fix actually solved the issue. Can you please explain a bit why we were even using $isWin8 in the first place? does the DigitalProductID keeps check of the OS on which the Office is installed? or is it something entirely different?
     
  10. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,186
    90,642
    340
    I made few changes, now it properly work, see the first code

    N character has a special meaning in product keys since Windows 8 / Office 2013 (so $isWin8 cover both)
    evey key should have 1 N, and it should not be the last character
     
  11. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    Awesome, now the code works perfectly and also thanks for such interesting information.

    I seriously googled many keys and found almost every key has a N in either 1st or 2nd group. But I also found 1 or 2 keys which had N at the last of the group. I guess those will be counted as Invalid?!
     
  12. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,186
    90,642
    340
    Last group is fine, but not the last character in last group xxxxN
     
  13. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    Hello,

    I was trying to convert this code into a C# console application but suck at doing bitwise operations. I traversed through the registry and found the data on which the operations will have to be performed but the later part of the powershell script about bitwise ANDing or XORing the values is a bit confusing for me to translate it to C#. Rest of the syntax is pretty same as that of C# with a mere difference of keywords.

    Can anyone provide a code for the same or if just help me out with the logical operations?
     
  14. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
  15. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    Thanks as always for reply. But yes, I tried that code and I've encountered a few code issues that prevented my application from compiling.
    The error prone section where I had issues was:

    Code:
    // Extract byte 52 to 67 inclusive.
                ArrayList hexPid = new ArrayList();
                for (int i = keyStartIndex; i <= keyEndIndex; i++)
                {
                    hexPid.Add(digitalProductId);
                }
                for (int i = decodeLength - 1; i >= 0; i--)
                {
                    // Every sixth char is a separator.
                    if ((i + 1) % 6 == 0)
                    {
                        decodedChars[i] = '-'; // I had to add '[i]' after decodedChars to avoid 'cannot implicitly convert type char to char[]'
                    }
                    else
                    {
                        // Do the actual decoding.
                        int digitMapIndex = 0;
                        for (int j = decodeStringLength - 1; j >= 0; j--)
                        {
                            int byteValue = (digitMapIndex << 8) | (byte)hexPid[j]; //<----Runtime InvalidCastException
                            hexPid[j] = (byte)(byteValue / 24);
                            digitMapIndex = byteValue % 24;
                            decodedChars[j] = digits[digitMapIndex]; // had to add '[j]' after decodedChars to avoid 'cannot implicitly convert type char to char[]'
                        }
                    }
                }
    I've commented the lines where I made the changes or are error prone during runtime. I might be doing some very stupid mistake which I can't figure out yet and hence I'm seeking help.
     
  16. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    17,186
    90,642
    340
    @abhinavp649
    Code:
                ArrayList hexPid = new ArrayList();
                for (int i = keyStartIndex; i <= keyEndIndex; i++)
                {
                    hexPid.Add(digitalProductId[i]); // missing [i]
                }
                for (int i = decodeLength - 1; i >= 0; i--)
                {
                    // Every sixth char is a separator.
                    if ((i + 1) % 6 == 0)
                    {
                        decodedChars[i] = '-';
                    }
                    else
                    {
                        // Do the actual decoding.
                        int digitMapIndex = 0;
                        for (int j = decodeStringLength - 1; j >= 0; j--)
                        {
                            int byteValue = (digitMapIndex << 8) | hexPid[j];
                            hexPid[j] = (byte)(byteValue / 24);
                            digitMapIndex = byteValue % 24;
                            decodedChars[i] = digits[digitMapIndex]; // [i] not [j]
                        }
                    }
                }
    see Keys.cs from Microsoft Toolkit source
    Code:
            /// <summary>
            /// Decode Data read in from a DigitalProductID
            /// </summary>
            /// <param name="digitalProductId">Byte array containing DigitalProductID data</param>
            /// <param name="keyStartIndex">Where in the byte array does the Product Key start</param>
            /// <returns>Product Key contained in the DigitalProductID</returns>
            private static string DecodeProductKey(IList<byte> digitalProductId, int keyStartIndex)
            {
                int keyEndIndex = keyStartIndex + 15;
    
                const int numLetters = 24;
                // Possible alpha-numeric characters in product key.
                char[] digits = {'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y', '2', '3', '4', '6', '7', '8', '9'};
    
                // Check if Windows 8/Office 2013 Style Key (Can contain the letter "N")
                int containsN = (digitalProductId[keyStartIndex + 14] / 6) & 1;
                digitalProductId[keyStartIndex + 14] = (byte)((digitalProductId[keyStartIndex + 14] & 0xF7) | ((containsN & 2) * 4));
    
                // Length of decoded product key
                const int decodeLength = 29;
    
                // Length of decoded product key in byte-form.
                // Each byte represents 2 chars.
                const int decodeStringLength = 15;
    
                // Array of containing the decoded product key.
                char[] decodedChars = new char[decodeLength];
    
                // Extract byte 52 to 67 inclusive.
                List<byte> hexPid = new List<byte>();
                for (int i = keyStartIndex; i <= keyEndIndex; i++)
                {
                    hexPid.Add(digitalProductId[i]);
                }
                for (int i = decodeLength - 1; i >= 0; i--)
                {
                    // Every sixth char is a separator.
                    if ((i + 1) % 6 == 0)
                    {
                        decodedChars[i] = '-';
                    }
                    else
                    {
                        // Do the actual decoding.
                        int digitMapIndex = 0;
                        for (int j = decodeStringLength - 1; j >= 0; j--)
                        {
                            int byteValue = (digitMapIndex << 8) | hexPid[j];
                            hexPid[j] = (byte)(byteValue / numLetters);
                            digitMapIndex = byteValue % numLetters;
                            decodedChars[i] = digits[digitMapIndex];
                        }
                    }
                }
                // Remove first character and put N in the right place
                if (containsN != 0)
                {
                    int firstLetterIndex = 0;
                    for (int index = 0; index < numLetters; index++)
                    {
                        if (decodedChars[0] != digits[index])
                        {
                            continue;
                        }
                        firstLetterIndex = index;
                        break;
                    }
                    string keyWithN = new string(decodedChars);
    
                    keyWithN = keyWithN.Replace("-", string.Empty).Remove(0, 1);
                    keyWithN = keyWithN.Substring(0, firstLetterIndex) + "N" + keyWithN.Remove(0, firstLetterIndex);
                    keyWithN = keyWithN.Substring(0, 5) + "-" + keyWithN.Substring(5, 5) + "-" + keyWithN.Substring(10, 5) + "-" + keyWithN.Substring(15, 5) + "-" + keyWithN.Substring(20, 5);
    
                    return keyWithN;
                }
    
                return new string(decodedChars);
            }
    
     
  17. abhinavp649

    abhinavp649 MDL Novice

    Dec 29, 2020
    9
    1
    0
    The solutions were everywhere. I realize it was me who wasn't looking properly in the first place :oops:. I didn't knew the source for the toolkit I used a lot has a source available to learn :eek:. Thank you once again. :D