dopromax
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

该系列文章以《逆向工程核心原理》为原型,调整其中对于新手需要耗费大量时间解决之处,更方便于新手入门。也同时解决互联网上扫描件电子书版本的阅读障碍(如截图模糊等)。
前言
代码逆向分析中,“打补丁”操作是不可或缺的重要主题。利用“打补丁”技术不仅可以修复已有程序的BUG,还可以向程序中添加新功能。“打补丁”的对象可以是文件、内存,还可以是程序的代码、数据等。本示例中,我们将使用“打补丁”技术把HelloWorld.exe程序消息窗口显示的“Hello World!”字符串更改为其他字符串。
我们后面会经常用到“打补丁”技术
请记住,我们的目标是把消息对话框中显示的“Hello World!”字符串更改为其他字符串。前面我们已经查找到了调用MessageBoxW的部分和“Hello World!”字符串的地址,这已经算成功了一半。
按Ctrl+F2快捷键重新调试,并使调试流运行到main函数的起始地址处(401000)。在401000地址处按F2键设置断点,再按F9执行程序。main()函数的地址401000被用作“大本营”(40104F)后第一个“前进营”。
修改字符串的两种方法
我们先介绍2种简单的修改字符串的方法。
1.直接修改字符串缓冲区(buffer)
2.在其他内存区域生成新字符串并传递给消息函数。
这两种方法各有优缺点,我们详细的来了解一下。
1.直接修改字符串缓冲区
MessageBoxW函数的字符串参数“Hello World!”保存在地址4092A0处的一段缓冲区,只要修改这段内容,就可以修改MessageBoxW函数显示出的字符串。在Dump窗口中按Ctrl+G快捷键执行Go to命令,在弹出窗口中输入4092A0进入字符串缓冲区。然后使用鼠标选中4092A0地址处的字符串,按Ctrl+E快捷键打开编辑窗口。
从图中可以看出,Unicode形式的“Hello World!”字符串占据的区域为4092A0~4092B0 (Unicode编码中用2个字节表示1个字罗马母)。用新字符串覆盖写该区域。
如果新字符串大于原有字符串,执行覆盖操作时可能损坏字符串后面的数据,所以一定要小心。特别是字符串后面有非常重要的数据时,覆盖操作导致数据损坏就会引发程序内存引用错误。
在弹出的编辑器窗口Unicode文本框中输入“Hello bylibrary!”字符串
unicode字符串必须以NULL结束,它占据2个字节(添加NULL时不能直接在unicode文本框中进行,而要在HEX项目中添加)。
更改后的字符串“Hello bylibrary!”的长度要比原字符串“Hello World!”更长一些。原字符串后一般会存在某些有意义的数据,使用更长的字符串覆盖原字符串时,数据可能会遭到损坏,这是十分危险的。之所以上面我们要采用更长的字符串覆盖,一方面是为了打广告,一方面是为了更好的向大家演示,实际操作中不建议这样做。
我们再返回main()函数,(还记得第一个前进营吧。)
虽然指令保持不变,但原字符串已经被新字符串取代,用作MessageBoxW()函数的参数,并且参数的地址仍为4092A0,只是该地址空间中的内容(字符串)发生了改变。按F9运行程序后,将弹出消息窗口,可以看到显示出新字符串。
以上就是直接更改字符串缓冲区来修改的方法。这种方法的优点就是使用起来十分简单,但缺点是它对新字符串的长度有限制,新字符串的长度不应比原字符串长。
可执行文件保存字符串时一般会给字符串多留出一些空间,第二张图中的HelloWorld.exe程序就是如此。所以,如果你的运气足够好,使用更长的字符串覆盖原字符串时,即使原字符串后面的部分空间被侵占,程序仍然能正常运行。但是我们不建议大家这样做,随着这些不安定因素逐渐增多,整个系统的稳定性最终会遭到破坏。请记住,我们是解决问题的人,而不是制造麻烦的。
保存更改到可执行文件
上面的调试中,我们通过修改字符串缓冲区更改了程序显示的消息内容,但是这种更改只是暂时的,终止调试(结束Hello World进程)后,程序中的原字符串仍然没有改变,如果想把这种更改永久保存下来,就要把更改后的程序另保存为一个可执行文件。
我们选中更改后的“Hello bylibrary”字符串,单机鼠标右键,在弹出的菜单中选择Copy to execuable 菜单,打开如下所示的HEX窗口
在弹出的Hex窗口中单机鼠标右键,选择Save file菜单,在Save file as对话框中输入文件名“Hello Reversing.exe”后保存为.exe可执行文件。然后运行该文件,弹出消息窗口,显示的字符串已经变为“Hello bylibrary”。
2.在其他内存区域新建字符串并传递给消息函数
如果要用“Hello bylibrary world!!!”替换原字符串“Hello World!”,上述方法就不适用了。此时我们就要换一个方法。
按ctrl+F2我们来重新调试,再按F9运行,由于之前我们在main()函数的起始位置处(401000)设置了断点,所以调试流自动转到main()函数处。再看一下main()函数。
401007处有一条PUSH 004092A0命令,它把4092A0地址处的“Hello World”字符串以参数形式传递给MessageBoxW()函数。
向MessageBoxW()函数传递字符串参数时,传递的是字符串所在区域的首地址。如果改变了字符串地址,消息框就会显示变更后的字符串。在内存的某个区域新建一个长字符串,并把新字符串的首地址传递给MessageBoxW()函数,可以认为传递的是完全不同的字符串地址
上面这样的做法看起来是没有问题的,但是我们需要考虑另外一个问题:“应该在哪块区域创建新字符串呢?”,要解决这个问题,我们又要留坑了,这个需要在后面了解到PE文件格式以及虚拟地址结构相关知识才行。
我们在方法1中修改的字符串地址为4092A0,我们在dump窗口查看该部分。向下拖动滑动条,相应内存区域由NULL填充(NULL padding)结束。
这部分就是程序中未使用的NULL填充区域。
如果找不到你肯定又忘了ctrl+G这玩意了,4092A0,往下拖
应用程序被加载到内存时有一个最小的内存分配大小,一般为1000。即使程序运行时只占用100内存,它被加载到内存时仍然会分到1000左右的内存,这些内存一部分被程序占用,其余部分为空余区域,全部被填充为NULL。
我们最好把这个地方当作字符串缓冲区并传递给MessageBoxW函数,用快捷键Ctrl+E向结尾部分适当位置(比如409F50)写入新字符串(“Hello bylibrary!!!”)即可。
但是如果只是进行上面的操作,我们时没有办法更改消息框中的字符串的。既然我们已经新建了缓冲区,接下来就应该把新的缓冲区地址(409F50)作为参数传递给MessageBoxW函数。所以,我们需要在代码窗口中使用汇编命令修改代码。
我们把光标移至401007处,按空格键或者双击打开Assemble窗口。
在打开的窗口中输入“push 409F50”,地址409F50为新字符串“Hello bylibrary!!!”字符串。
用户可以在Assemble窗口中输入任何想要输入的汇编指令,输入当时就能在代码中体现出来,也可以被执行。这种“在运行过程中动态修改进程代码”的方式正是调试最强大的功能之一。
在OllyDbg中我们按F9运行程序,弹出消息窗口,可以看到字符串已经被修改了。
如果我们把修改后的代码重新保存为程序文件,可以发现程序无法正常运行,这是由409F50这一地址引起的。可执行文件被加载到内存并以进程形式运行时,文件并非原封不动的被载入内存,而是要遵循一定的规则进行。在这个过程中,通常进程的内存是存在的,但是相应的文件偏移并不存在。比如,我们上面的例子中,与内存409F50对应的文件偏移就不存在,所以修改后的程序无法正常运行。如果还是有疑惑,还是因为我们还没有掌握PE文件格式的相关知识的原因,这些问题我们可以以后再解决。
Q&A
经过前面四章的学习,有三个问题需要顺便给大家解答一下。
A.快捷键F4与F9的区别
两个都是“运行”命令,F9为Run(运行),F4为Run to Cursor(运行到光标处),F9是运行整个程序的命令,而F4仅运行到当前光标所在位置,可以把F4看作断点与F9命令的组合。
B.什么是启动函数
启动函数(Stubcode)不是用户编写的代码,而是编译器任意添加的代码。编译程序时,不同编译器会根据自身特点添加不同启动函数,特别是EP代码区域中存在着许多启动函数,它们也被称为启动代码(StartUp code)。调试程序时,我们不需要仔细分析这些启动函数,但是初学者有必要分清程序中哪些是启动函数,哪些是用户代码。希望大家调试时多看一看这些代码,熟悉后就能轻松区分。
C.到底什么是PE文件,为什么要等到后面才讲解?如果不懂得PE文件是否就无法调试?
PE是Portable Executable的简称,它是Windows操作系统下的可执行文件的格式,主要包含了对文件规格的描述,代码逆向分析技术的初学者学习它会感到非常吃力、无趣。所以我们并没有在前面详细讲解,更重要的是先让大家感受到调试的乐趣,然后再一点点地学习。此外,如果不了解PE文件结构的相关知识,将无法进行高级调试。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
