逆向学习 | C语言解析PE结构

admin 2025年4月10日00:05:51评论12 views字数 16496阅读54分59秒阅读模式

PE结构体

typedefstruct _IMAGE_DOS_HEADER {// DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedefstruct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

一、分析

解析PE结构,首先需要将可执行文件读取到内从中。然后读取PE的内容判断是否是可执行文件。进而读取各个部分的内容。

二、将文件读入到内存中

判断文件的大小

fp=fopen("C:/Users/gongxinao/Desktop/ipmsg.exe","rb");
if(!fp)
{
printf("打开exe文件失败!n");
returnNULL;
}

fseek(fp,0,SEEK_END);
fsize=ftell(fp);
fseek(fp,0,SEEK_SET);

printf("文件大小为%dn",fsize);

申请内存

char* FileBuffer =(char*)malloc(fsize);

if (!FileBuffer)
{
printf(" 分配空间失败! ");
fclose(fp);
returnNULL;
}

将文件内容读入到申请的内存中

fread(FileBuffer,fsize,1,fp);
charget_addres(){
//定义文件大小
int fsize;
    fsize=0;
//定义文件指针
FILE *fp;
fp=NULL;
//定义指针作为malloc函数的返回值。
char* FileBuffer;
    FileBuffer=NULL;
//读取文件判断文件大小
fp=fopen("C:/Users/gongxinao/Desktop/ipmsg.exe","rb");
if(!fp)
{
printf("打开exe文件失败!n");
returnNULL;
}
fseek(fp,0,SEEK_END);
fsize=ftell(fp);
fseek(fp,0,SEEK_SET);
printf("文件大小为%dn",fsize);
//申请内存空间
char* FileBuffer =(char*)malloc(fsize);
if (!FileBuffer)
{
printf(" 分配空间失败! ");
fclose(fp);
returnNULL;
}
//将文件读入内存中
fread(FileBuffer,fsize,1,fp);

fclose(fp);
return FileBuffer;
}

malloc函数的返回值:

分配成功返回指向该内存的地址,失败则返回 NULL。返回值的类型为函数的返回值类型是 void *。

void 指针可以指向任意类型的数据,就是说可以用任意类型的指针对 void 指针对 void 指针赋值。

https://www.runoob.com/w3cnote/c-void-intro.html

http://c.biancheng.net/cpp/html/137.html

在这里我们将返回的指针类型转化为char*

char* FileBuffer =(char*)malloc(fsize);

三、解析PE结构

首先接受函数返回值

char*  FileBuffer;
FileBuffer = NULL;
//该函数返回一个地址,类型为char*类型
FileBuffer = get_addres();
//打印首地址指向的内容
printf("%xn",*(FileBuffer));
逆向学习 | C语言解析PE结构

char类型的大小为1字节,8位,一位十六进制占4位,所以返回两位十六进制。

在PE结构的结构体中,每个字段都被定义位WORD、DWORD,所以我们可以将数据也转化成该类型。直接将char*类型强制转化为PWORD、PDWORD类型。

此时便可直接打印出结构体中的字段。

逆向学习 | C语言解析PE结构

初始化各个结构体指针

PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;

Dos头信息

pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer;
printf("********************DOC头********************n");
printf("MZ标志:%xn",pDosHeader->e_magic);
printf("PE偏移:%xn",pDosHeader->e_lfanew);

判断是否是有效的PE标志

if(*(PDWORD)((DWORD)FileBuffer+pDosHeader->e_lfanew))
{
printf("不是有效的PE标志n");
free(FileBuffer);
return0;
}

NT头信息

pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
printf("********************PE头********************n");
printf("PE:%xn",pFileHeader->Machine);
printf("节的数量:%xn",pFileHeader->NumberOfSections);
printf("SizeOfOptionalHeader:%xn",pFileHeader->SizeOfOptionalHeader);

可选PE头的大小

pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader+IMAGE_SIZEOF_FILE_HEADER);
printf("********************OPTIOIN_PE头********************n");
printf("OPTION_PE:%xn",pOptionalHeader->Magic);

//IMAGE_SIZEOF_FILE_HEADER大小为20,即标准PE头的大小是固定的20字节。
//可选PE头的大小不固定
逆向学习 | C语言解析PE结构

PS:

之前malloc返回的变量定义为char*类型,在后期转化的时候会出现很多问题。所以这里直接将返回值的类型定义为LPVOID。

LPVOID是一个没有类型的指针,也就是说你可以将任意类型的指针赋值给LPVOID类型的变量(一般作为参数传递)。

节表信息

pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader+pFileHeader->SizeOfOptionalHeader);
WORD dwNumberOfSection = pFileHeader->NumberOfSections;

for(int i=0;i<dwNumberOfSection;i++,pSectionHeader++)
{
printf("========================第%d个节信息:===============================n",i+1);
printf("节名字:");
for(DWORD j = 0;j<IMAGE_SIZEOF_SHORT_NAME;j++)
        {
printf("%c",pSectionHeader->Name[j]);
        }
printf("rn");
printf("Misc:tttt%08Xrn",pSectionHeader->Misc);
printf("VirtualAddress:ttt%08Xrn",pSectionHeader->VirtualAddress);
printf("SizeOfRawData:ttt%08Xrn",pSectionHeader->SizeOfRawData);
printf("PointerToRawData:tt%08Xrn",pSectionHeader->PointerToRawData);
printf("Characteristics:tt%08Xrn",pSectionHeader->Characteristics);
}
#include<stdio.h>
#include"stdlib.h"
#include"windows.h"
#include<malloc.h>

LPVOID get_addres(){

int fsize;

FILE *fp;

fp=NULL;
fsize=0;

fp=fopen("C:/Users/gongxinao/Desktop/ipmsg.exe","rb");
if(!fp)
{
printf("打开exe文件失败!n");
returnNULL;
}

fseek(fp,0,SEEK_END);
fsize=ftell(fp);
fseek(fp,0,SEEK_SET);

LPVOID FileBuffer =(LPVOID)malloc(fsize);

if (!FileBuffer)
{
printf(" 分配空间失败! ");
fclose(fp);
returnNULL;
}

fread(FileBuffer,fsize,1,fp);

fclose(fp);

return FileBuffer;
}
intmain(){
LPVOID  FileBuffer;
FileBuffer = NULL;
FileBuffer = get_addres();




PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;


if (*((PWORD)FileBuffer) != IMAGE_DOS_SIGNATURE)//pFileBuffer强转 WORD* 后 取指针指向的内容
{
printf("不是有效的MZ标志n");
free(FileBuffer);
return0;
}
pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer;
printf("********************DOC头********************n");
printf("MZ标志:%xn",pDosHeader->e_magic);
printf("PE偏移:%xn",pDosHeader->e_lfanew);



if(*(PDWORD)((DWORD)FileBuffer+pDosHeader->e_lfanew)!= IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志n");
free(FileBuffer);
return0;
}

pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)FileBuffer+pDosHeader->e_lfanew);
printf("********************NT头********************n");
printf("NT:%xn",pNTHeader->Signature);

pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
printf("********************PE头********************n");
printf("PE:%xn",pFileHeader->Machine);
printf("节的数量:%xn",pFileHeader->NumberOfSections);
printf("SizeOfOptionalHeader:%xn",pFileHeader->SizeOfOptionalHeader);


pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader+IMAGE_SIZEOF_FILE_HEADER);
printf("********************OPTIOIN_PE头********************n");
printf("OPTION_PE:%xn",pOptionalHeader->Magic);




pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader+pFileHeader->SizeOfOptionalHeader);
WORD dwNumberOfSection = pFileHeader->NumberOfSections;

for(int i=0;i<dwNumberOfSection;i++,pSectionHeader++)
{
printf("========================第%d个节信息:===============================n",i+1);
printf("节名字:");
for(DWORD j = 0;j<IMAGE_SIZEOF_SHORT_NAME;j++)
        {
printf("%c",pSectionHeader->Name[j]);
        }
printf("rn");
printf("Misc:tttt%08Xrn",pSectionHeader->Misc);
printf("VirtualAddress:ttt%08Xrn",pSectionHeader->VirtualAddress);
printf("SizeOfRawData:ttt%08Xrn",pSectionHeader->SizeOfRawData);
printf("PointerToRawData:tt%08Xrn",pSectionHeader->PointerToRawData);
printf("Characteristics:tt%08Xrn",pSectionHeader->Characteristics);
}

free(FileBuffer);
return0;
}

四、FileBuffer-ImageBuffer

分析

1、在将文件读入内存中,首先需要申请内存空间,申请内存空间的大小即:SizeOfImage的大小。

2、在从FileBuffer到ImageBuffer的过程中,有一部分是不变的,有部分是变化的。

DOS头+标准PE头+可选PE头+节表 = SizeOfHeaders,这部分的大小不发生变化。

逆向学习 | C语言解析PE结构

3、节的起始地址在磁盘文件中是按照 IMAGE_OPTIONAL_HEADER32 结构的 FileAlignment 字段的值进行对齐的,而当被加载到内存中时是按照同一结构中的 SectionAlignment 字段的值对其的,两者的值可能不同,所以一个节被装入内存后相对于文件头的偏移和在磁盘文件中的偏移可能是不同的。

也就是说第一个节的起始位置在内存中和文件中是不一样的。

逆向学习 | C语言解析PE结构

4、第一个节从PointerToRawData拷贝到VirtualAddress位置。

5、假设一个在内存中的地址为501234,求该地址在文件中的位置。

ImageBase:50000

PoniterToRawData:400

VirtualAddress:1000

判断位于内存中第几个节

501234 - 50000 = 1234

1000<1234<2000

VirtualAddress<1234<VirtualAddress  + misc.VirtualSize

计算偏移量

1234-1000=234

判断位于文件中第几个节

400 < 400+234 = 623 < 800

FileBuffer-ImageBuffer步骤:

1、申请内存空间,大小为sizeofimage。

2、拷贝不变的地方,大小为sizeofheader。

3、从拷贝PointerToRawData ,拷贝到VirtualAddress ,拷贝大小为SizeOfRawData。

ImageBuffer-NewBuffer步骤:

1、申请内存空间,大小为最后一个节在文件中的偏移+在文件中对齐之后的大小,即PointerToRawData+SizeOfRawData。

2、拷贝不变的地方,大小为sizeofheader。

3、从VirtualAddress 拷贝,拷贝到PointerToRawData ,拷贝大小为SizeOfRawData。

主函数中定义一级指针和二级指针。

//定义一级指针来获取程序在文件和内存中的地址。
LPVOID pFileBuffer = NULL;
LPVOID pImageBuffer = NULL;
//定义二级指针来接受一级指针的地址,二级指针作为函数参数,可以用来修改以上一级指针变量的值(这里传递的是一级指针的地址,所以成为传址调用)
LPVOID* ppFileBuffer = &pFileBuffer;
LPVOID* ppImageBuffer = &pImageBuffer;

定义函数从FileBuffer转换到ImageBuffer,因为要涉及到修改一级指针的值,也就是会修改地址,所以这里采用二级指针接受参数。

DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* ppImageBuffer)
#include<stdio.h>
#include"stdlib.h"
#include"windows.h"
#include<malloc.h>

DWORD get_addres(IN LPSTR lpszFile, OUT LPVOID* ppFileBuffer){

DWORD fsize;

FILE *fp;

fp=NULL;
fsize=0;

fp=fopen(lpszFile,"rb");
if(!fp)
{
printf("打开exe文件失败!n");
returnNULL;
}

fseek(fp,0,SEEK_END);
fsize=ftell(fp);
fseek(fp,0,SEEK_SET);

*ppFileBuffer = malloc(fsize);

if (!*ppFileBuffer)
{
printf(" 分配空间失败! ");
fclose(fp);
returnNULL;
}

fread(*ppFileBuffer,fsize,1,fp);

fclose(fp);

return fsize;
}


DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* ppImageBuffer)
{
//定义结构体
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
if (!pFileBuffer)
{
printf("pFileBuffer空指针n");
return0;
}

//获取DOS头、NT头、标准PE头、可选PE头、节表信息。
//DOS头
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;

//NT头
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

//标准PE头
pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader)+4);
//可选PE头
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);

//判断是否是有效的MZ标记
//判断是否是有效的PE文件
if(*((PWORD)pDosHeader) !=IMAGE_DOS_SIGNATURE)
{
printf("不是有效的Mz标记n");
return0;
}
if(*((PDWORD)((DWORD)pDosHeader + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志n");
return0;
}


//分配ImageBuffer的空间
*ppImageBuffer = malloc(pOptionalHeader->SizeOfImage);
if (!*ppImageBuffer)
{
printf("分配空间失败n");
return0;
}

printf("SizeOfImage:%xn", pOptionalHeader->SizeOfImage);
//初始化内存地址为0。
memset(*ppImageBuffer, 0, pOptionalHeader->SizeOfImage);

//拷贝头
printf("SizeOfHeaders:%xn", pOptionalHeader->SizeOfHeaders);
memcpy(*ppImageBuffer, pDosHeader, pOptionalHeader->SizeOfHeaders);

//节表
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

//循环节表,拷贝节表。
for(int i=1;i<= pFileHeader->NumberOfSections;i++,pSectionHeader++)
{
//拷贝节表数据,从哪里拷贝,拷贝到哪里,拷贝多少。
//将FileBuffer中从 其基址+PointerToRawData 的地方开始,拷贝SizeOfRawData大小的内存,到ImageBuffer 其基址+VirtualAddress 处
memcpy((LPVOID)((DWORD)(*ppImageBuffer) + pSectionHeader->VirtualAddress),(LPVOID)((DWORD)pDosHeader + pSectionHeader->PointerToRawData),pSectionHeader->SizeOfRawData);
}

pSectionHeader = NULL;
printf("拷贝完成n");
return pOptionalHeader->SizeOfImage;
}


intmain(int argc, char *argv[]){
//定义一级指针来获取程序在文件和内存中的地址。
LPVOID pFileBuffer = NULL;
LPVOID pImageBuffer = NULL;
//定义二级指针来接受一级指针的地址,二级指针作为函数参数,可以用来修改以上一级指针变量的值(这里传递的是一级指针的地址,所以成为传址调用)
LPVOID* ppFileBuffer = &pFileBuffer;
LPVOID* ppImageBuffer = &pImageBuffer;
//定义ImageBuffer的大小
int SizeOfImageBuffer;
//定于文件路径
char FilePath[] = "C:/Users/gongxinao/Desktop/ipmsg.exe";
//该函数返回文件的大小,并获取文件在内存中的起始地址,也就是修改了一级指针,所以要传递二级指针参数。
get_addres(FilePath,ppFileBuffer);
pFileBuffer = *ppFileBuffer;


if (!get_addres(FilePath, ppFileBuffer))
{
printf("文件读取失败n");
return0;
}

SizeOfImageBuffer = CopyFileBufferToImageBuffer(pFileBuffer, ppImageBuffer);
return0;
}

pFileBuffer是一级指针,值为地址,为文件首地址,该值在get_addres被改变。

ppImageBuffer是二级指针,值为地址,为一级指针pImageBuffer的地址,该指针的值为ImageBuffer的首地址。

判断拉伸是否正确

boolVerifyFileBufferAndImageBuffer(LPVOID pFileBuffer, LPVOID pImageBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
//DOS头
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;// 强转 DOS_HEADER 结构体指针
//NT头
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
//PE头
pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);//NT头地址 + 4 为 FileHeader 首址
//可选PE头
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);//SIZEOF_FILE_HEADER为固定值且不存在于PE文件字段中

//头验证
PBYTE PBFileBuffer = (PBYTE)pFileBuffer;
PBYTE PBImageBuffer = (PBYTE)pImageBuffer;
//printf("开始FB:%x   IB:%xn", FileBuffer, ImageBuffer);
for (int i = 0; i < pOptionalHeader->SizeOfHeaders; i++)
{
if (*PBImageBuffer != *PBFileBuffer)//pFileBuffer强转 WORD* 后 取指针指向的内容
{
printf("头验证失败n");
returnfalse;
}
PBImageBuffer++;
PBFileBuffer++;
}
//printf("结束FB:%x   IB:%xn", FileBuffer, ImageBuffer);
printf("头验证通过n");

//循环遍历节表进行节验证
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
for (int i = 1; i <= pFileHeader->NumberOfSections; i++)
{
PBFileBuffer = (PBYTE)((DWORD)pFileBuffer + pSectionHeader->PointerToRawData);
PBImageBuffer = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress);
for (int j = 0; j < pSectionHeader->SizeOfRawData; j++)
{
if (*PBImageBuffer != *PBFileBuffer)//pFileBuffer强转 WORD* 后 取指针指向的内容
{
printf("第%x节第%x个字节验证失败n", i, j);
returnfalse;
}
PBImageBuffer++;
PBFileBuffer++;
}
//节表++
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER);
}
//出循环后pSectionHeader指向不存在的节表,多了一节

pSectionHeader = NULL;  //所以pSectionHeader指针不可再使用
printf("节验证通过n");
returntrue;
}

五、空白区添加代码

计算E8和E9之后的硬编码

首先查看call指令和jmp指令的硬编码

逆向学习 | C语言解析PE结构
13:       function();
00401068 E8 9D FF FF FF       call        @ILT+5(function) (0040100a)
逆向学习 | C语言解析PE结构
00401005 E9 46000000jmp         main(00401050)
@ILT+5(?function@@YAHXZ):
0040100A E9 11 00 00 00       jmp         function(00401020)
逆向学习 | C语言解析PE结构

x = 40100a - 40106D = FFFF FF9D

x = 真正要跳转的地址 - E8的地址- 5(当前指令的长度)

查看MessageBox的硬编码。

004010288B F4                mov         esi,esp
0040102600                push        0
0040102600                push        0
0040102600                push        0
00401030600                push        0
00401032 FF 15 AC A2 4200    call        dword ptr [__imp__MessageBoxA@16 (0042a2ac)]
逆向学习 | C语言解析PE结构

将代码加入到空白区,要能够保证添加的代码小于空白区的大小。比如第一个节:

逆向学习 | C语言解析PE结构

数据的本身大小

VirtualSize:            0x000001e000020AE6     [V(VS),内存中大小(对齐前的长度).]

在文件中的对齐大小

SizeOfRawData:          0x000001e800021000     [R(RS),文件中大小(对齐后的长度).]

空白区的大小

00021000-00020AE6=51A

查找第一个节的数据起始位置

逆向学习 | C语言解析PE结构

第一个节对齐后的大小

逆向学习 | C语言解析PE结构

代码的空白区,可以看到在实际大小之后就是代码的空白区。

逆向学习 | C语言解析PE结构

真正要跳转的地址为(75c10f40)=E8下一条指令的地址 + x (E8之后的硬编码)

E8下一条指令的地址是在在内存中运行的地址而不是文件中的地址。所以下一条指令的地址不是如下图中指明的地址。

逆向学习 | C语言解析PE结构

但是该程序的文件偏移和内存偏移一样。真正的地址为ImageBase+内存中的偏移。

所以E8下一条指令的地址=imagebase+内存偏移=400000+21afd=421afd。

x = 75c10f40 - 421afd =

43 F4 7E 75

E9的下一条指令地址:21b02 + 400000 = 421b02

x = 41d26f - 421b02 = FFFF B76D

修改OEP

程序真正入口的地方:0001D26F + 400000 = 41d26f

逆向学习 | C语言解析PE结构

01D26F,在winhex的值为6FD201

逆向学习 | C语言解析PE结构

将程序真正的入口修改为自己定义的入口,即021AF0

逆向学习 | C语言解析PE结构

将OEP修改为F01A02

逆向学习 | C语言解析PE结构

https://www.freebuf.com/articles/network/265889.html

六、代码

1、读取文件大小

文件大小的类型可以使用int,也可以使用DWORD。DWORD 代表 unsigned long,DWORD是windows数据类型,而不是c数据类型。DWORD是无符号32位整型DWORD 一般用于返回值不会有负数的情况。
https://www.cnblogs.com/wxl845235800/p/7240128.html

#include"stdio.h"
#include<windows.h>
#include<iostream>

//

DWORD ReadFileSize(char FilePath[]){
FILE* pfile = NULL;
DWORD FileSize = 0;

pfile = fopen(FilePath, "rb");
if (!pfile) {
printf("文件打开失败!n");
}

fseek(pfile, 0, SEEK_END);
FileSize = ftell(pfile);
fseek(pfile, 0, SEEK_SET);


return FileSize;
}

intmain(int argc, char** argv){
char FilePath[] = "C:/Users/gongxinao/Desktop/ipmsg1.exe";
DWORD FileSize;
FileSize = ReadFileSize(FilePath);
printf("%dn", FileSize);
return0;
}

2、获取内存空间的起始地址和文件大小

输出文件大小,所以函数返回值为DWORD。

函数需要修改的值为pFileBuffer,即需要通过传址方式传递参数。

传入参数为文件路径。

DWORD GetFileAddressAndSize(PDWORD FileSize,LPSTR FilePath,LPVOID* ppFileBuffer)
{
FILE* pfile = NULL;

pfile = fopen(FilePath, "rb");
if (!pfile) {
printf("文件打开失败!n");
}

fseek(pfile, 0, SEEK_END);
*FileSize = ftell(pfile);
fseek(pfile, 0, SEEK_SET);

*ppFileBuffer = malloc(*FileSize);

if (!*ppFileBuffer)
{
printf("内存空间申请失败!");
fclose(pfile);
returnNULL;
}
fread(*ppFileBuffer, *FileSize, 1, pfile);

fclose(pfile);

return *FileSize;
}

intmain(int argc, char** argv){
char FilePath[] = "C:/Users/gongxinao/Desktop/ipmsg1.exe";
DWORD FileSize;
LPVOID pFileBuffer = NULL;
LPVOID* ppFileBuffer = &pFileBuffer;

FileSize = GetFileAddressAndSize(&FileSize,FilePath, ppFileBuffer);
printf("文件大小为:%dn", FileSize);
printf("此时FileBuffer的首地址为:OX%pn", *ppFileBuffer);
return0;
}

3、ImageBuffer和FileBuffer的相互转换

七、空白区添加代码

思路:

1、将文件读入内存中。

2、filebuffer到imagebuffer的转换。

3、判断代码的空白区大小,是否能够存储shellcode

即:sizeofRawData-misc.virtualsize与shellcode的长度的大小

4、将代码复制到空白区

复制的起始位置:virtualAddress + virtualSize

复制shellcode

5、修正E8、E9、OEP

x = 要跳转的地址 - E8下一条指令的地址

x = (MessageBox的地址 - (ImageBase+Virtualaddress + 0xd(13)))-Imagebase

空白区的起始位置到E8下一条指令的地址正好差13个地址。

6A 00 6A 00 6A 00 6A 00 E8 00 00 00 E9 00 00 00

修改OEP为当前代码的起始地址,即OEP = 当前代码的起始位置

pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize;

八、新增节

1、添加数据。

2、增加自己的节表。

3、检查sizeofheader去除Dos头、NT头、节表之后的空间是否够两个节表的大小。

4、修改Numberofsections的值。

5、修改可选PE头中的sizeofimage的大小。

6、修改新增节表的属性

  • PointerToRawData逆向学习 | C语言解析PE结构
  • SizeOfRawData和VirtualSize自定义大小
  • VirtualAddress

VirtualAddress+VirtualSize与VirtualAddress+SizeOfRawData比较,在按照内存对齐取整数3逆向学习 | C语言解析PE结构

内存对齐的大小为:

逆向学习 | C语言解析PE结构
逆向学习 | C语言解析PE结构

九、增加节

1、申请新的缓冲区,缓冲区大小为Imagebuffer大小+新增的数据大小

2、修改virtualsize和sizeofRawDate。

3、修改SizeofImage。

十、合并节

1、修改virtualsize和sizeofRawData的大小。

2、Virtualaddress和PointerToRawData位置不变。

合并所有节:

将第一个节的内存大小、文件大小改成一样。

VirtualSize = SizeOfRawData = 整个文件拉伸后的大小 - 第一节VirtualAddress
即相当于在拉伸后的文件中 从第一个节表开始到 最后 都作为第一个节的内存长度与文件长度

原文始发于微信公众号(土拨鼠的安全屋):逆向学习 | C语言解析PE结构

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月10日00:05:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   逆向学习 | C语言解析PE结构https://cn-sec.com/archives/3909686.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息