当前位置:文档之家› 怎样为程序打补丁

怎样为程序打补丁

一.本文实用于初学者,需要具备一定的汇编和系统底层的知识。
二.本文只是为了让广大网友共同提高一些基础知识,本人决无卖弄之意,只供需
要这方面知识的读者阅读,如果你是高手,或者不需要这方面知识,请跳过。
三.本文的实例均为普通程序,如有雷同,敬请谅解。
四.本文欢迎传抄转载,如果是商业用途,请联系本人。http://www.zoudan.
com zoudan@https://www.doczj.com/doc/b515245714.html,


打补丁是常见的一种软件升级和更新手段。打补丁的形式很多,本文所述的方
式基本上为修改原程序二进制代码的方式,适用于Windows95之后的平台(Win32)



Windows95(Win32)以后的可执行文件都是PE格式,.exe、.ocx、.dll.等等都
是常见的PE格式的文件映像,看一个文件是否为PE文件,不是看它的扩展名,而是
看它的文件头中是否有PE文件头标示和具体的文件内容。比如常见的软件"追捕"中
那个wry.dll,就不是一个PE格式的Dll(动态连接库)文件,通常用16进制编辑软
件打开文件首部观察便可得知。关于PE文件的具体格式和教程不是本文的描述范围
,大家可以查阅相关的资料,我网站上那篇"关于95下的可执行文件加密研究"中也
有比较详尽的描述。


PE文件是一个程序代码、数据的集合。其中的程序代码是该执行文件本身的,
它可能通过动态连接的形式访问其他附属的程序或者动态连接库,通常一个程序文
件中包含了大量的系统调用和对其他程序及动态连接库的调用。

言归正传,无论是破解,程序改良或者程序升级,在改动不大,且没有源程序
参考的情况下,最直接的方法就是更改PE可执行文件。大家都知道我制作的补丁程
序,基本上就是直接修改程序文件,使之达到我们需要得到的功能。

一个PE可执行文件中通常有很多段(section),分别描述不通的数据结构和代
码。最基本的几种段有.code(.text)(代码段)、.data(数据段)、.rdata、.
rscs(资源)等等,这些段的名称可以是程序自定,决定它们属性的不是他的名称
,而是PE文件头中的描述。有很多查看PE文件结构和内容的软件,我向大家推荐
Borland C自带的一个叫Tdump的小软件,很简单和方便,在现在的Borland C++
Builder和Borland C中都有,大家可以用他来了解PE文件的内部信息。

一般来说,修改资源段中的东西最为简单。那些对话框、字符串、位图等等。
修改这些资源很容易,因为有很多这方面的软件,最常见的就是VC++,只要用VC用
资源修改方式打开程序文件,所有这个程序相关的资源就会很容易的修改了。大家
可以随便找几个程序来试试手,挪挪对话框,改改属性,编辑文字,画画位图,


容易上手和熟悉。


修改程序文件的其他地方就没有修改资源段容易了,程序当然都是汇编机器代
码,数据也是未知的结构。关于怎样看懂、跟踪、了解程序的运作就是一件非常经
验的事情,不同的程序不同的方法,没有定式,它们唯一的共同点就是都是汇编代
码,都能用软件反汇编和动态调试。这也已经超出了本文的范畴,我想它需要扎实
的汇编基础,系统底层知识以及丰富的程序调试经验,不可能在很快的时间内掌握
。这需要耐心,长时间的实战以及积累。在此我向大家推荐我常用的工具,静态的
反汇编可以用IDA pro,使用该软件你会发现枯燥乏味的汇编代码变得井井有条,
且多了很多帮助理解的注释,当然还有很多重要程序库的符号,比如MFC库类,标
准C库等等。该程序是最好的反汇编工具,为我们读懂苦涩的机器指令提供了良好
的互动环境。光静态分析汇编代码无疑是极其困难的,光靠眼睛看不可能看到寄存
器,内存单元数据等等和程序运行息息相关的东西。关键是汇编代码苦涩难懂,动
态的调试就成了我们深入了解程序、调试程序、探测程序的最重要的手段。我所提
到的动态调试是汇编级别的,并非任何高级语言。值得提到的是有些程序带有调试
符号信息,那会为我们动态调试提供很大很大的便利,不过一般Release版本都不
会把调试信息链接到最终的程序文件中,粗心的程序员有可能犯这个错误。总之,
汇编级的调试一般来说都非常的艰苦,不过动态调试和静态阅读相结合还是会使效
率提高很多。动态调试的软件很多,而且都支持汇编级别的调试。一般集成变成环
境都带有这种工具,比如VC++,C++ Builder等等。专业调试软件有W32dasm、Trw
、Soft-ice等等,在这个领域里,Soft-ice无疑是绝对的佼佼者。我觉得几乎没有
Soft-ice触及不到的深度,Soft-ice为我们了解系统的任何一个细节提供了可能,
从DOS时代到windows时代,Soft-ice无疑是动态调试领域最重要的工具。好话说得
够多了,我不是在为它打广告,实事的确如此,网上很容易下载到它的各种版本。
Soft-ice虽然很厉害,但是对初学者来说的确不容易掌握,且更需要扎实的底层基
础。掌握了它就为掌握了系统的最底层提供了可能,这无疑是个核武器。


关于具体怎么放置补丁程序,用DLL动态连接库方式编制复杂的补丁程序等等细
节问题我将在以后的文章中介绍,请各位网友密切留意。

本篇总结:
1. 本系列文章讨论的主要话题和范围。
2. PE文件是Win32平台的可执行文件。
3. 关于修改资源段的问题。
4. 其他一些相关知识的介绍。
5. 几种

工具的推荐及其简要介绍。


一.本文实用于初学者,需要具备一定的汇编和系统底层的知识。
二.本文只是为了让广大网友共同提高一些基础知识,本人决无卖弄之意,只供需
要这方面知识的读者阅读,如果你是高手,或者不需要这方面知识,请跳过。
三.本文的实例均为普通程序,如有雷同,敬请谅解。
四.本文欢迎传抄转载,如果是商业用途,请联系本人。http://www.zoudan.
com zd_dan@https://www.doczj.com/doc/b515245714.html,


不知道各位把“基础篇”中的内容搞懂没有,我现在要带领大家提高啦,请各
位用心听讲!

这次要教大家的是如何为程序添加功能。大家都知道,在可执行文件当中只有
少量的空间和位置可以放置我们的补丁程序,这就注定了要实现比较复杂的功能是
非常有局限的。想象一下,如果在人家程序中添加大量的汇编代码,且只能是汇编
代码(不要问我为什么),并且调试运行。因为是在人家的运行环境中运行,必须
维护大量的寄存器,地址空间,堆栈等等,那将是何其困难和痛苦的一件事情。汇
编能作很多事情,但是也不是万能,要用汇编实现很多复杂的功能简直就是变态的
行为。我为大家制作的FolkQQ补丁程序,其实汇编代码也就几十行,功能也很简单
,就几十个字节。但这已经使我够痛苦的了,如果再多些,常人是吃不消的(调试
运行这些代码很费力,如果一个地方错了至少得重敲,而且死机重起是常事)。


看到这里,可能有同学会问,像你的SE补丁系列,实现了IP地址到具体物理
地址的转换,难道也是你用汇编写的吗(用汇编写这个会死人的)?当然不是,大
家注意到有个IPsearcher.dll,这是一个动态链接库,看名字就知道这是关于IP地
址转换成实际地址功能的东东。对,所有的转换工作就是在其中完成。他是我用
VC写的,程序很简单,但是如果用汇编写那就复杂了。我想我自己都没有毅力把它
用汇编写完,然后一个一个字节一个一个指令的敲到人家的程序中去。

呵呵,今天要教大家的就是如何在补丁程序中实现复杂的功能,具体问题在上
面已经描述了,方法就是为程序附带一个动态链接库,把所有复杂的功能都留在
DLL里面,让高级语言去作低级语言难于实现的功能,留出接口,供被打补丁的使
用即可。

关于什么是DLL,我想不用我再多说了。简单的理解就是一个函数库,别人调
用它,它具体完成,就这么简单。大家可以用软件看看我写的那个IPsearcher.
dll(用tdump,或者VC中的工具depends.exe),可以看到其中只有一个函数接口,
叫“_GetAddress”。给它一个IP地址的作为参数,它就能返回那个IP地址的

实际
地址。具体它的实现不在我们今天的讨论范围当中。

问题的关键就是如何在要被打补丁的程序中调用到这个动态链接库,让它为我
们添加的补丁程序服务。

再往下看,就需要各位同学具备一定的Win32 PE格式的可执行文件的基础了
(可以参考我以前写的“关于Win95下的可执行文件的加密研究”和“基础篇”)
。在PE文件中,指定了该程序要使用到的所有动态链接库,还有要调用的哪个函数
接口。这些信息都写在一个叫Import Table的数据结构当中。它描述了某个动态链
接库的某个函数接口的调用地址,这个是为了使系统能够为该程序装入动态链接库
并且重定位接口地址信息的。

可能大家有点晕菜了,没关系。让我用一个实例来解释一下Import Table。我
们用tdump 打开一个PE格式的可执行文件:

C:\>tdump example.exe |more
Turbo Dump Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise
Corporation

........

Name RVA Size
------------------ -------- --------
Exports 00000000 00000000
Imports 0019CFA0 0000017C
Resources 00287000 00077F18

........

# Name VirtSize RVA PhysSize Phys off Flags
-- -------- -------- -------- -------- -------- --------
01 .text 00157744 00001000 00158000 00001000 60000020 [CER]
02 .rdata 0004723A 00159000 00048000 00159000 40000040 [IR]
03 .data 000E5C28 001A1000 0002A000 001A1000 C0000040 [IRW]

.......

Section: Import
ImportLookUpTblRVA:0019D938
Time Stamp: 00000000
Forwarder Chain: 00000000 (index of first forwarder reference)

Imports from WINMM.dll
(hint = 0071) mixerOpen
(hint = 0065) mixerClose
(hint = 006B) mixerGetLineControlsA

先说说第一个出现红字的地方,这是这个程序的Import Table的RVA地址(什
么是RVA自己查)和长度;第二个红字是1A1000,它大于19CFA0,说明什么呢,
Import Table的地址在.rdata段当中(好像知道了也没什么用,呵呵,写都写了,
也不管这么多了);第三个红字的地方就是tdump解析出的Import Table具体的内
容,比如有个叫WINMM.DLL的动态链接库,需要调用它的序列号是0ach的
waveOutClose 函数。诸如此类的后面还有很多很多,通常一个程序需要调用至少
是几个的动态链接库(kernel32.dll,user32.dll等等)。

那Import Table在文件中具体的样子是什么呢,同学们可以用UltraEdit打开
偏移为19CFA0的地方看看。然后结合我给出的数据结构和tdump解析出的结果,具
体分析和理解一下。

以下摘抄于“关于Win95下的可执行文件的加密研究”

.idata块以一个IMAGE_IMPORT_DESCRIPTOR数组开始。每一个被PE文件隐式连结
进来的DLL都有一个IMAGE_IMPORT_DESCRIPTOR。在这个数组中,没有字段指出该结
构数组的项数,但它的最后一个单元是NULL,可以由此计数算出

该数组的项数。
IMAGE_IMPORT_DESCRIPTOR的格式如下:
Dword Characteristics
该字段是一个指针数组的RVA偏移。其中每一个指针都指向一个
IMAGE_IMPORT_BY_NAME结构
Dword TimeDateStamp
时间及日期标志,可以忽略。
Dword ForwarderChain
正向链结索引。我们的资料中没有函数正向链结的格式,也没有这一样的例子

Dword Name
以NULL结尾的ASCII字符的RVA地址,该字符串包含输入的DLL名,比如“
Kernel32.dll”或“USER32.DLL”。
PIMAGE_THUNK_DATA FirstThunk
该字段是在Image_thunk_data联合结构中的RVA偏移。大多数情况下,
Image_thunk_data是指IMAGE_IMPORT_BY_NAME结构的指针。如果不是一个指针的话
,那它就是该功能在DLL中的序号。

IMAGE_IMPORT_DESCRIPTOR重要的部分有输入的DLL名字及两个
IMAGE_IMPORT_BY_NAME指针数组。在执行文件中,这两个指针数组彼此平行,末尾
都是以Null表示数组的结束。下图给出了这种关系的图形描述。


为什么由两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构呢?第一个
Characteristics是单独的一项,而且不可改写,它有时被称为提示名表(Hint
Name Table)。第二个数组(FirstThunk所指)是由PE装入器重写的。装载程序迭
代搜索数组中的每一个指针,找到每一个IMAGE_IMPORT_BY_NAME结构所指的输入函
数的地址,然后装载器找到程序的地址改写IMAGE_IMPORT_BY_NAME指针。Jmp
dword ptr [xxxxxxxx]中的[xxxxxxxx]是指First Thunk数组中的一个入口。因为
它被称为输入地址表(Import Address Table)。

好了,不知道大家看明白了没有,对Import Table不解的地方可以仔细查看
我的文章,那里面有很详细的阐述。

我们的目标已经很明确了,把我们自己的DLL描述添加到被打补丁程序的
Import table当中,让系统在装入该程序的同时,装入我们自己的DLL,并且做好
重定位,为我们的补丁程序调用DLL中的接口做好准备。

我们可以直接修改Import Table,如果空间足够,我们可以这么干。但是
Import Table往往是个很小一块数据,可以为一个段,也可以塞在程序的任何地方
。所以Import Table的往往前后都是其他数据,我们要加一项都是很困难的事情(
其实一项也就20个字节)。那如果前后都是有数据的怎么半,没关系,直接全部挪
到其他空闲的地方去(什么地方空闲,请看“基础篇”)。但一定要记得的是,必
须修改PE部首的字段(第一个红字所描述的地方),改到你自己的Import Table所
在的地方,当然,长度也要修改(通常一个DLL就是一项,一项就是20个字节)。



最重要最关键的就是自己构造一个Import Table的表项了。(喝口水,稍等


关于表项的具体数据结构和描述已经在上面讲述了。

用UltraEdit改二进制文件
就像是给病人开刀,要格外小心谨慎。要记住,每个数据的每一位都像是病人身上
的肉肉,动刀前先要有信心,要有把握,谨慎,仔细,把稳。。。。

算了,说得我自己都紧张了,还是我一手一手示范讲解吧。(上个WC,不好
意思)

回来了,我接着和大家聊。让我们先来看看一个实际PE可之行文件的16进制
映像吧。就拿那个程序为例,我们已经知道它的Import Table的起始RVA地址
(0019CFA0),用UltraEdit打开该程序,按那个Goto按钮,接着输入地址“
0x19CFA0”回车。这样你就会在编辑其中看到该文件Import Table的样子。(如下
图)



大家可以看到这个可执行文件的从19cfa0到19d11b共17C个字节的Import Table
,其中最后20个字节是一个空表项,它表示这个Import Table的结束。可以看到,
在Import Table结束之后仍然有大量的未知数据,这就给我们添加表项带来了困难
。该怎么办呢,前面已经提到,挪到其他地方去。怎么挪?

1。先找空地方,我们找到001A0250,它在.rdata和.data中间,大家可以自己用
UltraEdit找各个段的接逢处,往往有空余的空间。(怎么找空隙请参阅“基础篇
”)

2。把UltraEdit的光标移动到地址19cfa0到19d11f(0x180h个字节),然后按
Ctrl-c拷贝。然后移动光标到1A0250,选住空白的0x180h个字节好(如果不选住再
粘贴就是插入了),按Ctrl-V。这样我们就把以前的Import Table拷贝到了新的地
方(以前Import Table 19cfa0的数据不用删除)。

3。拷贝完了之后还需要把PE文件头Dir Table中的Import Table的首址RVA指向到
新的地方。具体做法是,在文件头部查找16进制数A0 CF 19 00(也就是19cfa0),
把它改成50 02 1A 00 (1a0250),然后紧接着后面两个自己是Import Table的
长度,以前是17Ch,现在改成17Ch+14h=190h。(14h=20,这是一个表项的长度)


挪完了,让我们用tdump 检查一下文件:

Name RVA Size
------------------ -------- --------
Exports 00000000 00000000
Imports 001A0250 00000190

.......

Section: Import
ImportLookUpTblRVA:0019D938
Time Stamp: 00000000
Forwarder Chain: 00000000 (index of first forwarder reference)

Imports from WINMM.dll
(hint = 0071) mixerOpen
(hint = 0065) mixerClose
(hint = 006B) mixerGetLineControlsA

第一个红字的地方说明我们已经把Import Table的首址改到了1a0250长度是190
了。第二个红字的地方说明新的Import Table的表项数据是可靠的。

好了,最关键的最激动人心的时候到了,让我们真正为这个新的Import Table添
加表项,添加表项必须是要建立在你已经完全对Import Table结构了解的基础上,
输入的过程就是输入一些16进制数和字符串。下面是输入好后的屏幕切图,我将结

该图为大家讲解。

好了,大家已经看到了,这是添加好表项以后的Import Table,其中1a0250到
1a03b8和以前的Import Table一模一样。以前的Import Table在1a03b8以后是20个
字节的全零结束符。1a03b9到1a03cc是我们添加的表项,后面的20个字节是新的结
束符。然后空了16个字节,接着是DLL信息。

让我把新表项的每一个字段都给大家讲解一下。

1a03b9开始,第一个DWORD是1a0410。地址1a0410是指向的1a03f0,1a03f0这是
一个DLL接口的描述(具体结构请自行查阅资料)。可以看到01 00(0001)是
_GetAddress函数在DLL中的序列号(IPsearcher.dll中只有一个函数接口,序号是
1),然后是一个以0结尾的字符串“_GetAddress”这是函数的名称。

表项的第二个第三个DWORD是timestamp和向后的指针链,不管(只有一个函数
,所以指针链是0,表示结束)。

第四个DWORD是1a03e0,它指向DLL文件的文件名,图例中是“ipsearcher.dll
”,当然也需要以0结尾。

第五个DWORD是1a0418,这个地址是在图例中有数值,其实可以不管。因为这个
地址是系统在装入DLL之后填写的该函数接口的具体地址。我们的补丁程序就是调
用这个地址中的地址来调用_GetAddress这个函数。具体怎么访问呢?程序装入地
址是40000h,所以该地址在装入之后就是40000h+1a0418h=5a0418h。我们的补丁程
序使用call dword ptr [5a0418]就可以调用_GetAddress了。



呵呵,修改Import Table的工作已经结束。我使劲在讲,不知道大家明白了没
有,可不要说我没照顾大家的感受!

对了,还有一个很重要的事情。由于在系统装入DLL的时候需要在1a0418中填写
函数入口地址,所以,找个地址所在的段必须可写。关于怎么修改段表,使某一个
段可写,请参阅我以前的文章。

最后当然要用我们的tdump查看一下我们修改PE文件的结果:

Imports from WININET.dll
(hint = 0075) InternetQueryOptionA
(hint = 0052) InternetCanonicalizeUrlA
(hint = 0056) InternetCloseHandle
(hint = 007E) InternetSetFilePointer
(hint = 0077) InternetReadFile
(hint = 0074) InternetQueryDataAvailable
(hint = 0088) InternetWriteFile
(hint = 0069) InternetGetLastResponseInfoA
(hint = 005C) InternetCrackUrlA
(hint = 0071) InternetOpenUrlA
(hint = 006F) InternetOpenA
(hint = 0083) InternetSetStatusCallback

Imports from ipsearcher.dll
(hint = 0001) _GetAddress

看红字,呵呵,我们已经成功了。这样,被打补丁的程序在装入的时候会自动
装入ipsearcher.dll,并且把_GetAddress函数的入口地址填写到1a0418处。

以后该怎么办不是我们今天要探讨的内容,总之我们在被打补丁的程序中已经
能够调用新的动态链接库中的函数接口了,这无疑为在补丁中添加实现复杂的功能
提供了可能。


呵呵,很有用吧。我表达能力不太好,希望大家看懂了。不懂的自己先努力查
资料自己专研,实在不懂的再问我吧。

辛苦大家看这么多,下次再见吧!

相关主题
相关文档 最新文档