利用OD通过Call调用实现扫雷(win7旗舰版32位)

  一直对逆向感兴趣,就拿最简单的扫雷开始,对于XP系统中的扫雷,雷的数目以及雷区的地址都是固定的,可以直接通过Cheat Engine搜索出来,然后在OD中直接下内存访问断点,找到扫雷整个区域,获得行数和列数,再顺藤摸瓜找到左键点击的处理函数和右键点击的处理函数,读取雷区数据,一一比较如果是雷就右键标记,如果不是可以直接左键点开,即可完成秒杀。
  经过了大概半个月的时间,终于在查阅各种帖子和不断的摸索下,实现了Win7 32位系统扫雷的秒杀,哈哈,太开心了!
  因为XP系统的扫雷中雷区的地址是固定的,可以通过CE进行搜索出来,但Win7系统的扫雷是用C++写的,雷区不再是固定的,而是在鼠标第一次点击之后再产生雷的位置。对win7扫雷一开始没有一点思路,后来看了很多牛人写的帖子,在他们的基础上才能慢慢的入手。可以参考:
  看雪论坛《Vista的扫雷》:http://bbs.pediy.com/thread-40295.htm
  《Win7自动扫雷》:http://bbs.pediy.com/thread-120152.htm
  炒鸡嗨客协管徐的博客《Win7扫雷逆向调试》:
  http://blog.csdn.net/xfgryujk/article/details/52948705
这2篇文章里的思路都是通过对rand函数下断,分析找到雷存放的地址,每个格子里存放0或1,如果是1则是雷,是0则不是雷,发送消息模拟鼠标的单机,进行自动扫雷。其中,在《Vista的扫雷》提到了重定位的处理,解决方案2对于Win7 32位下的扫雷并不适用,这篇文章中提到:
  “假设扫雷入口地址设为dwCodeAddress,则[3931B8] 这个地址得到的方法是: ( dwCodeAddress & 0xFFFF0000 ) + 0x531B8”
  但是在Win7 32位的扫雷中,并不是这样的,用PEiD打开扫雷

利用OD通过Call调用实现扫雷(win7旗舰版32位)_第1张图片

可以看到,扫雷的入口点是0x0002e08f,用OD打开扫雷,停在了入口点,代码是

001CE08F > $  E8 B5050000   call MineSwee.001CE649
001CE094   .^ E9 4DFDFFFF   jmp MineSwee.001CDDE6

所以扫雷中地址的计算方法是:扫雷模块地址+偏移
入口点地址0x1ce08f = 扫雷模块地址+0x2e08f,所以此次加载扫雷模块地址为
MineSweeper.exe = 0x1A0000
  在此我找到了一种可以去除重定位的方法,PE头中的文件头的Characteristics字段指定了文件的类型,占用2字节,其第0比特为1,即表示文件中不存在重定位信息,用C32Asm打开扫雷,找到该字段


利用OD通过Call调用实现扫雷(win7旗舰版32位)_第2张图片

该字段为0x0102,其第0比特为0,即存在重定位信息,将其修改为1,将会给我们分析带来很大的方便(地址固定),即将该字段修改为0x0103,在下面的分析中我就用修改后的文件进行分析,该修改并不影响分析结果,同样适用于没修改的扫雷(因为偏移是固定的)。分析前面的过程在《Vista的扫雷》和《Win7自动扫雷》中讲得很详细,在此不做赘述,简单走一遍,看过的可以直接跳过。
  首先,用OD加载扫雷,对rand函数下断:bp rand,然后先将该断点禁止,按F9运行起来,启动该断点,扫雷断下

77C3C070 >  E8 59E5FFFF     call msvcrt.77C3A5CE                    ; rand 断点处
77C3C075    8B48 14         mov ecx,dword ptr ds:[eax+0x14]
77C3C078    69C9 FD430300   imul ecx,ecx,0x343FD
77C3C07E    81C1 C39E2600   add ecx,0x269EC3
77C3C084    8948 14         mov dword ptr ds:[eax+0x14],ecx
77C3C087    8BC1            mov eax,ecx
77C3C089    C1E8 10         shr eax,0x10
77C3C08C    25 FF7F0000     and eax,0x7FFF
77C3C091    C3              retn

一看便知该模块不是扫雷的模块,而是msvcrt。在堆栈窗口中看到,



在第1行处反汇编跟随(按Enter),到

0102D7F3  /$  8BFF          mov edi,edi                ;调用rand的小函数
0102D7F5  |.  55            push ebp
0102D7F6  |.  8BEC          mov ebp,esp
0102D7F8  |.  FF15 B8160001 call dword ptr ds:[<&msvcrt.rand>]       ; [rand
0102D7FE  |.  8B4D 0C       mov ecx,dword ptr ss:[ebp+0xC]
0102D801  |.  2B4D 08       sub ecx,dword ptr ss:[ebp+0x8]
0102D804  |.  99            cdq
0102D805  |.  41            inc ecx
0102D806  |.  F7F9          idiv ecx
0102D808  |.  8BC2          mov eax,edx
0102D80A  |.  0345 08       add eax,dword ptr ss:[ebp+0x8]
0102D80D  |.  5D            pop ebp
0102D80E  \.  C2 0800       retn 0x8

鼠标点到该函数第一行,OD在反汇编窗口下方会有提示

“本地调用来自 01020176, 01023169, 01023177, 01023BB3, 01027466, 010275B9, 01027E86, 01027E98”

对这8个call都进行下断,F9,会发现一直断在

01023BB3  |.  E8 3B9C0000   call MineSwee.0102D7F3                   ;  一直断,没用删除

删除这个断点,继续F9,回到游戏,不会中断了,开始游戏并点击一个格子,断在如下位置

01020176  |.  E8 78D60000   |call MineSwee.0102D7F3

往上看,则如《Vista的扫雷》中一样,是根据第一次点击的格子产生序号的代码

010200BB  /$  8BFF          mov edi,edi                              ;  布雷函数
……
010200C7  |.  E8 4AD70000   call MineSwee.0102D816
010200CC  |.  FF76 2C       push dword ptr ds:[esi+0x2C]
010200CF  |.  8945 F8       mov dword ptr ss:[ebp-0x8],eax
010200D2  |.  E8 4AD70000   call MineSwee.0102D821
010200D7  |.  6A 10         push 0x10
010200D9  |.  E8 C9DB0000   call MineSwee.0102DCA7                   ;  先申请4个DWORD
010200DE  |.  59            pop ecx
010200DF  |.  85C0          test eax,eax
010200E1  |.  74 0E         je short MineSwee.010200F1
010200E3  |.  6A 10         push 0x10
010200E5  |.  8BC8          mov ecx,eax
010200E7  |.  E8 6DF8FFFF   call MineSwee.0101F959                   ;  对上面申请的内存初始化为0 0 0x10 0
010200EC  |.  8945 FC       mov dword ptr ss:[ebp-0x4],eax
010200EF  |.  EB 04         jmp short MineSwee.010200F5
010200F1  |>  8365 FC 00    and dword ptr ss:[ebp-0x4],0x0
010200F5  |>  8B46 08       mov eax,dword ptr ds:[esi+0x8]           ;  行数dwRow 9
010200F8  |.  0FAF46 0C     imul eax,dword ptr ds:[esi+0xC] ;列数dwColumn和行数相乘,得到总方块数dwSum edx:eax
010200FC  |.  33FF          xor edi,edi                              ;  i = 0 循环计数器
010200FE  |.  85C0          test eax,eax
01020100  |.  7E 49         jle short MineSwee.0102014B
01020102  |>  8B4E 0C       /mov ecx,dword ptr ds:[esi+0xC]
01020105  |.  8BC7          |mov eax,edi
01020107  |.  99            |cdq                                     ;  edx:eax 64位寄存器
01020108  |.  F7F9          |idiv ecx                                ;  i/dwColumn=eax行号....edx列号
0102010A  |.  33C9          |xor ecx,ecx
0102010C  |.  2B55 08       |sub edx,dword ptr ss:[ebp+0x8]          ;  i列号-鼠标点击列号(从0开始)
0102010F  |.  2B45 0C       |sub eax,dword ptr ss:[ebp+0xC]          ;  i行号-鼠标点击行号(从0开始)
01020112  |.  85D2          |test edx,edx
01020114  |.  0F9DC1        |setge cl                       ;判断列号差值正负,设置cl为1或0,从而取绝对值
01020117  |.  8D4C09 FF     |lea ecx,dword ptr ds:[ecx+ecx-0x1]
0102011B  |.  0FAFCA        |imul ecx,edx
0102011E  |.  83F9 01       |cmp ecx,0x1
01020121  |.  7F 13         |jg short MineSwee.01020136              ;  第一个差值绝对值<=1,判断第二个差值
01020123  |.  33C9          |xor ecx,ecx
01020125  |.  85C0          |test eax,eax                            ;  判断行号差值正负
01020127  |.  0F9DC1        |setge cl
0102012A  |.  8D4C09 FF     |lea ecx,dword ptr ds:[ecx+ecx-0x1]
0102012E  |.  0FAFC8        |imul ecx,eax
01020131  |.  83F9 01       |cmp ecx,0x1
01020134  |.  7E 09         |jle short MineSwee.0102013F             ;  第二个差值<=1,什么都不做
01020136  |>  8B4D FC       |mov ecx,dword ptr ss:[ebp-0x4]
01020139  |.  57            |push edi                                ;  将序号传递进去
0102013A  |.  E8 12590100   |call MineSwee.01035A51                  ;  将序号保存到realloc空间中去
0102013F  |>  8B46 08       |mov eax,dword ptr ds:[esi+0x8]          ;  dwRow
01020142  |.  0FAF46 0C     |imul eax,dword ptr ds:[esi+0xC]         ;  dwColumn
01020146  |.  47            |inc edi
01020147  |.  3BF8          |cmp edi,eax
01020149  |.^ 7C B7         \jl short MineSwee.01020102
0102014B  |>  6A 10         push 0x10
0102014D  |.  E8 55DB0000   call MineSwee.0102DCA7                   ;  再申请4个DWORD
01020152  |.  59            pop ecx
01020153  |.  85C0          test eax,eax
01020155  |.  74 0E         je short MineSwee.01020165
01020157  |.  FF76 04       push dword ptr ds:[esi+0x4]
0102015A  |.  8BC8          mov ecx,eax
0102015C  |.  E8 F8F7FFFF   call MineSwee.0101F959                   ;  对上面申请的内存初始化为0 0 0x0A 0
01020161  |.  8BF8          mov edi,eax
01020163  |.  EB 31         jmp short MineSwee.01020196
01020165  |>  33FF          xor edi,edi
01020167  |.  EB 2D         jmp short MineSwee.01020196
01020169  |>  8B45 FC       /mov eax,dword ptr ss:[ebp-0x4]
0102016C  |.  8B00          |mov eax,dword ptr ds:[eax]
0102016E  |.  85C0          |test eax,eax
01020170  |.  76 2B         |jbe short MineSwee.0102019D
01020172  |.  48            |dec eax            ;81个方块,有81-9=72(0x48)个编号,生成0~0x47之间的随机数
01020173  |.  50            |push eax
01020174  |.  6A 00         |push 0x0                                ;  Arg1=0
01020176  |.  E8 78D60000   |call MineSwee.0102D7F3  ;第一次点击方块断在此处,调用rand,产生0~Arg2的随机数
0102017B  |.  8BD8          |mov ebx,eax
0102017D  |.  8B45 FC       |mov eax,dword ptr ss:[ebp-0x4]
01020180  |.  8B40 0C       |mov eax,dword ptr ds:[eax+0xC]
01020183  |.  FF3498        |push dword ptr ds:[eax+ebx*4]           ;  凭获得的随机数到上面数组中取值
01020186  |.  8BCF          |mov ecx,edi
01020188  |.  E8 C4580100   |call MineSwee.01035A51                  ;  保存到一个新数组中
0102018D  |.  8B4D FC       |mov ecx,dword ptr ss:[ebp-0x4]
01020190  |.  53            |push ebx
01020191  |.  E8 08EB0000   |call MineSwee.0102EC9E                  ;将已取出的数据从原数组中删除,以防重复
01020196  |>  8B07           mov eax,dword ptr ds:[edi]
01020198  |.  3B46 04       |cmp eax,dword ptr ds:[esi+0x4]         ;如果小于需要产生的雷数[esi+4],继续循环
0102019B  |.^ 75 CC         \jnz short MineSwee.01020169
0102019D  |>  33C9          xor ecx,ecx                              ;  循环i=0
0102019F  |.  390F          cmp dword ptr ds:[edi],ecx               ;  [edi]=产生的地雷总数
010201A1  |.  76 21         jbe short MineSwee.010201C4
010201A3  |>  8B47 0C       /mov eax,dword ptr ds:[edi+0xC]
010201A6  |.  8B0488        |mov eax,dword ptr ds:[eax+ecx*4]        ;  取第i个雷的位置
010201A9  |.  8B5E 0C       |mov ebx,dword ptr ds:[esi+0xC]          ;  列数
010201AC  |.  99            |cdq
010201AD  |.  F7FB          |idiv ebx                                ;  i/dwColumn=eax行号....edx列号
010201AF  |.  8B5E 44       |mov ebx,dword ptr ds:[esi+0x44]
010201B2  |.  8B5B 0C       |mov ebx,dword ptr ds:[ebx+0xC]       ;[[esi+0x44]+0xc]地址表,有dwColumn个地址
010201B5  |.  41            |inc ecx                                 ;  ++i
010201B6  |.  8B1493        |mov edx,dword ptr ds:[ebx+edx*4]        ;  根据雷所在列号取第edx(列号)个地址
010201B9  |.  8B52 0C       |mov edx,dword ptr ds:[edx+0xC]          ;  esi=[[minesweeper.exe+0x868B4]+0x10]
010201BC  |.  C60410 01  |mov byte ptr ds:[eax+edx],0x1  ;[[[[[esi+0x44]+0xc]+4*列号]+0xc]+行号],1表示有雷
010201C0  |.  3B0F          |cmp ecx,dword ptr ds:[edi]
010201C2  |.^ 72 DF         \jb short MineSwee.010201A3              ;  布雷完毕

按Ctrl+F9 2次执行到返回,或在堆栈窗口中找到第2个“返回到”,跟随到上上一层:

01026FB7  /$  8BFF          mov edi,edi                              ;  左键单击判断函数
01026FB9  |.  55            push ebp
01026FBA  |.  8BEC          mov ebp,esp
01026FBC  |.  A1 B4680801   mov eax,dword ptr ds:[0x10868B4]            ;该地址就是我们要找的地址
01026FC1  |.  53            push ebx
01026FC2  |.  56            push esi
01026FC3  |.  8B75 08       mov esi,dword ptr ss:[ebp+0x8]
01026FC6  |.  57            push edi
01026FC7  |.  C680 C5000000>mov byte ptr ds:[eax+0xC5],0x1
01026FCE  |.  FF76 1C       push dword ptr ds:[esi+0x1C]             ;  Y坐标
01026FD1  |.  8BF9          mov edi,ecx
01026FD3  |.  FF76 18       push dword ptr ds:[esi+0x18]             ;  X坐标
01026FD6  |.  8B0D B4680801 mov ecx,dword ptr ds:[0x10868B4]         ;  this指针
01026FDC  |.  32DB          xor bl,bl
01026FDE  |.  E8 35A4FFFF   call MineSwee.01021418           ;判断踩下的方块是不是雷,参数有方块的X、Y坐标
01026FE3  |.  85C0          test eax,eax                     ;将返回到此处
01026FE5  |.  7F 0A         jg short MineSwee.01026FF1
01026FE7  |.  89B7 9C000000 mov dword ptr ds:[edi+0x9C],esi
01026FED  |.  FEC3          inc bl
01026FEF  |.  EB 08         jmp short MineSwee.01026FF9
01026FF1  |>  50            push eax
01026FF2  |.  8BCF          mov ecx,edi
01026FF4  |.  E8 D4FBFFFF   call MineSwee.01026BCD
01026FF9  |>  C687 AC000000>mov byte ptr ds:[edi+0xAC],0x1
01027000  |.  5F            pop edi
01027001  |.  5E            pop esi
01027002  |.  8AC3          mov al,bl
01027004  |.  5B            pop ebx
01027005  |.  5D            pop ebp
01027006  \.  C2 0400       retn 0x4
其中,call MineSwee.01021418中的代码及注释如下(看过的可以跳过)
01021418   $  8BFF          mov edi,edi
0102141A   .  55            push ebp
0102141B   .  8BEC          mov ebp,esp
0102141D   .  8B49 10       mov ecx,dword ptr ds:[ecx+0x10]    ;this指针变成[[minesweeper.exe+0x868B4]+0x10]
01021420   .  5D            pop ebp
01021421   .^ E9 2AF8FFFF   jmp MineSwee.01020C50            ;跳转
此处有个跳转
01020C57  |.  8B5D 08       mov ebx,dword ptr ss:[ebp+0x8]           ;  ebx = X坐标
01020C5A  |.  56            push esi
01020C5B  |.  8BF1          mov esi,ecx                            ;esi ecx=[[minesweeper.exe+0x868B4]+0x10]
01020C5D  |.  8B46 40       mov eax,dword ptr ds:[esi+0x40]          ;  this->某对象
01020C60  |.  8B40 0C       mov eax,dword ptr ds:[eax+0xC]           ;  ->某对象
01020C63  |.  8B0498        mov eax,dword ptr ds:[eax+ebx*4]         ;  ->某对象
01020C66  |.  8B40 0C       mov eax,dword ptr ds:[eax+0xC]           ;  eax = &某对象
01020C69  |.  57            push edi
01020C6A  |.  8B7D 0C       mov edi,dword ptr ss:[ebp+0xC]           ;  edi = Y坐标
01020C6D  |.  8B04B8        mov eax,dword ptr ds:[eax+edi*4]        
;方块数据=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+4*X坐标]+0x0C]+4*Y坐标]
01020C70  |.  33C9          xor ecx,ecx
01020C72  |.  894D FC       mov dword ptr ss:[ebp-0x4],ecx
01020C75  |.  83F8 09       cmp eax,0x9                              ;  方块==9,该格子未开
01020C78  |.  74 1B         je short MineSwee.01020C95
01020C7A  |.  83F8 0B       cmp eax,0xB                              ;  方块==11,该格子问号?标志
01020C7D  |.  74 16         je short MineSwee.01020C95
01020C7F  |.  A1 B4680801   mov eax,dword ptr ds:[0x10868B4]
01020C84  |.  3848 18       cmp byte ptr ds:[eax+0x18],cl
01020C87  |.  74 5C         je short MineSwee.01020CE5
01020C89  |.  51            push ecx
01020C8A  |.  51            push ecx
01020C8B  |.  51            push ecx
01020C8C  |.  E8 58F90000   call MineSwee.010305E9
01020C91  |.  33C9          xor ecx,ecx
01020C93  |.  EB 50         jmp short MineSwee.01020CE5
01020C95  |>  394E 18       cmp dword ptr ds:[esi+0x18],ecx          ;  判断是不是鼠标第一次点击
01020C98  |.  75 20         jnz short MineSwee.01020CBA              ;  不是就跳走
01020C9A  |.  57            push edi
01020C9B  |.  53            push ebx
01020C9C  |.  8BCE          mov ecx,esi
01020C9E  |.  E8 18F4FFFF   call MineSwee.010200BB                   ;  第一次点击时 地雷产生和布雷
01020CA3  |.  6A 00         push 0x0
01020CA5  |.  57            push edi
01020CA6  |.  53            push ebx
01020CA7  |.  6A 00         push 0x0
01020CA9  |.  57            push edi                                 ;  压入Y
01020CAA  |.  53            push ebx                                 ;  压入X
01020CAB  |.  8BCE          mov ecx,esi
01020CAD  |.  E8 90FDFFFF   call MineSwee.01020A42
01020CB2  |.  895E 24       mov dword ptr ds:[esi+0x24],ebx
01020CB5  |.  897E 28       mov dword ptr ds:[esi+0x28],edi
01020CB8  |.  EB 23         jmp short MineSwee.01020CDD
01020CBA  |>  8B46 44       mov eax,dword ptr ds:[esi+0x44]          ;  this->某对象
01020CBD  |.  8B40 0C       mov eax,dword ptr ds:[eax+0xC]           ;  ->某对象
01020CC0  |.  8B0498        mov eax,dword ptr ds:[eax+ebx*4]         ;  ->某对象
01020CC3  |.  8B40 0C       mov eax,dword ptr ds:[eax+0xC]           ;  ->某对象
01020CC6  |.  380C07        cmp byte ptr ds:[edi+eax],cl
;雷地址=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x44]+0x0C]+4*X坐标]+0x0C]+Y坐标]

到此得到了地址0x10868b4,查看入口点代码

0102E08F > $  E8 B5050000   call MineSwee.0102E649
0102E094   .^ E9 4DFDFFFF   jmp MineSwee.0102DDE6

扫雷模块地址为0x1000000,所以
方块数据=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+4X坐标]+0x0C]+4Y坐标];分析一下取值有 1~8数字 9未开 10旗 11问号 12空
雷地址 =[[[[[[[minesweeper.exe+0x868b4]+0x10]+0x44]+0x0c]+4*X坐标]+0x0c]+Y坐标] 1表示有雷 0表示没雷
esi=[[minesweeper.exe+0x868B4]+0x10];minesweeper.exe表示扫雷模块地址
[esi+0x4] => 雷数
[esi+0x8] => 行数
[esi+0xc] => 列数
[esi+0x18] => 鼠标左键单击次数
第一次点击 列号:[esi+0x24]
行号:[esi+0x28]
然后找左键单击打开格子Call,既然是左键单击,该Call中的参数应该是点击格子的X坐标和Y坐标,所以在该Call之前应该有如下类似操作:

push X坐标
push Y坐标
Call 0x……

在我们之前的分析中寻找,发现有2个地方可疑
第一处:

01026FC7  |.  C680 C5000000>mov byte ptr ds:[eax+0xC5],0x1
01026FCE  |.  FF76 1C       push dword ptr ds:[esi+0x1C]             ;  Y坐标
01026FD1  |.  8BF9          mov edi,ecx
01026FD3  |.  FF76 18       push dword ptr ds:[esi+0x18]             ;  X坐标
01026FD6  |.  8B0D B4680801 mov ecx,dword ptr ds:[0x10868B4]         ;  this指针
01026FDC  |.  32DB          xor bl,bl
01026FDE  |.  E8 35A4FFFF   call MineSwee.01021418           ;判断踩下的方块是不是雷,参数有方块的X、Y坐标
01026FE3  |.  85C0          test eax,eax                     ;将返回到此处
01026FE5  |.  7F 0A         jg short MineSwee.01026FF1
01026FE7  |.  89B7 9C000000 mov dword ptr ds:[edi+0x9C],esi
01026FED  |.  FEC3          inc bl
01026FEF  |.  EB 08         jmp short MineSwee.01026FF9
01026FF1  |>  50            push eax
01026FF2  |.  8BCF          mov ecx,edi
01026FF4  |.  E8 D4FBFFFF   call MineSwee.01026BCD
第二处:
01020CA9  |.  57            push edi                                 ;  压入Y
01020CAA  |.  53            push ebx                                 ;  压入X
01020CAB  |.  8BCE          mov ecx,esi
01020CAD  |.  E8 90FDFFFF   call MineSwee.01020A42

对于第二处,有个跳转,不是第一次点击格子的话会跳过,因此排除。怀疑第一处是我们要找的Call,用CE加载扫雷,在自动汇编中写入测试脚本:

alloc(myscript,1024)
define(adr,minesweeper.exe+868b4)
define(fun1,minesweeper.exe+21418)
define(fun2,minesweeper.exe+26bcd)
label(s0)
label(end0)
myscript:
    push eax
    push ecx
    push ebx
    mov eax,[adr]
    mov [eax+0c5],1
    mov ecx,[adr]
    push 3
    push 3
    xor bl,bl
    call fun1
    test eax,eax
    jg s0
    jmp end0
s0:
push eax
call fun2
end0:
pop ebx
pop ecx
pop eax
ret

执行,得到myscript=某个地址,记住该地址,打开创建线程,输入该地址,确定,看扫雷打开了一大片,哈哈!这就是我们要找的左键单击打开一个格子的Call。现在便可以实现一键自动扫雷了,但是为了能够把雷标记出来,我们还可以找右键单击的Call。
右键单击格子会改变格子的状态,10为旗,11为问号,在第一颗方块数据处下硬件断点,即X=0 Y=0处,[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+40]+0x0C]+40],右键单击第一个格子,扫雷断下,进行回溯跟踪,在第二次返回后,找到了可疑的地方

01026BF7  /$  8BFF          mov edi,edi
01026BF9  |.  55            push ebp
01026BFA  |.  8BEC          mov ebp,esp
01026BFC  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
01026BFF  |.  56            push esi
01026C00  |.  FF70 1C       push dword ptr ds:[eax+0x1C]             ;  Y坐标,行数
01026C03  |.  8BF1          mov esi,ecx
01026C05  |.  FF70 18       push dword ptr ds:[eax+0x18]             ;  X坐标,列数
01026C08  |.  8B4E 24       mov ecx,dword ptr ds:[esi+0x24]          ;  找esi
01026C0B  |.  E8 9692FFFF   call MineSwee.0101FEA6                   ;  右键单击call
01026C10  |.  C686 AC000000>mov byte ptr ds:[esi+0xAC],0x1
01026C17  |.  5E            pop esi
01026C18  |.  5D            pop ebp
01026C19  \.  C2 0400       retn 0x4

和左键单击一样,也是压入Y坐标和X坐标为参数,然后调用一个Call,但是调用之前给ecx进行了赋值,来源于esi,又要找esi,但是在上层中寻找,并没有发现esi来源于什么地址。不如跟进该Call看看,

0101FEAC  |.  8BF1          mov esi,ecx
0101FEAE  |.  8B46 40       mov eax,dword ptr ds:[esi+0x40]
0101FEB1  |.  8B48 0C       mov ecx,dword ptr ds:[eax+0xC]
0101FEB4  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]           ;  X坐标
0101FEB7  |.  8B0C81        mov ecx,dword ptr ds:[ecx+eax*4]
0101FEBA  |.  8B51 0C       mov edx,dword ptr ds:[ecx+0xC]
0101FEBD  |.  8B4D 0C       mov ecx,dword ptr ss:[ebp+0xC]
0101FEC0  |.  8B148A        mov edx,dword ptr ds:[edx+ecx*4]

是不是很熟悉,原来ecx就是之前的那个指针,即[[minesweeper.exe+0x868b4]+0x10],在CE中写脚本进行测试:

alloc(myscript,1024)
define(adr,minesweeper.exe+868b4)
define(fun,minesweeper.exe+1FEA6)
myscript:
mov ecx,[[adr]+10]
push 0 ;Y
push 0 ;X
call fun
ret

执行后,发现扫雷并没有什么反应,有点失望!但是在扫雷中其他格子右键单击一下,发现第一个格子也变成被标记状态了,哈哈,这就是我们要找的右键单击格子Call,分析终于完成了!花了2周多时间,最后还是有点小激动,\(o)/
最后进行编程实现:

利用OD通过Call调用实现扫雷(win7旗舰版32位)_第3张图片

  界面如上,首先点击注入会获得共有几颗雷,并安装键盘钩子,按Home键进行一键扫雷,按F12进行全部标记。
利用OD通过Call调用实现扫雷(win7旗舰版32位)_第4张图片

最后,附一张扫雷自定义中难度最大时进行一键扫雷的截图,如下,24*30,共668颗雷。
利用OD通过Call调用实现扫雷(win7旗舰版32位)_第5张图片

主程序代码:

#include
#include "resource.h"
#include "..\GetProcessBase.h"

bool g_bGameTop = false;
typedef HHOOK(_stdcall *HookGameProc)();

BOOL CALLBACK MainProc(
    HWND hwndDlg,
    UINT UMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch (UMsg)
    {
    case WM_COMMAND:
        if (LOWORD(wParam) == IDC_INJECT)
        {
            DWORD dwESI, dwTotal, dwPid;
            HWND hWnd = FindWindow(NULL, "扫雷");
            if (!hWnd)
            {
                MessageBox(NULL, "扫雷未运行!", "提示Main", 0);
                return 0;
            }
            GetWindowThreadProcessId(hWnd, &dwPid); // 获取进程Id
            HMODULE hMine = GetProcessBase("MineSweeper.exe", dwPid);
            if (NULL == hMine)
            {
                MessageBox(NULL, "无法找到模块MineSweeper!", "提示Main", 0);
                return 0;
            }
            DWORD dwMineBaseAddr = (DWORD)hMine; // 模块句柄就是基址

            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
            if (!hProcess)
            {
                MessageBox(NULL, "打开扫雷进程失败!", "提示Main", 0);
                return 0;
            }
            DWORD dwRead;
            ReadProcessMemory(hProcess, (LPCVOID)(dwMineBaseAddr + 0x868b4), &dwESI, 4, &dwRead);
            ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x10), &dwESI, 4, &dwRead);
            ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x4), &dwTotal, 4, &dwRead);
            char cTmp[256] = { 0 };
            wsprintf(cTmp, "游戏中共发现 %d 颗雷", dwTotal);
            SetWindowText(hwndDlg, cTmp);
            CloseHandle(hProcess);
            HookGameProc HookProc = 0;
            HMODULE hDll = LoadLibrary("Win7MineCrackHook.dll");
            if (hDll)
            {
                HookProc = (HookGameProc)GetProcAddress(hDll, "HookMine");
                if (!HookProc)
                {
                    MessageBox(hwndDlg, "加载注入函数失败!", "Error", MB_OK);
                    return 0;
                }
            }
            else
            {
                MessageBox(hwndDlg, "加载Dll失败!", "Error", MB_OK);
                return 0;
            }
            HHOOK hkGame = HookProc();
            if(hkGame)
                SetDlgItemText(hwndDlg, IDC_EDIT, "安装外挂成功,按F12全部标记,按Home秒杀!");
            else
                SetDlgItemText(hwndDlg, IDC_EDIT, "安装外挂失败!");
        }
        else if (LOWORD(wParam) == IDC_SETTOP)
        {
            HWND hWnd = FindWindow(NULL, "扫雷");
            if (!hWnd)
            {
                MessageBox(NULL, "扫雷未运行!", "提示Main", 0);
                return 0;
            }
            if (!g_bGameTop)
            {
                SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,  SWP_NOSIZE | SWP_SHOWWINDOW);
                SetDlgItemText(hwndDlg, IDC_EDIT, "设置游戏置顶成功!");
                SetDlgItemText(hwndDlg, IDC_SETTOP, "取消游戏窗口置顶");
                g_bGameTop = true;
            }
            else
            {
                SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,  SWP_NOSIZE | SWP_SHOWWINDOW);
                SetDlgItemText(hwndDlg, IDC_EDIT, "取消游戏置顶成功!");
                SetDlgItemText(hwndDlg, IDC_SETTOP, "设置游戏窗口置顶");
                g_bGameTop = false;
            }
        }
        break;
    case WM_CLOSE:
        EndDialog(hwndDlg, 0);
        break;
    }
    return 0;
}

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
)
{
    DialogBox(hInstance, (LPCSTR)IDD_MAIN_DLG, NULL, MainProc);
}

GetProcessBase.h代码:

#pragma once
#include 

HMODULE GetProcessBase(char* szModule, DWORD dwPid) // 根据进程id获取进程基址
{
    HANDLE hSnapShot;
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
    if (hSnapShot == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, "无法创建快照", "提示Main", 0);
        return 0;
    }
    MODULEENTRY32 me32;
    me32.dwSize = sizeof(me32);
    if (Module32First(hSnapShot, &me32))
    {
        do
        {
            if (lstrcmpi(me32.szModule, szModule) == 0)
                return me32.hModule;
        } while (Module32Next(hSnapShot, &me32));
    }
    CloseHandle(hSnapShot);
    return 0;
}

该函数是获得扫雷模块地址的函数,相比《Vista的扫雷》中寻找程序入口点,该函数效率更高一些。
DLL钩子程序如下:

#include
#include "..\GetProcessBase.h"

HHOOK g_hHookMine = 0;

extern "C" LRESULT CALLBACK WinMineCrackProc(
    int code,
    WPARAM wParam,
    LPARAM lParam
)
{
    if ((wParam == VK_HOME) && ((lParam&(1 << 31)) == 0) && HC_ACTION == code) // 按Home键秒杀
    {
        DWORD dwPid = GetCurrentProcessId();
        HMODULE hMine = GetModuleHandle(0);
        if (NULL == hMine)
        {
            MessageBox(NULL, "无法找到模块MineSweeper!", "提示Dll", 0);
            return 0;
        }
        DWORD dwMineBaseAddr = (DWORD)hMine; // 模块句柄就是基址
        HANDLE hProcess = GetCurrentProcess();
        if (!hProcess)
        {
            MessageBox(NULL, "获取扫雷进程句柄失败!", "提示Dll", 0);
            return 0;
        }
        DWORD vaCallLeft1 = dwMineBaseAddr + 0x26bcd;
        DWORD vaCallLeft0 = dwMineBaseAddr + 0x21418;
        DWORD* pBase = (DWORD*)(dwMineBaseAddr + 0x868b4);
        _asm
        {
            push eax
            push ecx
            push ebx

            mov eax,dwMineBaseAddr
            mov eax,[eax+0x868b4]
            mov [eax+0xc5],1
            mov ecx,dwMineBaseAddr
            mov ecx,[ecx+0x868b4]
            push 0
            push 0
            xor bl,bl
            mov eax,vaCallLeft0
            call eax
            test eax,eax
            jg sHome
            jmp endHome
            sHome:      
            push eax
            mov eax,vaCallLeft1
            call eax
            endHome:    
            pop ebx
            pop ecx
            pop eax
        }
        DWORD* pESI = (DWORD*)(*pBase + 0x10);
        DWORD* pRow = (DWORD*)(*pESI + 0x8);
        DWORD* pColumn = (DWORD*)(*pESI + 0xc);
        DWORD* pAddr = (DWORD*)(*pESI + 0x44);
        pAddr = (DWORD*)(*pAddr + 0xc);
        BYTE bMine[30][24] = { 0 };
        DWORD* pMine = NULL;
        BYTE* pIsMine = NULL;
        unsigned int i = 0, j = 0; // i行 j列
        while (j < *pColumn)
        {
            pMine = (DWORD*)(*pAddr + 4 * j);
            pMine = (DWORD*)(*pMine + 0xc);
            i = 0;
            while (i < *pRow)
            {
                pIsMine = (BYTE*)(*pMine + i);
                bMine[j][i] = *pIsMine;
                ++i;
            }
            ++j;
        }
        i = 0;
        j = 0;
        while (j < *pColumn)
        {
            i = 0;
            while (i < *pRow)
            {
                if (0x00 == bMine[j][i])
                {
                    __asm
                    {
                        push eax
                        push ecx
                        push ebx

                        mov eax,dwMineBaseAddr
                        mov eax,[eax + 0x868b4]
                        mov [eax + 0xc5], 1
                        mov ecx,dwMineBaseAddr
                        mov ecx,[ecx + 0x868b4]
                        mov eax,i
                        push eax
                        mov eax,j
                        push eax
                        xor bl, bl
                        mov eax, vaCallLeft0
                        call eax
                        test eax,eax
                        jg s
                        jmp end
                        s:
                        push eax
                        mov eax, vaCallLeft1
                        call eax
                        end:
                        pop ebx
                        pop ecx
                        pop eax
                    }
                }
                ++i;
            }
            ++j;
        }
    }

如上注入后可直接操作内存,下面部分代码还是利用API读写内存,不建议这么用!

    if ((wParam == VK_F12) && ((lParam&(1 << 31)) == 0) && HC_ACTION == code) // - F12键全部标记
    {
        DWORD dwPid;
        HWND hWnd = FindWindow(NULL, "扫雷");
        if (!hWnd)
        {
            MessageBox(NULL, "扫雷未运行!", "提示Main", 0);
            return 0;
        }
        GetWindowThreadProcessId(hWnd, &dwPid); // 获取进程Id
        HMODULE hMine = GetProcessBase("MineSweeper.exe", dwPid);
        if (NULL == hMine)
        {
            MessageBox(NULL, "无法找到模块MineSweeper!", "提示Main", 0);
            return 0;
        }
        DWORD dwMineBaseAddr = (DWORD)hMine; // 模块句柄就是基址
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
        if (!hProcess)
        {
            MessageBox(NULL, "打开扫雷进程失败!", "提示Main", 0);
            return 0;
        }
        DWORD vaCallLeft1 = dwMineBaseAddr + 0x26bcd;
        DWORD vaCallLeft0 = dwMineBaseAddr + 0x21418;
        DWORD vaCallRight = dwMineBaseAddr + 0x1fea6;
        __asm
        {
            push eax
            push ecx
            push ebx

            mov eax, dwMineBaseAddr
            mov eax, [eax + 0x868b4]
            mov[eax + 0xc5], 1
            mov ecx, dwMineBaseAddr
            mov ecx, [ecx + 0x868b4]
            push 0
            push 0
            xor bl, bl
            mov eax, vaCallLeft0
            call eax
            test eax, eax
            jg sF12
            jmp endF12
            sF12:
            push eax
            mov eax, vaCallLeft1
            call eax
            endF12:
            pop ebx
            pop ecx
            pop eax
        }
        DWORD dwESI, dwRow, dwColumn, dwRead, dwAddr, dwMine;

        ReadProcessMemory(hProcess, (LPCVOID)(dwMineBaseAddr + 0x868b4), &dwESI, 4, &dwRead);
        ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x10), &dwESI, 4, &dwRead);
        ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x8), &dwRow, 4, &dwRead); // 行数
        ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0xc), &dwColumn, 4, &dwRead); // 列数
        ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x44), &dwAddr, 4, &dwRead);
        ReadProcessMemory(hProcess, (LPCVOID)(dwAddr + 0xc), &dwAddr, 4, &dwRead);
        unsigned int i = 0, j = 0; // i行 j列
        BYTE isMine;
        BYTE bMine[30][24] = { 0 };
        while (j < dwColumn)
        {
            ReadProcessMemory(hProcess, (LPCVOID)(dwAddr + 4 * j), &dwMine, 4, &dwRead);
            ReadProcessMemory(hProcess, (LPCVOID)(dwMine + 0xc), &dwMine, 4, &dwRead);
            i = 0;
            while (i < dwRow)
            {
                ReadProcessMemory(hProcess, (LPCVOID)(dwMine + i), &isMine, 1, &dwRead);
                bMine[j][i] = isMine;
                ++i;
            }
            ++j;
        }
        CloseHandle(hProcess);

        i = 0;
        j = 0;
        while (j < dwColumn)
        {
            i = 0;
            while (i < dwRow)
            {
                if (0x01 == bMine[j][i])
                {
                    __asm
                    {
                        push eax
                        mov eax, dwMineBaseAddr
                        mov eax, [eax + 0x868b4]
                        mov ecx, [eax + 0x10]
                        mov eax, i
                        push eax
                        mov eax, j
                        push eax
                        mov eax, vaCallRight
                        call eax
                        pop eax
                    }
                }
                ++i;
            }
            ++j;
        }
    }
    return CallNextHookEx(g_hHookMine, code, wParam, lParam);
    //return 0;
}

extern "C" _declspec(dllexport) HHOOK HookMine()
{
    HWND hWnd = FindWindow(NULL, "扫雷");
    if (NULL == hWnd)
    {
        MessageBox(NULL, "游戏未运行!", "提示", 0);
        return 0;
    }
    DWORD tid;
    tid = GetWindowThreadProcessId(hWnd, NULL);
    
    g_hHookMine = SetWindowsHookEx(WH_KEYBOARD, WinMineCrackProc, GetModuleHandle("Win7MineCrackHook.dll"), tid);
    return g_hHookMine;
}

你可能感兴趣的:(利用OD通过Call调用实现扫雷(win7旗舰版32位))