Reverse Engineering ‘Pools of Darkness’: Part 1

I thought I would reverse engineer Pools of Darkness to capture the process in a serries of blog posts.

This is the first, and I’ll add links to the later posts as we go. Part 2

Prerequisite:

Open game.exe in IDA Pro, jump to the start function (Ctrl+E). Here it is:

01-darkness-in-ida

In summary: it moves the executable to a new location in memory, then “jumps to the next function” via pushing the new memory address (seg001:0038) onto that stack and the returning to it (which is just a pop and jump).

The next blob of code seg001:0038 - seg001:00FE is a fancy pants scrambler, which by the way is exactly the same as Curse of the Azure Bonds had.

Line seg001:00FE is the interesting line:

seg001:00FE     jmp     dword ptr cs:[bx]  

This is where the descrambler jumps to the actual game code. The code in IDA is the scrambled version, so we want to get access to the descrambled memory layout. So quit IDA Pro, and don’t keep this database.

For Curse I used the DOS debugger to get the descrambled memory, so lets do it again.

Here is the generic script to dump the memory:

g AAAA:37  
p  
g BBBB:fe  
d 0 ffff  
a BBBB:fe  
mov dx,CCCC  
mov ds,dx  

p  
p  
d 0 ffff  
a 1ab1:103  
mov dx,DDDD  
mov ds,dx  

p  
p  
d 0 ffff  
q  

To get the values of AAAA, BBBB, CCCC and DDDD we start from a command prompt:

enter R to get the register dump

C:\\games\\DARKNESS>debug game.exe  
-r  
AX=0000  BX=0000  CX=F8CB  DX=0000  SP=0080  BP=0000  SI=0000  DI=0000  
DS=0BA1  ES=0BA1  SS=1DC7  CS=1AED  IP=0012   NV UP EI PL NZ NA PO NC  
1AED:0012 8CC0          MOV     AX,ES  
-  

DS is the beginning of the memory, and CCCC is DS + 0x1000 and DDDD is DS + 0x2000, AAAA is CS, and BBBB is DS + 10 + word_1F3CC, which happens to be 0x11C5, but I just step (p) six times, and read the value from AX

now we can run the debugger a couple more time to double check the base address are the same each run…. which they are.

now we can run the script as input to debug.exe to dump the 192Kb of game ram, via this command

debug game.exe < run.txt > out.txt  

I found a few times I had to kill the process, as the quit (q) command at the end was not working…

But you now have a file looking like this

-g 1aed:0037  
AX=0038 BX=0000 CX=0000 DX=0001 SP=007C BP=0000 SI=FFFF DI=FFFF  
DS=1AED ES=1D76 SS=1DC7 CS=1AED IP=0037 NV DN EI PL NZ NA PE NC  
1AED:0037 CB RETF  
-p  
AX=0038 BX=0000 CX=0000 DX=0001 SP=0080 BP=0000 SI=FFFF DI=FFFF  
DS=1AED ES=1D76 SS=1DC7 CS=1D76 IP=0038 NV DN EI PL NZ NA PE NC  
1D76:0038 06 PUSH ES  
-g 1d76:fe  
AX=0BA1 BX=0000 CX=0000 DX=F000 SP=4000 BP=0000 SI=2576 DI=4000  
DS=0BA1 ES=0BA1 SS=2576 CS=1D76 IP=00FE NV UP EI PL NZ NA PO NC  
1D76:00FE 2E CS:  
1D76:00FF FF2F JMP FAR [BX] CS:0000=0002  
-d 0 ffff  
0BA1:0000 CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 95 05 8A 03 . ........O.....  
0BA1:0010 95 05 17 03 95 05 84 05-03 04 01 00 02 FF FF FF ................  
0BA1:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 42 0B F1 49 ............B..I  
0BA1:0030 95 05 14 00 18 00 A1 0B-FF FF FF FF 00 00 00 00 ................  
0BA1:0040 05 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................  
0BA1:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 00 20 20 20 .!...........  

Next we need to turn that into a binary file, the code I wrote for Curse required only the hex dump lines so we trim the extra lines from out.txt file first.

#include <iostream>  
#include <stdio.h>  

using namespace std;  

unsigned char decode(char h, char l)  
{  
  unsigned char hv, lv;  
  if ( h >= 'A' && h <= 'F' )  
    hv = h - 'A' +10;  
  else  
    hv = h - '0';  

  if ( l >= 'A' && l <= 'F' )  
    lv = l - 'A' +10;  
  else  
    lv = l - '0';  

  return (lv+(hv*16));  
}  

void main()  
{  
  const int lineSize = 1024;  
  char line[lineSize];  

  FILE *out = fopen( "dump.bin", "wb" );  

  unsigned char byte;  

  while(cin.good())  
  {  
    cin.getline( line, lineSize );  

    int offset=11;  
    for(int i=0; i<8; i++)  
    {  
      char h,l;  
      h = line[offset+(i*3)];  
      l = line[offset+(i*3)+1];  

      byte = decode(h, l);  

      fwrite(&byte,1,1,out);  
    }  

    offset=35;  
    for(int i=0; i<8; i++)  
    {  
      char h,l;  
      h = line[offset+(i*3)];  
      l = line[offset+(i*3)+1];  

      byte = decode(h, l);  

      fwrite(&byte,1,1,out);  
    }  
  }  
  fclose(out);  
}  

Now using this like so:

dumpparse.exe < out2.txt

We get file dump.bin

Which we will load into IDA Pro in Part 2

Share

Comments:

Stu 2009-10-05 13:48:27

actually they are not scrambled they are linker packed by the MS linker, its called EXEPACK and if you ever see “Packed file is corrupt” this is an EXEPACK file. Its similar to PKLite, LZEXE, WWP, etc early versions would just pack relocations (each relocaiton was 4 bytes but most often all in the same segment, so it would basically do an RLE on the segment:offset runs and use only the offsetpart. later exepacks evolved into full blown pakcers like pklite, wwp etc).

to make your life easier, get “unp” (unprotect) and just depack it before running it through IDA…

I think all goldbox games were exepacked from Pool of Radiance to Dark Queen of Krynn. I didnt check buck rogers or FRUA, which was the last GB release.

lol sounds like you did a lot of hard work when there is a very simple answer ;)
ftp://ftp.sac.sk/pub/sac/pack/unp411.zip


Simeon 2009-10-05 15:01:12

Interesting, will differently check that out…

I know the version of Pool of Radiance that is on the AbandonWare siets, is cracked (password removed) and those versions are not packed.

I did all this original work years ago also… but your write if there is a tool to fix it up… oh man now I’m going to have to check it out now…..


Simeon 2009-10-05 15:14:18

Ok, Curse of the Azure bonds was packed with Exepack v4.05 or v4.06

Now trying to see if the merge overlay option works, can’t get it work yet….


Simeon 2009-10-05 15:21:16

ARRRGG i hate your fancy old-school tools.

Once you load the unpacked EXE into IDA it detect that it has “references to Pascal Overlays” which it then magically auto loads and solves EVERYTHING.

ARRRGGGGG!!!!

The work shown in this blog post originally took a week, the next blog post in the serries took months originally.


Stu 2009-10-05 15:44:11

sorry about my old school tools.. I’m an old school cracker ;) dealing with this old dos stuff is like riding a bike to me…

I hope you do still post the next bits as its fun to read. sorry if I blew some of your posts away with showing you the power of unp. but hey, you learned some stuff about using dos debug.exe right? :)


Simeon 2009-10-05 15:57:36

Well I learnt it years ago, all I was doing was searching for blog post fodder…

There is still merit, just for the fun of this is how a blind man would do it. But I’m not blind any more.

I’ll give it a bash anyway.