荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjk (kernel), 信区: Program
标  题: 怎样为程序打补丁(二)―――提高篇
发信站: 荔园晨风BBS站 (Sat Mar  2 22:11:28 2002), 转信

这里面有两幅图...
去来源的网站看看吧:
http://people.os.org.cn/zoudan/doc/patch_2.htm

怎样为程序打补丁(二)―――提高篇

声明:

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

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


   这次要教大家的是如何为程序添加功能。大家都知道,在可执行文件当中只有少量的空
间和 位置可以放置我们的补丁程序,这就注定了要实现比较复杂的功能是非常有局限的。
想象一 下,如果在人家程序中添加大量的汇编代码,且只能是汇编代码(不要问我为什么
),并且调 试运行。因为是在人家的运行环境中运行,必须维护大量的寄存器,地址空间
,堆栈等等,那 将是何其困难和痛苦的一件事情。汇编能作很多事情,但是也不是万能,
要用汇编实现很多复 杂的功能简直就是变态的行为。我为大家制作的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处。

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

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

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


本篇总结:
1. 本系列文章讨论的主要话题和范围。
2. PE文件是Win32平台的可执行文件。
3. 关于为程序添加动态连接库的问题。
4. 其他一些相关知识的介绍。
5. 需要参考我以前的文章

邹丹 于 2001年8月18日
--
             ____________________________________________
            |┏━━━━━━━━━━━━━━━━━━━━┓
            |┃ 欢迎光临荔园晨风 Linux 版,InstallBBS 版┃
            |┗━━━━━━━━━━━━━━━━━━━━┛
             ﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店