1.2 4.PE结构、SEH相关知识掌握
今天这节课程中,我们来讲解一下部分PE结构,以及SEH[Structured Exception Handling(结构化异常处理)]的知识。因为它在我们病毒编写中也是非常重要的。想象一下,如果我们连我们的被感染对象的文件格式都不熟悉的话,我们如何感染? 那么在上几节课程中,我们在文章中已经穿插了部分PE结构的内容,今天我们就来再深入的熟悉一下PE结构以及SEH的相关知识,为我们后面的文章做好铺垫。
1. 抛弃链接器, 手工定义PE结构程序:
当然我不喜欢很传统的给大家罗列一堆PE结构,然后来给大家讲解结构成员,我还是喜欢实际的的编写。所以这节课程,我们就通过编写一个自定义PE结构的程序来让大家充分的了解PE结构。(实际上在fasm中也就是抛弃fasm所有的宏,然后以二进制数据形式编译)
好了,我们了解下我们的PE结构组成部分。
|----------------|
|IMAGE_DOS_HEADER| ------ IMAGE_DOS_HEADER
|----------------|
| Dos STUB | ------ 16位DOS 代码
| |
|----------------|
| | ------ Signature
| |
|IMAGE_NT_HEADERS| ------ IMAGE_FILE_HEADER
| |
| | ------ IMAGE_OPTIONAL_HEADER32
|----------------|
| |
| SECTION_HEADER |
| | ------ 节表结构
|----------------|
| |
| SECTION | ------ 节
| |
|----------------|
OK,了解了以上后,我们就来手工的定义一个PE文件结构的程序,我们仅让它拥有一个节。那么接下来我们来一步一步的构造整个文件结构,首先是IMAGE_DOS_HEADER, 然后是Dos STUB[要不要都行],喜欢的就加上吧,...........一直到Section。
我们在开始之前要定义几个常量,方便后续的添加。首先是我们的基地址。我们定义为
Image_Base = 00400000h
然后是我们内存对齐值我们设置为1000h,以及我们的节映射到内存基于基地址的偏移 我们也设置为 1000h。那么我们定义
Code_Base = 1000h
好了,接下来我们就开始一步步的定义文件结构, 首先是IMAGE_DOS_HEADER 结构。
struct IMAGE_DOS_HEADER
e_magic dw ?
e_cblp dw ?
e_cp dw ?
e_crlc dw ?
e_cparhdr dw ?
e_minalloc dw ?
e_maxalloc dw ?
e_ss dw ?
e_sp dw ?
e_csum dw ?
e_ip dw ?
e_cs dw ?
e_lfarlc dw ?
e_ovno dw ?
e_res dw 4 dup(?)
e_oemid dw ?
e_oeminfo dw ?
e_res2 dw 10 dup(?)
e_lfanew dd ?
ends
IMAGE_DOS_HEADER结构中最重要的两个成员是e_magic,和e_lfanew。前者是两个字节的“MZ”标识符,后者是4个字节的指向IMAGE_NT_HEADERS结构的文件偏移。所以其余成员我们可以以0代替。定义如下
DOS_HEADER:
.e_magic db 'MZ'
.e_cblp dw 0
.e_cp dw 0
.e_crlc dw 0
.e_cparhdr dw 0
.e_minalloc dw 0
.e_maxalloc dw 0
.e_ss dw 0
.e_sp dw 0
.e_csum dw 0
.e_ip dw 0
.e_cs dw 0
.e_lfarlc dw 0
.e_ovno dw 0
.e_res dw 4 dup(0)
.e_oemid dw 0
.e_oeminfo dw 0
.e_res2 dw 10 dup(0)
.e_lfanew dd NT_HEADERS
;-------------------------------------
Dos_Stub:
mov ah, 4ch
int 21h
;------------------------------------
NT_HEADERS:
OK,我们定义完DOS_HEADER,我们来定义NT_HEADERS结构。
[1]:
struct IMAGE_NT_HEADERS
Signature dd ?
FileHeader IMAGE_FILE_HEADER
OptionalHeader IMAGE_OPTIONAL_HEADER32
ends
1. 首先第一个成员是Signature4个字节PE文件标志,它被定义为00004550h。ASCII码表示法则为dd “PE”。
2. 紧接着是我们的FileHeader成员。
[2]:
struct IMAGE_FILE_HEADER
Machine dw ?
NumberOfSections dw ?
TimeDateStamp dd ?
PointerToSymbolTable dd ?
NumberOfSymbols dd ?
SizeOfOptionalHeader dw ?
Characteristics dw ?
ends
(1): 第一个2字节成员Machine,表示运行平台。我们一般设置为14ch,表示 Intel I386。
(2): 第二个2字节成员NumberOfSections,表示我们的节数量,这个程序中我们设置一个Section,所以我们填写为1。
(3): 3 - 5 , 4字节成员因为没有什么用,所以我们设置为0.TimeDateStamp(文件创建时间) ,PointerToSymbolTable(COFF符号表的文件偏移量),NumberOfSymbols(符号表符号数量)。
(4): SizeOfOptionalHeader成员表示的是下面IMAGE_OPTIONAL_HEADER32结构的字节大小。我们用默认的IMAGE_OPTIONAL_HEADER32字节大小,0E0h。
(5): Characteristics成员表示的是文件的属性。我们普通的EXE文件一般是010fh。
[3:]
接下来是我们的OptionalHeader结构成员。
struct IMAGE_OPTIONAL_HEADER32
Magic dw ?
MajorLinkerVersion db ?
MinorLinkerVersion db ?
SizeOfCode dd ?
SizeOfInitializedData dd ?
SizeOfUninitializedData dd ?
AddressOfEntryPoint dd ?
BaseOfCode dd ?
BaseOfData dd ?
ImageBase dd ?
SectionAlignment dd ?
FileAlignment dd ?
MajorOperatingSystemVersion dw ?
MinorOperatingSystemVersion dw ?
MajorImageVersion dw ?
MinorImageVersion dw ?
MajorSubsystemVersion dw ?
MinorSubsystemVersion dw ?
Win32VersionValue dd ?
SizeOfImage dd ?
SizeOfHeaders dd ?
CheckSum dd ?
Subsystem dw ?
DllCharacteristics dw ?
SizeOfStackReserve dd ?
SizeOfStackCommit dd ?
SizeOfHeapReserve dd ?
SizeOfHeapCommit dd ?
LoaderFlags dd ?
NumberOfRvaAndSizes dd ?
DataDirectory IMAGE_DATA_DIRECTORY
rb sizeof.IMAGE_DATA_DIRECTORY*15
ends
(1): 首先第一个成员.Magic是一个标记,一般我们普通的可执行文件,这个标记值为 010Bh。
(2): 下面两个1字节MajorLinkerVersion一般为0.它表示为我们的链接程序的版本号。
(3): SizeOfCode表示代码段的大小。这个大小值是代码段对齐后的值。这里我们设置的文件对齐值是200h,所以我们这里填写为200h。(也就是512byte)。
(4): SizeOfInitializedData表示初始化数据的字节大小,不包括代码段。我们这里填写为0。
(5): SizeOfUninitializedData表示未初始化数据的字节大小。即我们常说的.bbs节,这里我们没用。所以填写0。
(6): AddressOfEntryPoint成员指向我们程序的OEP,这个值是一个RVA地址,相对于我们基地址的偏移。这里我们把这个值设置为我们定义的Code节的偏移。由于我们Code段节我们设置它映射内存的虚拟地址是1000h,所以我们需要通过这个1000h - Code段文件偏移 = 相对于Code段的偏移。 通过这个相对偏移 + 文件偏移 = RVA地址。我们把这个相对偏移定义为Rva_Del = Code_Base - @Code。
(7): BaseOfCode表示代码段起始RVA地址。我们直接用Code_Base即可。
(8): BaseOfData表示数据段的起始RVA地址。我们可以设置为0.
(9): ImageBase表示我们的基地址。我们这里设置我们上面定义好的Image_Base。
(10): MajorOperatingSystemVersion和MinorOperatingSystemVersion表示的是操作系统的主版本号,和次版本好,我们一般用不上。所以这两个成员可以设置为0.
(11): MajorImageVersion, MinorImageVersion 表示用户自定义主、次版本号。对我们来说没用,设置为0.
(12): MajorSubsystemVersion和MinorSubsystemVersion表示最低子系统版本号,我们通常设置MajorSubsystemVersion为4。
(13): Win32VersionValue通常是保留值,一般为0.
(14): SizeOfImage表示映像文件载入内存后的总大小。我们这里一个节 + 我们的PE头部。我们设置为2000h
(15): SizeOfHeaders表示size(DosHeader + DosStub + NtHeader + Section Header)
(16): CheckSum效验和值。一般EXE文件为0.
(17): Subsystem表示子系统值。 2 GUI, 3 CUI。
(18): DllCharacteristics指定DLLMain函数何时被调用。默认0.
(19): SizeOfStackReserve 在exe文件中默认的线程堆栈保留大小。这里我们设置为10000h,因为它在需要时才被提交。
(20): SizeOfStackCommit 表示开始委派的堆栈大小。默认是4KB。
(21): SizeOfHeapReserve表示在exe文件中默认的堆空间保留大小,同样我们设置为10000h。
(22): SizeOfHeapCommit表示开始委派的堆空间大小。默认是4KB。
(23): LoaderFlags默认为0。
(24): NumberOfRvaAndSizes表示下面数据目录段的成员数量。10h
(25): DataDirectory数据目录表。表中成员为16个IMAGE_DATA_DIRECTORY结构的成员。我们不需要任何结构,这里全部填0.
[4:]
接下来是节表结构。
struct IMAGE_SECTION_HEADER
Name1 rb 8
union
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ?
NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
ends
(1):首先第一个8字节成员表示节名称。
(2):VirtualSize表示节映射内存的大小。我们这里填写为1000h。
(3):VirtualAddress表示节空间映射到内存基于基地址的偏移。这里我们填写我们之前定义好的Code_Base。
(4):SizeOfRawData表示节在磁盘空间中的大小。我们通过定义节代码指令的最后偏移 - 节起始偏移即可求出。
(5): PointerToRawData表示节所在文件中的物理文件偏移。我们直接用我们定义的@Code 标号即可。
(6): PointerToRelocations PointerToLinenumbers NumberOfRelocations 没什么用处直接填充0.
(7): Characteristics表示节属性,这个属性很重要,它决定了节空间映射到内存后的内存空间属性。我们这里用E0000020(可读可写可执行)
OK.到这里我们的结构就完成了,然后就是在我们的代码段偏移中写代码了。我们仅写一个ret指令。
OK。我们来看下我们最后的程序代码。
;----------------------------------------- ; Format Pe File By:XFish ;------------------------------------------ Code_Base = 1000h File_Algin = 200h Rva_Del = Code_Base - @Code Image_Base = 00400000h ;________________________________________________________________ DOS_HEADER: .e_magic db 'MZ' .e_cblp dw 0 .e_cp dw 0 .e_crlc dw 0 .e_cparhdr dw 0 .e_minalloc dw 0 .e_maxalloc dw 0 .e_ss dw 0 .e_sp dw 0 .e_csum dw 0 .e_ip dw 0 .e_cs dw 0 .e_lfarlc dw 0 .e_ovno dw 0 .e_res dw 4 dup(0) .e_oemid dw 0 .e_oeminfo dw 0 .e_res2 dw 10 dup(0) .e_lfanew dd NT_HEADERS Dos_Stub: mov ah, 4ch int 21h NT_HEADERS: .Signature dd 'PE' ;++ ;IMAGE_FILE_HEADER .Machine dw 14ch ;.Inten I386 .NumberOfSections dw 1 .TimeDateStamp dd 0 .PointerToSymbolTable dd 0 .NumberOfSymbols dd 0 .SizeOfOptionalHeader dw 0E0h .Characteristics dw 010fh ;.exe Signature ;-- ;++ ;IMAGE_OPTIONAL_HEADER32 .Magic dw 10Bh ;.exe file .MajorLinkerVersion db 0 .MinorLinkerVersion db 0 .SizeOfCode dd File_Algin .SizeOfInitializedData dd 0 .SizeOfUninitializedData dd 0 .AddressOfEntryPoint dd @Code + Rva_Del ;oep .BaseOfCode dd Code_Base ;Code Section Rva .BaseOfData dd 0 ;Data Section Rva .ImageBase dd Image_Base .SectionAlignment dd Code_Base ;Section Mem Align .FileAlignment dd File_Algin ;Section Disk Align .MajorOperSystemVersion dw 0 ;system version 主 .MinorOperSystemVersion dw 0 ;system version 次 .MajorImageVersion dw 0 ;user version 主 .MinorImageVersion dw 0 ;user version 次 .MajorSubsystemVersion dw 4 .MinorSubsystemVersion dw 0 .Win32VersionValue dd 0 ;Reserved 0 .SizeOfImage dd 2000h .SizeOfHeaders dd File_Algin ;DosHeader + DosStub + NtHeader + Section Header .CheckSum dd 0 .SubSystem dw 2 ;Gui .DllCharacteristics dw 0 .SizeOfStackReserve dd 10000h .SizeOfStackCommit dd 1000h ;Stack = 4kb .SizeOfHeapReserve dd 10000h .SizeOfHeapCommit dd 1000h ;Heap = 4kb .LoaderFlags dd 0 .NumberOfRvaAndSizes dd 10h ;16 .Export rd 2 .Import rd 2 .Misc_Section rd 28 _SECTION_HEADER: .Name1 db '.code', 0, 0, 0 .VirtualSize dd 1000h .VirtualAddress dd Code_Base .SizeOfRawData dd @Code_End - @Code .PointerToRawData dd @Code .PointerToRelocations dd 0 .PointerToLinenumbers dd 0 .NumberOfRelocations dw 0 .NumberOfLinenumbers dw 0 .Characteristics dd 0E0000020h align 200h ;------------------------------------ @Code: ret @Code_End:
好了,通过以上的编码,相信你对PE结构已经很熟悉了。那么我们继续往下吧,我们来学习SEH技术的利用。
(2): SEH技术的利用。
关于seh技术,很久之前Hume就总结过了,所以我在这里不想过多总结了。大家看可以在这个地址中看下Hume的文章。
http://www.pediy.com/bbshtml/bbs4/kanxue310.htm
我要讲解下关于seh技术的利用,例如如何在我们的汇编自程序当中利用技巧,方便的排错。
传统的SEH的框架我想我就不多说了。
如下:
push SEH_Handler
push dword ptr fs:[0]
mov [fs:0], esp
`
大家看如下代码:
call .SetupSEH .ErrorHandler: mov esp, [esp+8] ;[esp+8],指向lpseh。lpseh是我们EXCEPTION_REGISTRATION结构的地址。 xor eax, eax pop dword [fs:eax] ;取出EXCEPTION_REGISTRATION->prev ;前一个_EXCEPTION_REGISTRATION结构 pop ecx ;修正堆栈 ret ;ExitThread .SetupSEH: xor edx, edx push dword [fs:edx] mov [fs:edx], esp int 3
OK,写一段我们自定义PE文件结构的异常处理过程代码。
;----------------------------------------- ; Format Pe File By:XFish ;------------------------------------------ include 'win32a.inc' Code_Base = 1000h File_Algin = 200h Rva_Del = Code_Base - @Code Image_Base = 00400000h ;________________________________________________________________ DOS_HEADER: .e_magic db 'MZ' .e_cblp dw 0 .e_cp dw 0 .e_crlc dw 0 .e_cparhdr dw 0 .e_minalloc dw 0 .e_maxalloc dw 0 .e_ss dw 0 .e_sp dw 0 .e_csum dw 0 .e_ip dw 0 .e_cs dw 0 .e_lfarlc dw 0 .e_ovno dw 0 .e_res dw 4 dup(0) .e_oemid dw 0 .e_oeminfo dw 0 .e_res2 dw 10 dup(0) .e_lfanew dd NT_HEADERS Dos_Stub: mov ah, 4ch int 21h NT_HEADERS: .Signature dd 'PE' ;++ ;IMAGE_FILE_HEADER .Machine dw 14ch ;.Inten I386 .NumberOfSections dw 1 .TimeDateStamp dd 0 .PointerToSymbolTable dd 0 .NumberOfSymbols dd 0 .SizeOfOptionalHeader dw 0E0h .Characteristics dw 010fh ;.exe Signature ;-- ;++ ;IMAGE_OPTIONAL_HEADER32 .Magic dw 10Bh ;.exe file .MajorLinkerVersion db 0 .MinorLinkerVersion db 0 .SizeOfCode dd File_Algin .SizeOfInitializedData dd 0 .SizeOfUninitializedData dd 0 .AddressOfEntryPoint dd @Code + Rva_Del ;oep .BaseOfCode dd Code_Base ;Code Section Rva .BaseOfData dd 0 ;Data Section Rva .ImageBase dd Image_Base .SectionAlignment dd Code_Base ;Section Mem Align .FileAlignment dd File_Algin ;Section Disk Align .MajorOperSystemVersion dw 0 ;system version 主 .MinorOperSystemVersion dw 0 ;system version 次 .MajorImageVersion dw 0 ;user version 主 .MinorImageVersion dw 0 ;user version 次 .MajorSubsystemVersion dw 4 .MinorSubsystemVersion dw 0 .Win32VersionValue dd 0 ;Reserved 0 .SizeOfImage dd @Code_End - @Code + 1000h .SizeOfHeaders dd File_Algin ;DosHeader + DosStub + NtHeader + Section Header .CheckSum dd 0 .SubSystem dw 2 ;Gui .DllCharacteristics dw 0 .SizeOfStackReserve dd 10000h .SizeOfStackCommit dd 1000h ;Stack = 4kb .SizeOfHeapReserve dd 10000h .SizeOfHeapCommit dd 1000h ;Heap = 4kb .LoaderFlags dd 0 .NumberOfRvaAndSizes dd 10h ;16 .Export rd 2 .Import rd 2 .Misc_Section rd 28 _SECTION_HEADER: .Name1 db '.code', 0, 0, 0 .VirtualSize dd 1000h .VirtualAddress dd Code_Base .SizeOfRawData dd @Code_End - @Code .PointerToRawData dd @Code .PointerToRelocations dd 0 .PointerToLinenumbers dd 0 .NumberOfRelocations dw 0 .NumberOfLinenumbers dw 0 .Characteristics dd 0E0000020h align 200h ;------------------------------------ @Code: db 0E8h ;call opcode dd SetupSEH - ErrorHandler ErrorHandler: db 8bh db 64h db 24h db 08h ;mov esp, [esp+8] db 31h db 0c0h ;xor eax, eax db 64h db 8fh db 00 ;pop dword [fs:0] db 59h ;pop ecx ret SetupSEH: db 31h db 0d2h ;xor edx, edx db 64h db 0ffh db 32h ;push dword [fs:0] db 64h db 89h db 22h ;mov dword [fs:0], esp db 0cdh db 03h ;int 3 ret @Code_End: