Post

Bypassing a CD-ROM check on a 27 years old game

Fully static analysis with binary patching

Bypassing a CD-ROM check on a 27 years old game

Heroes III

Heroes III is a game created by 3DO in 1999 and we are going to see how they protected their game back then

No pre-patched binaries or even the original binary will be given, this little writeup will only show how to counter the CDROM check to play this game again due to of some modern PC not having a CD reader

Patching

first, let’s start the game and see what is happening without the CDROM inserted on the PC

No CDROM

alright so we only have access to the multiplayer, let’s open up the binary in ida pro. Let’s try to search for the string “CD-ROM was not found!”

No strings

alright so we have no strings about the CD-ROM not being found so the next step is to search which WinAPI function is responsible for CD-ROM reading by searching “cdrom reading winapi” on google we quickly see this result

GetDriveTypeA

Return values

so now we have the WinAPI function which is responsible to read drives type and we also have return values, let’s try to look at which game function has a GetDriveTypeA reference

Import

Functions refs 1

we are very lucky because we have only one function that has the GetDriveTypeA function reference what if we decompile the sub_50C1C0() function?

Decompilation 1

Decompilation 2

nice we can clearly see that the game is checking for a CD-ROM present GetDriveTypeA(RootPathName) != 5 and GetDriveTypeA(RootPathName) == 5 previously we have seen the return value being “5” if the type is a CD-ROM so let’s rename this function to check_cdrom. If we look closely the function logic we can see a for loop checking for every drives letters (C: to Z:):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
LABEL_18:
    memset(v10, 0, 26);
    v15 = 0;
    for ( i = 2; i < 26; ++i )
    {
      if ( ((1 << i) & LogicalDrives) != 0 )
      {
        RootPathName[0] = i + 65;
        if ( GetDriveTypeA(RootPathName) == 5 )
        {
          v9 = v15;
          v10[v15] = i;
          v15 = v9 + 1;
        }
      }
    }
    v14 = 0;
    v13 = (char)v15;
    do
    {
      v15 = 0;
      Sleep(3000);
      ++v14;
    }
    while ( v14 < 2 );
    return 2;

If the game doesn’t find a CD-ROM then it retries twice for a total of 6 seconds and then returns 2 which acts as a CD-ROM not found error…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 v5 = _open(v4, 0x8000);
  if ( v12 )
  {
    v6 = (_BYTE *)(v12 - 1);
    v7 = *(_BYTE *)(v12 - 1);
    if ( !v7 || v7 == -1 )
      operator delete(v6);
    else
      *v6 = v7 - 1;
  }
  if ( v5 != -1 )
  {
    _close(v5);
    return 0;
  }

if the game finds a special file on the CD-ROM then it will return 0 which acts as a correct code, confirming that a legitimate game CD is present in the game, now that we know how this function works let’s see the xrefs of this function

Functions refs 2

Only one reference, it should be the game start

game start

dword_69957C is really interesting because it’s where the return value is stored, looking at refs we can see multiple refs which means the game is actually checking if the disk is really legitimate or not (Anti Piracy?)

Anti piracy

if we patch the return 2 to return 0 on the check_cdrom function it won’t work because of the multiple checks for example this one:

Anti piracy

dword_699290 is the same as dword_69957C (thanks ida…) but anyways we can see a big switch case so whats my next move you’ll say? i’ll just edit the return 2 to return 9 for example because in this switch case it doesn’t have any cases for 9

Anti piracy

launching the game now…

Anti piracy

boom singleplayer is now unlocked! you can now play this old game again without any CD reader on your pc, I hope you learned new things!

This post is licensed under CC BY 4.0 by the author.

Trending Tags