整体结构 PE结构一般来说:从起始位置开始依次是DOS头、NT头、节表以及具体的节。 基地址 程序加载进内存的起始地址。 (通常是跟MZ头一起判断) 2、NT头结构信息-PE文件头 IMAGE_FILE_HEADER(PE文件头)结构包含了PE文件的一些基本信息,最重要的是其中一个域指出了IMAGE_OPTIONAL_HEADER 3、NT头结构信息-PE可选头 (IMAGE_OPTIONAL_HEADER)是一个可选的结构,但实际上IMAGE_FILE_HEADER结构不足以定义PE文件属性,因此可选映像头中定义了更多的数据, 完全不必考虑两个结构区别在哪里,两者连起来就是一个完整的“PE文件头结构”。 (11)SectionAlignment:当被装入内存时的区块对齐大小。每个区块被装入的地址必定是本字段指定数值的整数倍。默认的对齐尺寸是目标CPU的页尺寸。
本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。 该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。 // 将ShellCode写出到PE程序的特定位置 // 参数1: 指定PE路径 参数2: 指定文件中的偏移(十进制) 参数3: 指定ShellCode文件 void WritePEShellCode( 文件中我们需要手动分析寻找空余块,并在注入成功后还需要自行修正PE文件内的入口地址等,这种方式适合于对PE结构非常熟悉的人可以,但也要花费一些精力去寻找分析,如下代码则是实现了自动化注入功能,该代码中FindSpace 当我们对特定的程序插入Shell后,则还需要对该程序增加一个标志,在PE结构中有许多地方可以写入这个标志,例如DOS头部存在一个e_cblp变量,通过向该变量写入一个标志,当需要判断是否被感染时读取此处并检查是否存在特定值即可
本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。 该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。 // 将ShellCode写出到PE程序的特定位置// 参数1: 指定PE路径 参数2: 指定文件中的偏移(十进制) 参数3: 指定ShellCode文件void WritePEShellCode(const 文件中我们需要手动分析寻找空余块,并在注入成功后还需要自行修正PE文件内的入口地址等,这种方式适合于对PE结构非常熟悉的人可以,但也要花费一些精力去寻找分析,如下代码则是实现了自动化注入功能,该代码中FindSpace 图片当我们对特定的程序插入Shell后,则还需要对该程序增加一个标志,在PE结构中有许多地方可以写入这个标志,例如DOS头部存在一个e_cblp变量,通过向该变量写入一个标志,当需要判断是否被感染时读取此处并检查是否存在特定值即可
,病毒分析,外挂技术等,在PE文件中我们最需要关注,PE结构,导入表,导出表,重定位表,下面将具体介绍PE的关键结构,并使用C语言编程获取到这些结构数据. 我们需要编程实现读取PE结构,在读取PE文件中的数据的前提下,我们先来打开文件,然后才能读取。 ◆ 在上面PE结构图中可知PE文件的开头部分包括了一个标准的DOS可执行文件结构,这看上去有些奇怪,但是这对于可执行程序的向下兼容性来说却是不可缺少的,当然现在已经基本不会出现纯DOS程序了,现在来说这个 PE文件的重要结构,接下来将通过编程读取出PE文件的开头相关数据。 节表结构定义: PE文件中的所有节的属性定义都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构邮过来描述一个节,节表总被存放在紧接在PE文件头的地方,也即是从
,病毒分析,外挂技术等,在PE文件中我们最需要关注,PE结构,导入表,导出表,重定位表,下面将具体介绍PE的关键结构,并使用C语言编程获取到这些结构数据. ,PE文件头被放置在节和节表的前面,上面介绍的是真正的PE文件,为了兼容以前的DOS系统,所以保留了DOS的文件格式,接下来将依次介绍这几种数据结构.我们需要编程实现读取PE结构,在读取PE文件中的数据的前提下 ◆在上面PE结构图中可知PE文件的开头部分包括了一个标准的DOS可执行文件结构,这看上去有些奇怪,但是这对于可执行程序的向下兼容性来说却是不可缺少的,当然现在已经基本不会出现纯DOS程序了,现在来说这个 PE文件的重要结构,接下来将通过编程读取出PE文件的开头相关数据。 节表结构定义: PE文件中的所有节的属性定义都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构邮过来描述一个节,节表总被存放在紧接在PE文件头的地方,也即是从
通过对数组中的特定位置进行替换来完善跳转功能,此处我们需要提取如下几个位置的特征字段; 00408001 数组下标第2位替换为ImageBase + pSection->VirtualAddress 0040800A 数组下标第11 0, 0, 0); HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0); // 找到PE [+] 写入代码节开始位置: 0x%08X \n", ImageBase + pSection->VirtualAddress); // 写入代码节终止位置 *(DWORD *)&Code[11 0, 0, 0); HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0); // 定位PE
PE结构-DOS头,本部分为参照吾爱破解论坛lyl610abc师傅PE文件笔记所整理的学习笔记。 DOS部首 DOS部首结构 其结构分为两部分: DOS ‘MZ’ HEADER 其在c中定义的结构体为_IMAGE_DOS_HEADER DOS sub DOS MZ头 DOS MZ头在C语言中所定义的结构体为 是为了往下兼容在低版本DOS系统上运行,所以该部分主要用于DOS系统 目前在32/64位Windows操作系统中还有效的成员只有两个 第一个成员:e_magic (WORD 2字节) 用于识别文件是否为PE 格式文件 值固定为4d 5a (MZ) 最后一个成员: e_lfanew (LONG 4字节) 存储PE头首地址 位于0x3c位置 DOS部首中的其他值已经在现阶段Windows系统中弃用,下面通过将对应值置换为
通过对数组中的特定位置进行替换来完善跳转功能,此处我们需要提取如下几个位置的特征字段;00408001 数组下标第2位替换为ImageBase + pSection->VirtualAddress0040800A 数组下标第11 PAGE_READWRITE, 0, 0, 0); HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0); // 找到PE printf("[+] 写入代码节开始位置: 0x%08X \n", ImageBase + pSection->VirtualAddress); // 写入代码节终止位置 *(DWORD *)&Code[11 PAGE_READWRITE, 0, 0, 0); HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0); // 定位PE
数据目录表的第二个成员指向导入表,该指针在PE开头位置向下偏移0x80h处,此处PE开始位置为0xF0h也就是说导入表偏移地址应该在0xf0+0x80h=170h如下图中,导入表相对偏移为0x21d4h 这里的0x21d4是一个RVA地址,需要将其转换为磁盘文件FOA偏移才能定位到导入表在文件中的位置,使用RvaToFoa命令可快速完成计算,转换后的文件偏移为0x11d4 此处我们也可以通过使用虚拟偏移地址减去实际偏移地址来得到这个参数 ,由于0x21d4位于.rdata节,此时的rdata虚拟偏移是0x2000而实际偏移则是0x1000通过使用2000h-1000h=1000h,接着再通过0x21d4h-0x1000h=11D4h同样可以得到相对 我们通过使用WinHex工具跳转到11d4位置处,读者此时能看到如下图所示的地址信息。 如上图就是导入表中的IID数组,每个IID结构包含一个装入DLL的描述信息,现在有三个导入DLL文件,则第四个是一个全部填充为0的结构,标志着IID数组的结束,每一个结构有五个四字节构成,该结构体定义如下所示
数据目录表的第二个成员指向导入表,该指针在PE开头位置向下偏移0x80h处,此处PE开始位置为0xF0h也就是说导入表偏移地址应该在0xf0+0x80h=170h如下图中,导入表相对偏移为0x21d4h 图片这里的0x21d4是一个RVA地址,需要将其转换为磁盘文件FOA偏移才能定位到导入表在文件中的位置,使用RvaToFoa命令可快速完成计算,转换后的文件偏移为0x11d4图片此处我们也可以通过使用虚拟偏移地址减去实际偏移地址来得到这个参数 ,由于0x21d4位于.rdata节,此时的rdata虚拟偏移是0x2000而实际偏移则是0x1000通过使用2000h-1000h=1000h,接着再通过0x21d4h-0x1000h=11D4h同样可以得到相对 图片我们通过使用WinHex工具跳转到11d4位置处,读者此时能看到如下图所示的地址信息。 图片如上图就是导入表中的IID数组,每个IID结构包含一个装入DLL的描述信息,现在有三个导入DLL文件,则第四个是一个全部填充为0的结构,标志着IID数组的结束,每一个结构有五个四字节构成,该结构体定义如下所示
Windows系统使用PE(Portable Executable)文件格式来存储可执行程序,其中包括重定位信息。当程序被加载到内存中时,系统会解析这些重定位信息,并将程序中的各种内存地址进行重定位。 图片重定位表也是分页排列的,每一页大小都是1000字节,通过使用FixRelocPage命令即可查询到当前程序中的重定位块信息,并以第一个为例,查询一下起始地址RVA为1000的页上,有哪些重定位结构, rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData; } } return -1;}// 打开PE = IMAGE_DOS_SIGNATURE) return false; // 获取 NT 头并判断是不是一个有效的PE文件 NtHeader = (PIMAGE_NT_HEADERS)(FileBase auto Offset = (TypeOffset*)(Reloc + 1); // 3.3 计算重定位项的个数 // Reloc->SizeOfBlock 保存的是整个重定位块的大小 结构体
了解其格式对恶意分析及使用高级的攻击手法有很大的帮助,很多高级的攻击手段都需要对 PE、PEB 有详细的了解。 二、PE 结构 1. NT Headers NT Headers 同 DOS Header 的 e_magic 位一样有一个校验结构是否有效的位 Signature,其值必定等于"PE\x00\x00",NT Headers PE 结构总结 整个程序从 offset=0 处即 DOS Header 到程序的最后一处 EOF 的所有块状区域经 File Alignment 对齐之后都是紧密贴合的没有任何空隙。 首先,编写一个函数读取 PE 文件: 读取到 PE 文件内容并保存到 pe_content 指针中,然后直接转换为 PIMAGE_DOS_HEADER 结构就是 DOS Header 了: 通过 DOS 最好通过 File Header 的 Machine 字段判断 PE 文件的架构后再调用对应的结构体进行解析: 通过微软的 IMAGE_FIRST_SECTION 宏定义加 NT Headers 地址获取到
Windows系统使用PE(Portable Executable)文件格式来存储可执行程序,其中包括重定位信息。 重定位表也是分页排列的,每一页大小都是1000字节,通过使用FixRelocPage命令即可查询到当前程序中的重定位块信息,并以第一个为例,查询一下起始地址RVA为1000的页上,有哪些重定位结构,如下图所示 SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData; } } return -1; } // 打开PE = IMAGE_DOS_SIGNATURE) return false; // 获取 NT 头并判断是不是一个有效的PE文件 NtHeader = (PIMAGE_NT_HEADERS auto Offset = (TypeOffset*)(Reloc + 1); // 3.3 计算重定位项的个数 // Reloc->SizeOfBlock 保存的是整个重定位块的大小 结构体
ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF 结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.读取ELF头: 首先需要先来编译一个简单的ELF文件,然后将文件编译并连接. ]# gcc -c lyshark.c [root@localhost ~]# gcc -o lyshark lyshark.o Linux系统中有一个默认命令readelf -h可以解析指定文件的头结构 printf("\n"); } return 0;}编译并运行即可读取出文件头部的前16个字节的字节数组,我们最需要关注的就是开头前4个字节,其标志着PE /elf lyshark头标志: 7f 45 4c 46 2 1 1 0 0 0 0 0 0 0 0 0 除此之外,读取其他头结构数据,代码与上方类似,只需要稍微改动一下就好.
PE文件中的资源都被组织成一个树形结构,其中最顶层为根节点(Root),下一级为资源类型(Type),再下一级为资源名称(Name),最终是实际的资源内容。 PIMAGE_RESOURCE_DIRECTORY是Windows PE可执行文件中的一个结构类型,用于描述资源(Resource)的树形结构,其中包括了每个资源的类型(Type)、名称(Name)和语言 资源的目录结构,每个资源目录包括以下字段: Characteristics:指定该目录的属性,如是否允许命名、是否允许ID等; TimeDateStamp:指定该目录的时间戳; MajorVersion // -------------------------------------------------- static char* szResName[0x11] = { 0, (char*)"鼠标指针 pResEntry[i].NameIsString) { if (pResEntry[i].Id < 0x11)
PE文件在被装入内存后JMP跳转后面的地址才会被操作系统确定并填充到指定的位置上,那么在程序没有被PE装载器加载之前0x00D22000地址处的内容是什么呢,我们使用上面的PE解析器对节表进行解析观察. Range) < file_size: print("-" * 60) print("0 1 2 3 4 5 6 7 8 9 10 11 ----------------- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | offset -------------------------- ----------------- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | offset -------------------------- 这是因为PE装载器会将其中一个结构修改为函数的地址jmp dword ptr[xxxx]其中的xxxx就是由FirstThunk字段指向的那个数组中的一员。
节表(Section Table)是Windows PE/COFF格式的可执行文件中一个非常重要的数据结构,它记录了各个代码段、数据段、资源段、重定向表等在文件中的位置和大小信息,是操作系统加载文件时根据节表来进行各个段的映射和初始化的重要依据 在执行PE文件的时候,Windows 并不在一开始就将整个文件读入内存,PE装载器在装载的时候仅仅建立好虚拟地址和PE文件之间的映射关系,只有真正执行到某个内存页中的指令或者访问页中的数据时,这个页面才会被从磁盘提交到内存中 节区的偏移: 节的起始地址在磁盘文件中是按照IMAGE_OPTIONAL_HEADER结构的FileAhgnment字段的值对齐的,而被加载到内存中时是按照同一结构中的SectionAlignment字段的值对齐的 一般来说,当一个PE文件被编译生成时则默认会存在.text,.data等基本节表,而每一个节表都是由一个IMAGE_SECTION_HEADER结构排列而成,每个结构都用来描述一个节,节表总被存放在紧接在 PE文件头的地方,也即是从PE文件头开始偏移为00f8h的位置,针对每一个节中的定义可查看节表结构体的定义; typedef struct _IMAGE_SECTION_HEADER { BYTE
ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF 结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些. gcc -c lyshark.c [root@localhost ~]# gcc -o lyshark lyshark.o Linux系统中有一个默认命令readelf -h可以解析指定文件的头结构 printf("\n"); } return 0; } 编译并运行即可读取出文件头部的前16个字节的字节数组,我们最需要关注的就是开头前4个字节,其标志着PE /elf lyshark 头标志: 7f 45 4c 46 2 1 1 0 0 0 0 0 0 0 0 0 除此之外,读取其他头结构数据,代码与上方类似,只需要稍微改动一下就好.
PE文件中的资源都被组织成一个树形结构,其中最顶层为根节点(Root),下一级为资源类型(Type),再下一级为资源名称(Name),最终是实际的资源内容。 PIMAGE_RESOURCE_DIRECTORY是Windows PE可执行文件中的一个结构类型,用于描述资源(Resource)的树形结构,其中包括了每个资源的类型(Type)、名称(Name)和语言 (Language),以及指向下一级PE资源目录的地址和相关信息等。 // --------------------------------------------------static char* szResName[0x11] = { 0, (char*)"鼠标指针 pResEntry[i].NameIsString) { if (pResEntry[i].Id < 0x11) {
PE结构是Windows系统下最常用的可执行文件格式,理解PE文件格式不仅可以理解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,DOS头是PE文件开头的一个固定长度的结构体,这个结构体的大小为 是PE文件格式的一部分,它包含了PE头和可选头的信息,用于描述PE文件的结构和属性。 2.2 DOS文件头详细解析 DOS头是PE文件开头的一个固定长度的结构体,这个结构体的大小为64字节(0x40)。 PE结构所使用的的结构定义略有不同,代码中已经对其进行了区分。 是PE文件格式的一部分,它包含了PE头和可选头的信息,用于描述PE文件的结构和属性。