《PE解析、PE修改、导入表注入》(内容、代码优化中,封装的时候我发现自己的代码贴丢了。。。正在重写 等等上传)。
2020-03-22 12:35:35 Author: bbs.pediy.com(查看原文) 阅读量:384 收藏

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,我们比较熟悉的DLL和exe文件都是PE文件。了解PE文件格式有助于加深对操作系统的理解,掌握可执行文件的数据结构机器运行机制,对于逆向破解,加壳等安全方面方面的同学极其重要。

PE在二进制文件中就是一堆0和1,但是它是按一定的格式来存储的,下面我先从宏观上来给大家介绍。

PE分dos头、NT头(包含标准PE头和可选PE头(包含数据目录))、节表。

下面我上一张图给大家看一下:


dos头(方块1第一块),NT头(方块1第二块),标准PE头(方块2),可选PE头(方块3),数据目录(方块5)、节表(方块4)。

下面我按着这个编号来介绍。

首先我讲一下dos头,如图


微软定义如下:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header 

   WORD   e_magic;          +0           // Magic number  DOS签名 4D5A "MZ"

   WORD   e_cblp;           +2           // Bytes on last page of file 

   WORD   e_cp;             +4           // Pages in file 

   WORD   e_crlc;           +6           // Relocations 

   WORD   e_cparhdr;        +8           // Size of header in paragraphs 

   WORD   e_minalloc;       +10           // Minimum extra paragraphs needed 

   WORD   e_maxalloc;       +12           // Maximum extra paragraphs needed 

   WORD   e_ss;             +14           // Initial (relative) SS value 

   WORD   e_sp;             +16           // Initial SP value 

   WORD   e_csum;           +18           // Checksum 

   WORD   e_ip;             +20           // Initial IP value 

   WORD   e_cs;             +22           // Initial (relative) CS value 

   WORD   e_lfarlc;         +24           // File address of relocation table 

   WORD   e_ovno;           +26           // Overlay number 

   WORD   e_res[4];         +28 +32 +34 +36           // Reserved words 

   WORD   e_oemid;          +38           // OEM identifier (for e_oeminfo) 

   WORD   e_oeminfo;        +40           // OEM information; e_oemid specific 

   WORD   e_res2[10];                 // Reserved words 

   LONG   e_lfanew;         +60     // File address of new exe header  指向PE头 3Ch

      } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 

虽然字段比较多,但是我们初学只要知道2个字段就ok了——   WORD   e_magic和LONG   e_lfanew;    e_magic字段是标记,用途就是标记这个文件是什么类型的文件,比如:exe,dll,sys文件都是4D5A开头的。


MZ是一个大佬名字的简称,不过他貌似已经挂了???xxxx 反正知道mz标记就是代表可执行文件就ok了。

lfanew字段就是标记NT头在文件里的偏移,文件头+lfanew就是NT头的位置(中间有一段没用的数据叫 dos  sub 下文增加节会讲)。


0x50 0x45是PE的标志 ,因为“PE”的ascll码就是0x50 0x45。

NT头

NT头包含标准PE头和可选PE头,文件的很多信息都在这里面。


Signature字段里存的就是pe标记,注意宽度是4,然后就是标准PE头。


标准PE头

标准PE头一共是20个字节,下面我讲一下字段的用处。

typedef struct _IMAGE_FILE_HEADER { 

       * WORD   [60]+4 Machine; 运行平台

       * WORD    NumberOfSections; [60]+6 文件的节数目

       * DWORD   TimeDateStamp;   [60]+8 文件创建日期和时间

        DWORD   PointerToSymbolTable;   [60]+12  指向符号表(用于调试)

        DWORD   NumberOfSymbols;  [60]+16 符号表中的符号数量(用于调试)

       * WORD    SizeOfOptionalHeader;[60]+20  可选头的长度

       * WORD    Characteristics; [60]+22  文件属性

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER

1.Machine字段,表示目标CPU 的类型。

几个常见的及其标识如下:

机器              标识     

Intel I386          14ch

MIPS R3000        162h

Alpha AXP          184h

Power PC           1F0h

MIPS R4000         184h

2.NumberOfSection,标识节的数目,关于节后面会详细讲。

3.TimeDateStamp

PE文件创建的时间,这个时间是指从1970年1月1日到创建该文件的所有的秒数。

4)PointerToSymbolTable。略

5)NumberOfSymbol。略

6)SizeOfOptionalHeader:紧跟着标准PE头后面的数据大小,这也是一个数据结构,它叫做可选PE头,其大小依赖于是64位还是32位文件。32位文件值通常是00E0h,对于64位值通常为00F0h。

7)Characteristics:文件属性,普通EXE文件这个字段值为010fh,DLL文件这个字段一般是0210h。


(标准pe头)

可选PE头

可选PE头,其大小依赖于是64位还是32位文件。32位文件值通常是00E0h,对于64位值通常为00F0h。


可选PE头字段多,但是重要的不多(加*的都很重要,不要慌,*是我加的)。

typedef struct _IMAGE_OPTIONAL_HEADER { 

       * WORD    Magic;  10B 32位PE 20B 64位PE 107 ROM映像[60]+24

        BYTE    MajorLinkerVersion;  链接器版本号   [60]+26                 

        BYTE    MinorLinkerVersion;  链接器副版本号  [60]+27

       * DWORD   SizeOfCode;  所有代码节的总和  该大小是基于文件对齐后的大小[60]+28

       * DWORD   SizeOfInitializedData;  所有含已初始化数据的节的总大小  [60]+32

       * DWORD   SizeOfUninitializedData;  所有含未初始化数据的节的大小    [60]+36

       * DWORD   AddressOfEntryPoint;  程序执行入口RVA     [60]+40

       * DWORD   BaseOfCode;  代码节的起始RVA              [60]+44

       * DWORD   BaseOfData;  数据节的起始RVA               [60]+48

       * DWORD   ImageBase;  程序的优先装载地址              [60]+52

       * DWORD   SectionAlignment;  内存中节的对齐粒度                 [60]+56                               

       * DWORD   FileAlignment;  文件中节的对齐粒度           [60]+60

        WORD    MajorOperatingSystemVersion;  操作系统主版本号 [60]+64

        WORD    MinorOperatingSystemVersion;  操作系统副版本号   [60]+66

        WORD    MajorImageVersion;  PE文件映像的版本号     [60]+68

        WORD    MinorImageVersion;                          [60]+70

        WORD    MajorSubsystemVersion;  子系统的版本号       [60]+72

        WORD    MinorSubsystemVersion;                     [60]+74

        DWORD   Win32VersionValue;  未用 必须设置0          [60]+76

        DWORD   SizeOfImage;  内存中整个PE文件的映像尺寸       [60]+80

        DWORD   SizeOfHeaders;  所有节表按照文件对齐粒度后的大小   [60]+84

        DWORD   CheckSum;  校验和                    [60]+88

        WORD    Subsystem;  指定使用界面的子系统       [60]+92

        WORD    DllCharacteristics;  DALL文件属性        [60]+94

      *  DWORD   SizeOfStackReserve;  初始化时保留的栈的大小  [60]+96

      *  DWORD   SizeOfStackCommit;  初始化时实际提交的栈的大小   [60]+100

      *  DWORD   SizeOfHeapReserve;  初始化时保留的堆的大小   [60]+104

      *  DWORD   SizeOfHeapCommit;  初始化时实际提交的堆的大小 [60]+108

        DWORD   LoaderFlags;  加载标志  未用      [60]+112

        DWORD   NumberOfRvaAndSizes;  下面的数据目录结构的数量  +116

        IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];+120 

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; 

额,重要的也蛮多。。。

详细字段的用处我会在下面代码里会详细说明,数据目录。。。。好吧,现在讲。

数据目录

在PE结构中最重要的就是节表和数据目录表,数据目录一共指向16张表,分别是:导出表、导入表、资源表、异常信息表、安全证书表、重定位表、调试信息表、版权所以表、全局指针表                           

TLS表、加载配置表、绑定导入表、IAT表、延迟导入表、COM信息表 最后一个保留未使用。                     

这么多表中我们只要注重导出表、导入表、重定位表、IAT表就可以了。他们直接影响程序运行。 注意:数据目录只是各种表的地址(rva)。需要寻址才能找到他们。


(数据目录,太长了没有截完)

每张记录宽度8字节 然后*16=128字节


经过解析就变成

代码实现解析PE头(PE头和标准PE头是两个东西)

代码实现思路就是按照字段宽度偏移来遍历dos头、NT头。(这里rva和foa都一样)

__int64 flen = 0;

//int arr_dos[30];

char* word_dos[] =

{

         "e_magic",  "e_cblp",  "e_cp",  "e_crlc",  "e_cparhdr",  "e_minalloc", "e_maxalloc",   "e_ss",  "e_sp",
         "e_csum",  "e_ip",  "e_cs", "e_lfarlc","e_ovno",   "e_res[4]",  "e_res[4]",  "e_res[4]", "e_res[4]",  "e_oemid",

         "e_oeminfo",   "e_res2[10]",  "e_res2[10]",    "e_res2[10]",  "e_res2[10]",     "e_res2[10]",   "e_res2[10]",
        "e_res2[10]",    "e_res2[10]",    "e_res2[10]",      "e_res2[10]",     "e_lfanew"

};

/////////////////////////////////////////////////////////////////char* NT_word[]

int arr_pe[8] = { 4,2,2,4,4,4,2,2 }; //标准pe每个字段的宽度

int pe_s = 0;//没用 凑数的

int arr_o_pe[] = { 2,1,1,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,4

,4,4,4,2,2,4

,4,4,4 ,4,4 };//取字段字节数

int arr_sch[10] = { 8,4,4,4,4,4,4,2,2,4 };

//char*// file_ser(char*, unsigned __int64);

char* file_open(char* fstr)//打开文件并保存文件流

{

         FILE* pf = fopen(fstr, "rb");

         if (pf == NULL)

         {

                   perror("The following error occurred");

                   return 0;

         }

         fseek(pf, 0, 2);

         unsigned __int64 fsize = ftell(pf);

         flen = fsize;

         rewind(pf);

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

         if (fbuff == NULL)

         {

                   perror("The following error occurred");

                   return 0;

         }

         memset(fbuff, 0, fsize);

         unsigned __int64 r = fread(fbuff, 1, fsize, pf);

         if (r == 0)

         {

                   perror("The following error occurred");

                   return 0;

         }

         //fclose(pf);

         //free(pf);

         //pf = 0;

         //print_pe(fbuff);

         //ftoi_32(fbuff);

         return fbuff;

}

先打开文件,然后调用打印函数。

void print_pe(char* ppe)//打印pe字段

{

         char* flag = ppe;//flag是游标

         for (int j = 0; j <= 58; j += 2)//dos头除了最后一个字段都是2个字节

         {

                   printf("%s   %X\n", word_dos[j / 2], *(short*)flag);

                   flag += 2;

         }

         printf("%s %X\n", word_dos[30], *(int*)flag);//e_lfanew字段

         int pe_offsets = *(int*)flag;

         flag = (char*)((int)ppe + pe_offsets);   //游标跳NT头

         printf("*************pe******************\n");

         for (int i = 0; i < 8; i++)//处理标准pe

         {

                   if (arr_pe[i] == 4)

                   {

                            printf("%X\n", *(int*)flag);

                            flag += 4;

                   }

                   else if (arr_pe[i] == 2)

                   {

                            printf("%X\n", *(short*)flag);

                            flag += 2;

                   }

         }

         printf("*************option pe******************\n");

         for (int i1 = 0; i1 < sizeof(arr_o_pe) / sizeof(arr_o_pe[0]); i1++)//处理可选pe

         {

                   if (arr_o_pe[i1] == 4)

                   {

                            printf("%X\n", *(int*)flag);

                            flag += 4;

                   }

                   else if (arr_o_pe[i1] == 2)

                   {

                            printf("%X\n", *(short*)flag);

                            flag += 2;

                   }

                   else if (arr_o_pe[i1] == 1)

                   {

                            printf("%X\n", *(char*)flag);

                            flag += 1;

                   }

         }

         return;

}

PE文件中所有节的属性都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构用来描述一个节,结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。全部有效结构的最后以一个空的IMAGE_SECTION_HEADER结构作为结束,所以节表中总的IMAGE_SECTION_HEADER结构数量等于节的数量加一。节表总是被存放在紧接在PE文件头的地方。

另外,节表中 IMAGE_SECTION_HEADER 结构的总数总是由PE文件头 IMAGE_NT_HEADERS 结构中的 FileHeader.NumberOfSections 字段来指定的。

一个节表的宽度是40个字节,最后一个节表后面要跟上40个字节的0,也就是结束标志。(也可以没有结束标志,不过可能会炸。。。)


(节表)

一个文件至少由1个节表组成,每个节表记录了节的地址(rva和foa),大小,权限等,每节表占40个字节。

下面来说说每一个字段和作用。

typedef struct _IMAGE_SECTION_HEADER {                                                                           

    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; +0                                                                     

    union {                                                                                

            DWORD   PhysicalAddress;                                                                                   

            DWORD   VirtualSize;                                                                            

    } Misc;                                  +8   双字 是该节在没有对齐前的真实尺寸,该值可以不准确。

    DWORD   VirtualAddress;             +12 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址址.                                                 

    DWORD   SizeOfRawData;           +16 节在文件中对齐后的尺寸.                                                 

    DWORD   PointerToRawData;               +20 节区在文件中的偏移.     raw地址+偏移=下一节的地址                                     

    DWORD   PointerToRelocations; +24 在obj文件中使用 对exe无意义                                              

    DWORD   PointerToLinenumbers;        +28 行号表的位置 调试的时候使用                                       

    WORD    NumberOfRelocations;          +32 在obj文件中使用  对exe无意义                                           

    WORD    NumberOfLinenumbers;       +34 行号表中行号的数量 调试的时候使用                                           

    DWORD   Characteristics;             +36 节的属性                                              

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

联合体就当成一个变量来用就可以了,宽度是最大的那一个变量。

1、Name          8个字节 一般情况下是以"\0"结尾的ASCII吗字符串来标识的名称,内容可以自定义.                                                            

注意:该名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理.                                                             

2、Misc  双字 是该节在没有对齐前的真实尺寸,该值可以不准确。                                        

3、VirtualAddress 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址.

4、SizeOfRawData  节在文件中对齐后的尺寸.                                                                

5、PointerToRawData 节区在文件中的偏移.                                                            

6、PointerToRelocations 在obj文件中使用 对exe无意义                                                              

7、PointerToLinenumbers 行号表的位置 调试的时候使用                                                              

8、NumberOfRelocations 在obj文件中使用  对exe无意义                                                          

9、NumberOfLinenumbers 行号表中行号的数量 调试的时候使用                                              

10、Characteristics 节的属性                                                             

节的属性:

[值:00000020h] [IMAGE_SCN_CNT_CODE                // Section contains code.(包含可执行代码)]

[值:00000040h] [IMAGE_SCN_CNT_INITIALIZED_DATA    // Section contains initialized data.(该块包含已初始化的数据)]

[值:00000080h] [IMAGE_SCN_CNT_UNINITIALIZED_DATA  // Section contains uninitialized data.(该块包含未初始化的数据)]

[值:00000200h] [IMAGE_SCN_LNK_INFO                // Section contains comments or some other type of information.]

[值:00000800h] [IMAGE_SCN_LNK_REMOVE              // Section contents will not become part of image.]

[值:00001000h] [IMAGE_SCN_LNK_COMDAT              // Section contents comdat.]

[值:00004000h] [IMAGE_SCN_NO_DEFER_SPEC_EXC       // Reset speculative exceptions handling bits in the TLB entries for this section.]

[值:00008000h] [IMAGE_SCN_GPREL                   // Section content can be accessed relative to GP.]

[值:00500000h] [IMAGE_SCN_ALIGN_16BYTES           // Default alignment if no others are specified.]

[值:01000000h] [IMAGE_SCN_LNK_NRELOC_OVFL         // Section contains extended relocations.]

[值:02000000h] [IMAGE_SCN_MEM_DISCARDABLE         // Section can be discarded.]

[值:04000000h] [IMAGE_SCN_MEM_NOT_CACHED          // Section is not cachable.]

[值:08000000h] [IMAGE_SCN_MEM_NOT_PAGED           // Section is not pageable.]

[值:10000000h] [IMAGE_SCN_MEM_SHARED              // Section is shareable(该块为共享块).]

[值:20000000h] [IMAGE_SCN_MEM_EXECUTE             // Section is executable.(该块可执行)]

[值:40000000h] [IMAGE_SCN_MEM_READ                // Section is readable.(该块可读)]

[值:80000000h] [IMAGE_SCN_MEM_WRITE               // Section is writeable.(该块可写)]

可选PE头下面就是节表,所以我们可以用dos头+lfanew+NT头的宽度来定位,注意可选pe头宽度不确定,所以只能一步一步偏移过来,下面代码我会讲。


这个文件一共有6张表,而且在标准pe头里已经记录了


PE加载过程

当你打开一个exe时,操作系统首先会读取PE的数据,然后再把节给加载到内存里,

但是加载的时候有可能会拉升节,因为内存对齐是0x1000也就是一个页。但是老的编译器生成的PE文件的文件对齐是0x200,所以这种文件会被拉升。

PE加载的过程:                                                                    

1、根据SizeOfImage的大小,开辟一块缓冲区(ImageBuffer).                                                                 

2、根据SizeOfHeader的大小,将头信息从FileBuffer拷贝到ImageBuffer                                

3、根据节表中的信息循环讲FileBuffer中的节拷贝到ImageBuffer中.                                                                  

                                                                                    

代码练习

练习1 打印节表

void print_section(char* ppe)

{

         int arr_sch[10] = { 8,4,4,4,4,4,4,2,2,4 };

char* sch = (char*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 24);/*pe头*/

                                                                                                                                                                  //printf("%x..\n", *(short*)sch);

sch += *(short*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 20);

//printf("%x...", *(short*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 20));

//printf("%x..\n", *(short*)sch);

char str[9] = { 0 };

printf("**********section*************\n");

for (int i = 0; i < *(short*)((int)ppe + (*(int*)((int)ppe + 60))/*dos+*/ + 6/*节表个数*/); i++)

{

           for (int j = 0; j < 10; j++)//处理

           {

                    if (arr_sch[j] == 4)

                    {

                             printf("%X\n", *(int*)sch);

                             sch += 4;

                    }

                    else if (arr_sch[j] == 2)

                    {

                             printf("%X\n", *(short*)sch);

                             sch += 2;

                    }

                    else if (arr_sch[j] == 8)

                    {

                             //sch += 8;

                             memset(str, 0, 9);

                             for (int k = 0; k < 8; k++)

                             {

                                       str[k] = *(sch++);

                             }

                             printf("%s \n", str);

                    }

           }

}

}

练习2文件流转映像

char* ftoi_32(char* fp)

{

    if (*(short*)fp != 0x5a4d)

    {

        cout << "错误 不是mz";

        return 0;

    }

    char* pfbuff = fp;

    char* buff = pfbuff;

    char* soi = (char*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 80);/*sizeofimage*/

                                                                        /*if (*(int*)(soi-24) == 0x1000)

                                                                        {

                                                                        return fp;

                                                                        }*/

    printf("%x..", *(unsigned int*)soi);

    char* imbuff = (char*)malloc(*(unsigned int*)soi);

    char* imagebuff = imbuff;

    memset(imagebuff, 0, *(unsigned int*)soi);

    printf("%x soh ", *((int*)(soi + 4)));

    //if (*((int*)(soi + 4))<= 0x1000)// 拷贝头+拉升

    //{

    int i = 0;

    while (i++ <= *(unsigned int*)(soi + 4))

    {

        *(char*)imagebuff++ = *(char*)pfbuff++;

    }

    int sct_sum = *(short*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 6);//numberofsection

    char* sord = (char*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 24);/*pe头*/

    sord += *(short*)((int)fp + (*(int*)((int)fp + 60))/*dos+*/ + 20);

    //pe头末尾+可选pe头的宽度=节表

    sord += 16;//sizeofrawdata

    imagebuff = (imbuff + *(int*)(sord - 4));

    pfbuff = (fp + *(int*)(sord + 4));

    printf("%x  ", *(int*)sord);

    printf("\n%x  ", *(int*)(sord - 4));

    for (int s = 0; s < sct_sum; s++)

    {

        //pfbuff = (fp + *(int*)(sord -4));

        printf("节大小%x  ", *(int*)sord);

        //cout << sct_sum << endl;

        for (int s1 = 0; s1 < *(int*)sord; s1++)

        {

            *(char*)imagebuff++ = *(char*)pfbuff++;

        }

        sord += 40;//下一个节表的sizeofrawdata

        imagebuff = imbuff + (*(int*)(sord - 4));

        pfbuff = (fp + *(int*)(sord + 4));

    }

    FILE* fp1 = NULL;

    fp1 = fopen("copyi3.exe", "wb");

    fwrite((char*)imbuff, 1, *(int*)(soi), fp1);

    //cout << u;

    fclose(fp1);

    fp = NULL;

    //free(imagebuff);

    //free(pfbuff);

    //  free(imbuff);

    //itof_32(imbuff);

    return imbuff;

}

练习3映像转文件流

char* itof_32(char* imbuff,char* name)//filebuffer to imagebuffer

{

if (*(short*)imbuff != 0x5a4d)

{

           cout << "错误 不是mz";

           return 0;

}

char* imgb = imbuff;

unsigned int fbuff_size = 0;

int sct_cnt = *(short*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 6/*节表个数*/);

char* sord = (char*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 24);/*pe头*/

sord += *(short*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 20);

//pe头末尾+可选pe头的宽度=节表

sord += 16;//sizeofrawdata

printf("    %x  ", *((int*)sord));

int si = 0;

while (si++ < sct_cnt)

{

           fbuff_size += (*(int*)sord);

           sord += 40;

}

fbuff_size += *(int*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 84);/*sizeofheaders*/

////**************************************缩短头

char* fbuff = (char*)malloc(flen);

memset(fbuff, 0, flen);

if (!fbuff)

{

           perror("The following error occurred");

           getchar();

           return 0;

}

char* buff = fbuff;

memset(buff, 0, flen);

char* soh = (char*)((int)imbuff + (*(int*)((int)imbuff + 60))/*dos+*/ + 84);/*sizeofheaders*/

                                                                                                                                                                                   //int fa = *(int*)(soh - 24);//FileAlignment;

                                                                                                                                                                                   //if (*(int*)soh < fa)// 拷贝头+缩短

                                                                                                                                                                                   //{

int i = 0;

while (i++ <= *(int*)(soh))

{

           *(char*)fbuff++ = *(char*)imbuff++;

}

//************************************缩短节

sord -= 40 * sct_cnt;//第一个节表的SizeOfRawData;

printf("    %x  ", *(int*)(sord - 4));

imbuff = (imgb + *(int*)(sord - 4));

fbuff = (buff + *(int*)(sord + 4));

int s = 0;

while (s++ < sct_cnt)//节缩短

{

//                 cout << sct_cnt << endl;

           int s1 = 0;

           while (s1++ < *(int*)sord)

           {

                    *(char*)fbuff++ = *(char*)imbuff++;

           }

           sord = (char*)(((int)sord) + 40);//下一个节表的sizeofrawdata

           fbuff = (buff + *(int*)(sord + 4));

           imbuff = imgb + *(int*)(sord - 4);

}

//file_ser(buff, fbuff_size);

FILE* fp1 = fopen(name, "wb");

fwrite((char*)buff, 1, flen, fp1);

fclose(fp1);

fp1 = NULL;

return buff;

}

通常每个节都会遗留一些0,这和对齐有关系,比如说文件对齐是200h,存储的数据只有10h,那么这个节里还会有190h的空间给我们随便搞。如果想在别人程序里加一段自己的程序,就可以在代码节或其他节的空隙里添加硬编码,然后修改OEP让程序一开始先执行你的程序,然后再跳到原来的OEP上。


放代码前,先来了解2个硬编码,E8,E9。

E8对应的汇编语句就是call,E9就是jmp。

E8后面跟地址(4个字节)E9也是。不过跳转的地址不能写死,有一个公式就是:

真正要跳转的地址 = E8这条指令的下一行地址 + X

X = 真正要跳转的地址 - E8这条指令的下一行地址

写代码的时候只要计算一下x,然后把x填到E8/E9后面就可以了。

代码练习

练习1 内存偏移转文件偏移

int rva_to_foa(char* i,int n)

{

         Int  rva = 0;

         Int  rva_offset = 0;

         rva_offset = n;

         int sct_cnt = *(short*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 6/*节表个数*/);

         char* sord = (char*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 24);/*pe头*/

         sord += *(short*)( (int)i + (*(short*)((int)i + 60))/*可选头大小*/ + 20);

         sord += 12;//VirtualAddress;

         sord += 40 * (sct_cnt - 1);//最后一个节表的virtusladdress

         int s = sct_cnt;

         while (s > 0)

         {

                   if (rva_offset >= *(int*)sord)

                   {

                            break;

                   }

                   sord -= 40;

                   s--;

         }

         if (s == 0)//偏移在头部

         {

                   //printf("foa%x\n", rva_offset);

                   return rva_offset;

         }

         rva_offset -= *(int*)(sord);//减fva+foa

         rva_offset += *(int*)(sord + 8);

         //printf("foa%x\n", rva_offset);

         return rva_offset;

}

练习2文件偏移转内存偏移

int foa_to_rva(char* i, int n)

{

         Int  rva = 0;

         Int  rva_offset = 0;

         rva_offset = n;

         int sct_cnt = *(short*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 6/*节表个数*/);

         char* sord = (char*)((int)i + (*(int*)((int)i + 60))/*dos+*/ + 24);/*pe头*/

         sord += *(short*)((int)i + (*(short*)((int)i + 60))/*可选头大小*/ + 20);

         sord += 20;//foa;

         sord += 40 * (sct_cnt - 1);//最后一个节表的virtusladdress

         int s = sct_cnt;

         while (s > 0)

         {

                   if (rva_offset >= *(int*)sord)

                   {

                            break;

                   }

                   sord -= 40;

                   s--;

         }

         if (s == 0)//偏移在头部

         {

                   //printf("foa%x\n", rva_offset);

                   return rva_offset;

         }

         rva_offset -= *(int*)(sord);//减foa+rva

         rva_offset += *(int*)(sord-8 );

         //printf("rva%x\n", rva_offset);

         return rva_offset;

}

练习3 在代码节空白区添加代码

//先调用文件打开函数-》文件buffer转映像buff-》添加代码函数(也就是这个)-》映像buff

//转文件buff函数存盘

char* inseart_code(char* imbuff)

{

         char* buff = imbuff;

         char* sod = (imbuff + (*(int*)(imbuff + 60) + 24));//pe头尾

         sod += *(short*)(imbuff + (*(int*)(imbuff + 60) + 20));

         while (strcmp(sod, ".text") != 0)

         {

                   sod += 40;

         }

         buff = (imbuff + (*(int*)(sod + 12))) + (*(int*)(sod + 8));//节的空白区

         char* oep = (imbuff + (*(int*)(imbuff + 60) + 40));

         //      oep += *(short*)(imbuff + (*(int*)(imbuff + 60) + 20));

         int roep = *(int*)oep;

         *(int*)oep = buff - imbuff;//代码入口

         char a[] = { 0x6A,0,0x6A,0,0x6A,0,0x6A,0,0xE8,0,0,0,0,0xE9,0,0,0,0 };

         int dest_addr = 0x75fc0047;//messageBox 的地址  这个地址会改变

         int x = dest_addr - (((int)(buff - imbuff) + 13) + 0x400000);

         int y = 0x400000 + roep - (((int)(buff - imbuff) + 18) + 0x400000);

         *(int*)(a + 9) = x;

         *(int*)(a + 14) = y;

         int s = 0;

         //75FC0026    8BEC            mov ebp, esp

         while (s <= (sizeof(a) / sizeof(a[0])))

         {

                   (*(char*)buff++) = a[s];

                   s++;

         }

         return imbuff;

}


这个比较简单,多余文字我不写了。直接上代码。。

代码练习

练习1 新增节

char* inseart_jb(char* imbuff,int size)

{

         char* buff = imbuff;

         char* sh = (char*)((imbuff + (*(int*)(imbuff + 60))) + 24)/*pe*/;

         int sh_cnt = *(short*)(sh - 18);//节表个数

         int old_size = *(int*)(sh + 56);

         *(int*)(sh + 56) += size;//sizeofimg扩大

         flen += size;

         unsigned int nl = *(int*)(sh + 56);

         (*(short*)(sh - 18))++;//节表数+1

         sh += *(short*)(sh - 4);//节表

         int s = 0;

         while (s++ < sh_cnt)

         {

                   sh += 40;

         }

         char code[] = {

                   0x2E, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x40, 0xE9, 0x01,

                   0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x10,

                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

                   0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0xf0

         };//节表

         strcpy(code, "tttt");

         unsigned int va = 0;

         int vs_rs = size;//尺寸

         if (*(int*)(sh - 32) >= *(int*)(sh - 24))

         {

                   va = (*(int*)(sh - 32)) + (*(int*)(sh - 28));

         }

         else

         {

                   va = (*(int*)(sh - 24)) + (*(int*)(sh - 28));

         }

         unsigned int ra = (*(int*)(sh - 20)) + (*(int*)(sh - 24));

         *(int*)(code + 8) = vs_rs;

         *(int*)(code + 12) = va;//p偏移

         *(int*)(code + 16) = vs_rs;

         *(int*)(code + 20) = ra;

         s = 0;

         while (s<40)

         {

                   *sh = code[s];

                   sh++;

                   s++;

         }

         //****************************新增节

         char* buff1 = (char*)realloc(imbuff,nl);

         memset((char*)(buff1 + old_size), 0, size);

          imbuff=buff1 ;

         return buff1;

}

由于有的时候节表剩余空间不够80个字节,所以就不能增加节(当然可以也可以把dos sub给覆盖掉)但可以用扩大最后节/合并节 

         扩大最后节:                                                       

         1、拉伸到内存                                                     

         2、分配一块新的空间:SizeOfImage + Ex                                                

         3、将最后一个节的SizeOfRawData和VirtualSize改成N                                               

            SizeOfRawData = VirtualSize = N                                                    

            N = (SizeOfRawData或者VirtualSize 内存对齐后的值) + Ex                                                       

         4、修改SizeOfImage大小                                                  

            SizeOfImage = SizeOfImage + Ex                

  

         合并节


(字有点丑。。。)  最后把节的属性改到最大就ok。

代码练习

练习1扩大最后节

char* expand_lastjb(char* imgbuff)

{

         char* si = (imgbuff + (*(unsigned int*)(imgbuff + 60)) + 80);

         (*(int*)si) += 0x1000;//扩大映像尺寸

         unsigned sl = (*(unsigned int*)si);

         char* buff = (char*)realloc(imgbuff,sl);

         if (buff==0)

         {

                   printf("内存分配失败--expand——lastjb");

                   getchar();

                   return 0;

         }

         memset(buff, 0, sl);

         imgbuff = buff;

         char* sh = (imgbuff + (*(unsigned int*)(imgbuff + 60)));//pe

         int sh_cnt = *(short*)(sh + 6);

         sh += ((*(short*)(sh + 20)) +24);//节表头

         int s = 0;

         while (s++ < (sh_cnt-1))

         {

                   sh += 40;

         }

         *(int*)(sh + 36) |= 0x60000020; //加上代码节的权限

         *(int*)(sh + 8) += 0x1000;

         *(int*)(sh + 16) += 0x1000;

         return imgbuff;

}

练习2合并节

char* merge_jb(char* imbuff)

{

         char* buff = imbuff;

         int all_jb_char = 0;//保存所有节表的属性;

         char* pe = (imbuff + (*(int*)(imbuff + 60)));//pe头

         char* sh = (pe + 24 + (*(short*)(pe + 20)) );//节表

         int code_size = 0;

         int s = 0;

         while (s <(*(short*)(pe+6)))  //得到节表的大小和权限

         {

                   if(s == (*(short*)(pe + 6))-1)

                   if (*(int*)(sh + 8 + 40 * s) >= *(int*)(sh + 16 + 40 * s))//virtualsize和sizeofrawdata做比较

                   {

                            code_size += *(int*)(sh + 8 + 40 * s) + *(int*)(sh + 12 + 40 * s) - (*(int*)(pe + 84));

                   }

                   else

                   {

                            code_size += *(int*)(sh + 16 + 40 * s)+ (*(int*)(sh + 12 + 40 * s) - (*(int*)(pe + 84)));

                   }

                   all_jb_char |= *(int*)(sh + 36 + 40 * s);

                   for (int i = 0; i < 40 && s!=0; i++)//除第一个节表外都置位0

                   {

                            *(sh+i+40*s) = 0;

                   }

                   s++;

         }

         *(int*)(sh + 8) = code_size;

         *(int*)(sh + 16) = code_size;

         *(int*)(sh + 36) = all_jb_char;

         *(short*)(pe + 6) = 1;//numberofsection置位1;

         return imbuff;

}

1、如何定位导出表:                                       

数据目录项的第一个结构,就是导出表.                                        

typedef struct _IMAGE_DATA_DIRECTORY {                                      

    DWORD   VirtualAddress;                                        

    DWORD   Size;                                          

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;               
                     

VirtualAddress  导出表的RVA                                          

Size 导出表大小                                               


字段说明

         


工作原理

1.按名称导出


2.按序号导出


代码练习

练习1 按序号导出函数地址

int GetFunctionAddrByOrdinals(char* fbuff, int ord)

{

         char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

         char* data_dir = pe + 120;//数据目录

         char* exportt = fbuff + rva_to_foa(fbuff, *(int*)data_dir);//导出表头

         if (*(int*)data_dir == 0)

         {

                   printf("此文件没有导出表");

                   getchar();

                   return 0;

         }

         int* aof = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(exportt + 28)));//地址表头

         int nof = *(int*)(exportt + 20);// 所有导出函数的个数

         int* arr_fun = (int*)malloc(sizeof(int)*nof);

         int base = *(int*)(exportt + 16);

         int s = 0;

         while (s<nof)

         {

                   arr_fun[s] = *(int*)(aof + s);//函数表

                   //printf("rav:%x ", arr_fun[s]);

                   if (ord - base == s)

                   {

                            printf("rav:%x ", arr_fun[s]);

                            return arr_fun[s];

                   }

                   s++;

         }

         printf("无此函数");

         getchar();

         return 0;

}

练习2 按名字导出函数地址

int GetFunctionAddrByName(char* fbuff, char* name)//导出表:按名字导出+显示导出函数信息

{

         char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

         char* data_dir = pe + 120;//数据目录

         char* export1 = fbuff+ rva_to_foa(fbuff, *(int*)data_dir);//回来发现export是关键字 不能拖内存 所以又定义了一个

         char* export=export1;//导出表头  

         if (*(int*)data_dir == 0)

         {

                   printf("此文件没有导出表");

                   getchar();

                   return 0;

         }

         int NumberOfNames= *(int*)(export+24);

         int* aon = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(export+32)));//名称地址表头

         //aon = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(aon)));

         int* arr_name = (int*)malloc(sizeof(int)*NumberOfNames);

         int base = *(int*)(export+16);

         int flag = -1;//索引

         int s = 0;

         while (s<NumberOfNames)

         {

                   arr_name[s]=(int)(fbuff + rva_to_foa(fbuff, *(int*)(aon+s)));//函数名地址

                   //printf("%s\n", arr_name[s]);

                   if (strcmp((char*)arr_name[s],name) == 0)

                   {

                            flag = s;

                   }

                   s++;

         }

         if (flag == -1)//判断有没有这个函数

         {

                   printf("没有此函数");

                   getchar();

                   return 0;

         }

         short* aono = (short*)(fbuff + rva_to_foa(fbuff, *(int*)(export + 36)));//序号表

         short* arr_name_ord = (short*)malloc(sizeof(short)*NumberOfNames);

          s = 0;

         while (s<NumberOfNames)

         {

                   arr_name_ord[s] = *(short*)(aono+s)+base;//函数序号表

                   //printf("%d\n", arr_name_ord[s]);

                   s++;

         }

         int* aof = (int*)(fbuff + rva_to_foa(fbuff, *(int*)(export + 28)));//地址表头

         int nof = *(int*)(export + 20);// 所有导出函数的个数

         int* arr_fun= (int*)malloc(sizeof(int)*nof);

         s = 0;

         while (s<nof)

         {

                   arr_fun[s] = *(int*)(aof + s);//函数表

                   printf("rav:%x ", arr_fun[s]);

                   printf("%x ", arr_name_ord[s]);

                   printf("%s\n",arr_name[s]);

                   s++;

         }

         printf("%x", arr_fun[arr_name_ord[flag]-base]);

         return arr_fun[arr_name_ord[flag]];//返回地址

}

练习3 移动导出表到新增节

//此文件已经新增节处理过了。

char* move_export(char* fbuff)

{

    char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

    char* data_dir = pe + 120;//数据目录

    char* exportt = fbuff + rva_to_foa(fbuff, *(int*)data_dir);//导出表头

    if (*(int*)data_dir == 0)

    {

        printf("此文件没有导出表");

        getchar();

        return 0;

    }

    char* nexport=  fbuff + rva_to_foa(fbuff, getlastjb_rva_add(fbuff));//定位到最后一个节的开始地址

    char* nexporth = nexport;

    *(int*)data_dir = foa_to_rva(fbuff,nexport-fbuff);

    int s = 0;

    while (s<40)

    {

        *(char*)(nexport + s) = *(char*)(exportt + s);

        s++;

    }

    int nf = *(int*)(exportt + 20);//numberoffunction

    int nn = *(int*)(exportt + 24);//numberofnames

    char* name = fbuff + rva_to_foa(fbuff, *(int*)(exportt+12));//dll名字的地址

    char* paddr = fbuff + rva_to_foa(fbuff, *(int*)(exportt + 28));

    char* pnaddr = nexport + 40;

    s = 0;

    *(int*)(nexporth + 28) = foa_to_rva(fbuff,pnaddr-fbuff);//复原AddressOfFunctions;

    while (s<nf)//拷贝函数表

    {

        *(int*)(pnaddr)= *(int*)(paddr+4*s) ;

        /*

        pnaddr= (char*)((int*)pnaddr+1);

        paddr = (char*)((int*)paddr + 1);

        */   //这个表达式比较麻烦所以用下面的+4

        pnaddr += 4;      //函数地址!

        s++;

    }

    paddr = fbuff + rva_to_foa(fbuff, *(int*)(exportt + 36));

    s = 0;

    *(int*)(nexporth + 36) = foa_to_rva(fbuff, pnaddr - fbuff);// AddressOfNameOrdinals

    while (s<nn)//拷贝函数序号表

    {

        *(short*)(pnaddr) = *(short*)(paddr+2*s);

        pnaddr += 2;

        s++;

    }

    paddr = fbuff +rva_to_foa(fbuff, *(int*)(exportt + 32));

    s = 0;

    int strl = strlen(name);//两个地址转换偏移

    char* straddr =name;//存储字符串的地方;

    char* nstradd = pnaddr + 4 * nn; //地址表后

    *(int*)(nexporth + 32) = foa_to_rva(fbuff, pnaddr - fbuff);//AddressOfNames;

    while (s<nn+1)//拷贝函名称数表

    {

        if (s!=0)//s=0时复制dll的NAME

        {

        *(int*)(pnaddr) =(foa_to_rva(fbuff,nstradd-fbuff));

        *(int*)(nexporth + 12) = (int)foa_to_rva(fbuff,pnaddr-fbuff);//定位name

        pnaddr += 4;//表指针后移

        paddr += 4;

        }

        for (int i = 0; i < strl; i++)

        {

            *nstradd++= *straddr++;

        }

        *nstradd=0;// 少0

        nstradd++;

        strl = strlen(fbuff + rva_to_foa(fbuff, *(int*)paddr));

        straddr = fbuff + rva_to_foa(fbuff, *(int*)(paddr));

        s++;

    }

    return fbuff;

}

重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你占用,你必须转移到别的地址,这就需要基址重定位。你可能会问,不是说过每个进程都有自己独立的虚拟地址空间吗?既然都是自己的,怎么会被占据呢?对于EXE应用程序来说,是这样的。但是动态链接库就不一样了,我们说过动态链接库都是寄居在别的应用程序的空间的,所以出现要载入的基地址被应用程序占据了或者被其它的DLL占据了,也是很正常的,这时它就不得不进行重定位了。

1.重定位表定位


2.重定位表解析


代码练习

练习1打印重定位表

void print_relocation(char* fbuff)

{

         char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

         char* data_dir = pe + 120;//数据目录

         if (*(int*)(data_dir+8*5) == 0)

         {

                   printf("文件没有重定位表");

                   getchar();

                   return;

         }

         char* relocation = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8 * 5));//重定位表头

         char* flag = relocation;

         int s = 0;

         int s1 = 0;

         int rva = 0;//记录偏移

         int b = 0;

         printf("%x\n", 12 & 0x3);

         while (*(int*)flag != 0 && *(int*)(flag+4) != 0)

         {

                   rva = *(int*)flag;

                   printf("*************%x\n", rva);

                   flag += 8;//    DWORD   SizeOfBlock

                   s = 0;

                   s1 = (*(int*)(flag - 4)-8) / 2;

                   while (s++ < s1)//当前页有多少个地址

                   {

                            b = *(short*)flag;

                            //if (b & 0x3000 == 0x3000)

                            printf("%x\n",rva+(b & 0xfff));

                            flag += 2;

                   }

         }

}

练习2移动重定位表

char* move_relocation(char* fbuff,int offset)//移动重定位表到新增节 偏移可以为0 也可以修改重定位数据

{

    char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

    char* data_dir = pe + 120;//数据目录

    if (*(int*)(data_dir + 8 * 5) == 0)

    {

        printf("文件没有重定位表");

        getchar();

        return 0;

    }

    char* relocation = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8 * 5));//重定位表头

    char* flag = relocation;

    int s = 0;

    int s1 = 0;

    int rva = 0;//记录偏移

    short b = 0;

    char*  nrelocation = fbuff + rva_to_foa(fbuff, (getlastjb_rva_add(fbuff)));//移动的目的地foa

    *(int*)(data_dir + 8 * 5) = foa_to_rva(fbuff, nrelocation - fbuff);//改变数据目录的偏移;

    while (*(int*)flag != 0 && *(int*)(flag + 4) != 0)

    {

        rva = *(int*)flag;

        *(int*)nrelocation = (*(int*)flag);

        nrelocation += 4;

        *(int*)nrelocation = *(int*)(flag+4);

        nrelocation += 4;

        //这里可以用别的库函数代替

        printf("*************%x\n", rva);

        flag += 8;//    DWORD   SizeOfBlock

        s = 0;

        s1 = (*(int*)(flag - 4) - 8) / 2;

        while (s++ < s1)//当前页有多少个地址

        {

            b = *(short*)flag;

            //if (b & 0x3000 == 0x3000)

            *(int*)(fbuff + rva_to_foa(fbuff,rva + (b & 0x0fff))) += offset;

        //  else

            *(short*)nrelocation = (*(short*)flag);

            //*(short*)nrelocation = (*(short*)flag);

            nrelocation += 2;

            printf("%x\n", rva + (b & 0xfff));

            flag += 2;

        }

    }

        s = 0;

        while (s++<2)//为了避免意外情况,后面补8个0 表示结束

        {

            *(int*)nrelocation=0;

            nrelocation += 4;

        }

    return fbuff;

}

0x8导入表

在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。

于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。

导入表结构:                                                                                            

typedef struct _IMAGE_IMPORT_DESCRIPTOR {                                                                                      

    union {                                                                                         

        DWORD   Characteristics;                                                                                                         DWORD   OriginalFirstThunk;   //RVA 指向IMAGE_THUNK_DATA结构数组               };                                                                                            

    DWORD   TimeDateStamp;         //时间戳                                     

    DWORD   ForwarderChain;                                                                                                       

    DWORD   Name;                                                 //RVA,指向dll名字,该名字已0结尾                                     

    DWORD   FirstThunk;             //RVA,指向IMAGE_THUNK_DATA结构数组                                    

} IMAGE_IMPORT_DESCRIPTOR;                                                                                       

typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;                
                                                                         

OriginalFirstThunk字段就是指向INT表,FirstThunk字段指向IAT表。通常情况下程序加载前这个两个表的内容是一样的,如果TimeDateStamp;字段为-1的话,IAT表存的就是地址。这是绑定导入表的内容,本文不做阐述。

程序加载前和加载后IAT表的值可能不一样。在时间戳为-1、dll时间戳没有变化、dll没有发生重定位的情况下,IAT表的值都是函数的绝对地址。一般情况下程序加载前IAT表的值和INT表的值是一样的,程序加载后IAT表的内容变成了函数的地址,INT则不变。


                                                                                                                                             

     typedef struct _IMAGE_THUNK_DATA32 {                                          

             union {                                          

                 PBYTE  ForwarderString;                                            

                 PDWORD Function;                                     

                 DWORD Ordinal;                                          

                 PIMAGE_IMPORT_BY_NAME  AddressOfData;                                       

             } u1;                                     

         } IMAGE_THUNK_DATA32;                                          

         typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;         

这个就是INT表的结构,当成一个4字节的容器就ok。。它通常指向留一个表,也就是函数名结构数组。

  也就是这个东西。。

typedef struct _IMAGE_IMPORT_BY_NAME {                                                                                                              

    WORD    Hint;                                                     //可能为空,编译器决定 如果不为空 是函数在导出表中的索引                                               

    BYTE    Name[1];                                               //函数名称,以0结尾                                               

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;                                                         


代码练习    

练习1 输出导入表

void print_import(char* fbuff)

{

    char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

    char* data_dir = pe + 120;//数据目录

    if (*(int*)(data_dir + 8) == 0)

    {

        printf("文件没有导入表");

        getchar();

        return ;

    }

    char* importt = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8));//导入表第一张表的表头

    char* pimportt = importt;

    char* poft=0;

    char* pft = 0;

    char* str=0;

//  int b = 0xffffffff >> 31;

    //printf("%d",b );

    while (*(int*)pimportt != 0 && *(int*)(pimportt + 16) != 0)

    {

        char* name =fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 12));

        printf("*******************%s\n",name);

        poft = fbuff+rva_to_foa(fbuff,*(int*)(pimportt));//poft是  FirstThunk;  指向的地址;        

        pft = fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 16));//pft是  OriginalFirstThunk指向的地址;        

        while (*(int*)(poft) != 0)

        {

            if (*(unsigned int*)(poft) >> 31 == 1)//算术右移和逻辑右移不一样

            {

                printf("int:%0x ", (*(int*)(poft) << 1) / 2);//位运算把第一位去掉 再/2 =取低31位

                poft += 4;

                if (*(int*)(pimportt + 4) == -1)//时间戳

                {

                    printf("iat: %x\n", *(int*)(pft));

                }

                else

                    printf("iat:%0x \n", (*(int*)(pft) << 1) / 2); //位运算把第一位去掉 再/2 =取低31位

                pft += 4;

                continue;

            }

            str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(poft));

            printf("int:%s  ", str);

            poft += 4;

            if (*(int*)(pimportt + 4) == -1)//时间戳

            {

                printf("iat: %x\n", *(int*)(pft));

            }

            else

            {

                str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(pft));

                printf("iat: %s\n", str);

            }

                pft += 4;

        }

        pimportt += 20;

    }

}

      导入表注入原理:                                                

     当Exe被加载时,系统会根据Exe导入表信息来加载需要用到的DLL,导入表注入的原理就是修改exe导入表,将自己的DLL添加到exe的导入表中,这样exe运行时可以将自己的DLL加载到exe的进程空间.                                                                          

                  

注意事项:移动导入表时,最好不要移动IAT表,因为它里面存的有可能是函数的绝对地址,移动的话可能会需要重定位,否则程序会崩溃。我当时移动导入表然后注入的时候,程序不能运行,找bug找了好久,最后打开od调试时发现IAT表出问题了,它载入内存时没有变成函数地址(有可能改一下时间戳就ok,但最后我放弃移动IAT表了。。。)


左图是我手动移动导入表后的IAT表,右图是用工具注入dll后的IAT表。

下面来说说我导入表注入的思路(因为我比较懒,所以直接选择把导入表移动到新增节里了。。。):

1. 找到原表,2.找到新增节的rva,3. 先把自己构造的导入表放进去,然后一边遍历原表一边复制(我把新增节分成4块,导入表块,INT表块,dll名称块,函数名结构数组块)IAT表不动。

效果:

原文件



注入后的文件



代码实现导入表注入

void inject_dll(char* fbuff, char* fun_name,char* dll_name)

{

         char* pe = (fbuff + (*(int*)(fbuff + 60)));//pe头

         char* data_dir = pe + 120;//数据目录

         if (*(int*)(data_dir + 8) == 0)

         {

                   printf("文件没有导入表");

                   getchar();

                  return;

         }

         char* importt = fbuff + rva_to_foa(fbuff, *(int*)(data_dir + 8));//导入表第一张表的表头

         char* pimportt = importt;

         char* poft = 0;

         char* pft = 0;

         char* str = 0;

         char* pdest = fbuff + rva_to_foa(fbuff, getlastjb_rva_add(fbuff));//新导出表的位置

         char* niat = pdest + 0x500;//新iat表位置

         char* nint = pdest + 0x800;//新int表位置

         char* ndllname = pdest + 0x1100;//新dll名

         char* nstr = pdest + 0x1800;//新dll名数组

         //构建导入表  注入的dll

         *(int*)pdest = foa_to_rva(fbuff,nint - fbuff);

         *(int*)(pdest + 4) = 0;

         *(int*)(pdest + 8) = 0;

         *(int*)(pdest + 12) = foa_to_rva(fbuff, ndllname - fbuff);

         *(int*)(pdest + 16) = foa_to_rva(fbuff, niat - fbuff);

         pdest += 20;

         strcpy(ndllname, dll_name);

         //*(short*)ndllname = 0;

         ndllname += strlen(dll_name)+1;

         *(int*)nint = foa_to_rva(fbuff,nstr-fbuff);

         *(int*)niat = *(int*)nint;

         *(int*)(nint+4) = 0;

         *(int*)(niat +4) = 0;

         nint += 8;

         niat += 8;

         *(short*)nstr = 0;//函数名结构数组

         nstr += 2;

         strcpy(nstr, fun_name);

         nstr += strlen(fun_name) + 1;

         while (*(int*)pimportt != 0 && *(int*)(pimportt + 16) != 0)

         {//iat表没有复制

                   char* name = fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 12));

                   printf("*******************%s\n", name);

                   *(int*)pdest = 0;//foa_to_rva(fbuff, nina - fbuff);

                   *(int*)(pdest + 4) = -0;

                   *(int*)(pdest + 8) = 0;

                  *(int*)(pdest + 12) = foa_to_rva(fbuff, ndllname - fbuff);

                   *(int*)(pdest + 16) = *(int*)(pimportt + 16);//foa_to_rva(fbuff, nita - fbuff);

                   pdest += 20;

                   /*for (int i = 0; i < 20; i++)

                   {

                            *(pdest + i) = *(pimportt + i);

                   }*/

                   strcpy(ndllname,name);//拷贝dll名字

                   //*(short*)ndllname = 0;

                   ndllname += strlen(name) + 1;

                   poft = fbuff + rva_to_foa(fbuff, *(int*)(pimportt));//poft是 OriginalFirstThunk  指向的地址;        

                   pft = fbuff + rva_to_foa(fbuff, *(int*)(pimportt + 16));//pft是  FirstThunk指向的地址;        

                   while (*(int*)(poft) != 0)

                   {

                            /*if (*(unsigned int*)(poft) >> 31 == 1)//当ina里存的是序号 ,算术右移和逻辑右移不一样

                            {

                                     printf("int:%0x ", (*(int*)(poft) << 1) / 2);//位运算把第一位去掉 再/2 =取低31位

                                     *(int*)nina = 0;//*(int*)(poft);//复制ina

                                     if (*(int*)(pimportt + 4) == -1)//时间戳 当ita是函数地址

                                     {

                                               printf("iat: %x\n", *(int*)(pft));

                                               *(int*)nita = *(int*)(pft);//复制ina

                                     }

                                     else

                                     {

                                               printf("iat:%x \n", (*(int*)(pft) << 1) / 2);//位运算把第一位去掉 再/2 =取低31位

                                               *(int*)nita = *(int*)(pft);//复制ina

                                     }       

                                     nina += 4;

                                     nita += 4;

                                     pft += 4;

                                     poft += 4;

                                     continue;

                            }*/

                            str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(poft));

                            printf("int:%s  ", str);

                            *(int*)niat = foa_to_rva(fbuff,nstr-fbuff);//复制ina 指向字符串

                            *(short*)nstr = *(short*)(str-2);//函数名结构数组

                            nstr += 2;

                            strcpy(nstr, str);

                            nstr += strlen(str) + 1;

                            //nina += 4;

                   /*     if (*(int*)(pimportt + 4) == -1)//时间戳 当ita是函数地址

                            {

                                     printf("iat: %x\n", *(int*)(pft));

                                     *(int*)nita = *(int*)(pft);//复制ina

                                     //nita += 4;

                            }

                            else

                            {

                                     str = fbuff + 2 + rva_to_foa(fbuff, *(int*)(pft));

                                     printf("iat: %s\n", str);

                                     *(int*)nita = *(int*)nina;//指向同一个地方

                                     //nita += 4;

                            }*/

                            //*(int*)nina = 0;

                            //nita += 4;

                            nint += 4;

                            poft += 4;

                            pft += 4;

                   }

                   for (int i = 0; i < 2; i++)//补0

                   {

                            *(int*)nint = 0;

                            nint += 4;

                            //*(int*)nitt = 0;

                            //nita += 4;

                   }

                   pimportt += 20;

         }

                   for (int i = 0; i < 4; i++)//补0

                   {

                            *(int*)pdest = 0;

                            pdest += 4;

                   }

         *(int*)(data_dir + 8) = getlastjb_rva_add(fbuff);//修改数据目录

}

Ok,我讲完了,完整代码我一会发到下面。我要去肝win32了,下次见,886+。

哦哦,我这篇文章主要发的代码比较多,理论少了点。所以我放几个参考资源来了解、掌握PE。

书籍:《PE权威指南》

视频:https://ke.qq.com/course/179553?taid=1062952866332001&tuin=437f9e13

帖子:

1.   https://www.cnblogs.com/You0/p/4229529.html

2.       https://www.cnblogs.com/You0/p/4238829.html

3.       https://www.cnblogs.com/You0/p/4238954.html

4.       https://www.cnblogs.com/You0/p/4239024.html



文章来源: https://bbs.pediy.com/thread-258246.htm
如有侵权请联系:admin#unsafe.sh