Diablo 2 Key Generator: Reverse Engineering the CD-Key Algorithm (Part 1)

I grew up with an Amiga 500+ and for anyone who owned an Amiga, you'll know how common cracked software was. I remember those code wheels for monkey island (https://www.oldgames.sk/codewheel/secret-of-monkey-island-dial-a-pirate) and having to enter word 4 from paragraph 2 on page 8 of a manual to play a game. It always amazed me how people were able to not only patch these checks out but some games had a cheat menu on the crack screen.

Moving forward a few years we have CD keys and protection and you have keygens with the chiptune music and funky art styles and this is what I'm going to try to replicate. I have chosen Diablo 2 as my target, I believe it's the 2007 rerelease with a 26 character key. This content and project isn't to pirate Diablo 2, in fact, I really don't enjoy the Diablo series, it's purely to educate myself on how to make a keygen.

The Diablo 2 installer looks something like this (see below) and if you enter an incorrect key you get a new window with the message "The CD-Key entered in the Diablo II field was invalid. Please make sure that it was typed in correctly."

Diablo 2 installer with CD key field

With that in mind, there are a few options, I can search for the string and see what calls the string, or I can look for what calls the dialogue box to prompt for the CD key. I'll search for strings for now and see what hits I get for the words invalid.

Ghidra search results for 'invalid'

That's interesting, it leads me to a function which I've named CD_Key_Error_Report, it seems to check if the username is valid and then from what I can tell as long as the CD key isn't null it goes to case 6 in a switch case. I have renamed a few things in here, maybe incorrectly, time will tell on that.

CD_Key_Error_Report Function
undefined4 * __thiscall CD_Key_Error_Report(int param_1_00,undefined4 *param_2,char *param_3)

{
  char cVar1;
  char *pcVar2;
  bool bVar3;
  undefined4 uVar4;
  uint *puVar5;
  undefined4 *puVar6;
  uint uVar7;
  char *pcVar8;
  undefined4 local_e0 [14];
  undefined4 local_a8 [14];
  uint local_70 [6];
  uint local_58 [6];
  undefined2 local_40 [6];
  uint local_34 [6];
  uint local_1c;
  uint local_18;
  uint status_flag;
  void *exception_list;
  undefined1 *puStack_c;
  undefined4 return_flag;
undefined4 * __thiscall CD_Key_Error_Report(int param_1_00,undefined4 *param_2,char *param_3)

{
  char cVar1;
  char *pcVar2;
  bool bVar3;
  undefined4 uVar4;
  uint *puVar5;
  undefined4 *puVar6;
  uint uVar7;
  char *pcVar8;
  undefined4 local_e0 [14];
  undefined4 local_a8 [14];
  uint local_70 [6];
  uint local_58 [6];
  undefined2 local_40 [6];
  uint local_34 [6];
  uint local_1c;
  uint local_18;
  uint status_flag;
  void *exception_list;
  undefined1 *puStack_c;
  undefined4 return_flag;
  
  pcVar2 = param_3;
  return_flag = 0xffffffff;
  puStack_c = &LAB_005d8f02;
  exception_list = ExceptionList;
  status_flag = 0;
  ExceptionList = &exception_list;
  switch(*(undefined4 *)(param_1_00 + 0x6c)) {
  case 1:
    ExceptionList = &exception_list;
    pcVar8 = FUN_005b6950(param_3);
    if (((char *)0x1d < pcVar8 + -2) ||
       (pcVar8 = Check_CD_Key_Format(pcVar2,'\\"',0x7fffffff), pcVar8 != (char *)0x0)) {
      puVar5 = CreateButton(local_34,"{InvalidUserName}");
      return_flag = 0;
      FUN_0043b760(param_2,(int *)puVar5);
      FUN_00404240(local_34);
      ExceptionList = exception_list;
      return param_2;
    }
  default:
switchD_0045fd29_caseD_6:
    if (*pcVar2 == '\0') {
      uVar7 = 0;
    }
    else {
      pcVar8 = pcVar2;
      do {
        cVar1 = *pcVar8;
        pcVar8 = pcVar8 + 1;
      } while (cVar1 != '\0');
      uVar7 = (int)pcVar8 - (int)(pcVar2 + 1);
    }
    FUN_00404480((uint *)(param_1_00 + 0x48),pcVar2,uVar7);
    FUN_00432330(param_2);
    ExceptionList = exception_list;
    return param_2;
  case 2:
    ExceptionList = &exception_list;
    uVar7 = FUN_0048d3b0(param_3);
    if ((char)uVar7 != '\0') goto switchD_0045fd29_caseD_6;
    puVar5 = CreateButton(local_34,"{InvalidCDKeyFor%s}");
    return_flag = 1;
    break;
  case 3:
    param_3 = (char *)0x0;
    ExceptionList = &exception_list;
    uVar4 = FUN_0048d420(pcVar2,(ulong *)¶m_3);
    if (((char)uVar4 != '\0') && (bVar3 = FUN_0045fae0(param_1_00,(int)param_3), bVar3))
    goto switchD_0045fd29_caseD_6;
    puVar5 = CreateButton(local_34,"{InvalidCDKeyFor%s}");
    return_flag = 2;
    break;
  case 4:
  case 5:
  case 6:
    local_18 = 0;
    ExceptionList = &exception_list;
    CDKeyValidator((int)param_3,&local_18,&local_1c,local_40);
    bVar3 = FUN_0045fae0(param_1_00,local_18);
    if (bVar3) goto switchD_0045fd29_caseD_6;
    if (*(int *)(param_1_00 + 0x6c) == 4) {
      puVar5 = CreateButton(local_70,"{InvalidCDKeyFor%s}");
      return_flag = 3;
      status_flag = 2;
      puVar6 = FUN_0043b760(local_e0,(int *)puVar5);
      return_flag = 4;
      uVar7 = 6;
    }
    else {
      puVar5 = CreateButton(local_58,"{InvalidAuthenticationKey}");
      return_flag = 5;
      status_flag = 8;
      puVar6 = FUN_0043b760(local_a8,(int *)puVar5);
      return_flag = 6;
      uVar7 = 0x18;
    }
    status_flag = uVar7;
    FUN_00433500(param_2,puVar6);
    if ((uVar7 & 0x10) != 0) {
      uVar7 = uVar7 & 0xffffffef;
      FUN_004308a0((int)local_a8);
    }
    if ((uVar7 & 8) != 0) {
      uVar7 = uVar7 & 0xfffffff7;
      FUN_00404240(local_58);
    }
    if ((uVar7 & 4) != 0) {
      uVar7 = uVar7 & 0xfffffffb;
      FUN_004308a0((int)local_e0);
    }
    if ((uVar7 & 2) == 0) {
      ExceptionList = exception_list;
      return param_2;
    }
    puVar5 = local_70;
    goto LAB_0045fdd9;
  }
  FUN_0043b760(param_2,(int *)puVar5);
  puVar5 = local_34;
LAB_0045fdd9:
  FUN_00404240(puVar5);
  ExceptionList = exception_list;
  return param_2;
}
CD_Key_Error_Report.c • Function at 0045FD29 Download Code

The function I have named CDKeyValidator is located at 0045FE53 in Ghidra. I need my trusty debugger XDBG32 to have a look at this in action. I can set a breakpoint here and verify what is being passed as arguments, I would expect to see the CD key here or somewhere near here.

Debugger showing CD key being passed to function

There it is, being passed into the function as expected I head in and this is where the fun begins with this seemingly simple function.

CDKeyValidator Function
void __cdecl CDKeyValidator(int param_1,uint *param_2,uint *param_3,undefined2 *param_4)

{
  undefined4 extraout_ECX;
  int iVar1;
  uint local_44;
  uint local_40;
  undefined4 local_3c;
  undefined4 local_38;
  byte local_34 [52];
  
  if ((param_1 != 0) && (param_4 != (undefined2 *)0x0)) {
    FUN_00401040(param_1,(int)local_34);
    local_40 = 0;
    local_3c = 0;
void __cdecl CDKeyValidator(int param_1,uint *param_2,uint *param_3,undefined2 *param_4)

{
  undefined4 extraout_ECX;
  int iVar1;
  uint local_44;
  uint local_40;
  undefined4 local_3c;
  undefined4 local_38;
  byte local_34 [52];
  
  if ((param_1 != 0) && (param_4 != (undefined2 *)0x0)) {
    FUN_00401040(param_1,(int)local_34);
    local_40 = 0;
    local_3c = 0;
    iVar1 = 0x34;
    local_44 = 0;
    local_38 = 0;
    do {
      FUN_00401000(4,5,(int)&local_44,(int)&local_44,(uint)local_34[iVar1 + -1]);
      iVar1 = iVar1 + -1;
    } while (0 < iVar1);
    FUN_00401370((int)&local_44);
    FUN_004010a0(extraout_ECX,(int)&local_44);
    *param_2 = local_44 >> 10;
    *param_3 = (local_44 & 0x3ff) << 0x10 | local_40 >> 0x10;
    *param_4 = (short)local_40;
    *(undefined4 *)(param_4 + 1) = local_3c;
    *(undefined4 *)(param_4 + 3) = local_38;
  }
  return;
}
CDKeyValidator.c • Function at 0045FE53 Download Code

In part 2 I will start delving into each of these functions but before I finish up part 1 you should know, I like pen and paper, I like to write stuff down, I think it helps my mind focus as I find out new thing. I have worked through a lot of this project, it's not yet finished but here are some of my notes. See you in part 2

Handwritten notes about Diablo 2 key algorithm

Related Projects

Project thumbnail

S.W.I.N.E. Cheat Codes

Finding and enabling hidden cheat codes in S.W.I.N.E.'s remastered version.

Game Hacking Assembly
Project thumbnail

Swamp Fun with Early Math

Bypassing CD protection in an old Shrek educational game.

CD Protection Windows API