Geeks With Blogs
Joaquin Jares Just another geek's blog

The question from my previous post was: how do I validate a Product Key from a Wix Setup? The answer has many components.

First, you have to create a dialog for your product key in Wix. This dialog should have some sort of formatted input (say, a repetition of six 4-letter groups). It should also have some sort of validation.

Next, you need to have a custom action that will validate the key. It’s very important for this custom action not to be obvious to crack. Wix is very easy to modify as is, if you’re going to use the same code to validate the key in your product, let them bypass the control in the setup; it will be much harder to bypass in the product.

So first things first, let’s start with Wix. It’s actually very easy.

If you’re using WixMondo, you have most of the work cut out for you. You only need to add a new dialog to the dialogs directory. This dialog should include a MakedEdit control, which is where your key will be displayed.

The mask for the MaskedEdit is somewhat complex, and the documentation isn’t good. But for this exercise, the result is actually pretty simple. A mask of & will recognize any single character or number, and the – will display a separator. My mask in this example is a property named PIDTemplate (which is a standard variable name for the product key’s mask) declared like this:

  <Property Id="PIDTemplate">
    <![CDATA[&&&&-&&&&-&&&&-&&&&-&&&&-&&&&]]>
  </Property>
The key is associated to the Control via the Text property, like this:

    <Control Id="Key" Type="MaskedEdit" Height="15" Width="330" X="32" Y="148" Text="[PIDTemplate]"  Property="KEY" />
I think that you can paste the key directly into the attribute instead of using the property, but that really didn’t work for me.

Finally, you have to provide validation via the DoAction event in the Publish section for the Next button:

<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17"     Default="yes" Text="$(loc.WixUINext)">
  <Publish Event="DoAction" Value="MSIKey">1</Publish>
  <Publish Event="NewDialog" Value="[WixUI_ProdKeyDlg_Next]">VALIDATEKEY =  "1"</Publish>
  <Publish Event="SpawnDialog" Value="BadKeyDlg"><![CDATA[VALIDATEKEY <> "1"]]></Publish>
</Control>
Notice that the events will run in order. That is, your MSIKey will run first (setting the VALIDATEKEY property in the process), followed by the NewDialog if VALIDATEKEY equals 1 and the SpawnDialog if the VALIDATEKEY doesn’t. NewDialog will replace your current step with the following step (defined in UI.wxs) and SpawnDialog will show a new smaller dialog with an error, that you must also define.

The full code for the dialog will then be (sorry for the formatting, the whole point is to give you a good copy paste point):

<Include>
  <Property Id="PIDTemplate">
    <![CDATA[&&&&-&&&&-&&&&-&&&&-&&&&-&&&&]]>
  </Property>

  <Dialog Id="ProductKeyDlg" Width="370" Height="270" Title="$(loc.ProdKeyDlg_Title)">
    <Control Id="Key" Type="MaskedEdit" Height="15" Width="330" X="32" Y="148" 
          Text="[PIDTemplate]"  Property="KEY" />
    <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" 
          Text="$(loc.WixUIBack)">
       <Publish Event="NewDialog" Value="[WixUI_ProdKeyDlg_Back]">1</Publish>
    </Control>
    <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" 
           Text="$(loc.WixUINext)">
       <Publish Event="DoAction" Value="ValidateKey">1</Publish>
       <Publish Event="NewDialog" Value="[WixUI_ProdKeyDlg_Next]">VALIDATEKEY = "1"</Publish>
       <Publish Event="SpawnDialog" Value="BadKeyDlg"><![CDATA[VALIDATEKEY <> "1"]]></Publish>
    </Control>
    <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" 
           Text="$(loc.WixUICancel)">
       <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
    </Control>
    <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" 
           Text="$(loc.ProdKeyDlgBannerBitmap)" />
    <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
    <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
    <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" 
           NoPrefix="yes" Text="$(loc.ProdKeyDlgDescription)" />
    <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" 
           NoPrefix="yes" Text="$(loc.ProdKeyDlgTitle)" />
    <Control Id="EnterKey" Type="Text" Height="20" Width="154" X="25" Y="116" 
           Text="$(loc.ProdKeyDlgEnterKey)" />
  </Dialog>
</Include>

I will spare you the code for the error dialog. If you need one, just copy Cancel.wxi or OutOfDisk.wxi and change the name and description. Add it in the includes section of UI.wxs, and you’re good to go.

Next comes the Custom Action, which is actually the hardest part. In MSI, you can create a Custom Action in several ways. The most common for this kind of thing are JScript/VBScript, Managed (as in, C#) and Native. My reasons for using native, following:

1. Nobody in their right mind should right a piece of JScript code and think that it’s safe. Period. You can easily extract the JScript from the MSI, and then your product will not be safe because you have given any potential attacker your key validation code. Also, JScript is somewhat harder to debug than the other two options. JScript can’t be used from your product’s code (most of the time), so you’ll have to duplicate your logic and your code to call it if needed.

2. Managed is a little harder to exploit, but just a little. The real drawback here is that you can’t really use Managed code in the DoAction event of a Control. If you really want to, you can refer to a post in Pablo’s blog that tells you how to do it. It’s a very hard and very manual process you’ll have to do every time you compile your assembly which basically fools the MSI executable into thinking that your code is native when in fact it isn’t. Managed can be used from you code easily from your product if your product is also written in the same managed code. I don’t think you can use Java for a custom action in MSI, but I really didn’t check into it.

3. Native is the hardest to exploit, but also the hardest to write, especially if you’re not used to it. Also, it’s the recommended choice for any custom action run from an MSI. Finally, a native dll may be imported in almost any language that you are using for your product.

There’s also another option you have available for the custom action: wether the dll containing it will be stored in binary form somewhere in the database, or if you want to store it in the file system before running it. I’m going to show how to use it from binary, but you’ll usually store it in the disk if you want to use it from your product too. Either way, Wix configuration is almost the same:

<Binary Id="ProductKeyCode" SourceFile="Binary\ProductKeyCode.dll" />
<CustomAction Id="MSIKey" 
     BinaryKey="ProductKeyCode" DllEntry="MSIValidateKey" />

The only difference between this and using the deployed version is that you will have a FileKey instead of the BinaryKey, and the dll as part of your regular directory structure.

And finally for the complex part, here’s how you create the dll. I’m not a native developer by trade, so the following may have huge conceptual errors. Please forgive me for them and help me correct them using the comments.

First, you have to create a Win32 application in Visual Studio, and select the type as Static Library in the wizard. Don’t include MFC or Precompiled Headers. The wizard will create a .cpp and a .h file for you to edit. It will also include a set of std files.

Open the stdafx.h file, and add the following:

#include <msi.h>
#include <msiquery.h>
#include <stdlib.h>
#pragma comment(lib, "msi.lib")

#include <comutil.h>
#include <string>
#include <tchar.h>

The msi.h and msi.lib are important, the others are being used only for this sample. You may not have the msi.h, msiquery.h or msi.lib available in your computer. If you don’t, you’ll need to install the Windows Installer SDK to get them. Also, you may need to add the folder where the msi files are to your includes and libs directories, or copy the files to the actual include and lib directories.

The actual code (in your cpp file) should look like this:

#include "stdafx.h"
#include "ProductKeyCode.h"

int __stdcall GetItemFromKey(TCHAR* szValueBuf, int iPos)
{
    std::basic_string<TCHAR> str = szValueBuf;
    wchar_t ptrKey [5] = TEXT("****");
    str.copy(ptrKey, 4, (iPos - 1) * 5);

    int iRet = wcstol(ptrKey, (wchar_t**)NULL, 16);

    str.~basic_string();

    return iRet;
}

BOOL __stdcall KeyValidationMain(TCHAR* szValueBuf)
{
    int key0 = GetNumberFromKey(szValueBuf, 1);
    int key1 = GetNumberFromKey(szValueBuf, 2);
    int key2 = GetNumberFromKey(szValueBuf, 3);
    int key3 = GetNumberFromKey(szValueBuf, 4);
    int key4 = GetNumberFromKey(szValueBuf, 5);
    int key5 = GetNumberFromKey(szValueBuf, 6);
        
// Your validation goes here. 
    return TRUE;
}

UINT __stdcall MSIKeyValidation(MSIHANDLE hInstall)
{
//    MessageBox(NULL, TEXT("Debug"), TEXT("Debug"), 0);
    TCHAR* szValueBuf = NULL;
    DWORD cchValueBuf = 0;
    UINT uiStat =  MsiGetProperty(hInstall, TEXT("KEY"), TEXT(""),
                                     &cchValueBuf);
    if (ERROR_MORE_DATA == uiStat)
    {
        ++cchValueBuf; // does not include terminating null, so add 1
        szValueBuf = new TCHAR[cchValueBuf];
        if (szValueBuf)
        {
            uiStat = MsiGetProperty(hInstall, TEXT("KEY"), szValueBuf
                    , &cchValueBuf);
        }
    }
    if (ERROR_SUCCESS != uiStat)
    {
        if (szValueBuf != NULL) 
        delete [] szValueBuf;
        return ERROR_INSTALL_FAILURE;
    }

    if (ValidationKeyMain(szValueBuf) == TRUE)
    {
        MsiSetProperty(hInstall, TEXT("VALIDATEKEY"), TEXT("1"));
    }
    else
    {
        MsiSetProperty(hInstall, TEXT("VALIDATEKEY"), TEXT("0"));
    }

    delete [] szValueBuf;    

    return ERROR_SUCCESS;
}

There are two exported methods here: MsiKeyValidation and KeyValidationMain. GetItemFromKey is a private helper method that may be declared elsewhere. Notice that the actual validation occurs in KeyValidationMain. This method will run your validation algorithm (a cryptographic thing, mostly) and return TRUE if the key is valid or FALSE otherwise. My convention is that, in case of error, I return false. MsiKeyValidation is a simple wrapper to integrate with MSI.

The commented MessageBox at the beginning of MSIKeyValidation is important, I’ll get back to that in a second. MSIKeyValidation will get the KEY property from the Msi database. It will always look the same: “    -    -    -    -    -    “, with whatever the user writes in the spaces or spaces if the users doesn’t write anything. This makes it very safe to cut in the GetItemFromKey method. In this last method, I return a base 16 number for each part of the key (I use hex in this sample), but you’ll generally use it directly as text.

Finally, to make the exported files visible, you have to include a .def file, with the following contents:

LIBRARY    "ProductKeyCode"
EXPORTS
    MSIKeyValidation
    KeyValidationMain

This library should compile as is. If you open it with depends, you should be able to see the functions in the exports section. A setup constructed this way will work on most computers, but I found out that sometimes, it doesn’t work. If you’re finding that your dll isn’t running (and you go to the %temp% directory, and you don’t see it appearing there, and you can’t attach to it), then go to Project Properties, find C/C++ –> Code Generation and select Multi-Threaded Debug in the Runtime Library option. I really don’t know why, but it will fix it.

With this done, you’re ready to deploy with a product key. If you want to debug the dll, you’ll have to reenable the message box. Run the setup and wait for the message box to appear (it will appear when you click Next on the Product Key dialog). At this point, attach Visual Studio to the newest msiexec process, and you should be able to debug. The executable for Msi opens a new process for each Custom Action. In any case (script, managed or native) the correct way to attach a debugger is the same.

As an added bonus, if you want to call your validation from managed code, you should write the following code:

[DllImport("ProductKeyCode.dll", CallingConvention = 
CallingConvention.StdCall, EntryPoint="KeyValidationMain")]
static extern bool KeyValidationMain(
[MarshalAs(UnmanagedType.LPTStr)]string szKey);

And that’s it! Now you can access the same algorithm from managed code.

Posted on Sunday, July 27, 2008 2:44 PM Setup | Back to top


Comments on this post: Product Keys and Setup – Custom Action

# re: Product Keys and Setup – Custom Action
Requesting Gravatar...
I was hoping you could provide a more detailed description for the use of <![CDATA[&&&&-&&&&-&&&&-&&&&-&&&&-&&&&]]>

For example, suppose you had <![CDATA[12345<### ###>@@@@@@]]> What do the angle brackets mean and what do the symbols and numerals on either side of them mean?

richard.waliser@garmin.com
Left by Rich on Sep 18, 2008 1:14 AM

# re: Product Keys and Setup – Custom Action
Requesting Gravatar...
Richard,
The fact is I really don't know the details on the mask and for my needs just knowing that & maps to letter/number was enough. I'll make a point on researching this further and make a new blog post once I understand it. Sorry I couldn't be more helpful. J.
Left by Joaquin Jares on Sep 18, 2008 2:34 AM

Your comment:
 (will show your gravatar)


Copyright © Joaquin Jares | Powered by: GeeksWithBlogs.net