欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

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

[原创]利用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

2篇文章里的思路都是通过对rand函数下断,分析找到雷存放的地址,每个格子里存放01,如果是1则是雷,是0则不是雷,发送消息模拟鼠标的单机,进行自动扫雷。其中,在《Vista的扫雷》提到了重定位的处理,解决方案2对于Win7 32位下的扫雷并不适用,这篇文章中提到:

“假设扫雷入口地址设为dwCodeAddress,则[3931B8] 这个地址得到的方法是: ( dwCodeAddress & 0xFFFF0000 ) + 0x531B8

但是在Win7 32位的扫雷中,并不是这样的,用PEiD打开扫雷

可以看到,扫雷的入口点是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打开扫雷,找到该字段

该字段为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

对这8call都进行下断,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+ecw-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+ecw-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]+4*X坐标]+0x0C]+4*Y坐标];分析一下取值有 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(LeftClick,1024)

define(adr,minesweeper.exe+868b4)

define(fun1,minesweeper.exe+21418)

define(fun2,minesweeper.exe+26bcd)

label(s0)

label(end0)

LeftClick:

     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]+4*0]+0x0C]+4*0],右键单击第一个格子,扫雷断下,进行回溯跟踪,在第二次返回后,找到了可疑的地方

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(RightClick,1024)

define(adr,minesweeper.exe+868b4)

define(fun,minesweeper.exe+1FEA6)

RightClick:

mov ecx,[[adr]+10]

push 0 ;Y

push 0 ;X

call fun

ret

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

最后进行编程实现:


界面如上,首先点击注入会获得共有几颗雷,并安装键盘钩子,按Home键进行一键扫雷,按F12进行全部标记。

  最后,附一张扫雷自定义中难度最大时进行一键扫雷的截图,如下,24*30,共668颗雷。


主程序代码:

#include<Windows.h>

#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 <tlhelp32.h>

 

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<Windows.h>

#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;

        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;

        __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 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 (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;

        }

    }

    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;

}

有不足之处,希望大家提出,欢迎讨论!

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功