zoukankan      html  css  js  c++  java
  • 逆向编程一,PE结构拉伸内存

    PE的加载从文件到内存有一个拉伸的过程,拉伸的原因是因为PE在文件中的对齐字节和在内存中的对齐字节可能不一样(文件对齐字节<=内存对齐字节,为了节省磁盘空间,目前的pe文件大部分文件和内存对齐字节都是一样的)。文件对齐字节在可选PE头里:

    _IMAGE_OPTIONAL_HEADER:

    32 4 SectionAlignment 内存对齐 当加载进内存时节的对齐值(以字节计)。它必须≥FileAlignment。默认是相应系统的页面大小。
    36 4 FileAlignment 文件对齐

    用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)。它应该是界于512和64K之间的2的幂(包括这两个边界值)。默认是512。如果SectionAlignment小于相应系统的页面大小,那么FileAlignment必须与SectionAlignment相等。

    下面模拟PE文件加载时内存拉伸的过程:

    1、读取文件,判断文件大小,分配一段与文件大小相同的内存缓冲区,filebuffer

    2、读取DOS头结构,根据DOS头,判断第一个WORD e_magic,这是dos头标记‘MZ’,用来判断是否是pe文件格式,读取最后一个DWORD(e_lfanew)指向NT头

    3、读取NT头结构,NT头包括PE签名、标准PE头和可选PE头。根PE头中的SizeOfImage是在内存中拉伸后的大小

    4、分配内存空间imagebuffer,填充空数据0,大小为内存中拉伸后的大小

    5、将头信息(dosheader+ntheader)复制到imagebuffer,dosheader+ntheader类似于首个节,拉伸也是在其后面补充0,所以其实位置和真实数据位置不变,可以直接复制。

    6、遍历节表,把每个节依次复制到imagebuffer中。

    以下是实现的代码

    一、头文件

    #pragma once
    #define _CRT_SECURE_NO_WARNINGS//高版的VS默认不让使用scanf,fopen等函数,说是scanf,fopen等函数不安全,而代替其函数的是scanf_s,fopen_s等函数,后边有个"_s"的形式。想要使用, 可以在源文件开头加个:

    #include <Windows.h>


    //函数声明

    //**************************************************************************

    //ReadPEFile:将文件读取到缓冲区

    //参数说明:

    //lpszFile 文件路径 //pFileBuffer 缓冲区指针

    //返回值说明:

    //读取失败返回0  否则返回实际读取的大小

    //******************************************************
    DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer);



    //******************************************************

    //CopyFileBufferToImageBuffer:将文件从FileBuffer复制到ImageBuffer

    //参数说明:

    //pFileBuffer  FileBuffer指针

    //pImageBuffer ImageBuffer指针

    //返回值说明: //读取失败返回0  否则返回复制的大小
    //******************************************************
    DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer);



    //******************************************************

    //CopyImageBufferToNewBuffer:将ImageBuffer中的数据复制到新的缓冲区

    //参数说明:

    //pImageBuffer ImageBuffer指针

    //pNewBuffer NewBuffer指针

    //返回值说明:

    //读取失败返回0  否则返回复制的大小

    //******************************************************
    DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer);


    //******************************************************

    //MemeryTOFile:将内存中的数据复制到文件

    //参数说明:

    //pMemBuffer 内存中数据的指针

    //size 要复制的大小

    //lpszFile 要存储的文件路径

    //返回值说明:

    //读取失败返回0  否则返回复制的大小

    //******************************************************
    BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile);



    二、函数实现

    #include "pestudy.h"
    #include<iostream>


    DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
    {
        FILE* file = fopen(lpszFile, "r+b");
        if (file == NULL)
            return 0;
        int flength;
        fseek(file, 0, SEEK_END);
        flength = ftell(file);
        fseek(file, 0, SEEK_SET);
        *pFileBuffer = malloc(flength);
        if (pFileBuffer == NULL)
            return 0;
        size_t readSize= fread(*pFileBuffer, 1, flength, file);
        fclose(file);
        return flength;
    }




    DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer)
    {
        //dos头
        _IMAGE_DOS_HEADER* pDosHeader;//dos头
        _IMAGE_NT_HEADERS* pNtHeader;//nt头

        DWORD dSectionPosition;

        //dos头
        pDosHeader = (_IMAGE_DOS_HEADER*)pFileBuffer;
        if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
        {
            printf("读取的文件不是exe文件");
            return 0;
        }

        //NT头
        pNtHeader = (_IMAGE_NT_HEADERS*)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
        if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
        {
            printf("读取的文件不是exe文件");
            return 0;
        }

        //节表位置
        dSectionPosition = (DWORD)pFileBuffer + pDosHeader->e_lfanew + sizeof(IMAGE_FILE_HEADER) + sizeof(DWORD) + pNtHeader->FileHeader.SizeOfOptionalHeader;
        
        
        //节表
        PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)dSectionPosition;

        //分配内存
        *pImageBuffer = malloc(pNtHeader->OptionalHeader.SizeOfImage);//SizeOfImage:当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数; 一般文件大小与加载到内存中的大小是不同的。 -> 00 00 50 00'
        if (*pImageBuffer == NULL)
            return 0;
        memset(*pImageBuffer, 0, pNtHeader->OptionalHeader.SizeOfImage);
        //写入header
        memcpy(*pImageBuffer, pFileBuffer, pNtHeader->OptionalHeader.SizeOfHeaders);

        
        //遍历节表
        while (pNtHeader->FileHeader.NumberOfSections--)
        {
            memcpy(PVOID((DWORD)*pImageBuffer + (pSectionHeader)->VirtualAddress) , PVOID((DWORD)pFileBuffer+(pSectionHeader)->PointerToRawData), pSectionHeader->SizeOfRawData);
            pSectionHeader++;
        }    
        return pNtHeader->OptionalHeader.SizeOfImage;
    }



    DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer)
    {
        _IMAGE_DOS_HEADER* dosHeader;
        _IMAGE_NT_HEADERS* ntHeader;
        DWORD setionPosition;
        dosHeader = (_IMAGE_DOS_HEADER*)pImageBuffer;
        if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
        {
            printf("内存缓冲区的文件格式有问题");
            return 0;
        }

        ntHeader = (_IMAGE_NT_HEADERS*)((DWORD)pImageBuffer+dosHeader->e_lfanew);
        if (ntHeader->Signature != IMAGE_NT_SIGNATURE)
        {
            printf("内存缓冲区中的文件格式有问题");
            return 0;
        }

        setionPosition = (DWORD)pImageBuffer + dosHeader->e_lfanew + 4 + sizeof(_IMAGE_FILE_HEADER) + ntHeader->FileHeader.SizeOfOptionalHeader;

        //节表
        PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)setionPosition;
        //找到最后一个节表
        PIMAGE_SECTION_HEADER pLastSetion =(PIMAGE_SECTION_HEADER)(pSectionHeader + ntHeader->FileHeader.NumberOfSections - 1);

        size_t fileSize = pLastSetion->PointerToRawData + pLastSetion->SizeOfRawData;

        *pNewBuffer = malloc(fileSize);
        memset(*pNewBuffer, 0, fileSize);

        //复制头信息
        memcpy(*pNewBuffer, pImageBuffer, ntHeader->OptionalHeader.SizeOfHeaders);

        //遍历节表
        while (ntHeader->FileHeader.NumberOfSections--)
        {
            memcpy(PVOID((DWORD)*pNewBuffer + (pSectionHeader)->PointerToRawData), PVOID((DWORD)pImageBuffer + (pSectionHeader)->VirtualAddress), pSectionHeader->SizeOfRawData);
            pSectionHeader++;
        }

        return fileSize;
    }

    BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile)
    {
        FILE* file= fopen(lpszFile, "wb");
        if (file == NULL)
        {
            printf("打开文件失败");
            return 0;
        }
        fwrite(pMemBuffer, size, 1, file);
        fclose(file);
        return 1;
    }

    三、调用


    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include "student.h"
    #include "pestudy.h"

    using namespace std;

    int main()
    {
        //MessageBoxA(NULL, "", "", 0);
        LPVOID pFileBuffer=NULL;
        
        LPSTR pePath = LPSTR("C:\MFCApplication1.exe");
        LPSTR toPath = LPSTR("C:\newnotepad.exe");

        if (size_t fsize=ReadPEFile(pePath, &pFileBuffer))
        {
            LPVOID pImageBuffer=NULL;
            if (size_t imageSize=CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer))
            {
                free(pFileBuffer);
                pFileBuffer = NULL;
                LPVOID pNewBuffer=NULL;
                if (size_t nsize = CopyImageBufferToNewBuffer(pImageBuffer, &pNewBuffer))
                {
                    MemeryTOFile(pNewBuffer, nsize, toPath);
                }
                if (pNewBuffer != NULL)
                    free(pNewBuffer);
            }
            if (pImageBuffer != NULL)
                free(pImageBuffer);
        }
        if (pFileBuffer != NULL)
            free(pFileBuffer);
        return 0;
    }

    四、容易遇到的问题

    1、我读取exe到内存缓冲区,少一个字节,导致后面结构错了一位

     

     原因是因为读取PE的时候用的是函数 fopen(lpszFile, "r"),第二个参数是读取模式,r表示可读,w表示可写。b表示二进制读取方式,Windows如果没有指定二进制读取的话,默认是按文本模式打开,Windows默认的换行符CR+LF就会转换成一个单个的LFCR:Carriage Return,对应ASCII中转义字符 ,表示回车 LF:Linefeed,对应ASCII中转义字符 ,表示换行

     参考:https://blog.csdn.net/liqing19850102/article/details/12040193

    2、释放或分配内存提示已触发了一个断点

    出现这个错误的原因是因为有内存溢出, 检查分配内存的大小够不够,是不是写入的数据大小超过了分配的大小。

  • 相关阅读:
    AJAX MANAGER
    关闭IE浏览器的自动RSS阅读功能
    WCF REST Configuration for ASP.NET AJAX and plain REST Services
    jquery, json与ashx的完美结合
    ASP.NET 页面中的 ValidateRequest属性
    ASP.NET中的healthMonitoring
    GZip Compress SoapExtension
    ASP.NET MVC : UrlHelper
    Microsoft Ajax Content Delivery Network
    ASP.NET: TextBox控件如果设置为ReadOnly则PostBack之后可能丢失数据的问题
  • 原文地址:https://www.cnblogs.com/cnzryblog/p/12625002.html
Copyright © 2011-2022 走看看