第三章 计算机病毒结构分析 (2)
( 6)引入表( Import Table)
两个概念:
– 引入函数:是被某模块调用的但又不在调用者模块中的函数,因而命名为 Import(引入)函数。引入函数实际位于一个或者更多的 DLL里。调用者模块里只保留一些函数信息,包括函数名及其驻留的 DLL名。
– Data Directory,Optional Header最后一个成员就是
DataDirectory,它是一个 IMAGE_DATA_DIRECTORY结构数组,
共有 15个成员。 Data Directory包含了 PE文件中各重要数据结构的位置和尺寸信息。每个成员包含了一个重要数据结构的信息。 Data Directory的每个成员都是 IMAGE_DATA_DIRECTORY结构类型,其定义如下所示,
IMAGE_DATA_DIRECTORY STRUCT
– VirtualAddress dd //数据结构的相对虚拟地址 (RVA)
– isize dd //VirtualAddress所指向数据结构的字节数
IMAGE_DATA_DIRECTORY ENDS
15个成员序号 便移 包含信息 简介
0 96 Export symbols 导出表
1 104 Import symbols 导入表
2 112 Resources 资源
3 120 Exception 异常
4 128 Security 安全
5 136 Base relocation 重定位表
6 144 Debug 调试信息
7 152 Copyright string 版权信息
8 160 GlobalPTR 全局指针相对虚拟地址表
9 168 Thread local storage (TLS) 本地线程存储
10 172 Load configuration 装载配置表
11 180 Bound Import 具体资料不祥
12 188 Import Address Table 引入函数的地址(宿主程序中的地址)表
13 192 Delay Import 具体资料不祥
14 200 COM descriptor COM描述子
15 208 Reserved 未使用
引入表:实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组。每个结构包含 PE
文件引入函数的一个相关 DLL的信息。如果该 PE文件从 10个不同的 DLL中引入函数,
那么这个数组就有 10个成员。该数组以一个全 0的成员结尾。
IMAGE_IMPORT_DESCRIPTOR结构的定义如下:
IMAGE_IMPORT_DESCRIPTOR STRUCT
– Union
Characteristics dd
OriginalFirstThunk dd
– Ends
– TimeDateStamp dd
– ForwarderChain dd
– Name dd
– FirstThunk dd
IMAGE_IMPORT_DESCRIPTOR ENDS
OriginalFirstThunk含有指向一个 IMAGE_THUNK_DATA结构数组的 RVA。
FirstThunk与 OriginalFirstThunk的结构相同,只是作用不同。
IMAGE_THUNK_DATA是一个指向 IMAGE_IMPORT_BY_NAME结构的指针。注意 IMAGE_THUNK_DATA 包含了指向一个
IMAGE_IMPORT_BY_NAME 结构的指针,而不是结构本身。
IMAGE_IMPORT_BY_NAME结构保存着一个引入函数的相关信息。
IMAGE_IMPORT_BY_NAME 结构的定义:
– IMAGE_IMPORT_BY_NAME STRUCT
Hint dw //引入函数在原dll中的索引号
Name db //引入函数在原dll中的名字
– IMAGE_IMPORT_BY_NAME ENDS
假设有几个 IMAGE_IMPORT_BY_NAME 结构,我们收集起这些结构的 RVA(= IMAGE_THUNK_DATAs)组成一个数组,并以 0结尾,然后再将数组的 RVA放入 OriginalFirstThunk。
FirstThunk与 OriginalFirstThunk区别
OriginalFirstThunk IMAGE_IMPORT_BY_
NAME
FirstThunk
| |
IMAGE_THUNK_DATA ---> Function 1 <--- IMAGE_THUNK_DATA
IMAGE_THUNK_DATA ---> Function 2 <--- IMAGE_THUNK_DATA
IMAGE_THUNK_DATA ---> Function 3 <--- IMAGE_THUNK_DATA
IMAGE_THUNK_DATA ---> Function 4 <--- IMAGE_THUNK_DATA
..,--->,.,<---,.,
IMAGE_THUNK_DATA ---> Function n <--- IMAGE_THUNK_DATA
PE文件执行前
O r i g i n a l F i r s t T h u n k
T i m e D a t e S t a m p
F o r w a r d e r C h a i n
N a m e
F i r s t T h u n k
I M A G E _ T H U N K _ D A T A
I M A G E _ T H U N K _ D A T A
I M A G E _ T H U N K _ D A T A
8 0 0 0 0 0 1 0 H ( s t r c m p )
0 ( 结 束 符 )
I M A G E _ T H U N K _ D A T A
I M A G E _ T H U N K _ D A T A
I M A G E _ T H U N K _ D A T A
8 0 0 0 0 0 1 0 H ( s t r c m p )
0 ( 结 束 符 )
0 2 F 6 E x i t P r o c e s s
0 1 1 1 R e a d F i l e
0 0 2 B W r i t e F i l e
I M A G E _ I M P O R T _ D E S C R I P T O R
I M A G E _ I M P O R T _ B Y _ N A M E
K e r n e l 3 2,d l l
PE文件执行时
OriginalFirstThunk IMAGE_IMPORT_BY_NA
ME
FirstThunk
| |
IMAGE_THUNK_DATA ---> Function 1 Address of Function 1
IMAGE_THUNK_DATA ---> Function 2 Address of Function 2
IMAGE_THUNK_DATA ---> Function 3 Address of Function 3
IMAGE_THUNK_DATA ---> Function 4 Address of Function 4
..,--->,..,.,
IMAGE_THUNK_DATA ---> Function n Address of Function n
O r i g i n a l F i r s t T h u n k
T i m e D a t e S t a m p
F o r w a r d e r C h a i n
N a m e
F i r s t T h u n k
I M A G E _ T H U N K _ D A T A
I M A G E _ T H U N K _ D A T A
I M A G E _ T H U N K _ D A T A
8 0 0 0 0 0 1 0 H ( s t r c m p )
0 ( 结 束 符 )
E x i t P r o c e s s 的 入 口 地 址
R e a d F i l e 的 入 口 地 址
W r i t e F i l e 的 入 口 地 址
s t r c m p 的 入 口 地 址
0 ( 结 束 符 )
0 2 F 6 E x i t P r o c e s s
0 1 1 1 R e a d F i l e
0 0 2 B W r i t e F i l e
I M A G E _ I M P O R T _ D E S C R I P T O R
I M A G E _ I M P O R T _ B Y _ N A M E
K e r n e l 3 2,d l l
j m p d w o r d p r t [ 0 ]
j m p d w o r d p r t [ 1 ]
j m p d w o r d p r t [ 2 ]
j m p d w o r d p r t [ 3 ]
用函数名调用:
IMAGE_IMPORT_BY_NAME
用序号调用,IMAGE_THUNK_DATA值的低位字指示函数序数,而最高二进制位
(MSB)设为 1 。
– 例如,如果一个函数只由序数引出且其序数是
1234H,那么对应该函数的
IMAGE_THUNK_DATA值是 80001234H。
列出某个 PE文件的所有引入函数的步骤:
第一,校验文件是否是有效的 PE。
第二,从 DOS header 定位到 PE header。
第三,获取位于 OptionalHeader的数据目录( DataDirectory)的地址。
第四,转至数据目录的第二个成员提取其 VirtualAddress值。
第五,利用上值定位第一个 IMAGE_IMPORT_DESCRIPTOR结构。
第六,检查 OriginalFirstThunk值。若不为 0,则顺着
OriginalFirstThunk里的 RVA值转入那个 RVA数组。若
OriginalFirstThunk为 0,就改用 FirstThunk值。有些连接器生成 PE文件时会置 OriginalFirstThunk值为 0,这应该算是个 bug。不过为了安全起见,我们还是先检查 OriginalFirstThunk的值。
第七,对于每个数组元素,我们用 IMAGE_ORDINAL_FLAG32来检查该元素的最高位。如果该元素值的最高二进位为 1,那么函数是由序数引入的,可以从该值的低字节提取序数。如果元素值的最高二进位为 0,就可将该值作为 RVA转入 IMAGE_IMPORT_BY_NAME数组,
跳过 Hint就是函数名字了。
第八,再跳至下一个数组元素提取函数名,一直到数组底部 (它以 null
结尾 )。现在我们已遍历完一个 DLL的引入函数,接下去处理下一个
DLL。
第九,即跳转到下一个 IMAGE_IMPORT_DESCRIPTOR并处理之。
依次循环直到数组结尾 (IMAGE_IMPORT_DESCRIPTOR 数组以一个全 0域元素结尾 )。
( 7)引出表( Export Table)
附加概念:引出函数,数据目录
引出表是数据目录的第一个成员,又可称为 IMAGE_EXPORT_DIRECTORY。
IMAGE_EXPORT_DIRECTORY共有11
个成员,部分介绍如下:
域名 含义
nName 模块的真实名称。该域是必须的,因为文件名可能会改变。这种情况下,PE装载器将使用这个内部名字。
nBase 基数,(引出序数- nBase) 就是函数地址数组的索引值了。
NumberOfFunctions 模块引出的函数 /符号总数。
NumberOfNames 通过名字引出的函数 /符号数目。该值不是模块引出的函数 /符号总数,这是由上面的 NumberOfFunctions给出。本域可以为 0,
表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数 /符号,那么数据目录中引出表的 RVA为 0。
AddressOfFunctions 模块中有一个指向所有函数 /符号的 RVAs数组,本域就是指向该
RVAs数组的 RVA。简言之,模块中所有函数的 RVAs都保存在一个数组里,本域就指向这个数组的首地址。
AddressOfNames 类似上个域,模块中有一个指向所有函数名的 RVAs数组,本域就是指向该 RVAs数组的 RVA。
AddressOfNameOrdinals RVA,指向包含上述 AddressOfNames数组中相关函数之序数的
16位数组。
两类输出方式:
– AddressOfFunctions和 NumberOfFunctions
– AddressOfNames和 NumberOfNames
AddressOfNameOrdinals是用来统一上述两类输出方式的。这就相当于一个别名。
它们的关联方式如下:
AddressOfNames
AddressOfNameOrdinals
| |
RVA of Name 1 <--> Index of Name 1
RVA of Name 2 <--> Index of Name 2
RVA of Name 3 <--> Index of Name 3
RVA of Name 4 <--> Index of Name 4
...,..,.,
RVA of Name N <--> Index of Name N
.,,
N a m e
n B a s e ( 假 设 值 x )
N u m b e r O f F u n c t i o n s
N u m b e r O f N a m e s
A d d r e s s O f F u n c i t o n s
A d d r e s s O f N a m e s
A d d r e s s O f N a m e O r d i n a l s
I M A G E _ E X P O R T _ D I R E C T O R Y
F i l e n a m e,d l l
函 数 入 口 地 址 R V A
函 数 入 口 地 址 R V A
.,,
函 数 入 口 地 址 R V A
函 数 入 口 地 址 R V A
.,,
0
1
a
b
R V A
R V A
.,,
R V A
.,,
M y F u n 1
M y F u n 2
.,,
M y F u n 3
.,,
1
a
.,,
b
.,,
无 名 函 数 ( 序 号 x )
M y F u n 1 函 数 ( 序 号 x + 1 )
M y F u n 2 函 数 ( 序 号 x + a )
M y F u n 3 函 数 ( 序 号 x + b )
根据引出函数名,怎样来获取其地址呢?
1.定位到 PE header。
2.从数据目录读取引出表的虚拟地址。
3.定位引出表获取名字数目 (NumberOfNames)。
4.并行遍历 AddressOfNames和 AddressOfNameOrdinals指向的数组匹配名字。如果在 AddressOfNames 指向的数组中找到匹配名字,从
AddressOfNameOrdinals 指向的数组中提取索引值。
– 例如,若发现匹配名字的 RVA存放在 AddressOfNames 数组的第 77个元素,那就提取 AddressOfNameOrdinals数组的第 77个元素作为索引值。
如果遍历完 NumberOfNames 个元素,说明当前模块没有所要的名字。
5.从 AddressOfNameOrdinals数组提取的数值作为
AddressOfFunctions数组的索引。也就是说,如果值是 5,就必须读取 AddressOfFunctions数组的第 5个元素,此值就是所要函数的 RVA。
IMAGE_EXPORT_DIRECTORY结构的
nBase成员
nBase是一个基数。(引出序数- nBase)就是函数地址数组的索引值了。
如果程序员在 def文件中设定起始序数号为 200,
这意味着 AddressOfFunctions数组至少有 200个元素。即使这前面 200个元素并没使用,但它们必须存在。
有了 nBase,就节约了 200个空元素。注意 nBase
并不影响 AddressOfNameOrdinals数组的值。
根据函数序数获取函数地址的步骤
1.定位到 PE header。
2.从数据目录读取引出表的虚拟地址。
3.定位引出表获取 nBase值。
4.减掉 nBase值得到指向 AddressOfFunctions 数组的索引。
5.将该值与 NumberOfFunctions作比较,大于等于后者则序数无效。
6.通过上面的索引就可以获取
AddressOfFunctions 数组中的 RVA了。
通过名字和序号访问的比较
从序数获取函数地址比函数名快捷容易,
因为这种方法不需要遍历 AddressOfNames
和 AddressOfNameOrdinals这两个数组。
虽然 GetProcAddress支持两种调用方式,
但是,用序号调用的时候比较复杂(要构造一个特殊的数值)。
另外,在管理上,有名字会更好。
WinPE 察看器演示
– Exe
– Dll
源代码级 PE察看器演示
2 Win32文件型病毒编制技术
Ring-3病毒的兼容性较好
Ring-3病毒需要 API的支持
– 公开的
– 未公开的
技术包括:
2.1病毒的重定技术
为什么需要重定位?
– 正常程序的变量和函数的相对地址都是预先计算好的。
– 病毒是附加在宿主程序中的程序段,其问题在于:病毒变量和病毒函数的相对地址很难计算。
– 解决方法:动态找一个参照点,然后再根据参照点的地址确定病毒函数和病毒变量的地址。
call delta
delta:
pop ebp

lea eax,[ebp+(offset var1-offset delta)]
参照量 delta在内存中的地址 + 变量 var1与参考量之间的距离 = 变量 var1在内存中的真正地址举例介绍
dwVar dd?
call @F
@@:
pop ebx
sub ebx,offset @B
mov eax,[ebx+offset dwVar]
编译文件 (假设 )
00401000 00000000 BYTE 4 DUP(4)
00401004 E800000000 call 00401009
00401009 5B pop ebx;ebx = 00401009
0040100A 81EB09104000 sub ebx,00401009;ebx = 0
00401010 8B8300104000 mov eax,dword prt[ebx +
00401000];mov eax,00401000;mov eax,dwVar
如果被定位到 00801000处
00801000 00000000 BYTE 4 DUP(4)
00801004 E800000000 call 00801009
00801009 5B pop ebx;ebx = 00801009
0080100A 81EB09104000 sub ebx,00401009;ebx = 00400000
00801010 8B8300104000 mov eax,dword prt[ebx +
00401000];mov eax,[00801000];mov eax,dwVar
2.2获取 API函数
为什么要获得 API函数?
– 正常程序用引入表获得
– 病毒只是一个依附在正常程序中的代码段,没有自己的引入表
– 思路:去动态连接库中寻找 ---〉 找相应连接库
(kernel32,user32等 )在执行时的基地址。
– 寻找基地址的方法包括(以 kernel32为例):
a) 利用程序的返回地址,在其附近搜索 Kernel32的基地址
Kernel32 的 push
在应用程序中用 esp在堆栈中获取。
Call CreateProcess
CreatePrcess入口
Push 返回地址
Jmp 应用程序

Push eax
Call ExitThread
程序入口
ret
OS kernel32.dll 应用程序
为什么能够从 4GB 的内存中得到 Kernel32.dll 的基地址呢?其实是这样的,Dll 有一个非常特殊的特性:当有别的程序调用它的时候,它的文件映象就会动态地映射到调用进程的内存地址空间。
一般情况下,一个程序在运行的时候,Kernel32.dll 这个 Dll 都会被映射到该程序的内存地址空间,成为它的一部分 ——这样一来,
我们就可以在宿主的内存地址空间中搜索到 Kernel32.dll 的基地址了,
例子
GetKBase:
mov edi,[esp+04h];这里的 esp+04h是不定的,主要看从程序第一条指令执行到这里有多少 push;操作,如果设为 N个 push,则这里的指令就是 Mov edi,[esp+N*4h]
and edi,0FFFF0000h
,while TRUE
,if DWORD ptr [edi] == IMAGE_DOS_SIGNATURE ;判断是否 MZ
mov esi,edi
add esi,DWORD ptr [esi+03Ch] ;esi指向 PE标志
,if DWORD ptr [esi] ==IMAGE_NT_SIGNATURE;是否有 PE标志
,break;如果有跳出循环
,endif
,endif
sub edi,010000h ;分配粒度是 10000h,dll必然加载在 xxxx0000h处
,if edi < MIN_KERNEL_SEARCH_BASE; MIN_KERNEL_SEARCH_BASE等于 70000000H
mov edi,0bff70000h;如果上面没有找到,则使用 Win9x的 KERNEL地址
,break
,endif
,endw
mov hKernel32,edi ;把找到的 KERNEL32.DLL的基地址保存起来
b)对相应操作系统分别给出固定的
Kernel32模块的基地址
对于不同的 windows操作系统来说,
Kernel32模块的地址是固定的,甚至一些
API函数的大概位置都是固定的。
– Windows 98为 BFF70000
– Windows 2000为 77E80000
– Windows XP为 77E60000
缺点是兼容性差
GetAPI
在得到了 Kernel32的模块地址以后,我们就可以在该模块中搜索我们所需要的 API地址。
对于给定的 API,可以通过直接搜索 Kernel32.dll
导出表的方法来获得其地址,
同样我们也可以先搜索出 GetProcAddress和
LoadLibrary两个 API函数的地址,然后利用这两个 API函数得到我们所需要的 API函数地址。
3.3 文件搜索
FindFirstFile,该函数根据文件名查找文件;
FindNextFile,该函数根据调用 FindFirstFile函数时指定的一个文件名查找下一个文件;
FindClose,该函数用来关闭由 FindFirstFile函数创建的一个搜索句柄;
WIN32_FIND_DATA,该结构中存放着找到文件的详细信息。
FindFile Proc
– a) 指定找到的目录为当前工作目录
– b) 开始搜索文件 (*.*)
– c) 该目录搜索完毕?是则返回,否则继续
– d) 找到文件还是目录?是目录则调用自身函数
FindFile,否则继续
– e) 是文件,如符合感染条件,则调用感染模块,否则继续
– f) 搜索下一个文件 (FindNextFile),转到 C继续
FindFile Endp
3.5内存映射文件
内存映射文件提供了一组独立的函数,这些函数使应用程序能够像访问内存一样对磁盘上的文件进行访问。这组内存映射文件函数将磁盘上的文件的全部或者部分映射到进程虚拟地址空间的某个位置,以后对文件内容的访问就如同在该地址区域内直接对内存访问一样简单。这样,对文件中数据的操作便是直接对内存进行操作,大大地提高了访问的速度,这对于计算机病毒来说,对减少资源占有是非常重要的。
应用步骤
a)调用 CreateFile函数打开想要映射的 HOST程序,返回文件句柄
hFile。
b)调用 CreateFileMapping函数生成一个建立基于 HOST文件句柄
hFile的内存映射对象,返回内存映射对象句柄 hMap。
c)调用 MapViewOfFile函数将整个文件(一般还要加上病毒体的大小)
映射到内存中。得到指向映射到内存的第一个字节的指针 (pMem)。
d)用刚才得到的指针 pMem对整个 HOST文件进行操作,对 HOST程序进行病毒感染。
e)调用 UnmapViewFile函数解除文件映射,传入参数是 pMem。
f)调用 CloseHandle来关闭内存映射文件,传入参数是 hMap。
g)调用 CloseHandle来关闭 HOST文件,传入参数是 hFile。
3.5病毒如何感染其他文件
PE病毒感染其他文件的常见方法是在文件中添加一个新节,然后,把病毒代码和病毒执行后返回宿主程序的代码写入新添加的节中,同时修改 PE文件头中入口点
( AddressOfEntryPoint),使其指向新添加的病毒代码入口。这样,当程序运行时,
首先执行病毒代码,当病毒代码执行完成后才转向执行宿主程序。
病毒感染其他文件的步骤
1.判断目标文件开始的两个字节是否为,MZ”。
2.判断 PE文件标记,PE”。
3.判断感染标记,如果已被感染过则跳出继续执行宿主程序,否则继续。
4.获得 Data Directory(数据目录)的个数,(每个数据目录信息占 8个字节)。
5.得到节表起始位置。 (数据目录的偏移地址 +数据目录占用的字节数 =节表起始位置 )
6.得到节表的末尾偏移(紧接其后用于写入一个新的病毒节信息)
节表起始位置 +节的个数 *(每个节表占用的字节数 28H)=节表的末尾偏移。
7.开始写入节表
– a)写入节名( 8字节)。
– b)写入节的实际字节数( 4字节)。
– c)写入新节在内存中的开始偏移地址( 4字节),同时可以计算出病毒入口位置。
– 上一个节在内存中的开始偏移地址 +(上一个节的大小 /节对齐 +1) *节对齐 =本节在内存中的开始偏移地址。
– d)写入本节(即病毒节)在文件中对齐后的大小。
– e)写入本节在文件中的开始位置。
– 上节在文件中的开始位置 +上节对齐后的大小 =本节(即病毒)在文件中的开始位置。
– f)修改映像文件头中的节表数目。
– g)修改 AddressOfEntryPoint(即程序入口点指向病毒入口位置),同时保存旧的 AddressOfEntryPoint,以便返回宿主并继续执行。
– h)更新 SizeOfImage(内存中整个 PE映像尺寸 =原 SizeOfImage+病毒节经过内存节对齐后的大小)。
– i)写入感染标记(后面例子中是放在 PE头中)。
– j)在新添加的节中写入病毒代码。
– ECX =病毒长度
– ESI =病毒代码位置(并不一定等于病毒执行代码开始位置)
– EDI =病毒节写入位置
– k)将当前文件位置设为文件末尾。
3.6如何返回到宿主程序
jmp old AddressOfEntryPoint
病毒演示
病毒示例代码文件型病毒功能说明,
病毒引导说明:
文件型病毒,没有引导部分演示病毒传染说明:
传染范围,Virus.exe所在目录传染目标:可执行文件 (.exe)
传染过程:搜索目录内的可执行文件,逐个感染病毒触发说明:
触发条件:运行 Virus.exe程序或被 Virus.exe感染的程序文件型病毒功能说明,
病毒破坏说明:
破坏能力:无害型 传染时减少磁盘的可用空间,在可执行文件上附加一个节 (4K)
破坏方式:攻击文件 在可执行文件上附加一个节 (4K),修改可执行文件的入口地址破坏范围,Virus.exe所在目录病毒查杀说明:
病毒危害等级:低,属于无害型病毒病毒特征类型,Bloodhound.W32.1(Norton AntiVirus检测结果,这是
Symantec软件来临时指代新病毒的文件)
病毒查杀方法:删除所有被感染的文件步骤 0(预备 ):
当防病毒程序如 Norton AntiVirus(注:此处的测试应以当前测试机器上安装杀毒软件为准,本示例运行时机器上安装的只有 Norton AntiVirus,故以此为参照 )自动防护功能打开 时,鼠标光标位于病毒程序上时,会看到如下的图例:
图例 0
图解说明:注解 0-1
表示 Norton AntiVirus的自动防护功能打开着;
注解 0-2就是防病毒软件在用户鼠标置于图例
0中注解 0-3处的
Virus.exe程序上时的报警提示。
演示说明:作为自己开发的病毒程序,为了能够更好的演示病毒的特征,在开发过程中我们没有采用病毒的保护机制,故而防病毒软件根据病毒程序的特征即可给出该程序为病毒程序的报警提示。
步骤 1(运行预备 ):
关闭防病毒软件的自动防护功能,点击病毒程序 Virus.exe,运行该程序,参见下图:
图例 1
图解说明:注解 1-1
表示 Norton AntiVirus的自动防护功能被关闭;
注解 1-2为病毒程序运行主界面,本实验为了能够比较直观演示病毒程序,让用户知道正在运行某个程序,故而有此主界面;但实际的病毒在感染其他程序前不会让用户感觉到病毒程序正在运行的,往往都是隐藏运行,或者是寄生在宿主程序中,这方面可参照病毒的引导机制的介绍。
演示说明:详见备注
提示用户是否观看感染过程,如下图:
图例 2
图解说明:注解 2-1
提示用户是否观看病毒的感染过程,选择“是”
观看感染过程,选择
“否”运行原程序,由于该病毒程序不具有其他功能,所以选“否”
时就直接关闭程序。
演示说明:该文件型病毒侧重于病毒感染过程的演示,所以在感染部分的提示比较详细。
步骤 2
步骤 3:
开始感染提示,如下图:
图例 3
图解说明:注解 3-1
提示用户病毒程序开始感染其他程序。
演示说明:无步骤 4:
提示病毒感染范围,如下图:
图例 4
图解说明:注解 4-1
提示用户病毒程序感染目标是病毒程序所在目录里的程序。
演示说明:为了减少病毒破坏的范围,在编写该病毒程序时,限定其感染范围为目录内感染,这也同时减少了病毒的破坏范围;但就病毒特征而言,它还是很好的体现了病毒程序自我复制的这一特征。
步骤 5:
提示当前搜索到的合法的目标程序,如下图:
图例 5
图解说明:注解 5-1
提示说明当前搜索的目标程序是 ebookedit.exe
可执行文件。
演示说明:无步骤 6:
判断目标程序是否是合法的可感染的程序,如下图:
图例 6
图解说明:注解 6-1
提示目标程序是否为合法的可感染程序。
演示说明:
Virus.exe可以感染的目标程序为 PE文件中的可执行文件 (.exe),对于文件型病毒传染机理可参照病毒的传染机制。
步骤 7:
感染情况说明提示,如下图:
图例 7
图解说明:注解 7-1
提示对病毒的感染情况进行说明。
演示说明:
Virus.exe感染方式是在宿主文件附加一个节
(4K),被感染的宿主程序运行时先跳转到该节处,
运行感染代码;感染结束后再返回宿主程序的入口地址执行宿主程序。
这也体现了本病毒程序的破坏方式,作为一个演示的病毒程序,破坏程度应该在可控范围内,
一些恶性的破坏方式可参照病毒的破坏机制说明。感染结果情况可参照 步骤 12的图例,
Virus.exe被防病毒软件查出为病毒也正是因为该操作,具体参照 步骤
18的图例。
步骤 8:
感染过程隐藏说明 (小技巧 ),如下图:
图例 8
图解说明:注解 8-1
提示说明病毒程序加入中 sleep的代码的目的。
演示说明:此处加入该说明是也是为了说明是否存在病毒运行的一种判断思路,在手工查毒时有时就是依照该手段来判断当前机器是否有异常程序在运行;
如果机器没有明显的程序在运行,而硬盘的指示灯一直闪烁,在高速运转着,则可粗略判断机器有异常程序在运行。
防病毒的相关技术可参照防病毒基础技术部分介绍。当然对于病毒程序本身而言,为了避免被发现,就需要通过
sleep一段时间再进行感染来降低被发现的可能性。
步骤 9:
提示是否观看其他程序感染过程,如下图:
图例 9
图解说明:注解 9-1
提示是否观看其他程序感染过程。
演示说明:无步骤 10:
病毒感染结束或用户取消操作的提示,如下图:
图例 10
图解说明:注解 10-
1是病毒感染结束或用户取消操作后的提示。
演示说明:感染结束或用户取消后就会运行宿主程序。如果我们隐藏前面介绍的这些步骤的对话框和主界面,
则给用户的感觉就是运行了一个正常的程序,
只是程序启动运行的时间相对慢了一点,这也就体现了病毒程序的潜伏性这一特征。
步骤 11:
该图例与步骤 6的图例同步,该图例是目标程序不合法或已被感染后的提示,如下图:
图例 11
图解说明:注解 11-1
是目标程序不合法或已被感染的提示。
演示说明:
Virus.exe可感染的目标为标准的可执行文件
(绝大部分的,exe文件 ),
在感染过程中对已感染的程序会进行检查是否已被感染,若已经被感染则不再进行感染:一可以提高病毒感染的效率,二可以防止多次感染使文件增大而引起用户的察觉。
步骤 12:
比较目标程序感染前后的变化 (注要指程序大小 ),如下图 (图例 12;图例 12A和图例 12B;
图例 12C和图例 12D):
图例 12
图解说明:注解 12-
1是 KeyMaker.exe程序感染前的大小 298
KB(305,664 字节 );注解 12-2是
KeyMaker.exe程序感染后的大小 302
KB(309,760)。
步骤 12:
比较目标程序感染前后的变化 (注要指程序大小 ),如下图 (图例 12;图例 12A和图例 12B;
图例 12C和图例 12D):
图例 12A
步骤 12:
比较目标程序感染前后的变化 (注要指程序大小 ),如下图 (图例 12;图例 12A和图例 12B;
图例 12C和图例 12D):
图例 12B
图解说明:图例
12A,12B,12C,12D
是通过 PE Viewer程序打开目标程序的图示。
对照图例 12A和 12B的注解:注解 1表示
KeyMaker.exe程序的节数由感染前的 9个增加到 10个;注解 2程序大小的变化由
00043000增加到
00044000;注解 3是程序入口地址的变化由
00043DE8变为
00054B93;注解 4是程序占用空间的变化由
00054000增加到
00055000;注解 5是程序校验和的变化由
0004C1FB变为
0004C543。
步骤 12:
比较目标程序感染前后的变化 (注要指程序大小 ),如下图 (图例 12;图例 12A和图例 12B;
图例 12C和图例 12D):
图例 12C
步骤 12:
比较目标程序感染前后的变化 (注要指程序大小 ),如下图 (图例 12;图例 12A和图例 12B;
图例 12C和图例 12D):
图例 12D
图解说明:对照图例 12C和 12D的注解:
注解 1表示增加的一个节的具体内容。
演示说明:这就是程序被感染后的结果,
目标程序被增加一个节
(4 KB=4096字节 ),程序入口地址被修改,程序大小和占有空间增加
4K。
步骤 13:
测试感染后程序是否为病毒携带程序的图示,如下图:
图例 13
图解说明:注解 13-
1表示 Norton AntiVirus
的自动防护功能打开着;
注解 13-2就是防病毒软件在用户鼠标置于图例
13中注解 13-3处的
KeyMaker.exe程序上时的报警提示。
演示说明:由此病毒报警提示可知
KeyMaker.exe已被感染,成为病毒 Virus.exe
携带者,并已具有病毒程序 Virus.exe的特征。
此处为了防止 Virus.exe
影响测试结果,在测试时把 Virus.exe放到临时目录 temp中,主目录留下被 Virus感染的程序。
步骤 14:
病毒携带程序 KeyMaker.exe的运行情况,如下图:
图例 14
图解说明:注解 14-
1表示 Norton AntiVirus
的自动防护功能被关闭;
注解 14-2为病毒携带程序 KeyMaker.exe运行初始界面;注解 14-3表示当前运行是
KeyMaker.exe。
演示说明:运行
KeyMaker.exe来检查程序被感染的情况以及该程序是否具有感染功能,在演示时为了能够测试效果,应该将其他的可执行文件换成未被感染的程序,只需从原先的测试包中将除
KeyMaker.exe外的可执行文件拷贝过来复制即可。
步骤 15:
是否观看病毒携带程序 KeyMaker.exe的感染过程提示,如下图:
图例 15
图解说明:注解 15-
1是否观看病毒携带程序 KeyMaker.exe的感染过程提示。选择“是”
观看感染过程,参照 步骤 16图示;选择“否”
运行原程序,参照 步骤
17图示。
演示说明:无步骤 16:
病毒携带程序 KeyMaker.exe的开始感染提示,如下图:
图例 16
图解说明:注解 16-
1提示病毒携带程序
KeyMaker.exe的开始感染其他程序,后面的步骤与 步骤 4开始类似。
演示说明:无步骤 17:
病毒携带程序 KeyMaker.exe不进行感染运行主程序图示,如下图:
图例 17
图解说明:注解 17-
1是病毒携带程序
KeyMaker.exe的运行界面。
演示说明:无步骤 18:
病毒携带程序 ebookedit.exe的感染过程被防病毒程序检测到的图示,如下图:
图例 18
图解说明:注解 18-
1表示 Norton AntiVirus
的自动防护功能打开着;
注解 18-2是目标感染程序 (此处是刚复制过来的未感染的测试程序 );
注解 18-3表示正在运行的是病毒携带程序
ebookedit.exe;注解
18-4提示当前感染过程结束,在宿主程序
ebookcode.exe增加一个节,修改了文件头;
注解 18-5提示该过程被检测有病毒程序在运行,
从而进行病毒预警提示。
演示说明:详见备注
3从 ring3到 ring0概述
Win9x时代
– 由于 Win9x未对 IDT,GDT,LDT加以保护,我们可以利用这一点漏洞来进入 ring0。
– 用 SHE,IDT,GDT,LDT等方法进入 ring0的例子清参考 CVC杂志、已公开的病毒源码和相关论坛等。
在 NT/2K/XP时代
– webcrazy写的 Win2K下进入 ring0的 C教程,这篇文章非常值得研究 ring0病毒的技术人员参考。
– 由于 Win2K已经有了比较多的安全审核机制,即使我们掌握了这种技术,如果想在 Win2K下进入 ring0还必须具有 Administrator权限。
– 我们必须同时具备病毒编制技术和黑客技术才能进入
Win2k的 ring0,由此可以看出当前的病毒编制技术越来越需要综合能力。