dll线程注入

扫雷程序分析和作弊功能实现

在第一节中,分析了扫雷程序的逆向过程,确定了扫雷格子在内存中对应的位置;在第二节中,使用dll线程注入的方法,使玩家能够确定哪个格子有雷,哪个格子没雷。

  1. 对程序的分析

因为是带ui的程序,一开始不知道从哪开始分析,所以用cheat engine找到计算时间的内存地址,这个地址是0x0100579C

之所以关注计时器的问题,是因为这个扫雷的ui上,右上角那个计时器是有长度限制的,它最多只能显示3位数,到达上限或点到雷程序结束之后,时间是会停止的。

因此程序核心的代码可能通过计时器代码间接地找到。

直接在ida里找这个内存地址的引用

一共7个,先看写操作的地方

这三个操作中,两个inc指令,一个mov指令,分别来看一下两个inc指令,因为这个时间的变化是自增的,很符合inc指令的特点。

先看第二个inc指令,在sub_10037E1+4F这个inc上,看到了setTimer函数。

再来看一下另一条inc指令,打开看了一下:

这段代码里的3E7换成十进制是999,突然想到这个计数器的最大值是999 :

但是我这时候还不清楚到底是哪个inc起到了修改时间的作用

到这里之后就要动态调试看一下计时器的具体逻辑了

先下一个内存断点在0x0100579C

然后开始运行程序

多触发几次断点会发现,那个setTimer附近的inc 0x0100579C除了程序开始执行了一次一直再也没被触发,一直是另外一个inc在来回触发断点,(也就是右边这张图)

而左边这张图,在触发这断点之后,继续运行,会更新UI上时间的显示

这回程序的逻辑就很清晰了,在0x0100282D这个读操作之后,更新界面上的显示,在0x01002FE9这个inc写操作之后,修改内存中的时间,所以重点就在这个inc操作上

观察这段代码:

在inc 0x0100579C之前,是一个比较时间是否大于等于999,和一个dword_1005164是否为0的判断

由于扫雷在失败之后、时间到达999之后都会停止自增(这件事我用cheat engine验证了一下,他并不会判定失败或者下一秒变为000)

因此猜测,判断游戏结束的代码应该是和dword_1005164有关的

看一下它的交叉引用

有点多,还是继续动态调试吧(

在0x01005164下一个写操作的内存断点,故意点到雷上

和0与之后,结果从1变成0了

这个and操作是一个函数的开头,没法往前找它干了什么,所以需要弄清谁调用了它

一路运行到return,发现回到了35B0,而这里是处于0x10003512函数内

而且当我在0x01003512函数入口下断点的时候,每次点击都会触发这函数(即使没点到雷上)因此点没点到雷应该是在这里判断的

这个01003512函数从ida和od都可以看到它是一个有两个参数的函数

而且在看到od的参数,一个7一个3,我又回去看了一下我点的格子的位置,发现他似乎就是我点的坐标

于是我又重复试了几次,发现它果然就是坐标

因此只要追踪这坐标参数去到哪了,就可以找到判断雷在哪的代码

在3512这个函数里只有这四个地方用到了坐标

一个一个看,首先是第一处

这是一个数组取下标的操作,然后根据结果v2和一个叫10057A4的值进行了一个三分支

这三个分支虽然看不出来都是干什么的,但是可以看到,前两个分支里有前面见到过的sub_100347C,把计时器停下的控制变量就是这里置零的

这个函数是带参数的(一个0一个1),所以我们先动态调试一下,到底前两个分支中,哪个是点到雷的

原来是第二个分支(参数为0)是点到雷的处理函数

剩下的第一个分支和第三个分支是要看v2这个变量,所以要搞清楚1005340这个数组是什么,在od里打开

这可能是游戏的棋盘?但现在还不确定,再具体调试一下

由于前面看到了,v2是32*横坐标+纵坐标取值出来的,因此我们如果点了(1,1),需要关注下标为33的地方(即0x5361),这是第一次进入3512函数的时候:

这是一路f8,执行到发生变化的时候(call 1003084之后发生的变化),框起来的是发生变化的

结合游戏界面的显示:

这一对比,就可以发现,0x41对应UI界面上的数字1,0x42对应UI界面上的数字2,0x40对应不再能点,且没有数字显示的格子

然后挨着它们的还有0x8f,0x0f,既然都是没翻开的格子,却有两种表示方式,因此可能这是代表有雷或者没雷

为了验证,接下来分别点一下上图中,第一行1右边的那个格子,和第二行最右边那个1右边的格子。

点了这个0x8f的格子直接炸了,猜测0x8f是有雷的格子

然后再对比一下其他雷的位置和我们的猜想一不一样

对比一下确实是一样的,例如第三行那两个1左边是连着两个0x8f,例如第三行那个2下边和右下边都是0x8f

因此可以基本确定0x8f是有雷,0xf是没有雷

到此确定了游戏格子对应的内存位置0x01005360附近,下一节将分析每个格子都对应哪个内存,以及如何实现作弊功能

  1. DLL注入实现作弊

鉴于上述分析内容,可以直接在指定的内存地址中,读取游戏中哪些格子是有雷的,哪些格子是正常的。

因此通过dll线程注入的方法,访问扫雷游戏的内存,显示哪里有雷来实现作弊功能。

首先,根据上一节的逆向内容,判断格子和内存的对应关系,这是初始状态的棋盘:

然后点一下:

再点一下:

由于这是一个二维的问题,猜测横纵坐标与内存地址应该大概一个线性的关系:

并且在逆向过程中,程序给纵坐标乘了一个32,加上x,来从数组里取值,因此猜测,y的系数可能是32

但是有一个关键的问题是,只知道格子和内存的对应关系是没有的,实际要获取的是鼠标在对话框上的坐标位置

关于如何获取鼠标悬停的位置,去网上搜了一下,可以通过dll注入执行一个叫SetWindowLong的函数,这个函数第三个参数是自己编写的一个回调函数,可以捕获窗口上发生的事件消息,例如通过捕获VM_MOUSEMOVE事件,获取当前鼠标的坐标位置:

可以看到能把坐标显示出来,由此可以知道格子的宽和高

108和123分别是这个格子最左边和最右边的点,那么这个格子宽度就是16了,同理高度也是16。

然后最左上角的格子的左上角坐标是(12,55),这样就知道所有格子的范围了

(看到网上还有说可以直接用截图工具算出来的,🐄)

这样计算起来就很简单了:

realx = (x - 12) / 16 + 1

realy = (y - 55) / 16 + 1

最终的dll注入代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{

if (Msg == WM_MOUSEMOVE)

{

int x, y;

x = LOWORD(lParam);

y = HIWORD(lParam);

//char tmp[20] = { 0 };

//sprintf_s(tmp, "%d %d", x, y);

//SetWindowTextA(hWnd, tmp);

int realx, realy;

realx = (x - 12) / 16 + 1;

realy = (y - 55) / 16 + 1;

if (*(PBYTE)((DWORD)0x1005340 + realx + realy * 32) == 0x8F)

{

SetWindowText\(hWnd, L"这里有雷"\);

}

else

{

SetWindowText\(hWnd, L"扫雷"\);

}

}

return CallWindowProc(My_Proc, hWnd, Msg, wParam, lParam);

}

最终效果:(截不到鼠标)

注入器不知道为什么自己写的createRemoteThread不起作用,用的别人的:

https://github.com/MountCloud/InjectDllTool


dll线程注入
https://isolator-1.github.io/2024/01/05/windows/dll线程注入/
Author
Isolator
Posted on
January 5, 2024
Licensed under