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."
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.
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.
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;
}
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.
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.
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;
}
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