文件格式(或文件类型)是指电脑为了存储信息而使用的对信息的特殊编码方式,是用于识别内部储存的资料。比如有的储存图片,有的储存程序,有的储存文字信息。每一类信息,都可以一种或多种文件格式保存在电脑存储中。每一种文件格式通常会有一种或多种扩展名可以用来识别,但也可能没有扩展名。扩展名可以帮助应用程序识别的文件格式。
对于硬盘机或任何电脑存储来说,有效的信息只有0和1两种。所以电脑必须设计有相应的方式进行信息-位元的转换。对于不同的信息有不同的存储格式。
有些文件格式被设计用于存储特殊的数据,例如:图像文件中的JPEG文件格式仅用于存储静态的图像,而GIF既可以存储静态图像,也可以存储简单动画;Quicktime格式则可以存储多种不同的媒体类型。文本类的文件有:txt文件一般仅存储简单没有格式的ASCII或Unicode的文本;HTML文件则可以存储带有格式的文本;PDF格式则可以存储内容丰富的,图文并茂的文本。
同一个文件格式,用不同的程序处理可能产生截然不同的结果。例如Word 文件,用Microsoft Word观看的时候,可以看到文本的内容,而以无格式方式在音乐播放软件中播放,产生的则是噪声。一种文件格式对某些软件会产生有意义的结果,对另一些软件来看,就像是毫无用途的数字垃圾。
用扩展名识别文件格式的方式最先在数字设备公司的CP/M操作系统被采用。而后又被DOS和Windows操作系统采用。扩展名是指文件名中,最后一个点(.)号后的字母序列。例如,HTML文件通过.htm或.html扩展名识别;GIF图形文件用.gif扩展名识别。
而不同的文件格式是如何被机器识别的呢?毕竟我们在桌面上,把后缀名从(.txt)改成(.jpg),虽然是把这个文档改成了图片,但是其实我们都知道这时候双击打开它,并不能显示出一张图片。那计算机到底如何判断的这个文件是什么呢?实际上,不同的文件格式有独特属于它自己的文件格式标志。在文件一开始的地方,会存着对应文件的标志,由它决定了这个文件是什么类型的文件。判断出文件的类型后,再根据相应类型的文件格式,对后面的内容进行解析。当我们用十六进制编辑器的时候就可以看到这一切。
- PE(exe,dll)文件头:4D5A
-
- JPEG (jpg),文件头:FFD8FF
-
- PNG (png),文件头:89504E47
-
- GIF (gif),文件头:47494638
-
- TIFF (tif),文件头:49492A00
-
- Windows Bitmap (bmp),文件头:424D
-
- CAD (dwg),文件头:41433130
-
- Adobe Photoshop (psd),文件头:38425053
-
- Rich Text Format (rtf),文件头:7B5C727466
-
- XML (xml),文件头:3C3F786D6C
-
- HTML (html),文件头:68746D6C3E
-
- Email [thorough only] (eml),文件头:44656C69766572792D646174653A
-
- Outlook Express (dbx),文件头:CFAD12FEC5FD746F
-
- Outlook (pst),文件头:2142444E
-
- MS Word/Excel (xls.or.doc),文件头:D0CF11E0
-
- MS Access (mdb),文件头:5374616E64617264204A
-
- WordPerfect (wpd),文件头:FF575043
-
- Postscript (eps.or.ps),文件头:252150532D41646F6265
-
- Adobe Acrobat (pdf),文件头:255044462D312E
-
- Quicken (qdf),文件头:AC9EBD8F
-
- Windows Password (pwl),文件头:E3828596
-
- ZIP Archive (zip),文件头:504B0304
-
- RAR Archive (rar),文件头:52617221
-
- Wave (wav),文件头:57415645
-
- AVI (avi),文件头:41564920
-
- Real Audio (ram),文件头:2E7261FD
-
- Real Media (rm),文件头:2E524D46
-
- MPEG (mpg),文件头:000001BA
-
- MPEG (mpg),文件头:000001B3
-
- Quicktime (mov),文件头:6D6F6F76
-
- Windows Media (asf),文件头:3026B2758E66CF11
-
- MIDI (mid),文件头:4D546864
PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,NT头,节表以及具体的节
DOS头的作用是兼容MS-DOS操作系统中的可执行文件,对于32位PE文件来说,DOS所起的作用就是显示一行文字,提示用户:我需要在32位windows上才可以运行。我认为这是个善意的玩笑,因为他并不像显示的那样不能运行,其实已经运行了,只是在DOS上没有干用户希望看到的工作而已,好吧,我承认这不是重点。但是,至少我们看一下这个头是如何定义的:
我们只需要关注两个域:
e_magic:一个WORD类型,值是一个常数0x4D5A,用文本编辑器查看该值位‘MZ’,可执行文件必须都是'MZ'开头。
e_lfanew:为32位可执行文件扩展的域,用来表示DOS头之后的NT头相对文件起始地址的偏移。
顺着DOS头中的e_lfanew,我们很容易可以找到NT头,这个才是32位PE文件中最有用的头,定义如下:
下图是一张真实的PE文件头结构以及其各个域的取值:
Signature:类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是'PE’。
IMAGE_FILE_HEADER是PE文件头,c语言的定义是这样的:
每个域的具体含义如下:
Machine:该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。
NumberOfSections:该PE文件中有多少个节,也就是节表中的项数。
TimeDateStamp:PE文件的创建时间,一般有连接器填写。
PointerToSymbolTable:COFF文件符号表在文件中的偏移。
NumberOfSymbols:符号表的数量。
SizeOfOptionalHeader:紧随其后的可选头的大小。
Characteristics:可执行文件的属性,可以是下面这些值按位相或。
可以看出,PE文件头定义了PE文件的一些基本信息和属性,这些属性会在PE加载器加载时用到,如果加载器发现PE文件头中定义的一些属性不满足当前的运行环境,将会终止加载该PE。
另一个重要的头就是PE可选头,别看他名字叫可选头,其实一点都不能少,不过,它在不同的平台下是不一样的,例如32位下是IMAGE_OPTIONAL_HEADER32,而在64位下是IMAGE_OPTIONAL_HEADER64。为了简单起见,我们只看32位。
Magic:表示可选头的类型。
MajorLinkerVersion和MinorLinkerVersion:链接器的版本号。
SizeOfCode:代码段的长度,如果有多个代码段,则是代码段长度的总和。
SizeOfInitializedData:初始化的数据长度。
SizeOfUninitializedData:未初始化的数据长度。
AddressOfEntryPoint:程序入口的RVA,对于exe这个地址可以理解为WinMain的RVA。对于DLL,这个地址可以理解为DllMain的RVA,如果是驱动程序,可以理解为DriverEntry的RVA。当然,实际上入口点并非是WinMain,DllMain和DriverEntry,在这些函数之前还有一系列初始化要完成,当然,这些不是本文的重点。
BaseOfCode:代码段起始地址的RVA。
BaseOfData:数据段起始地址的RVA。
ImageBase:映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。
SectionAlignment:节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。
FileAlignment:节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。
MajorOperatingSystemVersion、MinorOperatingSystemVersion:所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。
MajorImageVersion、MinorImageVersion:映象的版本号,这个是开发者自己指定的,由连接器填写。
MajorSubsystemVersion、MinorSubsystemVersion:所需子系统版本号。
Win32VersionValue:保留,必须为0。
SizeOfImage:映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
SizeOfHeaders:所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。
CheckSum:映象文件的校验和。
Subsystem:运行该PE文件所需的子系统,可以是下面定义中的某一个:
SizeOfStackReserve:运行时为每个线程栈保留内存的大小。
SizeOfStackCommit:运行时每个线程栈初始占用内存大小。
SizeOfHeapReserve:运行时为进程堆保留内存大小。
SizeOfHeapCommit:运行时进程堆初始占用内存大小。
LoaderFlags:保留,必须为0。
NumberOfRvaAndSizes:数据目录的项数,即下面这个数组的项数。
DataDirectory:数据目录,这是一个数组,数组的项定义如下:
VirtualAddress:是一个RVA。
Size:是一个大小。
这两个数有什么用呢?一个是地址,一个是大小,可以看出这个数据目录项定义的是一个区域。那他定义的是什么东西的区域呢?前面说了,DataDirectory是个数组,数组中的每一项对应一个特定的数据结构,包括导入表,导出表等等,根据不同的索引取出来的是不同的结构,头文件里定义各个项表示哪个结构,如下面的代码所示:
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
提示:可以使用16进制编辑器进行查看哦
壳是在一些计算机软件里也有一段专门负责保护软件不被非法修改或反编译的程序。
它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。
我们通常将 壳 分为两类,一类是压缩壳,另一类是加密壳。
压缩壳:
压缩壳早在 DOS 时代就已经出现了,但是当时因为计算能力有限,解压开销过大,并没有得到广泛的运用。
使用压缩壳可以帮助缩减 PE 文件的大小,隐藏了 PE 文件内部代码和资源,便于网络传输和保存。
通常压缩壳有两类用途,一种只是单纯用于压缩普通 PE 文件的压缩壳,而另一种则会对源文件进行较大变形,严重破坏 PE 文件头,经常用于压缩恶意程序。
常见的压缩壳有:Upx、ASpack、PECompat
加密壳 :
加密壳或称保护壳,应用有多种防止代码逆向分析的技术,它最主要的功能是保护 PE 免受代码逆向分析。
由于加密壳的主要目的不再是压缩文件资源,所以加密壳保护的 PE 程序通常比原文件大得多。
目前加密壳大量用于对安全性要求高,对破解敏感的应用程序,同时也有恶意程序用于避免(降低)杀毒软件的检测查杀。
常见的加密壳有:ASProtector、Armadillo、EXECryptor、Themida、VMProtect
壳的加载过程 :
1、保存入口参数
①加壳程序初始化时保存各寄存器的值
②外壳执行完毕,恢复各寄存器值
③最后再跳到原程序执行
通常用 pushad / popad、pushfd / popfd 指令对来保存和恢复现场环境
2、获取所需函数 API
①一般壳的输入表中只有 GetProcAddress、GetModuleHandle 和 LoadLibrary 这几个API 函数
②如果需要其他 API 函数,则通过 LoadLibraryA(W) 或 LoadLibraryExA(W) 将 DLL 文件映射到调用进程的地址空间中
③如果 DLL 文件已被映射到调用进程的地址空间里,就可以调用 GetModuleHandleA(W) 函数获得 DLL 模块句柄
④一旦 DLL 模块被加载,就可以调用 GetProcAddress 函数获取输入函数的地址
3、解密各区块数据
①处于保护源程序代码和数据的目的,一般会加密源程序文件的各个区块。在程序执行时外壳将这些区块数据解密,以让程序正常运行
②外壳一般按区块加密,按区块解密,并将解密的数据放回在合适的内存位置
4、跳转回原程序入口点
①在跳转回入口点之前,一般会恢复填写原 PE 文件输入表(IAT),并处理好重定位项(主要是 DLL 文件)
②因为加壳时外壳自己构造了一个输入表,因此在这里需要重新对每一个 DLL 引入的所有函数重新获取地址,并填写到 IAT 表中
③做完上述工作后,会将控制权移交原程序,并继续执行
火绒剑(无法与深信服EDR同一环境下使用)、procexp、processminer等
ExefoPE、 DetectItEasy
Ollydbg(只能分析32位程序)、 X64dbg、 Windbg、 gdb(linux)
IDApro
HashMyFiles(对文件进行计算哈希值)
010Editor(查看16进制状态并修改)
脱壳工具(具体根据壳的种类下载专脱工具)
Pwndbg(gdb插件)