什么是编程

我们编程普通理解是编写各种语法和代码,学习了专门的语法就可以将程序编好。我学习编程这么久才发现编程最终编的不是代码而是编的文件。
程序员将代码编好后,编译器要将这些代码融入到可执行文件中去组成一个完整的文件结构才能执行,光有代码部分是无法执行的。就比如说我们平常看到的文本文件,作为一个文件没有任何格式需求随便写点内容到里面,然后点开就可以看到你写入的东西。这就是最简单的文件,当然还有很多文件类型,之所以说不同的文件类型其实就是文件格式不一样,特殊的文件类型都有一定的文件格式,文件的内容由多个固定或者不固定的部分组成。比如说我们常见的可执行文件,后缀名为.exe ,也就是程序员口中的PE文件,也是我最近研究过的文件格式,这些资料网上都有,要想完整的理解程序世界就得从文件开始理解。
简单的说PE文件分为两个部分,第一部分就是头部,也就是文件头,第二部分就是尾部,也就是映射部分。头部又分为多个部分,DOS部分,附加部分,NT部分。这些部分又细分到各个结构,结构里面的数据又分为多种长度。这些数据主要是存放的代码部分和资源部分在内存中的映射偏移量。这样系统程序就可以通过文件的头部数据知道要从那些地方执行,需要资源部分的那些数据,光说不练嘴把式,下面我会贴出部分代码,后续我还会上传自己编写的辅助工具供大家参考和了解。同时如果自己想编一种文件格式的话也是可以的,我学习的是C#,还了解一点点汇编,现在比较流行的和未来的趋势是64位的程序,所以我也是编写的64位的相关程序,我代码里面将整个PE文件的格式用枚举和结构体写进了我的程序里面,本人写这些东西的第一目的是了解程序,同时也可以用来了解其他人写的程序。下面是部分代码:

/上面是PE文件头结构里的补充部分,下面是PE文件结构,从DOS结构体到可选头结构体////// <summary>/// /这个结构64个字节中,除了e_magic偏移为零,值为固定值常数0x4D5A,然后 最后一个变量e_lfanew偏移为0x3C指向PE文件头,其他的都没多少意义/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_DOS_HEADER{//这个结构64个字节中,除了e_magic偏移为零,值为固定值常数0x4D5A,然后 最后一个变量e_lfanew偏移为0x3C指向PE文件头,其他的都没多少意义,可以不用弄太明白,后面的备注有些是自己根据字面意思翻译的,不一定正确//结构总共占64个字节,前面的数字表示元素在此结构中的偏移量public char e_magic;      //0//WORD  e_magic;  // Magic number DOS可执行文件标记,值是一个常数0x4D5A,用文本编辑器查看该值位‘MZ’,可执行文件必须都是'MZ'开头。public char e_cblp;       //2//WORD  e_cblp;  // Bytes on last page of file  文件最后一页占的字节数 ,占2个字节,最大值FFFF远大于0x200和0x1000所以用两个字节也是绰绰有余的,其他长度 也是一样的,2个字节就够了public char e_cp;         //4//WORD  e_cp;    // Pages in file 文件占了多少页,一页512个字节,大小用16进制表示就是0x200 ,public char e_crlc;       //6//WORD  e_crlc;  // Relocations  重定位表数量,public char e_cparhdr;    //8//WORD  e_cparhdr; // Size of header in paragraphs   段中头部的大小,反正没什么用,public char e_minalloc;   //10//WORD  e_minalloc;  // Minimum extra paragraphs needed  所需扩展段最小值public char e_maxalloc;   //12//WORD  e_maxalloc;   // Maximum extra paragraphs needed  所需扩展段最大值,public char e_ss;         //14//WORD  e_ss;   // Initial (relative) SS value  DOS代码的初始化堆栈SSpublic char e_sp;         //16//WORD  e_sp;    // Initial SP value DOS代码的初始化堆栈指针SPpublic char e_csum;       //18//WORD  e_csum;  // Checksumpublic char e_ip;         //20//WORD  e_ip;   // Initial IP value  DOS代码的初始化指令入口[指针IP]public char e_cs;         //22//WORD  e_cs;  // Initial (relative) CS value  DOS代码的初始堆栈入口public char e_lfarlc;     //24//WORD  e_lfarlc; // File address of relocation table   重定位表在文件中的起始偏移地址,也就是指向文件中的重定位表。public char e_ovno;       //26//WORD  e_ovno;  // Overlay numberpublic E_RES e_res;       //28//WORD  e_res[4]; // Reserved words  保留字符,据说这里是占8个字节,类型用自定义的一个结构体代替 public char e_oemid;      //36//WORD  e_oemid;  // OEM identifier (for e_oeminfo)public char e_oeminfo;    //38//WORD  e_oeminfo; // OEM information; e_oemid specificpublic E_RES2 e_res2;     //40//WORD  e_res2[10]; // Reserved words   保留字符,据说这里占20个字节 ,类型用自定义的一个结构体代替public int e_lfanew;      //60//LONG  e_lfanew;   // File address of new exe header 此数据所在文件中的偏移固定为0x3C ,此处的值也是一个文件偏移,32为系统占4个字节,64位系统也是占4个字节,因为在文件里都是相对于文件起始处的偏移量占用4个字节对于一个头部结构来说足够了,至于64位地址只要加载地址是64位然后加上文件偏移即可为内存中的地址,用来指向PE文件头结构开始的偏移地址,通过这个偏移可以找到PE文件头结构,得到PE文件标识“PE00”即0x50450000。}

上面是PE文件头部的第一个固定部分DOS头部分,根据网上的资料,我将数据类型写入了这个固定的结构体里面,数据类型我也写成了C#里面对应的数据类型。下面全是相关的结构体,根据这些结构体数据的偏移位置,我们就可以准确的将映射到内存中的PE文件相关数据分离出来存储,同时也可以将其他加载的加壳程序有用的数据部分分离出来组成可运行程序,这些是之前一些破解加壳程序的常规办法,本人写这些东西仅供学习,知识是无罪的,学习的同时一定要知法守法。

/// <summary>/// PE文件头是一个结构体(IMAGE_NT_HEADERS32),里面还包含两个其它结构体,占用4B + 20B + 224B/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_NT_HEADERS32{//PE文件头是一个结构体(IMAGE_NT_HEADERS32),里面还包含两个其它结构体,占用4B + 20B + 224B//32+(64位)结构总共占4+20+240=264个字节,32位结构总共占4+20+224=248前面的数字表示元素在此结构中的偏移量//64位//32位public int Signature;                            //0//0//DWORD Signature;             // PE文件标识 4Bytes,Signature字段设置为0x00004550,ANCII码字符是“PE00”,标识PE文件头的开始,PE标识不能破坏。0x00004550是读取后的值,内存中实际是50450000public IMAGE_FILE_HEADER FileHeader;             //4//4//IMAGE_FILE_HEADER FileHeader;      // 20 Bytespublic IMAGE_OPTIONAL_HEADER32 OptionalHeader;   //24//24//IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 224 Bytes  PE32可执行文件,PE32+的情况为240字节,我这里主要是尽力完善PE32+的,也就是64位系统的。}

大家可以看到我结构体中又包含了其他结构体,右边注释里面我还标明的数据在结构里面的偏移量,将这些结构体和偏移对应到内存或者文件中去,就可以读取到准确的数据了。所以说编代码就是编文件,这些头部分其实是编译器完成的工作,虽然不需要我们去编写,但是可以通过文件结构了解自己编写的或者他人编写的程序。同一种文件类型任何程序员写出来的程序都要符号文件格式,不然的话系统是无法解读和运行的,所以只要你了解透了一种文件格式就能更容易的了解和掌控程序,其他的代码太多我就不贴了,后续我还会上传一些相关的自编软件和源码,喜欢C#编程的同志们一起努力吧。下面的代码我还没有写完整可以随便看一下:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Data;
using System.Threading;namespace myInjectest1
{public class PEClass64{//这个类主要是根据PE文件结构及其映射读取内存中目标进程相关数据,这里主要就读取部分IMAGE_NT_HEADERS32结构和IMAGE_SECTION_HEADER结构中的数据。用于辅助判断相关地址所在的位置。//比如说,我查询一个进程中的属性,我从那里开始收索,收索范围多大,最后收索到的结构到底是在那个段中。是代码段还是数据段中,就比较清楚,而且有些数据还可以用于作为基址。//想写一个通用型辅助工具,首先就是要实现界面通信的模拟控制,其次获取界面的一些数据作为判断依据也是很重要的,所有这个类是用于实现获取数据及比对的。这里只是预备类,为后面做准备。MyGame64 _game64;//定义一个对象MyGame0 _game0;//定义一个对象IntPtr _ProcessHand64;//定义一个进程句柄,64位系统的IntPtr _ProcessHand0;//定义一个进程句柄,32位系统的int _ProcID64;//定义一个进程ID,64位系统的int _ProcID0;//定义一个进程ID,32位系统的PE_File _PEFile64;//这里定义一个PE文件结构对象,可以根据这个对象的内容组成一个完整的PE文件,前提是要先读取文件信息,这个结构体是64位系统的。public PEClass64(){//构造函数,实列话对象的时候运行 _game64 = new MyGame64();//将对象实列化_game0 = new MyGame0();//将对象实列化_ProcessHand64 = _game64.ProcessHand;//赋值,方便 使用_ProcessHand0 = _game0.ProcessHand;//赋值,方便 使用_ProcID64 = _game64.ProcessID;//赋值,方便 使用_ProcID0 = _game0.ProcessID;//赋值,方便 使用_PEFile64 = new PE_File();//虽然这里实列化了结构体对象,但这个对象里面包含的字节数组现在都还是空的,实际数据要读取PE文件信息的时候写入进去。}/// <summary>/// 这是一个MyGame64类的对象属性,可读可赋值/// </summary>public MyGame64 Game64{//这里定义一个对象属性,表明除了构造函数实列化一个对象外,也可以直接将现有的对象传递过来赋值,有利于传递对象set {_game64=value;}get { return _game64; }}/// <summary>/// 这是一个MyGame0类的对象,可读可赋值,这个类支持的是32位的一些方法/// </summary>public MyGame0 Game0{//这里定义一个对象属性,表明除了构造函数实列化一个对象外,也可以直接将现有的对象传递过来赋值,有利于传递对象set { _game0 = value; }get { return _game0; }}/// <summary>/// 这个属性表示一个进程的句柄,初始化是从MyGame64类中的属性赋值得到的,可读可赋值/// </summary>public IntPtr ProcessHand64{//这里定义一个对象属性,表明除了构造函数实列化一个对象外,也可以直接将现有的对象传递过来赋值set { _ProcessHand64 = value; }get { return _ProcessHand64; }}/// <summary>/// 这个属性表示一个进程的句柄,初始化是从MyGame0类中的属性赋值得到的,可读可赋值/// </summary>public IntPtr ProcessHand0{//这里定义一个对象属性,表明除了构造函数实列化一个对象外,也可以直接将现有的对象传递过来赋值set { _ProcessHand0 = value; }get { return _ProcessHand0; }}/// <summary>/// 这个属性表示一个进程的唯一标识ID,初始化是从MyGame64类中的属性赋值得到的,可读可赋值/// </summary>public int ProcID64{//这里定义一个对象属性,表明除了构造函数实列化一个对象外,也可以直接将现有的对象传递过来赋值set { _ProcID64 = value; }get { return _ProcID64; }}/// <summary>/// 这个属性表示一个进程的唯一标识ID,初始化是从MyGame0类中的属性赋值得到的,可读可赋值/// </summary>public int ProcID0{//这里定义一个对象属性,表明除了构造函数实列化一个对象外,也可以直接将现有的对象传递过来赋值set { _ProcID0 = value; }get { return _ProcID0; }}/// <summary>/// 这个结构体类型属性保存的是一个完成的PE文件相关的信息,可以用此结构体对象中的数据组成完整PE文件,也可以修改这个结构体中包含的字节数组里的相关数据,可读可赋值。此结构体只支持64位系统。实际上只有数据目录多了几个固定的大小而已,其他的都是一样的偏移量。/// </summary>public PE_File PEfileData64{//这个PE文件属性说明可以根据PE文件结构数据既可读也可写set { _PEFile64 = value; }get { return _PEFile64; }}//这个类是用于PE文件,及内存中结构的学习和研究,微软对64位Windows平台上的PE文件结构叫做PE32+,就是把那些原来32位的字段变成了64位。//识别一个文件是不是PE文件不应该只看文件后缀名,还应该通过PE指纹,打开一个exe文件,发现文件的头两个字节都是MZ,0x3C位置保存着一个地址,查该地址处发现保存着“PE”,通过这些重要的信息(“MZ”和“PE”)验证文件是否为PE文件,这些信息即PE指纹。//PE结构,此部分只用来注释结构组成,用来说明和展示PE结构包含的相关部分,真正的结构在后面定义/*  DOS部分{IMAGE_DOS_HEADER结构; DOS  MZ 文件头 实际是一个64字节的IMAGE_DOS_HEADER结构体DOS Stub; 链接器填充的信息,大小不确定,不重要,主要有一段DOS上运行的代码段说,this Program  cannot be run in DOS mode 说这个程序不能在DOS模式下运行。虽然链接器填充的信息大小不确定,但是IMAGE_DOS_HEADER结构体的最后一个元素指明了下一个结构体PE文件头的偏移量。}PE文件头{包含多个结构的IMAGE_NT_HEADER32结构体“PE”.0.0 ; PE文件头标志,signature 占4个字节IMAGE_FILE_HEADER结构;PE文件表头,占20个字节IMAGE_OPTIONAL_HEADER32结构;PE文件表头可选部分,占224字节16xIMAGE_DATE_DIRECTORY结构;也是PE文件表头可选部分IMAGE_OPTIONAL_HEADER32的最后一部分,是数据目录表结构,16x表示有16个这样的结构,这是32位程序,64位程序多两个这样的结构,所以是18个。32位占224个字节,64位占240个字节。}节表{n x IMAGE_SECTION_HEADER结构; 由n个IMAGE_DOS_HEADER结构组成的,每个结构占40个字节且是按顺序排列,其中包含了块名、位置、长度、属性 、调试相关 }节数据;由不同属性数据组成的不同节,其中包括.text节、.data节、.rsrc节、.reloc节等等 *///PE文件到内存的映射//PE文件存储在磁盘时的结构和加载到内存后的结构有所不同。//当PE文件通过Windows加载器载入内存后,内存中的版本称为模块(Module),映射文件的起始地址称为模块句柄(hModule),也称为基地址(ImageBase)。//文件数据一般512字节(1扇区)对齐(现也多4k),32位内存一般4k(1页)对齐,512D = 200H,4096D = 1000H 文件中块的大小为200H的整数倍,内存中块的大小为1000H的整数倍,映射后实际数据的大小不变,多余部分可用0//填充:PE文件头部(DOS头+PE头)到块表之间没有间隙,然而他们却和块之间有间隙,大小取决于对齐参数/// <summary>/// 这里定义一个占8个字节的结结构体是专门为了IMAGE_DOS_HEADER结构,不重要的数据/// </summary>[StructLayout(LayoutKind.Sequential)]public struct E_RES{//这里定义一个占8个字节的结结构体是专门为了IMAGE_DOS_HEADER结构,不重要的数据public char e_res1;public char e_res2;public char e_res3;public char e_res4;}/// <summary>/// 这里定义一个占20个字节的结结构体是专门为了IMAGE_DOS_HEADER结构,不重要的数据/// </summary>[StructLayout(LayoutKind.Sequential)]public struct E_RES2{//这里定义一个占20个字节的结结构体是专门为了IMAGE_DOS_HEADER结构,不重要的数据public char e_res1;public char e_res2;public char e_res3;public char e_res4;public char e_res5;public char e_res6;public char e_res7;public char e_res8;public char e_res9;public char e_res10;}/// <summary>/// 这里定义一个占8个字节的结构专门为IMAGE_SECTION_HEADER放置节名称的如:.text节、.data节、.rsrc节、.reloc节等等/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_SIZEOF_SHORT_NAME{//这里定义一个占8个字节的结构专门为IMAGE_SECTION_HEADER放置节名称的如:.text节、.data节、.rsrc节、.reloc节等等public byte bt1;public byte bt2;public byte bt3;public byte bt4;public byte bt5;public byte bt6;public byte bt7;public byte bt8;}/// <summary>///这个结构是为IMAGE_OPTIONAL_HEADER32最后一个元素准备的数据结构表,最大有18个IMAGE_DATA_DIRECTORY结构,也就是数据目录表,32位系统最大有16个IMAGE_DATA_DIRECTORY结构,64位系统多给出16个字节,经验证应该是多给出了两个IMAGE_DATA_DIRECTORY结构,所以是18个。即使没有这么多表也是用零补齐的。数据目录表的第1个成员指向输出表/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_DATA_DIRECTORY_S{//这个结构是为IMAGE_OPTIONAL_HEADER32最后一个元素准备的数据结构表,最大有18个IMAGE_DATA_DIRECTORY结构,也就是数据目录表,即使没有这么多表也是用零补齐的。数据目录表的第1个成员指向输出表//这个数据目录表结构总共占8*18=144个字节,前面的数字表示元素在此结构中的偏移量。32位系统最大有16个IMAGE_DATA_DIRECTORY结构,64位系统多给出16个字节,经验证应该是多给出了两个IMAGE_DATA_DIRECTORY结构,所以是18个。public IMAGE_DATA_DIRECTORY DataDirectory1;       //0//第一个结构指向输出表public IMAGE_DATA_DIRECTORY DataDirectory2;       //8//public IMAGE_DATA_DIRECTORY DataDirectory3;       //16//public IMAGE_DATA_DIRECTORY DataDirectory4;       //24//public IMAGE_DATA_DIRECTORY DataDirectory5;       //32//public IMAGE_DATA_DIRECTORY DataDirectory6;       //40//public IMAGE_DATA_DIRECTORY DataDirectory7;       //48//public IMAGE_DATA_DIRECTORY DataDirectory8;       //56//public IMAGE_DATA_DIRECTORY DataDirectory9;       //64//public IMAGE_DATA_DIRECTORY DataDirectory10;      //72//public IMAGE_DATA_DIRECTORY DataDirectory11;      //80//public IMAGE_DATA_DIRECTORY DataDirectory12;      //88//public IMAGE_DATA_DIRECTORY DataDirectory13;      //96//public IMAGE_DATA_DIRECTORY DataDirectory14;      //104//public IMAGE_DATA_DIRECTORY DataDirectory15;      //112//public IMAGE_DATA_DIRECTORY DataDirectory16;      //120//public IMAGE_DATA_DIRECTORY DataDirectory17;      //128//public IMAGE_DATA_DIRECTORY DataDirectory18;      //136}/// <summary>/// IMAGE_FILE_HEADER 的最后一个元素的值的枚举,表示文件属性,指示文件是什么属性,而且文件属性可以多个属性用"或运算"的方法同时拥有,也就是可以多个属性并存/// </summary>public enum FILE_Characteristics{//IMAGE_FILE_HEADER 的最后一个元素的值的枚举,表示文件属性,指示文件是什么属性,而且文件属性可以多个属性用"或运算"的方法同时拥有,也就是可以多个属性并存 IMAGE_FILE_RELOCS_STRIPPED = 0x0001,         //文件中不存在重定位信息IMAGE_FILE_EXECUTABLE_IMAGE= 0x0002,         //文件是可执行的IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004,      //不存在行信息IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008,     //不存在符号信息IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010,       //调整工作集IMAGE_FILE_LARGE_ADDRESS_AWARE=0x0020,       //应用程序可以处理大于2G的地址IMAGE_FILE_BYTES_REVERSED_LO = 0x0080,       //小尾方式IMAGE_FILE_32BIT_MACHINE = 0x0100,           //只在32位平台上运行IMAGE_FILE_DEBUG_STRIPPED = 0x0200,          //不包含调试信息 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400, //不能从可移动磁盘运行IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800,       //不能从网络运行IMAGE_FILE_SYSTEM = 0x1000,                  //系统文件(如驱动程序)不能直接运行IMAGE_FILE_DLL = 0x2000,                     //这是一个DLL文件IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000,          //文件不能在多处理器计算机上运行 IMAGE_FILE_BYTES_REVERSED_HI = 0x8000        //大尾方式}/// <summary>/// 这个属性主要是跟映射基址的模式相关,不过现在高级版本的系统都实现了加载基址的随机化的保护,这些属性设置了也没用,已经一去不复返。/// </summary>public enum OPTIONAL_DllCharacteristics{//这些值来源于网页:https://blog.csdn.net/qwq1503/article/details/102511054IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040,     // DLL can move.()IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080,   // Code Integrity Image (统一基址)IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100,     // Image is NX compatible ()IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200,   // Image understands isolation and doesn't want it ()IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400,     // Image does not use SEH.  No SE handler may reside in this image ()IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800,     // Do not bind this image.()Reserved1 = 0x1000,     // Reserved.IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000,     // Driver uses WDM model ()Reserved2 = 0x4000,     // Reserved.IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000}/// <summary>/// IMAGE_SECTION_HEADER 的最后一个元素的值的枚举,表示节的属性,也类似于内存属性,表示这段数据是只读,能读能写,或者是可执行代码等等属性,也是可以通过“或运算”同时拥有多个属性的。/// </summary>public enum SECTION_Characteristics:uint{//IMAGE_SECTION_HEADER 的最后一个元素的值的枚举,表示节的属性,也类似于内存属性,表示这段数据是只读,能读能写,或者是可执行代码等等属性,也是可以通过“或运算”同时拥有多个属性的。Reserved1 = 0x00000000,//是一个保留枚举元素Reserved2 = 0x00000001,//是一个保留枚举元素Reserved3 = 0x00000002,//是一个保留枚举元素Reserved4 = 0x00000003,//是一个保留枚举元素IMAGE_SCN_TYPE_NO_PAD = 0x00000008,Reserved5 = 0x00000010,//是一个保留枚举元素IMAGE_SCN_CNT_CODE = 0x00000020,//表明节中包含代码IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040,//节中包含已初始化数据IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080,//节中包含未初始化数据IMAGE_SCN_LNK_OTHER = 0x00000100,//这也是一个保留枚举元素IMAGE_SCN_LNK_INFO = 0x00000200,//The section contains comments or other information. This is valid only for object files.Reserved6 = 0x00000400,//是一个保留枚举元素IMAGE_SCN_LNK_REMOVE = 0x00000800,//The section will not become part of the image. This is valid only for object files.IMAGE_SCN_LNK_COMDAT = 0x00001000,//The section contains COMDAT data. This is valid only for object files.Reserved7 = 0x00002000,//是一个保留枚举元素IMAGE_SCN_NO_DEFER_SPEC_EXC = 0x00004000,//Reset speculative exceptions handling bits in the TLB entries for this section.IMAGE_SCN_GPREL = 0x00008000,//The section contains data referenced through the global pointer.Reserved8 = 0x00010000,//是一个保留枚举元素IMAGE_SCN_MEM_PURGEABLE = 0x00020000,//这也是一个保留枚举元素IMAGE_SCN_MEM_LOCKED = 0x00040000,//这也是一个保留枚举元素IMAGE_SCN_MEM_PRELOAD = 0x00080000,//这也是一个保留枚举元素IMAGE_SCN_ALIGN_1BYTES = 0x00100000,//Align data on a 1-byte boundary. This is valid only for object files.意思就是对齐数据以一个字节为边界,只对应用程序对象文件有效,与映射内存无关的IMAGE_SCN_ALIGN_2BYTES = 0x00200000,//数据以2个字节对齐,也只适用于对象文件,后面的都是差不多,就是对齐的字节数不同,最大是8KIMAGE_SCN_ALIGN_4BYTES = 0x00300000,//IMAGE_SCN_ALIGN_8BYTES = 0x00400000,//IMAGE_SCN_ALIGN_16BYTES = 0x00500000,//IMAGE_SCN_ALIGN_32BYTES = 0x00600000,//IMAGE_SCN_ALIGN_64BYTES = 0x00700000,//IMAGE_SCN_ALIGN_128BYTES = 0x00800000,//IMAGE_SCN_ALIGN_256BYTES = 0x00900000,//IMAGE_SCN_ALIGN_512BYTES = 0x00A00000,//IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000,//IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000,//IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000,//IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000,//IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000,//NumberOfRelocations field in the section header is 0xffff, the actual relocation count is stored in the VirtualAddress field of the first relocation. It is an error ifIMAGE_SCN_LNK_NRELOC_OVFL is set and there are fewer than 0xffff relocations in the section.IMAGE_SCN_MEM_DISCARDABLE = 0x02000000,//节中数据在进程开始以后将被丢弃,如.reloc节IMAGE_SCN_MEM_NOT_CACHED = 0x04000000,//节中数据不会经过缓存IMAGE_SCN_MEM_NOT_PAGED = 0x08000000,//节中数据不会被交换到磁盘IMAGE_SCN_MEM_SHARED = 0x10000000,//节中的数据将被不同的进程共享IMAGE_SCN_MEM_EXECUTE = 0x20000000,//映射到内存后的页面包含可执行属性IMAGE_SCN_MEM_READ = 0x40000000,//映射到内存后的页面包含可读属性IMAGE_SCN_MEM_WRITE = 0x80000000//映射到内存后的页面包含可写属性}/// <summary>/// IMAGE_FILE_HEADER 的第一个元素,可运行的机器上,也就是表示能在什么CPU上运行,这里是机器种类也是CPU种类 /// </summary>public enum IMAGE_FILE_MACHINE{//IMAGE_FILE_HEADER 的第一个元素,可运行的机器上,也就是表示能在什么CPU上运行,这里是机器种类也是CPU种类 IMAGE_FILE_MACHINE_I386 = 0x014c, //x86 也就是32位系统IMAGE_FILE_MACHINE_IA64 = 0x0200, //Intel Itanium 安腾处理器也是64位的IMAGE_FILE_MACHINE_AMD64 = 0x8664 //x64 也就是64位系统}/上面是PE文件头结构里的补充部分,下面是PE文件结构,从DOS结构体到可选头结构体////// <summary>/// /这个结构64个字节中,除了e_magic偏移为零,值为固定值常数0x4D5A,然后 最后一个变量e_lfanew偏移为0x3C指向PE文件头,其他的都没多少意义/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_DOS_HEADER{//这个结构64个字节中,除了e_magic偏移为零,值为固定值常数0x4D5A,然后 最后一个变量e_lfanew偏移为0x3C指向PE文件头,其他的都没多少意义,可以不用弄太明白,后面的备注有些是自己根据字面意思翻译的,不一定正确//结构总共占64个字节,前面的数字表示元素在此结构中的偏移量public char e_magic;      //0//WORD  e_magic;  // Magic number DOS可执行文件标记,值是一个常数0x4D5A,用文本编辑器查看该值位‘MZ’,可执行文件必须都是'MZ'开头。public char e_cblp;       //2//WORD  e_cblp;  // Bytes on last page of file  文件最后一页占的字节数 ,占2个字节,最大值FFFF远大于0x200和0x1000所以用两个字节也是绰绰有余的,其他长度 也是一样的,2个字节就够了public char e_cp;         //4//WORD  e_cp;    // Pages in file 文件占了多少页,一页512个字节,大小用16进制表示就是0x200 ,public char e_crlc;       //6//WORD  e_crlc;  // Relocations  重定位表数量,public char e_cparhdr;    //8//WORD  e_cparhdr; // Size of header in paragraphs   段中头部的大小,反正没什么用,public char e_minalloc;   //10//WORD  e_minalloc;  // Minimum extra paragraphs needed  所需扩展段最小值public char e_maxalloc;   //12//WORD  e_maxalloc;   // Maximum extra paragraphs needed  所需扩展段最大值,public char e_ss;         //14//WORD  e_ss;   // Initial (relative) SS value  DOS代码的初始化堆栈SSpublic char e_sp;         //16//WORD  e_sp;    // Initial SP value DOS代码的初始化堆栈指针SPpublic char e_csum;       //18//WORD  e_csum;  // Checksumpublic char e_ip;         //20//WORD  e_ip;   // Initial IP value  DOS代码的初始化指令入口[指针IP]public char e_cs;         //22//WORD  e_cs;  // Initial (relative) CS value  DOS代码的初始堆栈入口public char e_lfarlc;     //24//WORD  e_lfarlc; // File address of relocation table   重定位表在文件中的起始偏移地址,也就是指向文件中的重定位表。public char e_ovno;       //26//WORD  e_ovno;  // Overlay numberpublic E_RES e_res;       //28//WORD  e_res[4]; // Reserved words  保留字符,据说这里是占8个字节,类型用自定义的一个结构体代替 public char e_oemid;      //36//WORD  e_oemid;  // OEM identifier (for e_oeminfo)public char e_oeminfo;    //38//WORD  e_oeminfo; // OEM information; e_oemid specificpublic E_RES2 e_res2;     //40//WORD  e_res2[10]; // Reserved words   保留字符,据说这里占20个字节 ,类型用自定义的一个结构体代替public int e_lfanew;      //60//LONG  e_lfanew;   // File address of new exe header 此数据所在文件中的偏移固定为0x3C ,此处的值也是一个文件偏移,32为系统占4个字节,64位系统也是占4个字节,因为在文件里都是相对于文件起始处的偏移量占用4个字节对于一个头部结构来说足够了,至于64位地址只要加载地址是64位然后加上文件偏移即可为内存中的地址,用来指向PE文件头结构开始的偏移地址,通过这个偏移可以找到PE文件头结构,得到PE文件标识“PE00”即0x50450000。}/// <summary>/// PE文件头是一个结构体(IMAGE_NT_HEADERS32),里面还包含两个其它结构体,占用4B + 20B + 224B/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_NT_HEADERS32{//PE文件头是一个结构体(IMAGE_NT_HEADERS32),里面还包含两个其它结构体,占用4B + 20B + 224B//32+(64位)结构总共占4+20+240=264个字节,32位结构总共占4+20+224=248前面的数字表示元素在此结构中的偏移量//64位//32位public int Signature;                            //0//0//DWORD Signature;             // PE文件标识 4Bytes,Signature字段设置为0x00004550,ANCII码字符是“PE00”,标识PE文件头的开始,PE标识不能破坏。0x00004550是读取后的值,内存中实际是50450000public IMAGE_FILE_HEADER FileHeader;             //4//4//IMAGE_FILE_HEADER FileHeader;      // 20 Bytespublic IMAGE_OPTIONAL_HEADER32 OptionalHeader;   //24//24//IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 224 Bytes  PE32可执行文件,PE32+的情况为240字节,我这里主要是尽力完善PE32+的,也就是64位系统的。}/// <summary>/// E文件的一些基本信息,该结构在微软的官方文档中被称为标准通用对象文件格式/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_FILE_HEADER{//IMAGE_FILE_HEADER(映像文件头或标准PE头)结构包含PE文件的一些基本信息,该结构在微软的官方文档中被称为标准通用对象文件格式(Common Object File Format,COFF)头,总共占20个字节//结构总共占20个字节,前面的数字表示元素在此结构中的偏移量public Int16 Machine;               //0//WORD Machine;        // 可运行在什么样的CPU上。0代表任意,Intel 386及后续:0x014C, x64: 0x8664。IMAGE_FILE_MACHINE_I386  0x014c  x86  ,IMAGE_FILE_MACHINE_IA64  0x0200  Intel Itanium ,IMAGE_FILE_MACHINE_AMD64 0x8664  x64public Int16 NumberOfSections;      //2//WORD NumberOfSections;   // 文件的区块(节)数,一个文件的节数不会太多,用两个字节绰绰有余public int TimeDateStamp;          //4//DWORD TimeDateStamp;     // 文件的创建时间。1970年1月1日以GMT计算的秒数,编译器填充的,不重要的值,时间就是一个字符串所有应占用不少字节public int PointerToSymbolTable;   //8//DWORD PointerToSymbolTable; // 指向符号表(用于调试),内存中其实使用的都是偏移,32位系统肯定是占4个字节 ,64位系统也是占4个字节,不同系统的起始加载地址不同而已。public int NumberOfSymbols;        //12//DWORD NumberOfSymbols;    // 符号表中符号的个数(用于调试),重要字段public Int16 SizeOfOptionalHeader;  //16//WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32结构的大小,可改变,32位为E0,64位为F0,重要字段,占两个字节也是绰绰有余,32位系统为E0=224字节,64位系统为F0=240字节.public Int16 Characteristics;       //18//WORD Characteristics;    // 文件属性,据了解占两个字节足够了,可以多属性取或操作。属性包括:可执行文件、系统文件(驱动程序)不可直接运行、DLL文件等,相关详细值写在前面枚举FILE_Characteristics里面}/// <summary>/// (可选映像头或扩展PE头)是一个可选的结构,是IMAGE_FILE_HEADER结构的扩展,大小由IMAGE_FILE_HEADER结构的SizeOfOptionalHeader字段记录(经验证是准确的) ,32位系统为E0=224字节,64位系统为F0=240字节。其中多出16个字节,经过比对数据,最有可能的是多出了两个IMAGE_DATA_DIRECTORY结构,也就是多预留了两个数据表。/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_OPTIONAL_HEADER32{//(可选映像头或扩展PE头)是一个可选的结构,是IMAGE_FILE_HEADER结构的扩展,大小由IMAGE_FILE_HEADER结构的SizeOfOptionalHeader字段记录(可能不准确) ,32位系统为E0=224字节,64位系统为F0=240字节.有待后面验证,前面的号码 就是元素在此结构中的偏移//32+(64位)结构总共占240个字节,32位结构总共占224个字节,前面的数字表示元素在此结构中的偏移量//Standard fields,标准字段部分,这里主要是针对64位机器编写的。经验证其他地方和32位系统都是一样的,只多给出了两个数据表。32位系统最多16个,64位系统却给出了18个数据表结构//64位//32位public Int16 Magic;                           //0//0//WORD  Magic;         //说明文件的类型 PE32:10BH PE32+(也就是PE64):20BH  Rom映像文件:107Hpublic byte MajorLinkerVersion;              //2//2//BYTE  MajorLinkerVersion;   //链接器主版本号public byte MinorLinkerVersion;              //3//3//BYTE  MinorLinkerVersion;   //链接器次版本号public int SizeOfCode;                      //4//4//DWORD  SizeOfCode;       //所有代码节的总和(基于文件对齐) 编译器填的 没用public int SizeOfInitializedData;            //8//8//DWORD  SizeOfInitializedData; //包含所有已经初始化数据的节的总大小 编译器填的 没用public int SizeOfUninitializedData;          //12//12//DWORD  SizeOfUninitializedData;//包含未初始化数据的节的总大小 编译器填的 没用//64位系统要在里面多加16个字节,前面的号码 就是元素在此结构中的偏移,但是加在了最后的数据目录结构里,这个结构里的指针都是说的内存中的偏移量,并非绝对地址,也不可能是绝对地址,都是偏移量加上映射加载的基地址组成的。public int AddressOfEntryPoint;            //16//16//DWORD  AddressOfEntryPoint;  //程序入口RVA  在大多数可执行文件中,这个地址不直接指向Main、WinMain或DIMain函数,而指向运行时的库代码并由它来调用上述函数,这里也是一个偏移量,为何64位系统里也是占4个字节,因为只要加载的基地址是64位的就够了,都是基地址加偏移的。public int BaseOfCode;                     //20//20//DWORD  BaseOfCode;       //代码起始RVA,编译器填的  没用,这个地址可能是加载到内存中代码段的起始地址的偏移量。为何64位系统里也是占4个字节,因为只要加载的基地址是64位的就够了,都是基地址加偏移的。public int BaseOfData;                     //24//24//DWORD  BaseOfData;       //数据段起始RVA,编译器填的  没用,这个地址有可能是模块中全局或者静态已初始化变量存储的起始地址的偏移量。为何64位系统里也是占4个字节,因为只要加载的基地址是64位的就够了,都是基地址加偏移的。////NT additional fields.64位比32位 多占 16个字节,如果每个可能的地方多占4个字节的话,这几个相对虚拟地址指针位置最有可能。//public int ImageBase;                      //28//28//DWORD  ImageBase;       //内存镜像基址 ,可链接时自己设置,托管代码测试这个值为零,模块加载到内存中的基址,32位PE通常设置位0x400000,但托管PE映射确实加载位置不确定的,64位系统加载也是通常这个值无效,所以这个值在64位系统是没有什么用的。public int SectionAlignment;                //32//32//DWORD  SectionAlignment;    //内存对齐   一般一页大小4k,经验证64位内段对齐为8K。public int FileAlignment;                   //36//36//DWORD  FileAlignment;     //文件对齐   一般一扇区大小512字节,现在也多4k,经验证64位系统内文件对齐也是8K。public Int16 MajorOperatingSystemVersion;     //40//40//WORD  MajorOperatingSystemVersion; //标识操作系统版本号 主版本号public Int16 MinorOperatingSystemVersion;     //42//42//WORD  MinorOperatingSystemVersion; //标识操作系统版本号 次版本号public Int16 MajorImageVersion;               //44//44//WORD  MajorImageVersion;   //PE文件自身的主版本号 public Int16 MinorImageVersion;               //46//46//WORD  MinorImageVersion;   //PE文件自身的次版本号 public Int16 MajorSubsystemVersion;           //48//48//WORD  MajorSubsystemVersion; //运行所需子系统主版本号public Int16 MinorSubsystemVersion;           //50//50//WORD  MinorSubsystemVersion; //运行所需子系统次版本号public int Win32VersionValue;               //52//52//DWORD  Win32VersionValue;   //子系统版本的值,必须为0public int SizeOfImage;                     //56//56//DWORD  SizeOfImage; //内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍  public int SizeOfHeaders;                   //60//60//DWORD  SizeOfHeaders;     //所有头+节表按照文件对齐后的大小,否则加载会出错public int CheckSum;                        //64//64//DWORD  CheckSum;        //校验和,一些系统文件有要求.用来判断文件是否被修改 public Int16 Subsystem;                       //68//68//WORD  Subsystem;       //子系统  驱动程序(1) 图形界面(2) 控制台、DLL(3)public Int16 DllCharacteristics;              //70//70//WORD  DllCharacteristics;   //文件特性 不是针对DLL文件的,其可取值写在枚举OPTIONAL_DllCharacteristics里面,可能跟文件映射的基址有关系,不过高版本的系统都已经是采用了随机加载的保护机制,不管你怎么设置都没用。public int SizeOfStackReserve;              //72//72//DWORD  SizeOfStackReserve;   //初始化时保留的栈大小 public int SizeOfStackCommit;               //76//76//DWORD  SizeOfStackCommit;   //初始化时实际提交的大小 public int SizeOfHeapReserve;               //80//80//DWORD  SizeOfHeapReserve;   //初始化时保留的堆大小public int SizeOfHeapCommit;                //84//84//DWORD  SizeOfHeapCommit;    //初始化时提交的堆大小public int LoaderFlags;                     //88//88//DWORD  LoaderFlags; public int NumberOfRvaAndSizes;             //92//92//DWORD  NumberOfRvaAndSizes;  //数据目录项的数量,名字里说的相对虚拟地址和大小说的就是数据目录结构public IMAGE_DATA_DIRECTORY_S DataDirectory; //96//96//IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,IMAGE_DATA_DIRECTORY_S结构里包含的是多个IMAGE_DATA_DIRECTORY结构 ,32位系统最多16个这样的结构,验证64位系统有18个这样的结构,如果没用到那么多表,其余的都是用零补齐,占8*18=144个字节,数据目录表的第1个成员指向输出表。数据目录表的第2个成员指向输入表。当前文件依赖几个模块就会有几张输入表且是连续排放的,如果有多个会连续存放直到连续出现20个0说明结束。}/// <summary>/// 数据目录结构,数据目录表,由数个相同的IMAGE_DATA_DIRECTORY结构组成,指向输出表、输入表、资源块,重定位表等。注释前面的数字表示在此结构中的偏移量,数据目录表的第1个成员指向输出表/// 而这些表实际位置应该也是在某个节或者块中,而且输出表是数据目录表的第一个成员,那么它在内存中的相对偏移量也就等于某个节的相对偏移量。/// 输出表(导出表):创建一个DLL或者EXE时,实际上创建了一组能让EXE或其他DLL调用的函数,这些函数都放在输出表里面,DLL文件通过输出表(Export Table)向系统提供输出函数名、序号和入口地址等信息。/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_DATA_DIRECTORY{//数据目录结构,数据目录表,由数个相同的IMAGE_DATA_DIRECTORY结构组成,指向输出表、输入表、资源块,重定位表等。注释前面的数字表示在此结构中的偏移量,数据目录表的第1个成员指向输出表,所以输出表在内存中的相对偏移量应该等于某个段或者块在内存中的相对偏移量。//结构总共占8个字节,前面的数字表示元素在此结构中的偏移量public int VirtualAddress;     //0//DWORD VirtualAddress;  //对应表的起始RVA,这里的虚拟地址其实也还是相对于文件中的偏移量。public int Size;               //4//DWORD Size;       //对应表长度,这些表也是另一个结构体。}/// <summary>/// 节表也称块表是一个IMAGE_SECTION_HEADER的结构数组,每个IMAGE_SECTION_HEADER结构40字节。每个IMAGE_SECTION_HEADER结构包含了它所关联的区块的信息,例如位置、长度、属性。先这么写,不知道真实64位的系统是否也是40个字节。前面的数字表示在此表中的偏移量/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_SECTION_HEADER{//节表也称块表是一个IMAGE_SECTION_HEADER的结构数组,每个IMAGE_SECTION_HEADER结构40字节。每个IMAGE_SECTION_HEADER结构包含了它所关联的区块的信息,例如位置、长度、属性。先这么写,不知道真实64位的系统是否也是40个字节。前面的数字表示在此表中的偏移量//结构总共占40个字节,前面的数字表示元素在此结构中的偏移量//#define IMAGE_SIZEOF_SHORT_NAME  8字节大小,说明这个节名字给了8个字节大小的内存空间存储 public IMAGE_SIZEOF_SHORT_NAME Name;     //0//BYTE  Name[IMAGE_SIZEOF_SHORT_NAME]; //块名。多数块名以一个“.”开始(例如.text),这个“.”不是必需的public int Misc;                         //8//union {意思是说这个数据可以是下面两个中的一个,通常表示的是VirtualSize,如果这里要填入两个元素的话,那总大小就不是40个字节了,也不知道64位系统是不是也只占40个字节//DWORD  PhysicalAddress; //常用第二个字段//DWORD  VirtualSize;   //加载到内存实际区块的大小(对齐前),为什么会变呢?可能是有时未初始化的全局变量不放bss段而是通过扩展这里//} Misc;  这是一个杂项,表示其中的任意一个数据public int VirtualAddress;               //12//DWORD  VirtualAddress;  //该块装载到内存中的RVA(内存对齐后,数值总是SectionAlignment的整数倍),这个值是编译的时候根据段对齐的大小计算的相对于加载基地址的偏移量。public int SizeOfRawData;                //16//DWORD  SizeOfRawData;  //该块在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大 例如bss节(SizeOfRawData为0),data节(关键看未初始化的变量放哪),这是文件对齐大小,实际多余的都是用0填充的。public int PointerToRawData;             //20//DWORD  PointerToRawData; //该块在文件中的偏移(FOA),这个值也是编译的时候根据文件对齐的大小计算的相对于文件起点的相对偏移量。/*调试相关,忽略*/public int PointerToRelocations;         //24//DWORD  PointerToRelocations; //在“.obj”文件中使用,指向重定位表的指针public int PointerToLinenumbers;         //28//DWORD  PointerToLinenumbers;public Int16 NumberOfRelocations;         //32//WORD  NumberOfRelocations;  //重定位表的个数(在OBJ文件中使用)。public Int16 NumberOfLinenumbers;         //34//WORD  NumberOfLinenumbers;public int Characteristics;              //36//DWORD  Characteristics; //块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志,相关详细值写在前面枚举SECTION_Characteristics里面。}/*输出表及 RVA与FOA的转换RVA:相对虚拟地址, VA:虚拟内存地址,FOA:文件偏移地址。 计算步骤:① 计算RVA = 虚拟内存地址 - ImageBase② 若RVA是否位于PE头:FOA == RVA③ 判断RVA位于哪个节:RVA >= 节.VirtualAddress (节在内存对齐后RVA )RVA <= 节.VirtualAddress + 当前节内存对齐后的大小偏移量 = RVA - 节.VirtualAddress;④ FOA = 节.PointerToRawData + 偏移量;输出表(导出表)创建一个DLL或者EXE时,实际上创建了一组能让EXE或其他DLL调用的函数,这些函数都放在输出表里面DLL文件通过输出表(Export Table)向系统提供输出函数名、序号和入口地址等信息。数据目录表的第1个成员指向输出表。① 如果文件对齐与内存对齐都是4k则不需要地址转换② 输出表大小是指输出表大小与其子表大小和 *///总结:输出表、输入表等所有表应该都在段或者块中,所以才能通过比对内存中的偏移量确定表在那个段中,再通过两者之差加上段在文件中的偏移量就得到了表在文件中的偏移量。//IMAGE_SECTION_HEADER结构中就有每个节或者块在文件中偏移量,所以数据目录结构中的各种输入表在内存中的偏移量应该是通过节中的文件内偏移转化得到的,节或者块是主要部分,数据目录表应该是扩展部分,但却又在节表或者块表前面,且一直往上dos头都是连续的。/// <summary>/// 输出表(导出表),数据目录表的第1个成员指向输出表,所以输出表在内存中的相对偏移量应该等于某个段或者块在内存中的相对偏移量。/// 创建一个DLL或者EXE时,实际上创建了一组能让EXE或其他DLL调用的函数,这些函数都放在输出表里面/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_EXPORT_DIRECTORY{//输出表实际是一个40字节的结构体(IMAGE_EXPORT_DIRECTORY),数据目录表的第1个成员指向输出表,//结构总共占40个字节,前面的数字表示元素在此结构中的偏移量,不知道32位和32+的输出表结构大小是否一样,如果只相对虚拟地址,每个文件都不大的话用4个字节够用public int Characteristics;         //0//DWORD Characteristics; //未定义,总是为0。public int TimeDateStamp;           //4//DWORD TimeDateStamp; //输出表创建的时间(GMT时间)public Int16 MajorVersion;           //8//WORD MajorVersion;  //输出表的主版本号。未使用,设置为0。public Int16 MinorVersion;           //10//WORD MinorVersion;  //输出表的次版本号。未使用,设置为0。public int Name;                    //12//DWORD Name; //指向一个ASCII字符串的RVA。这个字符串是与这些输出函数相关联的DLL的名字(例如"KERNEL32.DLL"),直接指向字符串起始偏移地址的,且字符串是以一个空字节结束的。public int Base;                    //16//DWORD Base; //导出函数起始序号(基数)。当通过序数来查询一个输出函数时,这个值从序数里被减去,其结果将作为进入输出地址表(EAT)的索引,这个值最小为1,因为数组的索引是从零开始的,如果第一个元素的话索引就是从零开始的。public int NumberOfFunctions;       //20//DWORD NumberOfFunctions; //输出函数地址表(Export Address Table,EAT)中的条目数量(最大序号 - 最小序号)public int NumberOfNames;           //24//DWORD NumberOfNames;   //输出函数名称表(Export Names Table,ENT)里的条目数量public int AddressOfFunctions;      //28//DWORD AddressOfFunctions;   // EAT的RVA(输出函数地址表RVA)EAT里的每个元素都占4个字节,内存中偏移地址是固定类型Int所以数组中的元素直接存储的是值。public int AddressOfNames;          //32//DWORD AddressOfNames;     // ENT的RVA(输出函数名称表RVA),每一个表成员指向ANCII字符串 表成员的排列顺序取决于字符串的排序,数组中的元素如果不是固定长度类型,比如说字符串类型,数组中存储的其实不是直接的字符串的值,而是存储这些字符串的偏移地址。也就是说数组中的每个元素的值都必须是固定长度,如果不是固定长度的类型就只能存储其内存中的偏移地址,间接存储。 public int AddressOfNameOrdinals;   //36//DWORD AddressOfNameOrdinals; // 输出函数序号表RVA,每个表成员2字节,这个数组里的每个元素都占2个字节,它的索引值和ENT的索引值相同,但它的每个元素的值是EAT的索引值。//AddressOfFunctions指向的是一个每个元素都占4个字节的数组,这个数组可看作一个输出函数在内存中的偏移地址组成的表。AddressOfNames指向的是一个由一个或多个偏移地址组成的数组,因为数组的每个元素的值占用长度必须是固定长度,这些数组的元素值才是指向那些记录输出函数名字对应的字符串,并且这些字符串都是以一个空字符串结尾的。//AddressOfNameOrdinals却是指向一个占两个字节的序号数组,这个数组里的元素保存的是输出方法在其数组里对应的索引号,而这个序号表自身的索引值和输出方法名字的索引值是对应的,名字的排序是字符串的顺序,跟对应的输出方法的偏移地址的索引不一定对应,简单的说就是在程序编译的时候,//首先存在EAT数组和ENT数组分别保存着输出方法的偏移地址和名字,然后建立再建立一个序号表将EAT和ENT联系起来,序号表自己的索引和ENT一样,但是其值保存的EAT的索引,这样就可用通过GetProcAddress API 取得相应输出方法对应的实际内存地址了。//但是托管代码它的输出方法名字说不定会被编译器简化处理,所以至少要取得输出表对应的ENT就能通过GetProcAddress API 得到输出表里对应方法的所有地址,当然也可用自己根据序号表自己找到对应的偏移地址然后加上模块加载地址就是输出方法的内存地址了。}//输出表的EAT、ENT已经序号表都只由一个元素组成的数组就没必要再写结构体了,单个元素的结构体没意义。这些数据需要3个同大小的数组存放。要修改指定输出表的数据只能根据输出表结构体的值及其保存数据的数组的索引值计算偏移值然后才能修改。/*输入表(导入表)PE 文件映射到内存后,Windows 将相应的 DLL文件装入,EXE 文件通过“输入表”找到相应的 DLL 中的导入函数,从而完成程序的正常运行数据目录表的第2个成员指向输入表。当前文件依赖几个模块就会有几张输入表且是连续排放的,如果有多个会连续存放直到连续出现20个0说明结束。重定位表如果PE文件不在首选的地址(ImageBase)载入,那么文件中的每一个绝对地址都需要被修正。需要修正的地址有很多,可以在文件中使用重定位表记录这些绝对地址的位置,在载入内存后若载入基地址与ImageBase不同再进行修正,若相同就不需要修正这些地址。数据目录项的第6个结构,指向重定位表(Relocation Table)参考资料就到输入表就结束吧,后面的重定位,资源表这些都不是我感兴趣的,全是一些系统需要处理的工作,我只需要得到代码段的一些信息就差不多了,现在程序都是页面交互的,主要控制鼠标键盘小心进行操控,代码段能找到一些方法的地址意义也不大,具体需要的参数很难弄,费力不讨好,只是为了实现辅助并非要破解*//// <summary>/// 输入表(导入表),数据目录表的第2个成员指向输入表。当前文件依赖几个模块就会有几张输入表且是连续排放的,如果有多个会连续存放直到连续出现20个0说明结束。/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_IMPORT_DESCRIPTOR{//输入表实际是个20字节的结构体 IMAGE_IMPORT_DESCRIPTOR ,数据目录表的第2个成员指向输入表。当前文件依赖几个模块就会有几张输入表且是连续排放的,如果有多个会连续存放直到连续出现20个0说明结束。//结构总共占20个字节,前面的数字表示元素在此结构中的偏移量//union {//这两个元素只能使用其中的一个,所以结构才是占20个字节//DWORD  Characteristics;      // 0 for terminating null import descriptor 如果是0则表示空引用的终结//DWORD  OriginalFirstThunk;     // RVA to original unbound INT (IMAGE_THUNK_DATA)未定义IMAGE_THUNK_DATA结构体数组的原始相对偏移量,INT是一个IMAGE_THUNK_DATA结构的数组。数组的每个元素指向IMAGE_IMPORT_BY_NAME结构public int DUMMYUNIONNAME;   //0//} DUMMYUNIONNAME;public int TimeDateStamp;    //4//DWORD  TimeDateStamp; // 0 if not bound,// -1 if bound, and real date\time stamp// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)// O.W. date/time stamp of DLL bound to (Old BIND)public int ForwarderChain;   //8//DWORD  ForwarderChain;         // -1 if no forwarderspublic int Name;             //12//DWORD  Name;                  //DLL(依赖模块)名字的指针。是一个以“00”结尾的ASCII字符的RVA地址public int FirstThunk;       //16//DWORD  FirstThunk;           // RVA to IAT (if bound this IAT has actual addresses)输入表的第一个相对虚拟地址(如果输入表绑定了确切地址)IAT是一个IMAGE_THUNK_DATA结构的数组。数组的每个元素也指向IMAGE_IMPORT_BY_NAME结构。这个IAT和INT在文件中是一模一样的,IAT相当于INT的一个副本,文件加载映射到内存中后IAT才会被链接器通过INT指向的引用输出的方法的序号绑定真正的相对虚拟地址给IAT。因为很多模块的加载基站都是未知的,所以确切地址肯定是未知的,引用的时候只能记录引用的序号,到内存中使用的时候必须要找到并转换成正确的地址}//FirstThunk指向一个IMAGE_THUNK_DATA结构数组的第一个元素地址,就类似于指向GOT表和PLT表中的GOT表中的一部分,根据资料得知,GOT表的前两个元素,一个是输出表的地址,一个是模块的加载地址。除此之外对应的每个IAT地址在绑定之前都不是直接调用的,也不可能直接用,而是写了一段代码用来解析地址的,资料说是链接器,个人猜测应该是内存中偏移量和模块加载地址结合成可用的虚拟地址,因为这些偏移量都之占4个字节的。通过实验得知,C#里面Marshal.GetFunctionPointerForDelegate(_mydele)就是将函数地址转换成代理,测试得知如果地址不是加载过的函数地址是无法调用的,所以这个地址也必须要在输入或者输出表里的包含的偏移地址才能被调用。/// <summary>///每个输入表包含两个大小相同的数组,IAT和INT数组的元素在加载到内存前都是IMAGE_THUNK_DATA结构,这个结构也都是指向IMAGE_IMPORT_BY_NAME结构体在内存中的偏移地址。///一旦输入表加载到内存中后就会将双包胎数组IAT的偏移地址根据IMAGE_IMPORT_BY_NAME结构体依次换成模块中对应方法的偏移量。/// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_THUNK_DATA{//内存映射转换数据,通常是一个数组,单个元素实际只占4个字节,目前只使用了 最后一个元素AddressOfData指向IMAGE_IMPORT_BY_NAME 结构体//union {//DWORD ForwarderString;   // 指向一个转向者字符串的RVA//DWORD Function;       // 被输入的函数的内存地址//DWORD Ordinal;       // 被输入的API的序数public int AddressOfData;        //DWORD AddressOfData;    // 指向IMAGE_IMPORT_BY_NAME//} u1;}/// <summary>/// 这个结构体通过4个字节提供模块中对应方法的名字,结构中第二个元素定义的是一个只有一个元素的数组,而且这个数组元素的长度是无法确定的。其实就是一个地址,而第一个元素并不重要/// 既然是一个变通的写法,那这个地址到底是偏移地址还是完整的虚拟内存地址呢,个人觉得应该是偏移地址,如果是完整地址,那么文件加载到内存的时候就地址是不确定的,只能是根据函数名字符串在文件中的偏移地址计算出的内存偏移地址。/// 其原型如下:/// typedef struct _IMAGE_IMPORT_BY_NAME {/// WORD    Hint;/// BYTE    Name[1];/// }/// 至于说这个结构总共占4个字节或者3个字节估计都是无依据的,具体我也不知道。要测试后才知道。 /// </summary>[StructLayout(LayoutKind.Sequential)]public struct IMAGE_IMPORT_BY_NAME{//内存映射引用方法的序号和名字,这个结构类似一个媒介,虽然文件中INT和IAT都是指向这个媒介的,映射到后,链接器就会根据这个结构提供的输出表相应的序号将真正的相对虚拟地址对应写入IAT中。所以说INT是给链接器准备的,IAT才是用来调用的。//这个结构总共占4个字节,前面的数字表示在这个结构中的偏移量public char Hint;   //0//Hint;  // 输出函数地址表的索引(不是导出序号),(究竟是啥没试验,因为看的很多资料说是序号),不必须,链接器可能将其置0public char Name;   //2//Name[1]; // 函数名字字符串,以“\0”作为字符串结束标志,大小不确定,这里应该是输出表里面名字编号,也只能是编号。通过方法编号及名字编号就能知道方法的名称}//下面根据PE结构定义一些需要的数据所在的偏移量,然后用读取目标进程内存获取这些数据,其他的无关紧要的数据不用管,为了方便查看写成枚举,包含两个元素,一个是偏移,一个是数据长度 。//IMAGE_DOS_HEADER 结构占64个字节,这个结构后面有一个DOS Stub 具体大小是不确定的,但是IMAGE_DOS_HEADER的最后一个元素e_lfanew是指向PE头的//e_lfanew 的偏移是0x3C 也就是60个字节。从模块的加载位置ImageBase往后60个字节开始读取4个字节就是PE头在文件中的相对偏移量//找到PE头就是找到了IMAGE_NT_HEADERS32结构的偏移量,这个结构32位系统是占用4B + 20B + 224B=248个字节,64位系统是占4+20+240=264个字节//其他的元素就根据偏移量来就行了,文件头部分不用考虑加载对齐问题。任何元素都要先知道e_lfanew的值/// <summary>/// 这个元素是IMAGE_DOS_HEADER的最后一个元素,他的值指向DOS Stub后的PE结构头,这个结构占64个字节,后面的所有元素的实际偏移量都要加上这个元素内存中的值。 /// </summary>public enum PE_HeadBegain{//这个元素是IMAGE_DOS_HEADER的最后一个元素,他的值指向DOS Stub后的PE结构头。后面的每个元素都以这个值开始加上自己结构里的偏移量和前面结构的大小e_lfanew = 60,//这个元素偏移值为60或者0x3c,这个偏移量是以映射文件的加载基地址为起始点的,这个偏移处取4个字节的值,这个值指向PE文件头结构IMAGE_NT_HEADERS32e_lfanew_L=4//占4个字节}//起始是Dos头结构体,占用固定大小,后面紧接着的是Dos链接器,其长度可以说是变动的,但由于Dos应用程序过时了,这部分也就只剩一句英文字母了,然后紧接着的就是PE头结构体了,这个结构体占用固定大小。//PE文件头32位系统是占用4B + 20B + 224B=248个字节,64位系统是占4+20+240=264个字节,接着就是N个节表,每个IMAGE_SECTION_HEADER结构40字节。每个IMAGE_SECTION_HEADER结构包含了它所关联的区块的信息,例如位置、长度、属性。先这么写,不知道真实64位的系统是否也是40个字节。前面的数字表示在此表中的偏移量//这个头文件中有多少个节,也即多少个块,这个数据就在IMAGE_FILE_HEADER结构中的第二个元素的值,占2个字节。我已经在上面定义了一个相关的枚举,标出了其偏移量和长度//这部分枚举是以PE头结构体的开始偏移处为初始参照偏移位置的,也就是结构体IMAGE_NT_HEADERS32的起始偏移当作起始偏移。这里只定义一些重要数据的偏移和占用长度相关的枚举以便使用。//(文件头结构体数据偏移枚举)后面的命名专门针对IMAGE_FILE_HEADER结构中的偏移量,这个偏移是相对于IMAGE_NT_HEADERS32结构的,因此以PE_F前缀命名/// <summary>/// 可运行在什么样的CPU上。0代表任意,Intel 386及后续:0x014C, x64: 0x8664。IMAGE_FILE_MACHINE_I386  0x014c  x86  ,IMAGE_FILE_MACHINE_IA64  0x0200  Intel Itanium ,IMAGE_FILE_MACHINE_AMD64 0x8664  x64/// </summary>public enum PE_F_Machine{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_FILE_HEADER中的第一个元素,在IMAGE_FILE_HEADER结构中的偏移量为0,IMAGE_FILE_HEADER结构在IMAGE_NT_HEADERS32结构中的偏移量为4 Machine = 4 + 0,//可运行在什么样的CPU上。0代表任意,Intel 386及后续:0x014C, x64: 0x8664。IMAGE_FILE_MACHINE_I386  0x014c  x86  ,IMAGE_FILE_MACHINE_IA64  0x0200  Intel Itanium ,IMAGE_FILE_MACHINE_AMD64 0x8664  x64Machine_L = 2//占2个字节}/// <summary>///文件的区块(节、段)数,一个文件的节数不会太多,用两个字节绰绰有余,IMAGE_FILE_HEADER中的第二个元素,结构总大小占20个字节。/// </summary>public enum PE_F_NumberOfSections{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_FILE_HEADER中的第二个元素,在IMAGE_FILE_HEADER结构中的偏移量为2,IMAGE_FILE_HEADER结构在IMAGE_NT_HEADERS32结构中的偏移量为4 NumberOfSections = 4 + 2,//文件的区块(节)数,一个文件的节数不会太多,用两个字节绰绰有余NumberOfSections_L=2//占2个字节}/// <summary>/// 指向符号表(用于调试)的内存偏移,可能是过时属性,高版本系统早不适用。具体是什么也不清楚,写着备用吧/// </summary>public enum PE_F_PointerToSymbolTable{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_FILE_HEADER中的第四个元素,在IMAGE_FILE_HEADER结构中的偏移量为8,IMAGE_FILE_HEADER结构在IMAGE_NT_HEADERS32结构中的偏移量为4 PointerToSymbolTable = 4 + 8,//指向符号表(用于调试)PointerToSymbolTable_L = 4//占4个字节}/// <summary>/// 符号表的数量,据说是用于调试的,可能是过时属性,高版本系统早不适用,不太清楚写了备用/// </summary>public enum PE_F_NumberOfSymbols{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_FILE_HEADER中的第五个元素,在IMAGE_FILE_HEADER结构中的偏移量为12,IMAGE_FILE_HEADER结构在IMAGE_NT_HEADERS32结构中的偏移量为4 NumberOfSymbols = 4 + 12,//符号表的数量,据说是用于调试的,不太清楚写了备用NumberOfSymbols_L = 4//占4个字节}/// <summary>///标明IMAGE_OPTIONAL_HEADER32结构的大小,可改变,32位为E0,64位为F0,重要字段,占两个字节也是绰绰有余,32位系统为E0=224字节,64位系统为F0=240字节,IMAGE_FILE_HEADER中的第六个元素. /// </summary>public enum PE_F_SizeOfOptionalHeader{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_FILE_HEADER中的第六个元素,在IMAGE_FILE_HEADER结构中的偏移量为16,IMAGE_FILE_HEADER结构在IMAGE_NT_HEADERS32结构中的偏移量为4 SizeOfOptionalHeader = 4 + 16,//IMAGE_OPTIONAL_HEADER32结构的大小,可改变,32位为E0,64位为F0,重要字段,占两个字节也是绰绰有余,32位系统为E0=224字节,64位系统为F0=240字节.SizeOfOptionalHeader_L=2//占2个字节}/// <summary>/// 标明整个文件的属性,跟内存属性要区分开来,文件属性包括:属性包括:可执行文件、系统文件(驱动程序)不可直接运行、DLL文件等,相关详细值写在前面枚举FILE_Characteristics里面/// </summary>public enum PE_F_Characteristics{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_FILE_HEADER中的第七个元素,在IMAGE_FILE_HEADER结构中的偏移量为18,IMAGE_FILE_HEADER结构在IMAGE_NT_HEADERS32结构中的偏移量为4 Characteristics = 4 + 18,//文件属性,据了解占两个字节足够了,可以多属性取或操作。属性包括:可执行文件、系统文件(驱动程序)不可直接运行、DLL文件等,相关详细值写在前面枚举FILE_Characteristics里面Characteristics_L = 2//占2个字节}/// <summary>///头文件中,第一个节所对应的偏移量和单个IMAGE_SECTION_HEADER所占的字节数。IMAGE_NT_HEADERS32结构后面接着就是N个IMAGE_SECTION_HEADER结构,而64位的IMAGE_NT_HEADERS32为4+20+240=264个字节。所以第一个节的偏移量就是264。单个结构占40个字节。/// </summary>这个枚举是自添的,用于指示第一个节头结构体所在的偏移量,这个偏移量就是紧挨着可选头末尾的。public enum PE_F_FirstSection{//PE文件头中第一个节表的偏移量,这个偏移量是以结构体IMAGE_NT_HEADERS32为基准的偏移量,就是从这个结构开始算偏移量的。FirstSection=264,//这里只标明第一个节表的偏移量,因为多少个节不确定,只能以此为基准后面增加偏移量。FirstSection_L = 40//每个节表占40个字节,这是IMAGE_SECTION_HEADER结构的大小,通常直接读取这个结构还是需要分解,还不如分开读取。}//(可选头结构体数据偏移枚举)后面的命名专门针对IMAGE_OPTIONAL_HEADER32结构中的偏移量,这个偏移是相对于IMAGE_NT_HEADERS32结构的,因此以PE_OP前缀命名/// <summary>/// 说明文件的类型 PE32:10BH PE32+(也就是PE64):20BH  Rom映像文件:107H/// </summary>public enum PE_OP_Magic{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第一个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为0(64位系统)0(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24Magic = 24 + 0,//说明文件的类型 PE32:10BH PE32+(也就是PE64):20BH  Rom映像文件:107HMagic_L = 2//32位系统占2个字节,64位系统占2个字节}/// <summary>/// 所有代码节的总和(基于文件对齐) 编译器填的 没用/// </summary>public enum PE_OP_SizeOfCode{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第四个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为4(64位系统)4(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfCode = 24 + 4,//所有代码节的总和(基于文件对齐) 编译器填的 没用SizeOfCode_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 包含所有已经初始化数据的节的总大小,可能是过时属性,高版本系统早不适用,已经初始化的全局变量数据总大小。编译器填的 没用,方法中非全局的变量一般都是在栈中申请,同时也随着方法运行完后栈被平衡而释放的/// </summary>public enum PE_OP_SizeOfInitializedData{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第五个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为8(64位系统)8(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfInitializedData = 24 + 8,//包含所有已经初始化数据的节的总大小 编译器填的 没用SizeOfInitializedData_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 包含未初始化数据的节的总大小,可能是过时属性,高版本系统早不适用,未初始化的全局变量占用的总字节大小。 编译器填的 没用,方法中非全局的变量一般都是在栈中申请,同时也随着方法运行完后栈被平衡而释放的/// </summary>public enum PE_OP_SizeOfUninitializedData{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第六个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为12(64位系统)12(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfUninitializedData = 24 + 12,//包含未初始化数据的节的总大小 编译器填的 没用SizeOfUninitializedData_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 程序入口RVA  在大多数可执行文件中,这个地址不直接指向Main、WinMain或DIMain函数,而指向运行时的库代码并由它来调用上述函数,IMAGE_OPTIONAL_HEADER32中的第七个元素/// </summary>public enum PE_OP_AddressOfEntryPoint{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第七个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为16(64位系统)16(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24AddressOfEntryPoint = 24 + 16,//程序入口RVA  在大多数可执行文件中,这个地址不直接指向Main、WinMain或DIMain函数,而指向运行时的库代码并由它来调用上述函数AddressOfEntryPoint_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>///代码起始RVA,编译器填的,IMAGE_OPTIONAL_HEADER32中的第八个元素/// </summary>public enum PE_OP_BaseOfCode{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第八个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为20(64位系统)20(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24BaseOfCode = 24 + 20,//代码起始RVA,编译器填的,据说没啥用BaseOfCode_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 数据段起始RVA,编译器填的,IMAGE_OPTIONAL_HEADER32中的第九个元素/// </summary>public enum PE_OP_BaseOfData{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第九个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为24(64位系统)24(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24BaseOfData = 24 + 24,//数据段起始RVA,编译器填的  没用BaseOfData_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>///模块加载到内存中的基址,可能是过时属性,高版本系统早不适用,32位PE通常设置位0x400000,但托管PE映射确实加载位置不确定的,64位系统加载也是通常这个值无效,所以这个值在64位系统是没有什么用的。可以通过自己编写的_myOp64.MyGame.GetModelMessageA()得到这个值,这个也是一切的开始。这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第第十个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为28(64位系统)28(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24/// </summary>public enum PE_OP_ImageBase{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第十个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为28(64位系统)28(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24ImageBase = 24 + 28,//这个是模块所在内存的镜像地址,可以通过遍历模块信息比对得到模块信息,其中就有这个镜像地址,这个数据是内存中读数据的起始点,我这里还是会标识结构中的正确偏移。ImageBase_L = 4//64位系统中这个元素的长度为8个字节}/// <summary>/// 内存段对齐   一般一页大小4k,IMAGE_OPTIONAL_HEADER32中的第十一个元素/// </summary>public enum PE_OP_SectionAlignment{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第十一个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为32(64位系统)32(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SectionAlignment = 24 + 32,//内存对齐   一般一页大小4k,64位系统对齐为8K,也就是它的值为2000HSectionAlignment_L = 4//占4个字节}/// <summary>///文件内段对齐   一般一扇区大小512字节,现在也多4k ,IMAGE_OPTIONAL_HEADER32中的第十二个元素/// </summary>public enum PE_OP_FileAlignment{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第十二个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为36(64位系统)36(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24 FileAlignment = 24 + 36,//文件对齐   一般一扇区大小512字节,64位系统也是512对齐。FileAlignment_L = 4//占4个字节}/// <summary>/// 内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍  /// </summary>public enum PE_OP_SizeOfImage{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为56(64位系统)56(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfImage = 24 + 56,//内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍  SizeOfImage_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 所有头+节表按照文件对齐后的大小,否则加载会出错,说明这个数据很总要/// </summary>public enum PE_OP_SizeOfHeaders{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十一个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为60(64位系统)60(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfHeaders = 24 + 60,//所有头+节表按照文件对齐后的大小,否则加载会出错SizeOfHeaders_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 校验和,一些系统文件有要求.用来判断文件是否被修改,可能是过时属性,高版本系统早不适用。/// </summary>public enum PE_OP_CheckSum{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十二个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为64(64位系统)64(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24CheckSum = 24 + 64,//校验和,一些系统文件有要求.用来判断文件是否被修改CheckSum_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 说明文件的子系统:  驱动程序(1) 图形界面(2) 控制台、DLL(3)/// </summary>public enum PE_OP_Subsystem{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十三个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为68(64位系统)68(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24Subsystem = 24 + 68,//说明文件的子系统:  驱动程序(1) 图形界面(2) 控制台、DLL(3)Subsystem_L = 2//32位系统占2个字节,64位系统占2个字节}/// <summary>/// 说明文件特性,应该是过时的特性,现在高版本系统内存映射都是随机加载的。 不是针对DLL文件的,其可取值写在枚举OPTIONAL_DllCharacteristics里面,/// </summary>public enum PE_OP_DllCharacteristics{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十四个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为70(64位系统)70(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24DllCharacteristics = 24 + 70,//说明文件特性 不是针对DLL文件的,其可取值写在枚举OPTIONAL_DllCharacteristics里面DllCharacteristics_L = 2//32位系统占2个字节,64位系统占2个字节}/// <summary>/// 初始化时保留的栈大小 ,可能是过时属性,高版本系统早不适用。/// </summary>public enum PE_OP_SizeOfStackReserve{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十五个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为72(64位系统)72(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfStackReserve = 24 + 72,//初始化时保留的栈大小 SizeOfStackReserve_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 初始化时实际提交的大小,可能是过时属性,高版本系统早不适用。/// </summary>public enum PE_OP_SizeOfStackCommit{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十六个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为76(64位系统)76(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfStackCommit = 24 + 76,//初始大化时实际提交的小SizeOfStackCommit_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 初始化时保留的堆大小,可能是过时属性,高版本系统早不适用。/// </summary>public enum PE_OP_SizeOfHeapReserve{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十七个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为80(64位系统)80(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfHeapReserve = 24 + 80,//初始化时保留的堆大小SizeOfHeapReserve_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 初始化时提交的堆大小,可能是过时属性,高版本系统早不适用。/// </summary>public enum PE_OP_SizeOfHeapCommit{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十八个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为84(64位系统)84(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24SizeOfHeapCommit = 24 + 84,//初始化时提交的堆大小SizeOfHeapCommit_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 字面上理解说是加载器标志,可能是过时属性,高版本系统早不适用。/// </summary>public enum PE_OP_LoaderFlags{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第二十九个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为88(64位系统)88(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24LoaderFlags = 24 + 88,//字面上理解说是加载器标志LoaderFlags_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 字面理解就是相对虚拟地址和大小的数量,其实就是数据目录的数量,其中包括输出表,输入表,等。简单理解就记录了一些数组在映像内存中的起始偏移量和数组大小。/// </summary>public enum PE_OP_NumberOfRvaAndSizes{//这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第三十个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为92(64位系统)92(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24NumberOfRvaAndSizes = 24 + 92,//字面理解就是相对虚拟地址和大小的数量,其实就是数据目录的数量,其中包括输出表,输入表,等。简单理解就记录了一些数组在映像内存中的起始偏移量和数组大小。NumberOfRvaAndSizes_L = 4//32位系统占4个字节,64位系统占4个字节}/// <summary>/// 第一个数据目录表的内存起始偏移量,第一个表的内容是指向输出表的。这个偏移是相对于结构体IMAGE_NT_HEADERS32的偏移量,每个表其实就是指向一个数组,并标明数组大小。/// </summary>public enum PE_OP_FirstDataDirectory{//第一个数据目录表的内存起始偏移量,这个偏移是相对于结构体IMAGE_NT_HEADERS32的偏移量,每个表其实就是指向一个数组,并标明数组大小。FirstDataDirectory = 24 + 96,//这里只标明第一个数据目录的偏移量,因为多少个表不确定,只能以此为基准后面增加偏移量。24是PE标识+结构体IMAGE_FILE_HEADER占用的大小,后面的96是第一个数据目录表在结构体IMAGE_OPTIONAL_HEADER32中的偏移量。FirstDataDirectory_L = 8 //每个数据目录表占8个字节,这是IMAGE_DATA_DIRECTORY结构的大小,通常直接读取这个结构还是需要分解,还不如分开读取。}//(数据目录表偏移枚举)后面的命名专门针对IMAGE_DATA_DIRECTORY结构中的偏移量,这个偏移只是相对于单个结构的,因此以PE_D前缀命名/// <summary>/// 数据目录表指向的数组的偏移地址,文件中的地址和文件的映射地址都是偏移地址,内存中的实际地址要加上模块的加载地址,所以偏移地址都是只占4个字节就可以了。/// </summary>public enum PE_D_VirtualAddress{//IMAGE_DATA_DIRECTORY中的第一个元素占4个字节,单个结构占8个字节。其值是一个指向一个数组的偏移地址。VirtualAddress=0,//数据目录表指向的数组的偏移地址,文件中的地址和文件的映射地址都是偏移地址,内存中的实际地址要加上模块的加载地址,所以偏移地址都是只占4个字节就可以了。VirtualAddress_L=4//以模块的加载地址为基准的偏移地址只须占4个字节就够用了。}/// <summary>/// 数据目录表指向的数组整体占用字节的多少,这个数组存储的是一些结构体。这些结构体中的数据或许又指向其他更多的结构体。/// </summary>public enum PE_D_Size{//IMAGE_DATA_DIRECTORY中的第二个元素占4个字节,单个结构占8个字节。其值是一个指明一个数组的大小。Size = 4,//数据目录表指向的数组的大小,第一个数据表指向的就是输出表数组,每个输出表占40个字节Size_L = 4//这个数组结构的大小还是挺大的,就拿第一个数据指向的输出表来说,每个数组元素就占40个字节,而这里的大小不是数组的元素数量,应该是整个占用字节的多少。}//(输出表偏移枚举)后面的命名专门针对IMAGE_EXPORT_DIRECTORY结构中的偏移量,这个偏移只是相对于单个结构的,因此以PE_E前缀命名/// <summary>/// 输出表所属的模块的全名,其值是一个偏移地址直接指向模块名的字符串。/// </summary>public enum PE_E_Name{//IMAGE_EXPORT_DIRECTORY中的第五个元素占4个字节,单个结构占40个字节。其值是一个偏移地址直接指向模块名的字符串。Name = 12,//输出表所属的模块的全名,其值是一个偏移地址直接指向模块名的字符串。Name_L = 4//内存中的偏移地址占4个字节。}/// <summary>/// 导出函数起始序号(基数)。当通过序数来查询一个输出函数时,这个值从序数里被减去,其结果将作为进入输出地址表(EAT)的索引,这个值最小为1,因为数组的索引是从零开始的,如果第一个元素的话索引就是从零开始的。索引号-Base=索引号。/// </summary>public enum PE_E_Base{//IMAGE_EXPORT_DIRECTORY中的第六个元素占4个字节,单个结构占40个字节。其值是一个占4个字节的整数,这个整数通常为1,如果通过序号查找函数偏移地址的话,序号表的索引号却是从0开始的,所有数组的索引号都是从0开始的,所以序号理论上是应该比数组里的索引值大1的。Base = 16,//导出函数起始序号(基数)。当通过序数来查询一个输出函数时,这个值从序数里被减去,其结果将作为进入输出地址表(EAT)的索引,这个值最小为1,因为数组的索引是从零开始的,如果第一个元素的话索引就是从零开始的。Base_L = 4//内存中的偏移地址占4个字节。}/// <summary>/// 输出函数地址表(Export Address Table,EAT)中的条目数量/// </summary>public enum PE_E_NumberOfFunctions{//IMAGE_EXPORT_DIRECTORY中的第七个元素占4个字节,单个结构占40个字节。其值是一个占4个字节的整数,这个整数记录的输出表中函数的总数。NumberOfFunctions = 20,//输出函数地址表(Export Address Table,EAT)中的条目数量NumberOfFunctions_L = 4//内存中的偏移地址占4个字节。}/// <summary>/// 输出函数名称表(Export Names Table,ENT)里的条目数量/// </summary>public enum PE_E_NumberOfNames{//IMAGE_EXPORT_DIRECTORY中的第八个元素占4个字节,单个结构占40个字节。其值是一个占4个字节的整数,这个整数记录的输出表中函数的名字总数。NumberOfNames = 24,//输出函数名称表(Export Names Table,ENT)里的条目数量NumberOfNames_L = 4//内存中的偏移地址占4个字节。}/// <summary>/// EAT的RVA(输出函数地址表RVA)EAT里的每个元素都占4个字节,内存中偏移地址是固定类型Int所以数组中的元素直接存储的是值。/// </summary>public enum PE_E_AddressOfFunctions{//IMAGE_EXPORT_DIRECTORY中的第九个元素占4个字节,单个结构占40个字节。其值是一个偏移地址直接指向函数的映射偏移地址组成的数组的起始偏移地址。AddressOfFunctions = 28,//EAT的RVA(输出函数地址表RVA)EAT里的每个元素都占4个字节,内存中偏移地址是固定类型Int所以数组中的元素直接存储的是值。AddressOfFunctions_L = 4//内存中的偏移地址占4个字节。}/// <summary>/// ENT的RVA(输出函数名称表RVA),每一个表成员指向ANCII字符串 表成员的排列顺序取决于字符串的排序,数组中的元素如果不是固定长度类型,比如说字符串类型,数组中存储的其实不是直接的字符串的值,而是存储这些字符串的偏移地址。也就是说数组中的每个元素的值都必须是固定长度,如果不是固定长度的类型就只能存储其内存中的偏移地址,间接存储。 /// </summary>public enum PE_E_AddressOfNames{//IMAGE_EXPORT_DIRECTORY中的第十个元素占4个字节,单个结构占40个字节。其值是一个偏移地址直接指向函数名的偏移地址组成的数组的起始偏移地址。AddressOfNames = 32,//ENT的RVA(输出函数名称表RVA),每一个表成员指向ANCII字符串 表成员的排列顺序取决于字符串的排序,数组中的元素如果不是固定长度类型,比如说字符串类型,数组中存储的其实不是直接的字符串的值,而是存储这些字符串的偏移地址。也就是说数组中的每个元素的值都必须是固定长度,如果不是固定长度的类型就只能存储其内存中的偏移地址,间接存储。 AddressOfNames_L = 4//内存中的偏移地址占4个字节。}/// <summary>/// 其值直接指向整个输出表包含的三表中的序号表,序号表的索引和ENT及EAT是一样的,而这个序号表的值保存的是EAT的索引值,不同的是ENT和EAT的值排序可能不一样。通常是通过函数名字比对得到序号表的索引值,然后读取序号表对应索引处的值就是EAT中对应函数名偏移地址的索引值,通过这个索引值在EAT找到对应的元素值就是指定函数的偏移地址了。/// </summary>public enum PE_E_AddressOfNameOrdinals{//IMAGE_EXPORT_DIRECTORY中的第十一个元素占4个字节,单个结构占40个字节。其值是一个偏移地址直接指向序号表。AddressOfNameOrdinals = 36,//其值直接指向整个输出表包含的三表中的序号表,序号表的索引和ENT及EAT是一样的,而这个序号表的值保存的是EAT的索引值,不同的是ENT和EAT的值排序可能不一样。通常是通过函数名字比对得到序号表的索引值,然后读取序号表对应索引处的值就是EAT中对应函数名偏移地址的索引值,通过这个索引值在EAT找到对应的元素值就是指定函数的偏移地址了。AddressOfNameOrdinals_L = 4//内存中的偏移地址占4个字节。}//(块或段信息偏移枚举)后面的命名专门针对IMAGE_SECTION_HEADER结构中的偏移量,这个偏移只是相对于单个结构的,因此以PE_S前缀命名/// <summary>///块名。多数块名以一个“.”开始(例如.text),这个“.”不是必需的,系统只给了8个字节的空间,所以名字不会太长,最多占8个字符,IMAGE_SECTION_HEADER中的第一个元素/// </summary>public enum PE_S_Name{//IMAGE_SECTION_HEADER中的第一个元素占8个字节,单个结构总大小占40个字节Name = 0,//块名。多数块名以一个“.”开始(例如.text),这个“.”不是必需的,长度只给了8个字节,应该是足够的,unicode每个数字或者英文字母都只占1个字节,中文占两个字节。Name_L=8//系统只给了最多8个字节的长度。}/// <summary>/// 加载到内存实际区块的大小(对齐前),为什么会变呢?可能是有时未初始化的全局变量不放bss段而是通过扩展这里,IMAGE_SECTION_HEADER中的第二个元素占4个字节 /// </summary>public enum PE_S_VirtualSize{//IMAGE_SECTION_HEADER中的第二个元素占4个字节,单个结构总大小占40个字节  VirtualSize = 8,//加载到内存实际区块的大小(对齐前),为什么会变呢?可能是有时未初始化的全局变量不放bss段而是通过扩展这里VirtualSize_L=4//占4个字节}/// <summary>///该块装载到内存中的RVA(内存对齐后,数值总是SectionAlignment的整数倍),IMAGE_SECTION_HEADER中的第三个元素占4个字节  /// </summary>public enum PE_S_VirtualAddress{//IMAGE_SECTION_HEADER中的第三个元素占4个字节,单个结构总大小占40个字节  VirtualAddress = 12,//该块装载到内存中的RVA(内存对齐后,数值总是SectionAlignment的整数倍)VirtualAddress_L=4//占4个字节}/// <summary>/// 该块在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大 例如bss节(SizeOfRawData为0),data节(关键看未初始化的变量放哪)/// </summary>public enum PE_S_SizeOfRawData{//IMAGE_SECTION_HEADER中的第四个元素占4个字节,单个结构总大小占40个字节SizeOfRawData = 16,//该块在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大 例如bss节(SizeOfRawData为0),data节(关键看未初始化的变量放哪)SizeOfRawData_L = 4//占4个字节}/// <summary>/// 该块在文件中的偏移(FOA),这个值也是编译的时候根据文件对齐的大小计算的相对于文件起点的相对偏移量。/// </summary>public enum PE_S_PointerToRawData{//IMAGE_SECTION_HEADER中的第五个元素占4个字节,单个结构总大小占40个字节PointerToRawData = 20,//该块在文件中的偏移(FOA),这个值也是编译的时候根据文件对齐的大小计算的相对于文件起点的相对偏移量。PointerToRawData_L = 4//占4个字节}/// <summary>/// 块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志/// </summary>public enum PE_S_Characteristics{//IMAGE_SECTION_HEADER中的第三个元素占4个字节,单个结构总大小占40个字节Characteristics = 36,//块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志Characteristics_L = 4//占4个字节}//读取部分,包括读取远程进程表头信息,和当前进程表头信息//接下来编写几个方法用来读取相关的数据,最好是一次性全读取到指定地方,便于以后展示和使用。/// <summary>/// 把那些需要读取的PE头信息通过读取内存写入这个结构中,具体需要那些就写那些,这里表示地址的,我都计算成了虚拟内存地址VA。这里直接计算成地址方便后面直接使用/// </summary>[StructLayout(LayoutKind.Sequential)]public struct PE_HeadMessage{//把那些需要读取的PE头信息通过读取内存写入这个结构中,具体需要那些就写那些,这里表示地址的,我都在后面加了一个后缀_R表示映射相对偏移,要得到真实的内存地址需要再加上模块的加载基址,这样是为了将内存中映射偏移和实际地址区分,偏移通常占4个字节,模块的加载基址,32位系统是4个字节,64位系统却是8个字节。public Int64 PE_modBaseAddr;//这个元素是添加的模块信息里面的,唯一一个实际的内存地址,这个就是模块的加载基址,这个是用于64位系统所以直接定义为int64,如果要将这个结构用于32位系统的化,只需修改这个元素类型。public int PE_HeadBegain_R;//IMAGE_DOS_HEADER的最后一个元素,他的值指向DOS Stub后的PE结构头。从PE_ImageBase加上枚举PE_HeadBegain的偏移处读取的值加上PE_ImageBasepublic Int16 PE_Machine;//指示当前PE文件可以适用于什么类型的CPU运行。public Int16 PE_NumberOfSections;//文件的区块(节、段)数,一个文件的节数不会太多,用两个字节绰绰有余,IMAGE_FILE_HEADER中的第二个元素,结构总大小占20个字节。这里展示出来都用int类型public Int16 PE_SizeOfOptionalHeader;//标明IMAGE_OPTIONAL_HEADER32结构的大小,可改变,32位为E0,64位为F0,重要字段,占两个字节也是绰绰有余,32位系统为E0=224字节,64位系统为F0=240字节,IMAGE_FILE_HEADER中的第六个元素。这里展示出来都用int类型,也可以作为判别标准public Int16 PE_Characteristics;//文件属性,据了解占两个字节足够了,可以多属性取或操作。属性包括:可执行文件、系统文件(驱动程序)不可直接运行、DLL文件等,相关详细值写在前面枚举FILE_Characteristics里面//上面的属性是文件头结构体里面的属性,下面是可选头结构体里面的属性。public Int16 PE_Magic;//说明文件的类型 PE32:10BH PE32+(也就是PE64):20BH  Rom映像文件:107Hpublic int PE_SizeOfCode;//所有代码节的总和(基于文件对齐) 编译器填的 没用public int PE_SizeOfInitializedData;//包含所有已经初始化数据的节的总大小 编译器填的 没用public int PE_SizeOfUninitializedData;//包含未初始化数据的节的总大小 编译器填的 没用public int PE_AddressOfEntryPoint_R;//程序入口RVA  在大多数可执行文件中,这个地址不直接指向Main、WinMain或DIMain函数,而指向运行时的库代码并由它来调用上述函数,IMAGE_OPTIONAL_HEADER32中的第七个元素public int PE_BaseOfCode_R;//代码起始RVA,编译器填的,IMAGE_OPTIONAL_HEADER32中的第八个元素public int PE_BaseOfData_R;//数据段起始RVA,编译器填的,IMAGE_OPTIONAL_HEADER32中的第九个元素,这个地址有可能是模块中全局或者静态已初始化变量存储的起始地址的偏移量public int PE_ImageBase_R;//模块加载到内存中的基址,这个属性已经过时,现在的系统都是随机加载的。可以通过自己编写的_myOp64.MyGame.GetModelMessageA()得到这个值,这个也是一切的开始。这个元素是IMAGE_NT_HEADERS32中的IMAGE_OPTIONAL_HEADER32中的第第十个元素,在IMAGE_OPTIONAL_HEADER32结构中的偏移量为40(64位系统)28(32位系统),IMAGE_OPTIONAL_HEADER32结构在IMAGE_NT_HEADERS32结构中的偏移量为24public int PE_SectionAlignment;//内存段对齐   一般一页大小4k,IMAGE_OPTIONAL_HEADER32中的第十一个元素public int PE_FileAlignment;//文件内段对齐   一般一扇区大小512字节,现在也多4k ,IMAGE_OPTIONAL_HEADER32中的第十二个元素public int PE_SizeOfImage;//模块在内存中映射区的大小。public int PE_SizeOfHeaders;//所有头+节表按照文件对齐后的大小,否则加载会出错public int PE_CheckSum;//校验和,一些系统文件有要求.用来判断文件是否被修改public Int16 PE_Subsystem;//说明文件的子系统:  驱动程序(1) 图形界面(2) 控制台、DLL(3)public Int16 PE_DllCharacteristics;//说明文件特性 不是针对DLL文件的,其可取值写在枚举OPTIONAL_DllCharacteristics里面public int PE_SizeOfStackReserve;//初始化时保留的栈大小public int PE_SizeOfStackCommit;//初始大化时实际提交的小public int PE_SizeOfHeapReserve;//初始化时保留的堆大小public int PE_SizeOfHeapCommit;//初始化时提交的堆大小public int PE_LoaderFlags;//字面上理解说是加载器标志public int PE_NumberOfRvaAndSizes;//相对虚拟偏移地址和大小的数量,其实就是数据目录的数量,其中包括输出表,输入表,等。简单理解就记录了一些数组在映像内存中的起始偏移量和数组大小。//上面是可选头结构体里的属性,其中有些属性是过时了的,下面是数据目录输出表的相关数据public PE_EXPORT_DIRECTORY PE_EXPORT;//因为每个模块只有一个输出表,所以定义一个准备好的结构体类型就可以了,其中的对象还要根据数据实列化的。public PE_SectionMessage[] PE_SectionMessageArry;//这里保存的是进程段相关信息的数组,每个进程最少有两个段}/// <summary>/// 从目标内存中读取模块所有的(段、节、块)信息,并转换成数据保存到这个结构里,通常只是有两个段,所以恐怕是要申请一个结构数组才能达到目标。/// </summary>这个结构里只存贮部分数据,那些没啥用的就不管了[StructLayout(LayoutKind.Sequential)]public struct PE_SectionMessage{//虽然内容与IMAGE_SECTION_HEADER结构差不多,但要表示的数据形式不一样,这个结构是将内存中数据读取并计算转换成可以直接看得懂的字符串或者可直接使用的地址的 public string Name;//块名。多数块名以一个“.”开始(例如.text),这个“.”不是必需的。通常只给出了8个字节的长度,所以不会很长public int VirtualSize;//加载到内存实际区块的大小(对齐前),为什么会变呢?可能是有时未初始化的全局变量不放bss段而是通过扩展这里。public int VirtualAddress_R;//该块装载到内存中的RVA(内存对齐后,数值总是SectionAlignment的整数倍),这里的偏移地址在名字后面加了一个_R后缀的都是映射相对偏移,需要加上模块基址才是有效的内存地址。public int SizeOfRawData;//该块在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大 例如bss节(SizeOfRawData为0),data节(关键看未初始化的变量放哪)public int PointerToRawData;//该块在文件中的偏移(FOA),这个值也是编译的时候根据文件对齐的大小计算的相对于文件起点的相对偏移量。public int Characteristics;//块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志}/// <summary>/// 从内存中根据映射偏移读取输出表的重要数据,其中包括模块名、序号基数、函数及函数名数量、输出表包含的三个表(函数表、函数名表、序号表)、我自己添的一个表(为了更直观的展示函数名)/// </summary>[StructLayout(LayoutKind.Sequential)]public struct PE_EXPORT_DIRECTORY{//展示的是输出表目录结构体IMAGE_EXPORT_DIRECTORY中的重要部分数据,而且形式也不一样,IMAGE_EXPORT_DIRECTORY结构体占40个字节。public string Name;//输出表所属模块的名字,这里直接存储名字public int Base;//输出表函数序号的基数,如果需要按照序号找函数的话,序号=基数+索引号public int NumberOfFunctions;//输出表中包含的函数总个数public int NumberOfNames;//输出表中包含的函数名字的总个数,通常情况这个值和函数总数是一样的。public int[] AddressOfFunctions;//EAT这个数组里面保存着输出表里所以函数的映射偏移地址。public int[] AddressOfNames;//ENT这个数组里面保存着输出表里所有函数名在内存中的偏移地址。public int[] AddressOfNameOrdinals;//这是一个序号表,这个数组里保存的是输出函数数组的索引值,索引值本来只占2个字节,这里我给他扩展到4个字节。这里只是为了配合数组长度的类型。序号表自己的索引值和ENT保持一致,ENT的排序按字符串编码排序的。public string[] Names;//这个数组用于保存从指定映射偏移处读取的函数名,便于更直观的展示出来。}/// <summary>/// 这个方法只需要一个参数,一个是模块的全名,其余需要的参数都通过类对象传递。这个方法只能读取远程目标进程内存相关PE头信息。所以这个模块名也是必须包含在目标进程里面/// </summary>PE头信息中包括了多个结构里相关的信息,最后还包含了进程所有的段相关信息,其中包括段名称,起始地址,原始大小和加载大小,已经段属性/// <param name="_ModuleName"></param>模块的全面如:myInjectest1.exe 这个模块名必须是已经加载到了目标进程里面的。不然是读取不到的。/// <returns></returns>public PE_HeadMessage GetPEHeadMessage(string _ModuleName){//这个方法返回一个结构PE_HeadMessage将PE头信息和相关偏移转换成虚拟内存地址VA PE_HeadMessage PE_Message = new PE_HeadMessage();//申请一个结构对象用于保存和返回PE头相关信息MyGame64.MODULEENTRY32 _moduleM = new MyGame64.MODULEENTRY32();//申请一个结构类对象用于获取模块的相关信息if (_ModuleName!=string.Empty){//传入的地址不为零,现在开始读取,计算和赋值 _moduleM = _game64.GetModelMessageA(_ModuleName, _game64.ProcessID);if (_moduleM.modBaseAddr!=0){//这里是比较查询结果,如果找到了相关数据就不会为零,找到了模块才能利用里面的数据继续下面的操作 PE_Message.PE_modBaseAddr = _moduleM.modBaseAddr;//这个值就可以直接赋值,是这个模块加载到内存中的基地址,得到这个地址后接下来就是读取指定内存了byte[] _e_lfanew = new byte[4];//这个字节数组用来保存读取到的PE头在内存中的起始偏移量,长度为4个字节,是int类型Int64 _BaseAddr = PE_Message.PE_modBaseAddr + (Int64)PE_HeadBegain.e_lfanew;//这里是从模块的起始部分Dos结构体最后一个元素处的内存地址bool _su1 = _game64.ReadMemoryA(_game64.ProcessHand,_BaseAddr , out _e_lfanew, (uint)_e_lfanew.Length);//读取PE头结构的起始相对偏移量if (_su1 == true){//如果PE头起始偏移量读取成功了,先转换数据,然后计算,然后赋值,然后再进行后面的读取和分离数据。 PE_Message.PE_HeadBegain_R = _game64.ByteArryToInt(_e_lfanew);//将PE头起始偏移地址(这个PE头起始偏移地址是指结构体IMAGE_NT_HEADERS32的起始偏移地址)赋值到结构里 ,采用模块加载基址加上PE头在文件中的偏移地址找实际地址的方法。// byte[] Machine = new byte[(int)PE_F_Machine.Machine_L];//指示当前PE文件可以适用于什么类型的CPU运行。// byte[] NumberOfSections = new byte[(int)PE_F_NumberOfSections.NumberOfSections_L];//文件的区块(节、段)数 数组byte// byte[] SizeOfOptionalHeader = new byte[(int)PE_F_SizeOfOptionalHeader.SizeOfOptionalHeader_L];//标明IMAGE_OPTIONAL_HEADER32结构的大小的数组byte// byte[] Characteristics = new byte[(int)PE_F_Characteristics.Characteristics_L];//文件属性,据了解占两个字节足够了,可以多属性取或操作。属性包括:可执行文件、系统文件(驱动程序)不可直接运行、DLL文件等,相关详细值写在前面枚举FILE_Characteristics里面//上面数组是用来存放文件头结构体重要属性// byte[] Magic = new byte[(int)PE_OP_Magic.Magic_L];//说明文件的类型 PE32:10BH PE32+(也就是PE64):20BH  Rom映像文件:107H// byte[] SizeOfCode = new byte[(int)PE_OP_SizeOfCode.SizeOfCode_L];//所有代码节的总和(基于文件对齐) 编译器填的 没用// byte[] SizeOfInitializedData = new byte[(int)PE_OP_SizeOfInitializedData.SizeOfInitializedData_L];//包含所有已经初始化数据的节的总大小 编译器填的 没用// byte[] SizeOfUninitializedData = new byte[(int)PE_OP_SizeOfUninitializedData.SizeOfUninitializedData_L];//包含未初始化数据的节的总大小 编译器填的 没用// byte[] AddressOfEntryPoint = new byte[(int)PE_OP_AddressOfEntryPoint.AddressOfEntryPoint_L];//定义程序入口RVA数组// byte[] BaseOfCode = new byte[(int)PE_OP_BaseOfCode.BaseOfCode_L];//代码起始RVA数组byte// byte[] BaseOfData = new byte[(int)PE_OP_BaseOfData.BaseOfData_L];//数据段起始RVA数组byte// byte[] ImageBase = new byte[(int)PE_OP_ImageBase.ImageBase_L];//意义上说是映射基址,在32位系统可能有效,在64位已经过时,不允许固定加载了,都是在一个范围内随机加载的,所以64位系统这个元素是过时元素。// byte[] SectionAlignment = new byte[(int)PE_OP_SectionAlignment.SectionAlignment_L];//内存段对齐数组byte// byte[] FileAlignment = new byte[(int)PE_OP_FileAlignment.FileAlignment_L];//文件内段对齐数组byte// byte[] SizeOfImage = new byte[(int)PE_OP_SizeOfImage.SizeOfImage_L];//模块在内存中映射区的大小。byte[] SizeOfHeaders = new byte[(int)PE_OP_SizeOfHeaders.SizeOfHeaders_L];//模块PE头大小。// byte[] CheckSum = new byte[(int)PE_OP_CheckSum.CheckSum_L];//校验和,一些系统文件有要求.用来判断文件是否被修改// byte[] Subsystem = new byte[(int)PE_OP_Subsystem.Subsystem_L];//说明文件的子系统:  驱动程序(1) 图形界面(2) 控制台、DLL(3)// byte[] DllCharacteristics = new byte[(int)PE_OP_DllCharacteristics.DllCharacteristics_L];//说明文件特性 不是针对DLL文件的,其可取值写在枚举OPTIONAL_DllCharacteristics里面// byte[] SizeOfStackReserve = new byte[(int)PE_OP_SizeOfStackReserve.SizeOfStackReserve_L];//初始化时保留的栈大小// byte[] SizeOfStackCommit = new byte[(int)PE_OP_SizeOfStackCommit.SizeOfStackCommit_L];//初始大化时实际提交的小// byte[] SizeOfHeapReserve = new byte[(int)PE_OP_SizeOfHeapReserve.SizeOfHeapReserve_L];//初始化时保留的堆大小// byte[] SizeOfHeapCommit = new byte[(int)PE_OP_SizeOfHeapCommit.SizeOfHeapCommit_L];//初始化时提交的堆大小// byte[] LoaderFlags = new byte[(int)PE_OP_LoaderFlags.LoaderFlags_L];//字面上理解说是加载器标志// byte[] NumberOfRvaAndSizes = new byte[(int)PE_OP_NumberOfRvaAndSizes.NumberOfRvaAndSizes_L];//相对虚拟偏移地址和大小的数量,其实就是数据目录的数量,其中包括输出表,输入表,等。简单理解就记录了一些数组在映像内存中的起始偏移量和数组大小。//上面数组是用来存放可选头结构体重要属性,其实只有需要读取内存的属性是需要数组的,大部分被注释掉的其实是不需要的代码,上面只有SizeOfHeaders需要从内存中读取,知道整个头部的大小后就可以将整个头的数据都一次性全读取下来,也不会很大,然后再从中分离其他数据,直接用自编函数GetByteArryData()就可以得到数据了。bool _su2 = _game64.ReadMemoryA(_game64.ProcessHand, (PE_Message.PE_modBaseAddr + PE_Message.PE_HeadBegain_R), out SizeOfHeaders, (uint)SizeOfHeaders.Length);//读取PE文件头部分的总大小。if (_su2 == true){//成功取得PE文件头部总大小后就可以申请数组,然后将整个头部一次读取到数组里面然后从中分离数据,如果反复多次读取内存虽然简单怕一但引发异常会影响其他数据读取。 PE_Message.PE_SizeOfHeaders = _game64.ByteArryToInt(SizeOfHeaders);//将PE头整体大小转换成整数并保存到结构体对象属性里面去备用。if (PE_Message.PE_SizeOfHeaders > 0){//PE文件头的大小必须是一个正整数, byte[] PEheadS = new byte[PE_Message.PE_SizeOfHeaders];//这个字节数组用于保存PE文件整个头部分,这部分的大小可以在可选头中的元素中找到SizeOfHeaders,还要映射部分的大小SizeOfImagebool _su3 = _game64.ReadMemoryA(_game64.ProcessHand, PE_Message.PE_modBaseAddr, out PEheadS, (uint)PEheadS.Length);//读取整个PE头的数据存放在数组中备用。if (_su3 == true){//整个头部数据已经成功读取后就可以开始从数组中分离想要的数据保存备用了。我自己编了一个分离数组数据的函数可以用PE_Message.PE_Machine = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_F_Machine.Machine, (int)PE_F_Machine.Machine_L));PE_Message.PE_NumberOfSections = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_F_NumberOfSections.NumberOfSections, (int)PE_F_NumberOfSections.NumberOfSections_L));PE_Message.PE_SizeOfOptionalHeader = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_F_SizeOfOptionalHeader.SizeOfOptionalHeader, (int)PE_F_SizeOfOptionalHeader.SizeOfOptionalHeader_L));PE_Message.PE_Characteristics = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_F_Characteristics.Characteristics, (int)PE_F_Characteristics.Characteristics_L));///上面结构对象元素是用来存放文件头结构体重要属性PE_Message.PE_Magic = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_Magic.Magic, (int)PE_OP_Magic.Magic_L));PE_Message.PE_SizeOfCode = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfCode.SizeOfCode, (int)PE_OP_SizeOfCode.SizeOfCode_L));PE_Message.PE_SizeOfInitializedData = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfInitializedData.SizeOfInitializedData, (int)PE_OP_SizeOfInitializedData.SizeOfInitializedData_L));PE_Message.PE_SizeOfUninitializedData = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfUninitializedData.SizeOfUninitializedData, (int)PE_OP_SizeOfUninitializedData.SizeOfUninitializedData_L));PE_Message.PE_AddressOfEntryPoint_R = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_AddressOfEntryPoint.AddressOfEntryPoint, (int)PE_OP_AddressOfEntryPoint.AddressOfEntryPoint_L));PE_Message.PE_BaseOfCode_R = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_BaseOfCode.BaseOfCode, (int)PE_OP_BaseOfCode.BaseOfCode_L));PE_Message.PE_BaseOfData_R = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_BaseOfData.BaseOfData, (int)PE_OP_BaseOfData.BaseOfData_L));PE_Message.PE_ImageBase_R = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_ImageBase.ImageBase, (int)PE_OP_ImageBase.ImageBase_L));PE_Message.PE_SectionAlignment = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SectionAlignment.SectionAlignment, (int)PE_OP_SectionAlignment.SectionAlignment_L));PE_Message.PE_FileAlignment = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_FileAlignment.FileAlignment, (int)PE_OP_FileAlignment.FileAlignment_L));PE_Message.PE_SizeOfImage = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfImage.SizeOfImage, (int)PE_OP_SizeOfImage.SizeOfImage_L));PE_Message.PE_CheckSum = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_CheckSum.CheckSum, (int)PE_OP_CheckSum.CheckSum_L));PE_Message.PE_Subsystem = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_Subsystem.Subsystem, (int)PE_OP_Subsystem.Subsystem_L));PE_Message.PE_DllCharacteristics = _game64.ByteArryToInt16(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_DllCharacteristics.DllCharacteristics, (int)PE_OP_DllCharacteristics.DllCharacteristics_L));PE_Message.PE_SizeOfStackReserve = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfStackReserve.SizeOfStackReserve, (int)PE_OP_SizeOfStackReserve.SizeOfStackReserve_L));PE_Message.PE_SizeOfStackCommit = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfStackCommit.SizeOfStackCommit, (int)PE_OP_SizeOfStackCommit.SizeOfStackCommit_L));PE_Message.PE_SizeOfHeapReserve = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfHeapReserve.SizeOfHeapReserve, (int)PE_OP_SizeOfHeapReserve.SizeOfHeapReserve_L));PE_Message.PE_SizeOfHeapCommit = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_SizeOfHeapCommit.SizeOfHeapCommit, (int)PE_OP_SizeOfHeapCommit.SizeOfHeapCommit_L));PE_Message.PE_LoaderFlags = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_LoaderFlags.LoaderFlags, (int)PE_OP_LoaderFlags.LoaderFlags_L));PE_Message.PE_NumberOfRvaAndSizes = _game64.ByteArryToInt(GetByteArryData(PEheadS, PE_Message.PE_HeadBegain_R + (int)PE_OP_NumberOfRvaAndSizes.NumberOfRvaAndSizes, (int)PE_OP_NumberOfRvaAndSizes.NumberOfRvaAndSizes_L));//上面结构对象元素是用来存放可选头结构体重要属性//PE头部分的文件头和可选头里的大部分数据都已经读取出来了,并将其值保存到结构体PE_HeadMessage里对应元素里面了,下面部分就开始处理节头信息和数据目录信息if (PE_Message.PE_NumberOfSections > 0){//成功取得了内存映射的节的总数,且大于零就可以开始读取每个节相关的信息了PE_SectionMessage[] SectionMessage = new PE_SectionMessage[PE_Message.PE_NumberOfSections];//这里申请一个节相关信息结构体数组,具体多少个元素,上面读取的内容里面已经读取了数量的。一般情况至少有两个。//接下来开始分离数据到每个数组中去,然后将数组中的数据通过计算转换成能轻易识别和使用的数据后赋值给SectionMessage数组元素if (SectionMessage != null && SectionMessage.Length > 0){//可选头后面紧跟着的就是节头,节头是由连续的结构体IMAGE_SECTION_HEADER组成,单个结构体占40个字节int _begain = PE_Message.PE_HeadBegain_R + 24 + (int)PE_Message.PE_SizeOfOptionalHeader;//数组PEheadS中存储的是整个PE头的全部数据,通过IMAGE_NT_HEADERS32结构体可以知道,可选头结构的后面就是段信息,而PE_HeadBegain_R就是IMAGE_NT_HEADERS32结构体的起始偏移量,往后面加4是PE标识,20就是文件头结构体的占用空间,再加上可选头的大小就是节信息的起始偏移地址了。因为PE_SizeOfOptionalHeader也是读取的数据,64位系统应该是240,32位系统则是224。所以现在这里的总数是264for (int i = 0; i < SectionMessage.Length; i++){//最外面的循环必须是节信息数组,我们要依次将它的元素赋值SectionMessage[i].Name = ASCIIEncoding.UTF8.GetString(GetByteArryData(PEheadS, _begain + (int)PE_S_Name.Name, (int)PE_S_Name.Name_L)).Replace("\0", "");//块名。多数块名以一个“.”开始(例如.text),这个“.”不是必需的。通常只给出了8个字节的长度,所以不会很长。将名字数组里的数据转换成字符串保存。SectionMessage[i].VirtualSize = _game64.ByteArryToInt(GetByteArryData(PEheadS, _begain + (int)PE_S_VirtualSize.VirtualSize, (int)PE_S_VirtualSize.VirtualSize_L));//加载到内存实际区块的大小(对齐前),为什么会变呢?可能是有时未初始化的全局变量不放bss段而是通过扩展这里。SectionMessage[i].VirtualAddress_R = _game64.ByteArryToInt(GetByteArryData(PEheadS, _begain + (int)PE_S_VirtualAddress.VirtualAddress, (int)PE_S_VirtualAddress.VirtualAddress_L));//该块装载到内存中的RVA(内存对齐后,数值总是SectionAlignment的整数倍)                                                 SectionMessage[i].SizeOfRawData = _game64.ByteArryToInt(GetByteArryData(PEheadS, _begain + (int)PE_S_SizeOfRawData.SizeOfRawData, (int)PE_S_SizeOfRawData.SizeOfRawData_L));//该块在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大 例如bss节(SizeOfRawData为0),data节(关键看未初始化的变量放哪)SectionMessage[i].PointerToRawData = _game64.ByteArryToInt(GetByteArryData(PEheadS, _begain + (int)PE_S_PointerToRawData.PointerToRawData, (int)PE_S_PointerToRawData.PointerToRawData_L));//该块在文件中的偏移(FOA),这个值也是编译的时候根据文件对齐的大小计算的相对于文件起点的相对偏移量。                                                 SectionMessage[i].Characteristics = _game64.ByteArryToInt(GetByteArryData(PEheadS, _begain + (int)PE_S_Characteristics.Characteristics, (int)PE_S_Characteristics.Characteristics_L));//块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志_begain = _begain + 40;//单个IMAGE_SECTION_HEADER占40个字节,每处理完一个节的相关信息就要将结构体的起始偏移量往前推40个字节。}//处理完每个段信息后,最后将数组赋值PE_Message.PE_SectionMessageArry = SectionMessage;//将最终的段相关信息数组赋值给头信息结构元素。}}//上面部分已经将节相关信息读取到结构体数组里面去了。if (PE_Message.PE_NumberOfRvaAndSizes > 0){//这里读取输出不相关数据。 }}}}}}}return PE_Message;}/// <summary>/// 这个方法是GetPEHeadMessage的重载方法,参数比原方法多了两个,既可以完成GetPEHeadMessage的功能读取远程目标进程PE头信息,也可以读取当前进程的PE头信息/// </summary>PE头信息中包括了多个结构里相关的信息,最后还包含了进程所有的段相关信息,其中包括段名称,起始地址,原始大小和加载大小,已经段属性/// <param name="ProcHand"></param>这个句柄必须有一定的权限。/// <param name="ProcID"></param>进程的唯一ID/// <param name="_ModuleName"></param>模块的全面如:myInjectest1.exe 这个模块名必须是已经加载到了目标进程里面的。不然是读取不到的。/// <returns></returns>public PE_HeadMessage GetPEHeadMessageA(IntPtr ProcHand, int ProcID, string _ModuleName){//这个方法返回一个结构PE_HeadMessage将PE头信息和相关偏移转换成虚拟内存地址VA PE_HeadMessage PE_Message = new PE_HeadMessage();//申请一个结构对象用于保存和返回PE头相关信息MyGame64.MODULEENTRY32 _moduleM = new MyGame64.MODULEENTRY32();//申请一个结构类对象用于获取模块的相关信息if (_ModuleName != string.Empty && ProcID != 0){//传入的地址不为零,现在开始读取,计算和赋值 _moduleM = _game64.GetModelMessageA(_ModuleName, ProcID);if (_moduleM.modBaseAddr != 0){//这里是比较查询结果,如果找到了相关数据就不会为零,找到了模块才能利用里面的数据继续下面的操作 PE_Message.PE_ImageBase = _moduleM.modBaseAddr;//这个值就可以直接赋值,是这个模块加载到内存中的基地址,得到这个地址后接下来就是读取指定内存了byte[] _e_lfanew = new byte[4];//这个字节数组用来保存读取到的PE头在内存中的起始偏移量,长度为4个字节,是int类型byte[] _PE_head = new byte[0x1000];//这个字节数组用来保存从PE头开始到最后的数据,然后根据偏移量从数组中将数据分离出来,大概4K已经完全足够了Int64 _BaseAddr = _moduleM.modBaseAddr + (Int64)PE_HeadBegain.e_lfanew;//模块的加载地址加上元素所在的偏移量,才是正确的读取地址。bool _su1 = _game64.ReadMemoryA(ProcHand, _BaseAddr, out _e_lfanew, (uint)_e_lfanew.Length);//读取PE头结构的起始相对偏移量if (_su1 == true){//如果PE头起始偏移量读取成功了,先转换数据,然后计算,然后赋值,然后再进行后面的读取和分离数据。 PE_Message.PE_HeadBegain = _moduleM.modBaseAddr + (Int64)_game64.ByteArryToInt(_e_lfanew);//将PE头起始虚拟内存赋值到结构里 ,采用模块加载虚拟内存地址加上PE头在文件中的偏移量。PE_Message.PE_FirstSection = PE_Message.PE_HeadBegain + (Int64)264;//IMAGE_NT_HEADERS32结构后面接着就是N个IMAGE_SECTION_HEADER结构,而64位的IMAGE_NT_HEADERS32为4+20+240=264个字节。所以第一个节的偏移量就是264且是相对于PE头开始位置的。bool _su2 = _game64.ReadMemoryA(ProcHand, PE_Message.PE_HeadBegain, out _PE_head, (uint)_PE_head.Length);//读取PE头结构的其他内容,全部读入byte数组中,然后通过分离计算。if (_su2 == true){//成功读取了PE头信息,虽然设置的读取大小是0x1000。但实际上并没有这么多,然后根据偏移量和长度将数据分离出来,并计算好保存到 PE_HeadMessage结构byte[] AddressOfEntryPoint = new byte[(int)PE_OP_AddressOfEntryPoint.AddressOfEntryPoint_L];//定义程序入口RVA数组byte[] BaseOfCode = new byte[(int)PE_OP_BaseOfCode.BaseOfCode_L];//代码起始RVA数组bytebyte[] BaseOfData = new byte[(int)PE_OP_BaseOfData.BaseOfData_L];//数据段起始RVA数组bytebyte[] SectionAlignment = new byte[(int)PE_OP_SectionAlignment.SectionAlignment_L];//内存段对齐数组bytebyte[] FileAlignment = new byte[(int)PE_OP_FileAlignment.FileAlignment_L];//文件内段对齐数组bytebyte[] NumberOfSections = new byte[(int)PE_F_NumberOfSections.NumberOfSections_L];//文件的区块(节、段)数 数组bytebyte[] SizeOfOptionalHeader = new byte[(int)PE_F_SizeOfOptionalHeader.SizeOfOptionalHeader_L];//标明IMAGE_OPTIONAL_HEADER32结构的大小的数组bytebool _Over = false;//这里定义一个是否处理完成的标识,一旦想要处理的数据都分离了,不管数组循环是否还要继续都可以结束,主要是节省时间for(int i=0;i<_PE_head.Length;i++){//前面将需要分离的数据数组定义好了,然后就在这个循环里面依次分别将数据分离到相应数组里面去。将数据 分离完成后再处理计算数据,然后再赋值if (_Over == false){if (i == (int)PE_OP_AddressOfEntryPoint.AddressOfEntryPoint){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < AddressOfEntryPoint.Length;a++ ){//匹配到偏移后,再用子循环分离数据到目标数组AddressOfEntryPoint[a]=_PE_head[i];i++;//数组中的元素同步往后推移赋值。}}if (i == (int)PE_OP_BaseOfCode.BaseOfCode){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < BaseOfCode.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组BaseOfCode[a] = _PE_head[i];i++;//数组中的元素同步往后推移赋值。}}if (i == (int)PE_OP_BaseOfData.BaseOfData){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < BaseOfData.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组BaseOfData[a] = _PE_head[i];i++;//数组中的元素同步往后推移赋值。}}if (i == (int)PE_OP_SectionAlignment.SectionAlignment){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < SectionAlignment.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组SectionAlignment[a] = _PE_head[i];i++;//数组中的元素同步往后推移赋值。}}if (i == (int)PE_OP_FileAlignment.FileAlignment){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < FileAlignment.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组FileAlignment[a] = _PE_head[i];i++;//数组中的元素同步往后推移赋值。}_Over = true;//从结构和偏移量可知道,目前这个元素应该是偏移量最大的,所以也是最后一个分离的数组。因此处理完成后就可以结束循环了。}if (i == (int)PE_F_NumberOfSections.NumberOfSections){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < NumberOfSections.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组NumberOfSections[a] = _PE_head[i];i++;//数组中的元素同步往后推移赋值。}}if (i == (int)PE_F_SizeOfOptionalHeader.SizeOfOptionalHeader){//这些数据的偏移量都是从IMAGE_NT_HEADERS32结构开始的,而这些数据也是从这个结构开始的,所以在这里的偏移也是数组的偏移。如是以模块加载地址作为起点,那就必须加上枚举PE_HeadBegain偏移处的值,也就是PE_HeadMessage结构第一个元素的值减去加载地址,因为这个值已经计算成虚拟地址了,而不是相对偏移量。这样更方便更实用。  for (int a = 0; a < SizeOfOptionalHeader.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组SizeOfOptionalHeader[a] = _PE_head[i];i++;//数组中的元素同步往后推移赋值。}}}else{//表明数据已经处理完可以结束循环了break;}}//上面循环分离了部分数据,下面开始计算赋值到结构里。PE_Message.PE_AddressOfEntryPoint = _moduleM.modBaseAddr + (Int64)_game64.ByteArryToInt(AddressOfEntryPoint);//程序的入口地址,这里已经加上了基站的,不再是相对地址了PE_Message.PE_BaseOfCode = _moduleM.modBaseAddr + (Int64)_game64.ByteArryToInt(BaseOfCode);//程序代码段起始地址,这里已经加上了基站的,不再是相对地址了PE_Message.PE_BaseOfData = _moduleM.modBaseAddr + (Int64)_game64.ByteArryToInt(BaseOfData);//程序数据段起始地址,这里已经加上了基站的,不再是相对地址了PE_Message.PE_SectionAlignment = _game64.ByteArryToInt(SectionAlignment);//程序段对齐页,64位系统对齐8K。对应值为2000HPE_Message.PE_FileAlignment = _game64.ByteArryToInt(FileAlignment);//程序文件内对齐,64位系统对齐是512。对应的值为200HPE_Message.PE_NumberOfSections = _game64.ByteArryToInt(NumberOfSections);//程序中存在的段的数量,基本上都有.code段和.data段PE_Message.PE_SizeOfOptionalHeader = _game64.ByteArryToInt(SizeOfOptionalHeader);//这个表示可选头所占的字节数,64位系统就是占240个字节,32位系统占224个字节//上面的是PE头相关数据,紧接着就是段信息。有多少个段,前面的数据已经读取了,然后根据这个数据申请一个结构数组读取数组中与节相关的数据。如下几个数组就是要使用到的相关数组,数组的长度是根据定义的结构中元素的枚举类定义的长度决定的。PE_SectionMessage[] SectionMessage = new PE_SectionMessage[PE_Message.PE_NumberOfSections];//这里申请一个节相关信息结构体数组,具体多少个元素,上面读取的内容里面已经读取了数量的。一般情况至少有两个。byte[] Name = new byte[(int)PE_S_Name.Name_L];//块名。多数块名以一个“.”开始(例如.text),这个“.”不是必需的。通常只给出了8个字节的长度,所以不会很长byte[] VirtualSize = new byte[(int)PE_S_VirtualSize.VirtualSize_L];//加载到内存实际区块的大小(对齐前),为什么会变呢?可能是有时未初始化的全局变量不放bss段而是通过扩展这里。byte[] VirtualAddress = new byte[(int)PE_S_VirtualAddress.VirtualAddress_L];//该块装载到内存中的RVA(内存对齐后,数值总是SectionAlignment的整数倍)byte[] SizeOfRawData = new byte[(int)PE_S_SizeOfRawData.SizeOfRawData_L];//该块在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大 例如bss节(SizeOfRawData为0),data节(关键看未初始化的变量放哪)byte[] Characteristics = new byte[(int)PE_S_Characteristics.Characteristics_L];//块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志//接下来开始分离数据到每个数组中去,然后将数组中的数据通过计算转换成能轻易识别和使用的数据后赋值给SectionMessage数组元素if(SectionMessage.Length >0){//如果存在节信息,通常情况下都不会 少于两个,这里只是出于严谨int _begain = 4 + 20 + PE_Message.PE_SizeOfOptionalHeader;//表示数据数组中段信息开始的偏移位置,通过IMAGE_NT_HEADERS32结构可以知道,这个结构的后面就是段信息,而我们的_PE_head数组就是从这个结构开始读取的往后4K左右的大小,这样写的话更容易看懂,后面如果要使用32位系统的话,就不需要再修改,因为PE_SizeOfOptionalHeader也是读取的数据,64位系统是240,32位系统则是224。所以现在这里的总数是264int _pianyi = 0;//这里定义一个结构偏移,用于计算现在应该在数据数组中的位置,只有找到正确的位置,读取的数据才会正确。for (int i = 0; i < SectionMessage.Length;i++ ){//最外面的循环必须是节信息数组,我们要依次将它的元素赋值_pianyi = _begain + (i * 40);//当前段在数据数组中的偏移,_begain段在是数组中的起始偏移,每处理完一个段,偏移量增加40个字节,且从零开始。for (int s = _begain + (i * 40); s < _PE_head.Length; s++){//这里开始分离数据数组里面的数据并计算处理后赋值,因为s是不断增加的,所以s必定大于_pianyi,且开始分离的位置不可能从第一个元素开始,而是从段信息所在的偏移处开始。if (s == (int)PE_S_Name.Name + _pianyi){//匹配到偏移处才能开始赋值数据,数据数组中的位置等于元素在数组中偏移位置时,说明对准起点了。 这里处理段名字for (int a = 0; a < Name.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组,确保得到正确的长度。Name[a] = _PE_head[s];//元素数组中的位置对应数据数组中的位置赋值分离s++;//数组中的元素同步往后推移赋值。}}if (s == (int)PE_S_VirtualSize.VirtualSize + _pianyi){//匹配到偏移处才能开始赋值数据,数据数组中的位置等于元素在数组中偏移位置时,说明对准起点了。 这里处理加载到内存实际区块的大小for (int a = 0; a < VirtualSize.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组,确保得到正确的长度。VirtualSize[a] = _PE_head[s];//元素数组中的位置对应数据数组中的位置赋值分离s++;//数组中的元素同步往后推移赋值。}}if (s == (int)PE_S_VirtualAddress.VirtualAddress + _pianyi){//匹配到偏移处才能开始赋值数据,数据数组中的位置等于元素在数组中偏移位置时,说明对准起点了。 这里处理该块装载到内存中的RVA(内存对齐后)for (int a = 0; a < VirtualAddress.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组,确保得到正确的长度。VirtualAddress[a] = _PE_head[s];//元素数组中的位置对应数据数组中的位置赋值分离s++;//数组中的元素同步往后推移赋值。}}if (s == (int)PE_S_SizeOfRawData.SizeOfRawData + _pianyi){//匹配到偏移处才能开始赋值数据,数据数组中的位置等于元素在数组中偏移位置时,说明对准起点了。 这里处理该块在文件中所占的空间(文件对齐后)for (int a = 0; a < SizeOfRawData.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组,确保得到正确的长度。SizeOfRawData[a] = _PE_head[s];//元素数组中的位置对应数据数组中的位置赋值分离s++;//数组中的元素同步往后推移赋值。}}if (s == (int)PE_S_Characteristics.Characteristics + _pianyi){//匹配到偏移处才能开始赋值数据,数据数组中的位置等于元素在数组中偏移位置时,说明对准起点了。 这里处理块的属性 该字段是一组指出块属性(例如代码/数据、可读/可写等)的标志for (int a = 0; a < Characteristics.Length; a++){//匹配到偏移后,再用子循环分离数据到目标数组,确保得到正确的长度。Characteristics[a] = _PE_head[s];//元素数组中的位置对应数据数组中的位置赋值分离s++;//数组中的元素同步往后推移赋值。}}//这个循环主要就是将数据分离,数据分离完成后,返回上一个循环将得到的数据计算处理赋值到最终数组元素。}//上面子循环已经将每个元素所需的数组都赋值得到数据了,现在将数据进行转换和赋值保存,以免byte数组里的数据被下一个循环覆盖。SectionMessage[i].Name = ASCIIEncoding.UTF8.GetString(Name).Replace("\0","");//将名字数组里的数据转换成字符串保存。SectionMessage[i].VirtualSize = _game64.ByteArryToInt(VirtualSize);//将段加载大小转换成整型赋值给元素SectionMessage[i].VirtualAddress = _moduleM.modBaseAddr + (Int64)_game64.ByteArryToInt(VirtualAddress);//该块装载到内存中的RVA(内存对齐后),这里已经通过计算加上了基址所以不再是RVA而是虚拟内存地址VASectionMessage[i].SizeOfRawData = _game64.ByteArryToInt(SizeOfRawData);//将段的原始大小转换成整型赋值给元素SectionMessage[i].Characteristics = _game64.ByteArryToInt(Characteristics);//将段属性转换成整型赋值给元素}//处理完每个段信息后,最后将数组赋值PE_Message.PE_SectionMessageArry = SectionMessage;//将最终的段相关信息数组赋值给头信息结构元素。}}}}}return PE_Message;}/现在写两个字节数组的方法,用于截取数组中的部分内容或者修改数组中的部分内容////// <summary>/// 从指定字节数据数组里的指定起始位置复制指定长度的数据到新数组/// </summary>/// <param name="_btArry">原数组</param>/// <param name="_DataIndex">起始索引值,大于等于零</param>/// <param name="_DataL">要取的数据长度,大于零</param>/// <returns></returns>public byte[] GetByteArryData(byte[] _btArry,int _DataIndex, int _DataL){//_DataIndex的取值大于等于零,_DataL要大于零byte[] _retArry=new byte[0];//定义一个返回数组,用于返回数据。if (_btArry != null & _DataIndex >= 0 & _DataL > 0){//这个方法处理的数组长度都不宜太长。if (_btArry.Length >= (_DataIndex + _DataL)){//要确保要取的数据在数组范围内,才能进程复制数据的操作byte[] _PassArry = _btArry;//将传入的数组赋值进来操作_retArry = new byte[_DataL];//确实返回数据的数组大小bool _bg = false;//定义一个开始复制的标签,达成条件就为真for (int i = 0; i < _PassArry.Length; i++){if (i == _DataIndex){//这里做一个开关启动判断_bg = true;//说明条件成立可以开始复制数据到返回数组里面了}if (_bg == true){//这里判断开关是否已经开启,如果开启就可以复制数据 _retArry[i - _DataIndex] = _PassArry[i];//复制数到数组}if (i == (_DataIndex + _DataL-1)){//这里做一个开关断开的判断,一旦条件达成就说明指定数据已经完成复制,同时也可以结束循环了 _bg = false;//将开关断开break;//结束循环}}}}return _retArry;//返回取得的数据数组}/// <summary>/// 这个方法用于用数据替换原数组里指定起始位置的数据。简单的说就是修改数组指定起始索引处的数据。/// </summary>/// <param name="_btArry">原数组</param>/// <param name="_DataIndex">起始修改索引</param>/// <param name="_DataArry">用于替代的数数组</param>/// <returns></returns>public byte[] SetByteArryData(byte[] _btArry, int _DataIndex, byte[] _DataArry){//_btArry和_DataArry不能为空,且_DataArry的长度必须在_btArry之内,_DataIndex索引必须大于等于零byte[] _retArry = new byte[0];//定义一个返回数组,用于返回数据。if (_btArry != null & _DataArry != null & _DataIndex >= 0){//这个方法处理的数组长度都不宜太长。if (_btArry.Length >= (_DataIndex+_DataArry.Length)){//要确保要取的数据在数组范围内,才能进程修改数据的操作byte[] _PassArry = _btArry;//将传入的数组赋值进来操作_retArry = _DataArry;//返回的数组应该和原来的数组一样,除了修改的部分,暂时先赋值要修改的数组。bool _bg = false;//定义一个开始修改的标签,达成条件就为真for (int i = 0; i < _PassArry.Length; i++){if (i == _DataIndex){//这里做一个开关启动判断_bg = true;//说明条件成立可以开始复制数据到返回数组里面了}if (_bg == true){//这里判断开关是否已经开启,如果开启就可以复制数据 _PassArry[i] = _retArry[i - _DataIndex];//先修改数组_PassArry中元素的值}if (i == (_DataIndex + _DataArry.Length - 1)){//这里做一个开关断开的判断,一旦条件达成就说明指定数据已经完成复制,同时也可以结束循环了_retArry = _PassArry;//最后将修改过的数组赋值给返回数组返回出去。_bg = false;//将开关断开break;//结束循环}}}}return _retArry;//返回取得的数据数组}//现在开始写PE文件结构,用于修改和保存PE文件内容/// <summary>/// 将一个完整的PE文件保存到这个结构体对象中,其中由三个结构对象组成,而这些结构最终保存的是字节数组。并且这些数组已经根据文件中指定的对齐大小完整对齐的。可以直接写入空文件组成一个完整的PE文件。/// </summary>[StructLayout(LayoutKind.Sequential)]public struct PE_File{//通过PE文件的可选头里的元素可以知道,计算机将PE文件分为两部分,一部分是头,二部分就是映射部分,也就是由多个节组成的部分。public PE_HeadS _HeadS;//PE文件头,从PE文件的开始到节信息头结束,从内存中读取的数据是内存对齐的,这里也保持内存对齐,如果要保存成文件再计算成文件对齐大小。public PE_SectionP[] _SectionsP;//各个节对应的数据,一个PE文件有一个或者多个节,其中保存着运行代码和资源及各种表,在文件中以一定的大小对齐的,所以每个节可能是不连续的。}/// <summary>/// 用于存放PE头信息的上部分,这部分是由Dos头、中间部分、PE头组成,中间部分虽然不固定,但Dos头的最后四个字节指向了PE头的偏移量,而PE头的大小是固定的,所以这部分也是很好读取的。/// </summary>[StructLayout(LayoutKind.Sequential)]public struct PE_HeadS{//这个结构也是一个字节数组,用于保存PE文件头部分,处理数据的时候都会以文件指定的对齐大小对齐,便于直接拼凑一个完整的PE文件  public byte[] PE_HeadArry;//这个字节数组用于保存PE头到节头的数据,以实际大小为准。}/// <summary>/// 这个结构体用于存储PE文件的每个节的数据,并且每个节都根据文件对齐大小对齐,每一个结构对象里都存储着一个完整节的字节数据/// </summary>[StructLayout(LayoutKind.Sequential)]public struct PE_SectionP{//这个结构也是一个字节数组,用于保存PE文件的各个节数据部分,处理数据的时候都会以文件指定的对齐大小对齐,便于直接拼凑一个完整的PE文件  public byte[] PE_SectionArry;//这个数组用于保存PE文件里每个节的数据,每个节单独满组文件对齐大小。}//上面的结构体中都包含字节数组,用于存放从内存中读取的PE文件的字节数据,这些结构体里面的数组都要根据实际大小和文件对齐需要在方法里面实列化需要大小并填入数据/}
}

编程初学者如何理解程序的原理相关推荐

  1. 编程初学者看不懂程序的几点建议

    首先,本人也是一位初学者,自己水平也有限,写此文章只是为了分享一些心得体会. 对于编程初学者来说,选择一门好的语言是很有必要的,想必看编程的你们也大致会一点基本的语法了,那么多读编程会对自己的基本功带 ...

  2. 深入理解程序执行原理

    计算机系统中有很多程序员习以为常但又十分神秘的存在:函数调用.系统调用.进程切换.线程切换以及中断处理. 函数调用能让程序员提高代码可复用性,系统调用能让程序员向操作系统发起请求,进程线程切换让多任务 ...

  3. 《Python编程初学者指南》——1.6 回到Game Over程序

    本节书摘来自异步社区<Python编程初学者指南>一书中的第1章,第1.6节,作者[美]Michael Dawson,王金兰 译,更多章节内容可以访问云栖社区"异步社区" ...

  4. 《C语言编程初学者指南》一2.9 理解运算符优先级

    本节书摘来自异步社区<C语言编程初学者指南>一书中的第2章,第2.9节,作者[美]Keith Davenport(达文波特) , M1ichael Vine(维恩),更多章节内容可以访问云 ...

  5. 适合编程初学者的开源项目:小游戏2048(微信小程序版)

    目标 为编程初学者打造入门学习项目,使用各种主流编程语言来实现. 2048游戏规则 一共16个单元格,初始时由2或者4构成. 1.手指向一个方向滑动,所有格子会向那个方向运动. 2.相同数字的两个格子 ...

  6. 【技术分享篇】从网卡到tcpip协议栈,再到应用程序丨tcp/ip网络编程丨网络api的实现原理丨sk_buff的作用

     从网卡 聊到tcp/ip协议栈,再到应用程序 1. posix tcp/ip网络编程 2. 网络api的实现原理 3. sk_buff的作用 [技术分享篇]面试中从网卡 聊到tcpip协议栈,再到应 ...

  7. 适合编程初学者的开源云笔记系统(微信小程序版)

    目标 为编程初学者打造入门学习项目,使用各种主流编程语言来实现.让想学编程的,一个都不落下. 上述基本涵盖了当前编程开发所有主流语言. 左侧为前端版本:安卓.iOS.鸿蒙.Flutter.Vue.un ...

  8. 初学者学习编程,如何训练自己的编程思维,资深程序员这样建议

    近给大家讲了一堂关于初学者如何从零基础到就业正确的学习步骤,在课堂中我提过两点困难是初学者最大的问题,其中一个问题就是学了后面忘了前面的问题,还有一个最重要的问题就是编程思维.今天主要给大家讲一下初学 ...

  9. java初学者面试_Java面试的前50个问题,面向初学者和经验丰富的程序员

    java初学者面试 您可以参加任何Java面试,无论是大四还是中级,经验或新来的人,一定会看到线​​程,并发和多线程中的几个问题. 实际上,这种内置的并发支持是Java编程语言的最强优势之一,并帮助它 ...

最新文章

  1. android 中使用AsyncTask实现简单的异步编程
  2. mysql按照datetime精确查询_MySQL datetime字段查询按小时:分钟排序
  3. C++ STL 中提供的算法
  4. web安全检查_如何利用现代Web检查器的功能
  5. 任正非:明年应届生招聘人数至少8000人
  6. 带孩子们做环球旅行的读后感_阜南七小教师风采之乔娜:做孩子们成长的记录者...
  7. 将Tomcat集成到eclipse中并写出第一条web语句
  8. 华为机试HJ27:查找兄弟单词
  9. 有道云怎么换行_markdown换行语法 有道云笔记markdown怎么换行?
  10. c++编程求解二元二次方程组_一道俄罗斯高难度解方程组题,错误率达99%+,中国学霸:确实很难...
  11. windows mysql memcached,Windows上的Memcached(不是memcache)PHP扩展
  12. 抓包工具神器,fiddler全解
  13. 戴尔r540服务器修改开机启动项,在BIOS设置中如何修改开机启动项
  14. 超强悍抓包工具和万能视频下载工具
  15. 经典四大排序(动图实现)
  16. square enix服务器维护,Square Enix解决《最终幻想14》的服务器问题
  17. 中国学术会议2009---001
  18. geogebra与matlab,浅谈Geogebra在大学数学教学中的应用
  19. HTML期末学生大作业 基于HTML+CSS+JavaScript通用的后台管理系统ui框架模板
  20. 编译原理 机械工业出版社 第一章第三章部分习题答案

热门文章

  1. wordpress 静态化 linux,将Wordpress全站静态化
  2. 计算机屏保后无法再次启动,Win7 64位旗舰版屏保不能启动
  3. Exception encountered during context initialization - cancelling refresh attempt--依赖缺失
  4. 测试人员如何把控项目进度
  5. matlab中文help,matlab中文帮助文档.pdf
  6. 4.3 ROS工作空间覆盖
  7. Unity绳子插件QuickRopes使用方法(让你快速创建你想要的绳索效果)
  8. 【调剂】曲阜师范大学2021年硕士研究生调剂公告
  9. win7系统未响应卡住_win7旗舰版程序未响应
  10. 应用软件系统架构设计的“七种武器”