本文为看雪论坛优秀文章
看雪论坛作者ID:Caim Astraea
本人最近刚刚开始学习逆向分析,突发奇想想弄一个Windows XP扫雷的辅助。发帖前,在论坛上一搜,好多人都做过这个了,还有人做过Windows7 32位,64位的,突然感觉自己好低级……
于是我又认真地把windowsXP扫雷的几乎所有汇编代码都分析了一遍,最后做了一个修改扫雷内部代码而实现悬停显示雷的操作。
效果如下:
进入正题,这是我的预计任务
首先,用OllyDbg打开windows xp的扫雷程序。
一开始如下图所示:
显然,是属于程序的入口部分代码,包含有初始化SHE,获取ImageBase,测试PE文件的有效性等等等等。为了实现找到回调函数这一个目标,首先需要知道获取来源。首先想到WNDCLASSA(或WNDCLASSW)和WNDCLASSEX这两个结构体,WNDCLASSW定义如下:这两个结构体都是用来初始化窗口类的,在RegisterClass中被引用,根据该结构体的定义,在此结构的(+0x04)位置处,就是窗口回调函数的地址。所以,从设置WNDCLASS成员时指定的地址或者从RegisterClass调用时的栈窗口,可以找到WNDCLASS的地址,进而找到lpfnWndProc的值。RegisterClass一般在主线程的WinMain中被调用,我向下跟踪,来到此处:目前窗口并没有创建,而接下来的第二个函数是exit的调用,标志着程序的退出,因此可以确认,下面的第一个函数,就是程序的WinMain函数。在第一个CALL处我进行了查看,发现是设置随机数种子,接下来明显地看到调用了LoadIconW,LoadCursorW,和GetStockObject三个API函数对WNDCLASSW进行设置,往下翻,立刻可以看见RegisterClassW。事实上,hIcon的位置在lpfnWndProc(+0x10)的位置,从LoadIcon的返回值的设置位置(2个地方)一开始我以为是这里,结果发现1005B18没有写入的代码,接着向下看:可知ebp-38-10=ebp-48就是回调函数的地址,接下来看到:可以确认,01001BC9就是回调函数的地址了,添加标签为lpfnWndProc根据对回调函数的整体浏览,发现对WM_LBUTTONDOWN的处理部分,有将(记住这个地址)1005144处的数据设为0这估计是为了实现左键按下时显示格子0的效果,添加标签为bLButtonUp(Down的非是Up (`へ´*)ノ)接着对WM_LBUTTONDOWN和WM_LBUTTONUP这两个消息的处理部分下断:在扫雷点击一个格子,在WM_LBUTTONDOWN断下,继续运行WM_LBUTTONUP自然不会断下,而游戏界面无变化,说明WM_LBUTTONDOWN不进行点击格子的操作。现在大概可以判断,是左键弹起表示了对一个块的点击。取消WM_LBUTTONDOWN的断点,继续点击,跟踪WM_LBUTTONUP,来到此处:开始是四个判断,根据对数据的分析,1005118应该保存一个POINT结构体,是当前点击的单位坐标,而1005334保存了扫雷区域的单位长度,1005338则是单位宽度。于是添加标签g_xPos,g_yPos,Length,Height那么此处的作用应该是,判断点击坐标是否在范围之内,否则函数跳到末尾。一开始,判断了两个位置的数据,不为0则设置间隔为1s的定时器,于是我大胆猜测,这是第一次点击时会调用的代码,设置定时器,进行计时。那么,10057A4和100579C就可能:一个是时间的数据,一个是游戏是否开始的BOOL型数据。然后让扫雷运行,再分别对10057A4和100579C下硬件写入断点,发现10057A4是每点开一个格子都会断下,并且计数+1,100579C是每秒都会断下,计数+1。那么,10057A4应该是翻开的格子数目,标签其为PressNum,10057C9应该是时间,标签其为Time。1003872的跳转一般不会实现,100388B的跳转一般一定实现,第一个跳转是判断1005000处的数据是否为零,通过对此地址设置硬件断点,可以发现,当“新游戏”时,此处的值被设置为1,而游戏结束后,此处的值被设置为16。第二个跳转发现访问了bLButtonDown这个地址,为0则跳转。这个地址前面见到过,在左键按下的时候被设置成了0,而左键弹起前一定会有按下操作,因此,这个地方一般也会跳过。(也可以写监测程序查看值的变化)例如有雷表BYTE arMine[..],需要读取第r行第c列。shl相当于<<,第二行是将edx左移五位,五个二进制位就是乘以2^5(=32)那么可以知道,1005340是雷表的头地址,标签其为arMine,读取方式为arMine[r*32+c]通过不断地对数据进行更新和检查,可以知道储存规律:雷表是一个以1个字节为元素长度的数组,0x10为游戏的边界。- 翻开时(非雷),是它的数字,是雷则为0xA(不是点击到的)
- 翻开时(非雷),是0x4,是雷则为0x8(不是点击到的)
上面的基本说的都没用(众所周知),接下来才是重头戏,如何实现,把鼠标悬停在一个格子上,如果那个格子有雷,就把雷显示出来。(透视的感觉有木有)首先,需要寻找一个可以安心写代码并能保存到文件的位置,将代码向下翻,找到1004A60的位置,下面都是0数据,可以使用,不过要先判断这个区域是否在扫雷文件内(不然保存不到文件里),RVA=4A60,转为文件FileOffset=3E60,而文件中.text节区是到4000,那么把它标签为InjectCode,于是在此处就可以放心地写入代码了。首先我在扫雷窗口生成后对WM_PAINT下断,然后在扫雷点开一格,并没有断下,说明重绘函数不会处理左键单击,格子变化之类的游戏进行中的小调整,在这里写入代码就没有用了(我一开始莽,直接在这里写,然并卵)。为了知道贴图的怎么变化的,我在BitBlt下了断,点击一下,来到此处:发现两个参数分别是我点下的x,y坐标,那么这个函数就是根据一个格的数值来绘图。标签其为UpdateBlockPaint,观察栈,发现:把雷表中的相应元素&0x1F后,作为8字节为元素长度的数组1005A20的索引值,就可以得到相应贴图的HDC。那么,显示的雷是0x8A,&0x1F得0xA,于是位置就是4*0xA+0x1005A20=1005A48,这个地址之后BitBlt中会用到哦~触发WM_MOUSEMOVE一定来到此处,左中右键按下也可能来到此处,一开始就判断左键是否按下,关于1005140的值,我用监测程序判断了一下:是为了实现,格子按下的效果,如果说左键按下情况下这次悬停在的格子和上次不同,就更改上次格子为不按下,这次格子为按下,关于PressOn和PressOff两个函数,还原后代码如下(我对IDA结果的类型表示不满意):在回调函数中,WM_MOUSEMOVE时左键没按下,跳到此处:刚好可以在这里修改代码(字节刚好为5,强迫症福利),把前面写代码的位置标签为InjectCode,把10020C1处改为:然后其中UpdateBlockPaint中没有越界判断(其实是我不想写了,这段代码费了我4个小时,Ollydbg打汇编代码就是自闭啊,写到后面由于长度,要把一些短跳改成长跳,awsl)所以,要把g_xPos和g_yPos直接初始化为0,就没有问题了至于代码中UpdateBlockPaint的越界判断,以及执行前对游戏是否开始的判断,可以自行添加。看雪ID:Caim Astraea
https://bbs.pediy.com/user-880853.htm
*本文由看雪论坛 Caim Astraea 原创,转载请注明来自看雪社区。好书推荐
文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458303164&idx=1&sn=14d5b324a308827f5d95314906ad44a5&chksm=b1818a3686f603208c3c81b9edfb7bb951e94e046048f4fb83f51147cde367a207683862091d#rd
如有侵权请联系:admin#unsafe.sh