PE文件结构从初识到简单shellcode注入

2022/6/30 5:21:01

本文主要是介绍PE文件结构从初识到简单shellcode注入,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本文首发自:https://tttang.com/archive/1553/

前言

​ 将自己学习的PE文件结构进行总结形成文章这件事情,一直躺在我的Notion TodoList里,但是一直是未完成的状态哈哈,拖了那么久也该让它状态变成已完成了。

PE文件简介

​ PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)。

​ 上面是百度复制来的,我个人理解的PE文件结构其实就类似于CTF Misc题中的jpg、png、zip文件格式类似,当你了解了这些文件格式后,你可以通过修改二进制数据来改图片宽高等属性,同样的,当你了解Windows PE文件结构后,你也可以修改PE文件的一些属性,比如修改程序入口点甚至修改程序运行逻辑。

文件对齐/内存对齐

​ 一个PE文件的结构有两种表现方式,一种是“躺”在硬盘里的时候,也就是程序未执行的时候,一种是你调用或者双击运行程序,程序载入内存后,两种表现方式其实结构大体相同,只是内部某个部分之间的间隔稍有不同,如下图:

​ 如上图可见,当PE文件加载到内存中后,DOS头到最后一个节区头的部分是一致的,而之后节区与节区之间的间隔,内存中的间隔会更大。但是,这种情况是不一定的,也有硬盘中和内存中的间隔是一致,这取决于PE文件结构中的某一个属性的设置。

​ 那么为什么需要这样的操作呢?直接把文件按照原样加载到内存中不是更方便吗?这其实是由于操作系统内存对齐所产生的一个操作,具体内存对齐可以通过百度百科了解:https://baike.baidu.com/item/内存对齐/9537460?fr=aladdin。内存对齐简单点说就是以一块内存一块内存来读取内存中的数据,而不是根据实际大小来读取,这样省去计算实际大小这个操作就提升了内存读取的速度。但是如果直接按照内存对齐的结构把PE文件存储到硬盘中,实际上有一大部分由于内存对齐而加进来的空间是无用的。在以前的计算机中,硬盘其实都不大,为了剩下这部分空间,所以就多了一个“裁剪”多余空间的操作,把对齐值调小一点,尽量减小无用的空间占用硬盘资源。而现在有些编译器的内存对齐和文件对齐是一样的了,那是因为如今的硬盘资源已经很充足,不需要废时间来省下这点空间了。

结构体

​ 结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员” ,其描述了一块内存区间的大小及解释意义。

​ 没错,上面这段我又是百度复制的。其实应该有一部分人在学习编程的时候,遇到结构体都不知道有啥大用,难道它只能用来做做编程练习吗?那肯定是不可能。就如百度百科解释所说的,结构体描述了一块内存区间的大小及解释意义,我们想象一下,当我们通过编程语言把PE文件读入内存中后,要怎么分别把DOS头、DOS存根等等每一块内容取出来呢?当然,通过类似于Python的切片来做是可以实现的,但是总感觉还是有点麻烦。这时候就需要用到结构体了,winnt.h头文件中定义了DOS头结构体指针类型PIMAGE_DOS_HEADER,当我们需要取DOS头数据的时候,就可以直接把内存中整块PE文件数据强制转换成PIMAGE_DOS_HEADER类型,这时候程序把PE文件数据作为PIMAGE_DOS_HEADER进行解析,你在C语言中就可以很轻松的通过pe_data->e_lfanew来很轻松的获取到DOS头中的某一个数据了。

​ 可能这样讲还是有点抽象,我们再分细一点来说。首先结构体其实类似于数组,也是一段连续的内存块,只不过他内存块中每一块的大小由结构体成员决定。一个int类型数组,我们假设一个单元格是一个字节,一个int类型元素占四个字节,在内存中分布如下图(实际int在内存中大部分操作系统是小端序存储,所以数据是反过来存的,参考:https://www.jianshu.com/p/f29873769488):

​ 我们定义一个结构体

struct person {
    int id;
    char name[6];
};
person p;
p.id = 1;
strcpy(p.name, "gcker");

​ person结构体它在内存中分布是这样的(int是小端序存储,char是大端序存储):

​ 当我们通过p.name来取数据的时候,实际就是从一块内存数据为01 00 00 00 67 63 6b 65 00中,从67开始取数据。再做个有趣的实验,代码如下:

char data[] = { 0x01, 0x00, 0x00, 0x00, 0x67, 0x63, 0x6b, 0x65, 0x72, 0x00 };
person *p_person = (person*)data;
printf("%s", p_person->name);
// 输出:gcker

​ 你们能理解我的意思吗



这篇关于PE文件结构从初识到简单shellcode注入的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程