第75章 修改彩球游戏

彩球游戏有多个衍生版本。本章采用的是1997年发布的BallTrix版。这款程序可从http://go. yurichev.com/17311

公开下载。它的图形界面如图75.1所示。

..\tu\7501.tif

图75.1 游戏界面

本章关注它的随机生成器,以及修改这个组件的具体方法。IDA在balltrix.exe里识别出了标准函数_rand。这个函数的地址是0x00403DA0。不仅如此,IDA还判断出该函数只会被这一处调用。

.text:00402C9C sub_402C9C       proc near               ; CODE XREF: sub_402ACA+52
.text:00402C9C                                          ; sub_402ACA+64 ...
.text:00402C9C
.text:00402C9C arg_0            = dword ptr  8
.text:00402C9C            
.text:00402C9C                  push    ebp
.text:00402C9D                  mov     ebp, esp
.text:00402C9F                  push    ebx
.text:00402CA0                  push    esi
.text:00402CA1                  push    edi
.text:00402CA2                  mov     eax, dword_40D430
.text:00402CA7                  imul    eax, dword_40D440
.text:00402CAE                  add     eax, dword_40D5C8
.text:00402CB4                  mov     ecx, 32000
.text:00402CB9                  cdq
.text:00402CBA                  idiv    ecx
.text:00402CBC                  mov     dword_40D440, edx
.text:00402CC2                  call    _rand
.text:00402CC7                  cdq
.text:00402CC8                  idiv    [ebp+arg_0]
.text:00402CCB                  mov     dword_40D430, edx
.text:00402CD1                  mov     eax, dword_40D430
.text:00402CD6                  jmp     $+5
.text:00402CDB                  pop     edi
.text:00402CDC                  pop     esi
.text:00402CDD                  pop     ebx
.text:00402CDE                  leave
.text:00402CDF                  retn  
.text:00402CDF sub_402C9C       endp

为了便于讨论,我们把_rand函数的调用方函数叫作“random”。在程序里有三处的代码调用了random函数。

调用random函数的前两处代码是:

.text:00402B16                  mov      eax, dword_40C03C ; 10 here
.text:00402B1B                  push     eax
.text:00402B1C                  call     random
.text:00402B21                  add      esp, 4
.text:00402B24                  inc      eax
.text:00402B25                  mov      [ebp+var_C], eax
.text:00402B28                  mov      eax, dword_40C040 ; 10 here
.text:00402B2D                  push     eax
.text:00402B2E                  call     random
.text:00402B33                  add      esp, 4

调用random函数的第三处代码是:

.text:00402BBB                  mov      eax, dword_40C058 ; 5 here
.text:00402BC0                  push     eax
.text:00402BC1                  call     random
.text:00402BC6                  add      esp, 4
.text:00402BC9                  inc      eax

综合上述代码,我们可以判定该函数只有一个参数。前两处传递的参数是10,第三处传递的参数是5。在观察游戏的界面后,可知棋盘是10×10的方阵,而彩球的颜色总共有5种。这三处调用random的指令,必定是坐标和颜色的生成指令。标准的随机函数rand()函数会生成一个在0~0x7FFF之间的返回值,用起来并不方便。实际上,编程人员会编写自己的随机函数以获取特定区间之内的随机返回值。本例需要的随机数是0~(n−1)之间的整数,n就是函数所需的唯一参数。这一假设可由任意一种debugger验证。

本章将修改第三处调用指令,让它的第三次返回值永远是0。为此,我们可把PUSH/CALL/ADD这三条指令改为NOP,然后再添加XOR EAX,EAX指令,以清空EAX寄存器。

.00402BB8: 83C410         add           esp,010
.00402BBB: A158C04000     mov           eax,[00040C058]
.00402BC0: 31C0           xor           eax,eax
.00402BC2: 90             nop
.00402BC3: 90             nop
.00402BC4: 90             nop
.00402BC5: 90             nop
.00402BC6: 90             nop
.00402BC7: 90             nop
.00402BC8: 90             nop
.00402BC9: 40             inc           eax
.00402BCA: 8B4DF8         mov           ecx,[ebp][-8]
.00402BCD: 8D0C49         lea           ecx,[ecx][ecx]*2
.00402BD0: 8B15F4D54000   mov           edx,[00040D5F4]

也就是说,我们修改调用random()函数的有关指令,让它的返回值固定为0。

修改程序后,它的运行界面如图75.2所示。

..\tu\7502.tif

图75.2 作弊成功

不得不说我们修改得很成功。我在当初修改这个游戏的时候,希望我的同事明白“没有必要执迷于你肯定会赢的游戏”。可惜我的劝阻没能成功。

另外还有一个问题:为什么random()函数的参数会是全局变量?这是因为棋盘大小是可调整的变量,不能在程序里把它写成常量。本例中的5和10只是它的默认值。