Using windbg to beat my dad at chess

My dad is awesome. He always beats me at chess. With a huge nod to this uninformed post – introduction to reverse engineering win32 applications where they debug minesweeper, I decided to dive into the windows 7 chess game and see if I could give myself a bit of an advantage. I wasn’t sure exactly what I wanted to do other than that. I’ll be using Windows 7 32 bit, and the file is at C:\Program Files\Microsoft Games\Chess\. This tutorial will probably not work with anything but Windows 32 bit. This is a beginner tutorial.

Recon and Defining what we want to do

Following the uninformed post, I wondered if chess might contain symbols also, as this would make my life easier. I have this set in my config, but if you don’t then you will want to set your symbol path.

0:000> .sympath srv*c:\debug*http://msdl.microsoft.com/download/symbols
Symbol search path is: srv*c:\debug*http://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: srv*c:\debug*http://msdl.microsoft.com/download/symbols
0:000> .reload /f *
0:000> lm
start    end        module name
001b0000 00474000   Chess      (pdb symbols)          c:\debug\Chess.pdb\1467728C9EEA429C9FA465213785E17C1\Chess.pdb
6e030000 6e06c000   OLEACC     (pdb symbols)          c:\debug\oleacc.pdb\DC8A57A3E8C648228F2C3650F2BE1D672\oleacc.pdb
6f900000 6f972000   DSOUND     (pdb symbols)          c:\debug\dsound.pdb\F38F478065E247C68EDA699606F56EED2\dsound.pdb

Awesome, we have a chess.pdb. In the uninformed post they use windbg to look at functions, but I find IDA Pro easier to read. Loading chess.exe into IDA we see quite a few functions right off the bat that look interesting. It looks like there’s a Pawn class, a knight class, a bishop class, etc

Pawn::GetCaptureMoves(int const * *)   .text 0102D605 00000017 R . . . B . .
Pawn::GetShadowRadius(void)            .text 0102D621 00000007 R . . . . . .
Knight::Knight(ESide)                  .text 0102D62D 0000001D R . . . B . .
Knight::Clone(void)                    .text 0102D64F 0000002B R . . . . . .
Knight::GetPassiveMoves(int const * *) .text 0102D67F 00000017 R . . . B . .
Knight::CanJump(void)                  .text 0102D69B 00000003 R . . . . . .
Knight::GetPieceType(void)             .text 0102D6A3 00000004 R . . . . . .
Knight::GetShadowRadius(void)          .text 0102D6AC 00000007 R . . . . . .
Bishop::Bishop(ESide)                  .text 0102D6B8 0000001D R . . . B . .
Bishop::Clone(void)                    .text 0102D6DA 0000002B R . . . . . .
Bishop::GetPassiveMoves(int const * *) .text 0102D70A 00000017 R . . . B . .
Bishop::GetPieceType(void)             .text 0102D726 00000004 R . . . . . .
Rook::Rook(ESide)                      .text 0102D72F 0000001D R . . . B . .
Rook::Clone(void)                      .text 0102D751 0000002B R . . . . . .

So there seem to be two outliers, knights and pawns. Knights have extra moves like canjump, and pawns can move certain places depending on other pieces, so this makes sense. Also, this gives us a big clue that these classes contain some of the logic we can use to determine which piece can move where.

So how should I beat my dad? He’s not a grandmaster, so maybe if I made bishops move like queens for me that would do the trick. There is also a board class, so another idea I had was to replace the bishops with queens when the board was setup, but that’s not the route I went.

There’s this function getpassivemove common to all the classes

0:010> x chess!*getpassivemove*
009bd781 Chess!Rook::GetPassiveMoves = <no type information>
009bd7f8 Chess!Queen::GetPassiveMoves = <no type information>
009bd67f Chess!Knight::GetPassiveMoves = <no type information>
009bd5d6 Chess!Pawn::GetPassiveMoves = <no type information>
009bd87b Chess!King::GetPassiveMoves = <no type information>
009bd70a Chess!Bishop::GetPassiveMoves = <no type information>

Setting a bp here it’s tough to tell what’s going on because it’s hit so frequently, but the functions are really simple, and for the most part they look VERY similar between pawn/rook/knight/king/etc classes

So let’s just replace the first instruction to jump to the other function. I had mona loaded into windbg here, but you can also do this with the metasploit asm shell or nasm.

What this does is modify the Chess!Bishop::GetPassiveMoves function and has it immediately jump to Chess!Queen::GetPassiveMoves. (The addresses on your box will certainly be different)

0:010> !py mona asm -s "mov eax, 0x0076d7f8#jmp eax"
Hold on...
Opcode results : 
---------------- 
 mov eax, 0x0076d7f8 = \xb8\xf8\xd7\x76\x00
 jmp eax = \xff\xe0
 Full opcode : \xb8\xf8\xd7\x76\x00\xff\xe0 

[+] This mona.py action took 0:00:02.172000

0:010> eb 0076d5d6 b8 f8 d7 76 00 ff e0
0:010> uf Chess!bishop::GetPassiveMoves
Flow analysis was incomplete, some code may be missing
Chess!bishop::GetPassiveMoves:
0076d5d6 b8f8d77600      mov     eax,offset Chess!Queen::GetCaptureMoves (0076d7f8)
0076d5db ffe0            jmp     eax
0:010> g

Sure enough, this works. When we run we can move anywhere with our bishops

powerful_bishop

Problems

At this point, even though we can move anywhere, we still have two problems we need to solve. 1) both black and white can move anywhere, so this doesn’t give me an advantage. What I really want is just white to be able to move anywhere 2) We can’t just write to this address because of ASLR and also because it’s a read only section of memory.

What does it mean for us that ASLR is enabled? Any static addresses will likely change from run to run of the chess game. Looking for non-aslred modules, there are none. By the way, I’m using mona here.

0:000> !py mona noaslr
Hold on...
No aslr & no rebase modules :
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------------
 Module info :
----------------------------------------------------------------------------------------------------------------------------------
 Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
----------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------

So for us, we can’t really rely on any hard coded addresses.

Additionally, even if we solved ASLR, our hard jump strategy will also fail because both white and black call the GetPassiveMoves function. We need a way to only modify that function for white.

Figuring out Whose Turn it is

Getting turn info took a bit of shooting in the dark also, but because of symbols it was relatively easy to track down.

First I put a breakpoint here:

bp Chess!GameState::GetTurn +3 "r eax; g"

This is called a lot, and it seems to return 2 or 0 for white, and 0, 1, or 2 for black. This function will probably work, but there’s another turn function too named toggleturn, so lets try that. This function seems perfect – it’s called once after every move. We can see it’s testing the value in [ecx+4] so we inspect that, and sure enough it’s 1 before a white move and 0 before a black move

bp Chess!GameState::toggleturn
dd ecx + 4

Programatically Changing the Game

I’m going to programattically debug the process. The way the uninformed post did things was cool, but it’s (more) difficult to go route way because we’re not messing with data, we’re messing with the program which is non writable. So how do we programatically debug?

There are a ton of ways. Mona uses this and it looks awesome: http://pykd.codeplex.com/. I’m a python guy, so usually I’d go that route, but I’m trying to learn powershell so I decided to try going that route and use this http://powerdbg.codeplex.com/. For the powershell to work you need to install this module.

The first thing I want to do is change the hard coded value to something I can switch back and forth. So I tried setting a breakpoint that I could disable per turn

bp Chess!Bishop::GetPassiveMoves "r eip=Chess!Queen::GetPassiveMoves;g"

This was waaaay too slow for the game to be playable. I had to figure out something else. This is when I noticed just how similar the getpassivemoves functions are

0:012> uf Chess!Bishop::GetPassiveMoves
Chess!Bishop::GetPassiveMoves:
00c5d70a 8bff            mov     edi,edi
00c5d70c 55              push    ebp
00c5d70d 8bec            mov     ebp,esp
00c5d70f 8b4508          mov     eax,dword ptr [ebp+8]
00c5d712 c700f07ec300    mov     dword ptr [eax],offset Chess!Bishop::sPassiveMoves (00c37ef0)
00c5d718 a1e87ec300      mov     eax,dword ptr [Chess!Bishop::sPassiveMovesCount (00c37ee8)]
00c5d71d 5d              pop     ebp
00c5d71e c20400          ret     4
0:012> uf Chess!queen::GetPassiveMoves
Chess!Queen::GetCaptureMoves:
00c5d7f8 8bff            mov     edi,edi
00c5d7fa 55              push    ebp
00c5d7fb 8bec            mov     ebp,esp
00c5d7fd 8b4508          mov     eax,dword ptr [ebp+8]
00c5d800 c700b880c300    mov     dword ptr [eax],offset Chess!Queen::sPassiveMoves (00c380b8)
00c5d806 a1b080c300      mov     eax,dword ptr [Chess!Queen::sPassiveMovesCount (00c380b0)]
00c5d80b 5d              pop     ebp
00c5d80c c20400          ret     4

They’re very close, and they’re the exact same number of bytes. We can just edit things on the fly, replacing the queen’s code with the bishop’s code and back again.

Import-Module PowerDbg

#global vars, populated later
$bishop_code = ""
$queen_code = ""


function bishop_to_queen
{
    $command =  "eb Chess!Bishop::GetPassiveMoves+a " + $queen_code
    Invoke-DbgCommand $command
}

function bishop_restore
{
    $command = "eb Chess!Bishop::GetPassiveMoves+a " + $bishop_code
    Invoke-DbgCommand $command
}


New-DbgSession -command 'C:\Program Files\Microsoft Games\Chess\Chess.exe'
Load-PowerDbgSymbols "srv*c:\debug*http://msdl.microsoft.com/download/symbols"


#get the bytes for the different bishop and queen functions
$bishop_array = (Invoke-DbgCommand "db Chess!Bishop::GetPassiveMoves+a L7").Split(" ")[2..8]
$bishop_code = [string]::join(" ", $bishop_array)

$queen_array = (Invoke-DbgCommand "db Chess!queen::GetPassiveMoves+a L7").Split(" ")[2..8]
$queen_code = [string]::join(" ", $queen_array)


bishop_to_queen


$white_turn = $true
Invoke-DbgCommand "bp Chess!GameState::ToggleTurn"


#this loops once per turn
while($true)
{
    if ($white_turn -eq $true)
    {
        $white_turn = $false
        bishop_to_queen
    }
    else
    {
        $white_turn = $true
        bishop_restore
    }

    $ret_error = Invoke-DbgCommand "g"

    if ($ret_error.Contains("No runnable debugees"))
    {
        break;
    }

}

And there we go, a runnable chess game where white bishops are super powerful. There are a few quirks, like if a bishop gets a king into checkmate with a queen move it doesn’t seem to register and you can kill the king and keep playing, but overall pretty good :)

king_killed

I am still a noob at reversing, but this was still a fun afternoon :)

Reverseme Windows Keygen

This one was challenging for me, and took me several hours, but was fun. I got caught up on certain parts that may not have been too difficult, but, yeah…

http://crackmes.de/users/tripletordo/ice9/

You can download the executable here Ice9.zip.

The first thing I noticed is probably the ‘trick’ which was simply a call to isdebuggerpresent. I modified the assembly immediately after from JNE to JE so that it only runs if a debugger is present, allowing me to attach my debugger.

00401071 74 0A JE SHORT Ice9.0040107D

This took a lot of trial and error. My strategy was to replicate the logic. Once I got to the point ‘ecx at 0040119c’ I was home free.

#include <iostream>
#include <string>
using namespace std;

void main (int argc, char *argv[]) {
  if ( argc != 2) {
    cout<<"Bad usage, enter a name > 4 letters"<<endl;
	return;
  }
  string name = argv[1];
  string ostring = name;
  int i;
  //first reverse the string
  for (i=0; i<name.length(); i++) {
    name[i] = ostring [name.length()-i-1];
  }
  
  if (name.length() < 4) {
    cout << "name must be more than 4 letters chief"<<endl;
	return;
  }
  

  int v1 = 0;
  int cum = 0;
  for (i=1; i<name.length(); i++) {
    v1 = name[i];
	if (name[i] <= 90) {
	  if (v1 >= 65)
	    v1 += 44;
	}
	cum += v1;
  } //ecx at 0040119C
  
  cum = 9 * (12345 * (cum + 666) - 23);
  
  char chr_403119 [122];
  unsigned int v;
  i=0;
  //no bounds checking
  do {
    v = cum;
	cum /= 0xA;
	chr_403119[i++] = v % 10 + 48;
  } while (v / 10);
  chr_403119[i] = '\0';
  
  printf ("%s", chr_403119);
  string serial = "";

  //reverse the string
  for (; i >= 0; --i) {
    serial += chr_403119[i];
  }
  cout<<serial<<endl;
  
  //append all chars except the 'first' three to the end 
  for (i=3; i< ostring.length(); i++) {
    serial += ostring[i];
  }
  
  cout<<serial<<endl;

}

My plan on this one, since it was interesting enough and because it’s relatively easy to break at the final value, is to break this a completely different way. I’d like to write a python debugging script that bypasses the isdebuggerpresent and just grabs the final value in the compare at 004011FF. This should be relatively straightforward, and hopefully a good ‘hello, world’ to the world of python debugging. Stay tuned.

Reverseme: Namegenme

This guy is here: http://crackmes.de/users/moofy/moofys_namegenme/

namegenme.zip

I had a fairly hard time with this one for some reason, although the solution was right in front of my face…

Most the logic for calculating the generation is in the function 00401852. The Serial is stored in a global variable, and the name is generated by taking certain bytes from the serial and doing addition on them.

Here is all the relevant logic, although finding it was sort of a pain.


.text:004018FD                 lea     eax, [ebp+var_10]
.text:00401900                 add     dword ptr [eax], 4
.text:00401903                 lea     eax, [ebp+var_14]
.text:00401906                 sub     dword ptr [eax], 3
.text:00401909                 lea     eax, [ebp+var_18]
.text:0040190C                 sub     dword ptr [eax], 2
.text:0040190F                 lea     eax, [ebp+var_1C]
.text:00401912                 add     dword ptr [eax], 2
.text:00401915                 lea     eax, [ebp+var_20]
.text:00401918                 dec     dword ptr [eax]
.text:0040191A                 lea     eax, [ebp+var_24]
.text:0040191D                 add     dword ptr [eax], 3
.text:00401920                 lea     eax, [ebp+var_28]
.text:00401923                 sub     dword ptr [eax], 2
.text:00401926                 lea     eax, [ebp+var_2C]
.text:00401929                 sub     dword ptr [eax], 4
.text:0040192C                 lea     eax, [ebp+var_30]
.text:0040192F                 add     dword ptr [eax], 3
.text:00401932                 lea     eax, [ebp+var_34]
.text:00401935                 inc     dword ptr [eax]

Here is a keygen written in C

void main(int argc, char* argv[]) {

  char Name [10];
  char* ser = argv[1];
  if (argc != 2 || strlen(argv[1]) < 21) {
    printf("Invalid serialn");
	return (-1);
  }

  Name[0] = ser[0] + 4;
  Name[1] = ser[1] - 3;
  Name[2] = ser[2] - 2;
  Name[3] = ser[6] + 2;
  Name[4] = ser[7] - 1;
  Name[5] = ser[8] + 3;
  Name[6] = ser[13] - 2;
  Name[7] = ser[14] - 4;
  Name[8] = ser[15] + 3;
  Name[9] = ser[20] + 1;
  Name[10] = "\0";

  printf("Name: %sn", Name);
}

Windows Password, geygen, password reverseme

Sat for an evenin’ o’ fun this holiday season.  I like these easy ones.  Last month I tried a harder one and found it discouraging.  I don’t have the sort of time to work on these for a full day, so these couple hour ones are a lot more fun to me at this point.

I found the binary on crackmes.de, here is a mirror.

Part 1: Find the Password.

Really easy.  Even me a beginner figured this out in about half an hour.  First thing it does is make sure the input is 8 characters long.  If it’s not it exits the program.

004013B7  |. 890424         MOV DWORD PTR SS:[ESP],EAX               ; |
004013BA  |. E8 B1060000    CALL &lt;JMP.&amp;msvcrt.strlen&gt;                ; strlen
004013BF  |. 83F8 08        CMP EAX,8
004013C2  |. 74 05          JE SHORT CrackMe#.004013C9               ;  if strlen(pass) == 8 (if it's not, 'something went wrong')
004013C4  |. E9 2C010000    JMP CrackMe#.004014F5

Next it goes through a little loop which compares each letter of the password you entered with the real password

004013C9  |> C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
004013D0  |> 837D F4 07     /CMP DWORD PTR SS:[EBP-C],7              ;  looks like for (i =0; i<7; i++) probably
004013D4  |. 7F 20          |JG SHORT CrackMe#.004013F6
004013D6  |. 8D45 F8        |LEA EAX,DWORD PTR SS:[EBP-8]
004013D9  |. 0345 F4        |ADD EAX,DWORD PTR SS:[EBP-C]
004013DC  |. 8D50 E0        |LEA EDX,DWORD PTR DS:[EAX-20]           ;  edx = ptr(ebp - 8) + ptr(ebp - c)
004013DF  |. 8D45 F8        |LEA EAX,DWORD PTR SS:[EBP-8]
004013E2  |. 0345 F4        |ADD EAX,DWORD PTR SS:[EBP-C]
004013E5  |. 83E8 20        |SUB EAX,20                              ;  eax = edx - 20
004013E8  |. 0FB600         |MOVZX EAX,BYTE PTR DS:[EAX]
004013EB  |. FEC0           |INC AL
004013ED  |. 8802           |MOV BYTE PTR DS:[EDX],AL
004013EF  |. 8D45 F4        |LEA EAX,DWORD PTR SS:[EBP-C]
004013F2  |. FF00           |INC DWORD PTR DS:[EAX]
004013F4  |.^EB DA          \JMP SHORT CrackMe#.004013D0
004013F6  |> 8D45 C8        LEA EAX,DWORD PTR SS:[EBP-38]            ; ||||||||
004013F9  |. 8D55 D8        LEA EDX,DWORD PTR SS:[EBP-28]            ; ||||||||
004013FC  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; ||||||||
00401400  |. 891424         MOV DWORD PTR SS:[ESP],EDX               ; ||||||||
00401403  |. E8 58060000    CALL <JMP.&msvcrt.strcmp>                ; |||||||\strcmp
00401408  |. 85C0           TEST EAX,EAX                             ; |||||||if this doesn't = 0 exit 'something went wrong'
0040140A  |. 0F85 E5000000  JNZ CrackMe#.004014F5                    ; |||||||
00401410  |. C70424 FE31400>MOV DWORD PTR SS:[ESP],CrackMe#.004031FE ; |||||||ASCII 0A,"Stage 1 co"

If you look closely it compares every letter + 1 to QbTTx1sE.  Meaning of course that the password is simply: PaSSw0rD

Part 2: KeyGen

The next part was a little more tricky for me.

On the screen you enter a name and a corresponding serial.

STAGE 2
00401423  |. E8 68060000    CALL <JMP.&msvcrt.printf>                ; |||||\printf
00401428  |. C70424 D331400>MOV DWORD PTR SS:[ESP],CrackMe#.004031D3 ; |||||ASCII "*******
0040142F  |. E8 5C060000    CALL <JMP.&msvcrt.printf>                ; ||||\printf
00401434  |. C70424 1E32400>MOV DWORD PTR SS:[ESP],CrackMe#.0040321E ; ||||ASCII 0A,"Name [2<=c"
0040143B  |. E8 50060000    CALL <JMP.&msvcrt.printf>                ; |||\printf
00401440  |. 8D45 B8        LEA EAX,DWORD PTR SS:[EBP-48]            ; |||
00401443  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |||ebp -48 or esp +4 = name
00401447  |. C70424 FB31400>MOV DWORD PTR SS:[ESP],CrackMe#.004031FB ; |||ASCII "%s"
0040144E  |. E8 2D060000    CALL <JMP.&msvcrt.scanf>                 ; ||\scanf
00401453  |. C70424 3632400>MOV DWORD PTR SS:[ESP],CrackMe#.00403236 ; ||ASCII "Serial : "
0040145A  |. E8 31060000    CALL <JMP.&msvcrt.printf>                ; |\printf
0040145F  |. 8D45 B0        LEA EAX,DWORD PTR SS:[EBP-50]            ; |
00401462  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |ebp -50 = serial
00401466  |. C70424 4132400>MOV DWORD PTR SS:[ESP],CrackMe#.00403241 ; |ASCII "%d"
0040146D  |. E8 0E060000    CALL <JMP.&msvcrt.scanf>                 ; \scanf
00401472  |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
00401479  |. C745 F4 000000>MOV DWORD PTR SS:[EBP-C],0
00401480  |> 8D45 B8        /LEA EAX,DWORD PTR SS:[EBP-48]           ; |
00401483  |. 890424         |MOV DWORD PTR SS:[ESP],EAX              ; |strlen (name)
00401486  |. E8 E5050000    |CALL <JMP.&msvcrt.strlen>               ; \strlen
0040148B  |. 3945 F4        |CMP DWORD PTR SS:[EBP-C],EAX            ;  while loop (for i = len(name)
0040148E  |. 77 1A          |JA SHORT CrackMe#.004014AA
00401490  |. 8D45 F8        |LEA EAX,DWORD PTR SS:[EBP-8]
00401493  |. 0345 F4        |ADD EAX,DWORD PTR SS:[EBP-C]
00401496  |. 83E8 40        |SUB EAX,40
00401499  |. 0FBE00         |MOVSX EAX,BYTE PTR DS:[EAX]
0040149C  |. 0345 B4        |ADD EAX,DWORD PTR SS:[EBP-4C]
0040149F  |. 48             |DEC EAX
004014A0  |. 8945 B4        |MOV DWORD PTR SS:[EBP-4C],EAX
004014A3  |. 8D45 F4        |LEA EAX,DWORD PTR SS:[EBP-C]
004014A6  |. FF00           |INC DWORD PTR DS:[EAX]                  ;  pointer to 0,1,...,length
004014A8  |.^EB D6          \JMP SHORT CrackMe#.00401480
004014AA  |> 8B45 B4        MOV EAX,DWORD PTR SS:[EBP-4C]            ; |||||eax calculated in loop, compared with serial
004014AD  |. 3B45 B0        CMP EAX,DWORD PTR SS:[EBP-50]            ; |||||
004014B0  |. 75 43          JNZ SHORT CrackMe#.004014F5              ; |||||
004014B2  |. C70424 4432400>MOV DWORD PTR SS:[ESP],CrackMe#.00403244 ; |||||ASCII 0A,"Stage 2 Co"

Although it can be a bit hard to read because of all the pointers (at least for me), it is definitely possible to discern the for loop and figure out it’s computing some value based on the name and writing to the same space (ebp – 4c) at every iteration.  This value is then compared to the serial to determine if the serial is valid or not.  After setting some breakpoints it becomes clear that  every value of the name -1 is added together and compared to the serial.  Here is a working keygen.

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string name = "";
    cout << "name: ";
    cin >> name;
    int total = 0;
    for(int i=0; i<name.length(); i++)
    {
            total += name[i];
    }
    total -= (name.length() + 1);
    cout << "Serial should be " << total << endl;

    system("PAUSE");
    return 0;
}

Part 3

This part was theoretically easy, but a bit hard for me to execute.  The goal is to remove a console nag.

004014DD  |. E8 AE050000    CALL <JMP.&msvcrt.printf>                ; |\printf

That line needs to turn to a nop.  In ollydbg just right click as say fill with nop, and it should be good.

Follow

Get every new post delivered to your Inbox.