dll线程注入
扫雷程序分析和作弊功能实现
在第一节中,分析了扫雷程序的逆向过程,确定了扫雷格子在内存中对应的位置;在第二节中,使用dll线程注入的方法,使玩家能够确定哪个格子有雷,哪个格子没雷。
- 对程序的分析
因为是带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附近,下一节将分析每个格子都对应哪个内存,以及如何实现作弊功能
- 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 |
|
最终效果:(截不到鼠标)
注入器不知道为什么自己写的createRemoteThread不起作用,用的别人的: