查看原文
其他

小白的第一次逆向之扫雷分析与辅助制作

2017-12-09 八岛 看雪学院


思路



1、想要实现的功能


  • 鼠标悬停,自动提示是否有雷

  • 一键自动扫雷


2、如何实现这些功能


Q:首先要了解如果想要实现想要的功能,需要进行什么操作?


A:实现鼠标移动 ,自动识别有没有雷


  • 需要hook掉回调函数,响应WM_MOUSEMOVE消息


Q:怎么判断鼠标所在的位置是不是有雷?是不是需要把屏幕坐标转换成扫雷区域的数组坐标?


A:找到雷的分辨类型;找到雷的数量;找到扫雷区域的大小


Q:如何实现一键扫雷


A:遍历扫雷的数组,对所有不是雷的区域;发WM_LBUTTONDOWN的消息,这样应该实现了扫雷的功能。同时,是不是还需要对数组坐标进行向屏幕坐标的转换



具体实现步骤



有了思路之后,进行具体的实现


找数据


找界面的区域大小和雷的数量的数据应该是最好找的

 

查看是否有重定位(如果有就关掉),上CE


  • 首先先找高度的基址
    通过几次尝试之后,发现

  • 那么这两地址应该就是高度的基址了,具体为什么有2个先不管,放到窗口中先放一放

  • 然后找宽度的基址
    同样的经过几次尝试之后,发现


这两个地址就该就是跟宽度相关的地址

  • 找雷数的基址
    步骤一样

  • 但是这里发现了3条数据,上面都是2条,有点不符合规律,所有就测试一下,到底这3个地址是不是全部都跟地雷数量有关(暂且按下不表,具体中od去看到底哪个才是)
    根据地址的连续性,稍微整理了一下


  • 找初始化地图数据的地址
    找到了地雷和区域大小的基址,那么只要找到更改这个对区域进行更改的函数,猜想应该可以找到布雷的方法和各种雷等数据的判断类型
    关键是如何找到这个函数,首先要想明白的一件事是,游戏对初始化的时候,肯定是会对数据进行调用的,那么是不是可以通过游戏初始化的时候,访问或者更改这里面的代码从而找到调用的地址,试一下把

通过故意还原游戏,从而找到了一访问某个数据的地址,或得这个地址的 数据为
01003712
这个时候,上od去看一下,是不是跟我们猜想的一样

01002ED5  /$  B8 60030000   MOV EAX,0x360                                    ;  eax = 0x360 ,不知道要干嘛,接着看

01002EDA  |>  48            /DEC EAX                                         ;  eax -=1

01002EDB  |.  C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0xF              ;  通过流程分析可以知道,0xf是空地的类型值,说明在初始化空地

01002EE2  |.^ 75 F6         \JNZ SHORT winmine.01002EDA                      ;  说明数组应该是0x360 这么大

01002EE4  |.  8B0D 34530001 MOV ECX,DWORD PTR DS:[<宽度0>]                     ;  ecx =宽度的数值,x轴

01002EEA  |.  8B15 38530001 MOV EDX,DWORD PTR DS:[<高度0>]                     ;  edx= 高度的数值,y轴

01002EF0  |.  8D41 02       LEA EAX,DWORD PTR DS:[ECX+0x2]                   ;  额,没看懂,把宽度+2 保存到eax中,eax == 宽度+2

01002EF3  |.  85C0          TEST EAX,EAX                                     ;  判断eax是不是为空

01002EF5  |.  56            PUSH ESI                                         ;  根据函数最后有pop esi,应该是保存环境

01002EF6  |.  74 19         JE SHORT winmine.01002F11                        ;  如果eax=0,跳转

01002EF8  |.  8BF2          MOV ESI,EDX                                      ;  esi  =高度的数值,y轴

01002EFA  |.  C1E6 05       SHL ESI,0x5                                      ;  esi = 代表y轴的高度*32

01002EFD  |.  8DB6 60530001 LEA ESI,DWORD PTR DS:[ESI+0x1005360]             ;  esi = [数组基址+0x20 +esi(高度*32)],应该是这个高度下的数组的最下一行

01002F03  |>  48            /DEC EAX                                         ;  eax = 宽度+2,说明左右的边界各占1,eax-=1

01002F04  |.  C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0x10             ;  10应该就是边界的类型了,这个应该是填充数组第一行的边界,从右往左

01002F0B  |.  C60406 10     |MOV BYTE PTR DS:[ESI+EAX],0x10                  ;  这个高度下区域最下面的边界,也是从右往左填充的

01002F0F  |.^ 75 F2         \JNZ SHORT winmine.01002F03                      ;  eax!=0 循环填充边界,也就是说填充的时候是 数组基址+0x20+高度*32+具体的列数

01002F11  |>  8D72 02       LEA ESI,DWORD PTR DS:[EDX+0x2]                   ;  同样的,esi 保存的是高度的数值+2,从而说明上下边界高度是1,而且不占游戏界面的区域

01002F14  |.  85F6          TEST ESI,ESI                                     ;  判断esi 是不是为空

01002F16  |.  74 21         JE SHORT winmine.01002F39                        ;  为空直接跳出了

01002F18  |.  8BC6          MOV EAX,ESI                                      ;  eax =高度的值+0x2

01002F1A  |.  C1E0 05       SHL EAX,0x5                                      ;  eax =(高度的值+0x2)*32

01002F1D  |.  8D90 40530001 LEA EDX,DWORD PTR DS:[EAX+<雷区界面基址>]              ;  这两行应该也是要填充边界的为止,具体的就不看了

01002F23  |.  8D8408 415300>LEA EAX,DWORD PTR DS:[EAX+ECX+0x1005341]         ;  edx 和eax应该是最下一行,最左边和最右边的值

01002F2A  |>  83EA 20       /SUB EDX,0x20                                    ;  0x20 应该是2行

01002F2D  |.  83E8 20       |SUB EAX,0x20

01002F30  |.  4E            |DEC ESI                                         ;  esi -=1

01002F31  |.  C602 10       |MOV BYTE PTR DS:[EDX],0x10                      ;  进行边界填充

01002F34  |.  C600 10       |MOV BYTE PTR DS:[EAX],0x10                      ;  边界填充

01002F37  |.^ 75 F1         \JNZ SHORT winmine.01002F2A

01002F39  |>  5E            POP ESI

01002F3A  \.  C3            RETN


通过od可以看到上面的代码,验证是不是真的,下个断点还原下场景,看看是不是回断下来


结果是断下来,说明这个地方很可能就是对界面进行初始化的函数,先来大致的分析一下这个函数到底做了什么

 

第一遍大体流程分析


0100367A  /$  A1 AC560001   MOV EAX,DWORD PTR DS:[<宽度1>]                       ;  eax == 宽度,暂且规定宽度是x轴坐标

0100367F  |.  8B0D A8560001 MOV ECX,DWORD PTR DS:[<高度1>]

01003685  |.  53            PUSH EBX

01003686  |.  56            PUSH ESI

01003687  |.  57            PUSH EDI                                           ;  保存寄存器环境

01003688  |.  33FF          XOR EDI,EDI                                        ;  edi == 0

0100368A  |.  3B05 34530001 CMP EAX,DWORD PTR DS:[<宽度0>]                       ;  宽度1 和宽度0 的数据进行对比

01003690  |.  893D 64510001 MOV DWORD PTR DS:[0x1005164],EDI                   ;  把0 移动到这个地址所在的空间内

01003696  |.  75 0C         JNZ SHORT winmine.010036A4                         ;  宽度0 和宽度1 不想等,跳转

01003698  |.  3B0D 38530001 CMP ECX,DWORD PTR DS:[<高度0>]                       ;  高度1和高度0进行对比

0100369E  |.  75 04         JNZ SHORT winmine.010036A4                         ;  高度0和高度1进行对比,不想等跳转,现在我有点怀疑这两组数据应该是用户设置的数据和实际填充的数据进行对比,下面可能是对数据的填充

010036A0  |.  6A 04         PUSH 0x4

010036A2  |.  EB 02         JMP SHORT winmine.010036A6

010036A4  |>  6A 06         PUSH 0x6                                           ;  push 04和push 06很有可能判断用户是不是进行了跟更改界面大小的设置,现在的ebx可能就是对这个设置进行保存

010036A6  |>  5B            POP EBX                                            ;  猜测是保存是否更改界面的类型的值,ebx ==06 或0x4

010036A7  |.  A3 34530001   MOV DWORD PTR DS:[<宽度0>],EAX                       ;  eax == 宽度1 ,把宽度1 赋值给宽度0,说明宽度1才是真正的宽度的基址

010036AC  |.  890D 38530001 MOV DWORD PTR DS:[<高度0>],ECX                       ;  ecx  == 高度0,把高度0赋值给高度1,说明高度0才是真正的高度的基址

010036B2  |.  E8 1EF8FFFF   CALL winmine.01002ED5                              ;  call 的这个函数目测是一个循环,先不搞,把整个大体流程分析完之后再搞

010036B7  |.  A1 A4560001   MOV EAX,DWORD PTR DS:[<地雷1>]                       ;  eax == 地雷1的地址

010036BC  |.  893D 60510001 MOV DWORD PTR DS:[0x1005160],EDI                   ;  edi=0,把0 移动到这个地址,暂时不明白是干嘛的

010036C2  |.  A3 30530001   MOV DWORD PTR DS:[<地雷0>],EAX                       ;  eax == 地雷1的地址,把地雷1中的值赋值给地雷0,说明地雷1才是地雷的真正的基址

010036C7  |>  FF35 34530001 PUSH DWORD PTR DS:[<宽度0>]                          ;  把宽度0(x轴的坐标)入栈

010036CD  |.  E8 6E020000   CALL <winmine.产生随机数>                               ;  ctrl+a 发现这个是rand函数,然后根据入栈的数据就可以说明这个是根据某个值产生随机的值,这个函数可以不用看了

010036D2  |.  FF35 38530001 PUSH DWORD PTR DS:[<高度0>]                          ;  高度0入栈

010036D8  |.  8BF0          MOV ESI,EAX                                        ;  esi = eax应该是函数返回值

010036DA  |.  46            INC ESI                                            ;  esi +1 ,根据宽度产生的随机数+1,esi保存根据宽度产生的随机值

010036DB  |.  E8 60020000   CALL <winmine.产生随机数>

010036E0  |.  40            INC EAX                                            ;  根据高度产生的随机值+1

010036E1  |.  8BC8          MOV ECX,EAX                                        ;  ecx = 根据高度产生的随机值

010036E3  |.  C1E1 05       SHL ECX,0x5                                        ;  ecx  =ecx*32,根据高度产生的随机值左移5位

010036E6  |.  F68431 405300>TEST BYTE PTR DS:[ECX+ESI+<雷区界面基址>],0x80           ;  ecx是随机数,esi也是随机数,+一个地址应该是去锁定其中的数组为止,说明这个值就是数组基址

010036EE  |.^ 75 D7         JNZ SHORT winmine.010036C7                         ;  跟80进行比较,可能说明80就是非雷的区域,如果不是非雷的区域,要么是边界要么是雷,所以需要跳转重新计算

010036F0  |.  C1E0 05       SHL EAX,0x5                                        ;  eax左移5位就是ecx中的值

010036F3  |.  8D8430 405300>LEA EAX,DWORD PTR DS:[EAX+ESI+<雷区界面基址>]            ;  把区域具体的数组的地址放到eax中

010036FA  |.  8008 80       OR BYTE PTR DS:[EAX],0x80                          ;  把eax中第一个字节火上80,哎呦可能是设置雷了,说明80是并不是空地,运行起来发现0f是空地,8f是地雷

010036FD  |.  FF0D 30530001 DEC DWORD PTR DS:[<地雷0>]                           ;  设置好雷之后对设置的地雷数量进行-1

01003703  |.^ 75 C2         JNZ SHORT winmine.010036C7                         ;  判断地雷0中保存的值是否为0,不是的化重复设置地雷

01003705  |.  8B0D 38530001 MOV ECX,DWORD PTR DS:[<高度0>]                       ;  ecx = 高度0的值

0100370B  |.  0FAF0D 345300>IMUL ECX,DWORD PTR DS:[<宽度0>]                      ;  ecx = 用户设置的高度的值* 用户设置的宽度的值

01003712  |.  A1 A4560001   MOV EAX,DWORD PTR DS:[<地雷1>]                       ;  eax = 地雷的值

01003717  |.  2BC8          SUB ECX,EAX                                        ;  ecx = 高度*宽度 -地雷数,这是干啥的?

01003719  |.  57            PUSH EDI                                           ;  根据后面有pop edi,应该是保存环境

0100371A  |.  893D 9C570001 MOV DWORD PTR DS:[0x100579C],EDI

01003720  |.  A3 30530001   MOV DWORD PTR DS:[<地雷0>],EAX

01003725  |.  A3 94510001   MOV DWORD PTR DS:[0x1005194],EAX

0100372A  |.  893D A4570001 MOV DWORD PTR DS:[0x10057A4],EDI

01003730  |.  890D A0570001 MOV DWORD PTR DS:[0x10057A0],ECX

01003736  |.  C705 00500001>MOV DWORD PTR DS:[0x1005000],0x1

01003740  |.  E8 25FDFFFF   CALL winmine.0100346A

01003745  |.  53            PUSH EBX

01003746  |.  E8 05E2FFFF   CALL winmine.01001950

0100374B  |.  5F            POP EDI

0100374C  |.  5E            POP ESI

0100374D  |.  5B            POP EBX

0100374E  \.  C3            RETN


接下来对几个关键函数进行分析,比如产生随机雷之前有个有很多循环的函数,估计事初始化界面数组的函数,进去分析一下


01002ED5  /$  B8 60030000   MOV EAX,0x360                                      ;  eax = 0x360 ,不知道要干嘛,接着看

01002EDA  |>  48            /DEC EAX                                           ;  eax -=1

01002EDB  |.  C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0xF                ;  通过流程分析可以知道,0xf是空地的类型值,说明在初始化空地

01002EE2  |.^ 75 F6         \JNZ SHORT winmine.01002EDA                        ;  说明数组应该是0x360 这么大

01002EE4  |.  8B0D 34530001 MOV ECX,DWORD PTR DS:[<宽度0>]                       ;  ecx =宽度的数值,x轴

01002EEA  |.  8B15 38530001 MOV EDX,DWORD PTR DS:[<高度0>]                       ;  edx= 高度的数值,y轴

01002EF0  |.  8D41 02       LEA EAX,DWORD PTR DS:[ECX+0x2]                     ;  额,没看懂,把宽度+2 保存到eax中,eax == 宽度+2

01002EF3  |.  85C0          TEST EAX,EAX                                       ;  判断eax是不是为空

01002EF5  |.  56            PUSH ESI                                           ;  根据函数最后有pop esi,应该是保存环境

01002EF6  |.  74 19         JE SHORT winmine.01002F11                          ;  如果eax=0,跳转

01002EF8  |.  8BF2          MOV ESI,EDX                                        ;  esi  =高度的数值,y轴

01002EFA  |.  C1E6 05       SHL ESI,0x5                                        ;  esi = 代表y轴的高度*32

01002EFD  |.  8DB6 60530001 LEA ESI,DWORD PTR DS:[ESI+0x1005360]               ;  esi = [数组基址+0x20 +esi(高度*32)],应该是这个高度下的数组的最下一行

01002F03  |>  48            /DEC EAX                                           ;  eax = 宽度+2,说明左右的边界各占1,eax-=1

01002F04  |.  C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0x10               ;  10应该就是边界的类型了,这个应该是填充数组第一行的边界,从右往左

01002F0B  |.  C60406 10     |MOV BYTE PTR DS:[ESI+EAX],0x10                    ;  这个高度下区域最下面的边界,也是从右往左填充的

01002F0F  |.^ 75 F2         \JNZ SHORT winmine.01002F03                        ;  eax!=0 循环填充边界

01002F11  |>  8D72 02       LEA ESI,DWORD PTR DS:[EDX+0x2]                     ;  同样的,esi 保存的是高度的数值+2,从而说明上下边界高度是1,而且不占游戏界面的区域

01002F14  |.  85F6          TEST ESI,ESI                                       ;  判断esi 是不是为空

01002F16  |.  74 21         JE SHORT winmine.01002F39                          ;  为空直接跳出了

01002F18  |.  8BC6          MOV EAX,ESI                                        ;  eax =高度的值+0x2

01002F1A  |.  C1E0 05       SHL EAX,0x5                                        ;  eax =(高度的值+0x2)*32

01002F1D  |.  8D90 40530001 LEA EDX,DWORD PTR DS:[EAX+<雷区界面基址>]                ;  这两行应该也是要填充边界的为止,具体的就不看了

01002F23  |.  8D8408 415300>LEA EAX,DWORD PTR DS:[EAX+ECX+0x1005341]           ;  edx 和eax应该是最下一行,最左边和最右边的值

01002F2A  |>  83EA 20       /SUB EDX,0x20                                      ;  0x20 应该是2行

01002F2D  |.  83E8 20       |SUB EAX,0x20

01002F30  |.  4E            |DEC ESI                                           ;  esi -=1

01002F31  |.  C602 10       |MOV BYTE PTR DS:[EDX],0x10                        ;  进行边界填充

01002F34  |.  C600 10       |MOV BYTE PTR DS:[EAX],0x10                        ;  边界填充

01002F37  |.^ 75 F1         \JNZ SHORT winmine.01002F2A

01002F39  |>  5E            POP ESI

01002F3A  \.  C3            RETN


通过分析可以看出,这个函数只是对空地和边界进行填充

 

通过上述的分析,大体知道了初始化雷区的的公式

 

划重点:数组基址+0x20 +esi(高度*32),如果这个位置的opcode是0x8f,说明是雷,0xf说明是空地

 

然后去看一下内存图


01005340 <>10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 

01005350   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005360   10 8F 8F 8F 0F 0F 0F 0F 8F 0F 0F 8F 0F 0F 8F 10  弿????

01005370   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005380   10 8F 8F 0F 0F 0F 8F 0F 0F 0F 0F 8F 8F 0F 0F 10  弿?弿

01005390   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

010053A0   10 0F 8F 0F 0F 0F 8F 8F 0F 8F 0F 0F 8F 8F 8F 10  ?弿?弿?

010053B0   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

010053C0   10 0F 8F 8F 0F 0F 8F 8F 0F 0F 8F 0F 8F 0F 0F 10  弿弿??

010053D0   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

010053E0   10 0F 0F 0F 0F 8F 8F 0F 0F 8F 0F 8F 0F 8F 8F 10  弿??弿

010053F0   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005400   10 8F 8F 8F 0F 0F 8F 8F 8F 8F 8F 8F 0F 8F 0F 10  弿?弿弿弿?

01005410   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005420   10 8F 0F 0F 8F 8F 0F 8F 0F 0F 8F 8F 8F 0F 0F 10  ?弿?弿?

01005430   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005440   10 0F 8F 0F 0F 0F 0F 0F 0F 8F 0F 0F 0F 8F 8F 10  ??弿

01005450   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005460   10 8F 8F 8F 8F 0F 8F 0F 8F 0F 8F 0F 8F 0F 8F 10  弿弿?????

01005470   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005480   10 8F 8F 8F 0F 0F 0F 8F 0F 0F 8F 0F 0F 8F 8F 10  弿???弿

01005490   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

010054A0   10 0F 8F 8F 0F 0F 0F 0F 0F 8F 0F 0F 8F 0F 8F 10  弿???

010054B0   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

010054C0   10 8F 8F 0F 0F 8F 8F 8F 0F 8F 8F 8F 8F 0F 8F 10  弿弿?弿弿?

010054D0   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

010054E0   10 8F 8F 0F 8F 0F 0F 8F 8F 8F 0F 8F 0F 8F 8F 10  弿?弿??弿

010054F0   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005500   10 8F 8F 8F 0F 8F 0F 0F 0F 8F 8F 0F 8F 8F 0F 10  弿??弿弿

01005510   0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 

01005520   10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 


从内存图可以看出,是每隔一行进行填充 ,因此可以总结数组的遍历公式

 

具体的某个x,y坐标下的值= 数组基址+ 高<<5(也就是+32,每隔一行进行填充)+x

 

而且通过填充的时候add eax ,2 等类似的消息可以看出,边界应该是0,内容是从1开始的,循环的时候要注意

 

现在去分析一下窗口回调函数(主要是找怎么把鼠标按下的点转换成数组下标的方法)

 

祭出 大微软的 spy ++

 

od直接定位到这个回调函数的位置

 

主要是想看看怎么把鼠标点击的屏幕坐标转换成数组下标的

 

定位之后发现 代码真长,可能超过300行了QAQ

01001BC9  /.  55            PUSH EBP                                         ;  回调函数

01001BCA  |.  8BEC          MOV EBP,ESP

01001BCC  |.  83EC 40       SUB ESP,0x40                                     ;  开辟局部空间

01001BCF  |.  8B55 0C       MOV EDX,[ARG.2]                                  ;  edx  =uMsg

01001BD2  |.  8B4D 14       MOV ECX,[ARG.4]                                  ;  edx = lParam

01001BD5  |.  53            PUSH EBX                                         ;  保存寄存器环境

01001BD6  |.  56            PUSH ESI                                         ;  保存寄存器环境

01001BD7  |.  33DB          XOR EBX,EBX                                      ;  ebx = 0

01001BD9  |.  57            PUSH EDI                                         ;  保存寄存器环境

01001BDA  |.  BE 00020000   MOV ESI,0x200                                    ;  esi =0x200,通过下面确定esi是wm_mousemove

01001BDF  |.  43            INC EBX                                          ;  ebx =1

01001BE0  |.  33FF          XOR EDI,EDI                                      ;  edi =0

01001BE2  |.  3BD6          CMP EDX,ESI                                      ;  edx和esi进行比较,也就是uMsg和0x200进行比较,现在可以确定esi是WM_MOUSEMOVE

01001BE4  |.  0F87 75030000 JA winmine.01001F5F                              ;  大于跳到这个地方去,需要看LBUTTONDOWN或LBUTTONUP的处理

01001BEA  |.  0F84 95040000 JE winmine.01002085                              ;  等于mousemove跳转到这个地方,小于的话就继续下面的代码,因为LBUTTONDOWN和LBUTTONUP都大于0x200,暂时先不看下面的了

01001BF0  |.  B8 00010000   MOV EAX,0x100

01001BF5  |.  3BD0          CMP EDX,EAX

01001BF7  |.  0F87 5C010000 JA winmine.01001D59

01001BFD  |.  0F84 97000000 JE winmine.01001C9A

01001C03  |.  8BC2          MOV EAX,EDX

01001C05  |.  48            DEC EAX                                          ;  Switch (cases 2..47)

01001C06  |.  48            DEC EAX

01001C07  |.  74 76         JE SHORT winmine.01001C7F

01001C09  |.  83E8 04       SUB EAX,0x4

01001C0C  |.  74 57         JE SHORT winmine.01001C65

01001C0E  |.  83E8 09       SUB EAX,0x9

01001C11  |.  74 2B         JE SHORT winmine.01001C3E

01001C13  |.  83E8 38       SUB EAX,0x38

01001C16  |.  0F85 8D050000 JNZ winmine.010021A9

01001C1C  |.  F605 00500001>TEST BYTE PTR DS:[0x1005000],0x8                 ;  Case 47 of switch 01001C05

01001C23  |.  0F85 80050000 JNZ winmine.010021A9

....

代码省略


只贴上回调函数的图了,其实还分析了很多的函数来掌握流程

 

通过

01002099  |.  8B45 14       MOV EAX,[ARG.4]                                  ;  从这里的代码开始,下面的就是坐标转成数组的公式了eax = lparam

0100209C  |.  C1E8 10       SHR EAX,0x10                                     ;  eax 右移16位,相当于eax  =(HIWORD)lparam

0100209F  |.  83E8 27       SUB EAX,0x27                                     ;  eax(HIWORD的lParam)-0x27

010020A2  |.  C1F8 04       SAR EAX,0x4                                      ;  eax算数右移4位

010020A5  |.  50            PUSH EAX                                         ;  eax 入栈

010020A6  |.  0FB745 14     MOVZX EAX,WORD PTR SS:[EBP+0x14]                 ;  入栈的第四个参数,就是lparam,eax = (LOWORD)lparam

010020AA  |.  83C0 04       ADD EAX,0x4                                      ;  (LOWROD)lparam 加0x4

010020AD  |.  C1F8 04       SAR EAX,0x4                                      ;  然后右移4为

010020B0  |.  50            PUSH EAX                                         ;  找到,这个地方就是转换为数组的下标了


这几行的汇编代码可以看出,再LBUTTONDOWN消息中,通过lParam的高低分分别获得y,x轴坐标

然后通过公式转换为具体的数组下标,转换方式如下

   //首先获取x和y轴的坐标        WORD xPos = GET_X_LPARAM(lParam);        WORD yPos = GET_Y_LPARAM(lParam);        //然后进行转换        //y轴转换        yPos -= 0x27;        yPos /= 16;        //x轴转换        xPos += 0x4;        xPos /= 16;


地图填充的公式和屏幕坐标转换为数组下标的方式已经找到了,

 

总结一下找到的数据:


  • 数组基地址:0x1005340

  • 高度基地址:0x10056A8

  • 宽度基地址: 0x10056AC

  • 地雷数基地址: 0x10056A4

  • 查找具体某个数组下标中内容的公式:数组基址+ 高<<5(也就是+32,每隔一行进行填充)+宽

  • 坐标与数组下标转换方法在上面


开写



DLL代码



MFC dll 静态编译


// MFCSAOLEI.cpp : 定义 DLL 的初始化例程。
//

#include "stdafx.h"
#include "MFCSAOLEI.h"
#include <windows.h>
#include <minwindef.h>
#include <Windef.h>

#pragma once
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//
//TODO:  如果此 DLL 相对于 MFC DLL 是动态链接的,
//        则从此 DLL 导出的任何调入
//        MFC 的函数必须将 AFX_MANAGE_STATE 宏添加到
//        该函数的最前面。
//
//        例如:
//
//        extern "C" BOOL PASCAL EXPORT ExportedFunction()
//        {
//            AFX_MANAGE_STATE(AfxGetStaticModuleState());
//            // 此处为普通函数体
//        }
//
//        此宏先于任何 MFC 调用
//        出现在每个函数中十分重要。  这意味着
//        它必须作为函数中的第一个语句
//        出现,甚至先于所有对象变量声明,
//        这是因为它们的构造函数可能生成 MFC
//        DLL 调用。
//
//        有关其他详细信息,
//        请参阅 MFC 技术说明 33 和 58。
//

// CMFCSAOLEIApp
BEGIN_MESSAGE_MAP(CMFCSAOLEIApp, CWinApp)
END_MESSAGE_MAP()


// CMFCSAOLEIApp 构造

CMFCSAOLEIApp::CMFCSAOLEIApp()
{
    // TODO:  在此处添加构造代码,
    // 将所有重要的初始化放置在 InitInstance 中
}


// 唯一的一个 CMFCSAOLEIApp 对象

CMFCSAOLEIApp theApp;

//扫雷窗口句柄
HWND g_hWnd =NULL;

//旧的回调函数的
WNDPROC g_oldProc =NULL;


BYTE *g_BaseOfArray = (BYTE*)0x1005340;
DWORD *g_BaseOfHight= (DWORD*)0x10056A8;
DWORD *g_BaseOfWidth  =(DWORD*)0x10056AC;
DWORD *g_BaseOfLandmine =(DWORD*)0x10056A4;


//是不是要写一个函数,用来获取

//自定义回调函数
LRESULT WINAPI CustomWinProc(
    _In_  HWND hWnd,
    _In_  UINT Msg,
    _In_  WPARAM wParam,
    _In_  LPARAM lParam
)
{
    //用来获取地图的信息
    int * pMapArray = new int[(*g_BaseOfHight)* (*g_BaseOfWidth)]{};

    //进行数组遍历
    //主要作用,保留
    for (DWORD y =1;y<*g_BaseOfHight+1;y++)
    {
        for (DWORD x = 1;x<*g_BaseOfWidth+1;x++)
        {
            BYTE code = *(BYTE*)(g_BaseOfArray + y * 32+x);
            //然后判断是不是地雷
            if (code ==0x8F)
            {
                //说明是地雷
                pMapArray[(y - 1)*(*g_BaseOfWidth)+(x-1)] = 0x8f;
            }
            else
            {
                pMapArray[(y - 1)*(*g_BaseOfWidth) + (x - 1)] = 0xf;
            }
        }
    }

    //移动鼠标的设置
    if (Msg == WM_MOUSEMOVE)
    {
        /*
        功能实现要求:
            实现能在窗口


dll 注入代码就不贴了

 

看一下效果图 :标题栏显示坐标并提示有无雷,F5 一键扫雷

 


最后:迷之logo

 

//不知道图床正常加载了没






本文由看雪论坛 八岛 原创

转载请注明来自看雪社区



热门阅读


点击阅读原文/read,

更多干货等着你~



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存