闲来无事之--记用BAT(批处理脚本)实现文件下载功能(续)

综上所述,"DOS信息部分"对应框架的代码为:

代码

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   4D 5A 00 5B D5 E2 C0 EF  B6 BC C3 BB D3 C3 2C 2C   MZ.[这里都没用,,
00000010   B1 C8 C8 E7 CE D2 D0 B4  3A CE D2 D2 B2 D6 BB 2C   比如我写:我也只,
00000020   CA C7 D2 BB B0 E3 CB A7  2C B2 BB CA C7 CC D8 2C   是一般帅,不是特,8
00000030   B1 F0 CB A7 B5 C4 C0 B2  5D 00 00 00 40 00 00 00   别帅的啦]...@...


可以看到最后的4个字节"40000000"也就是00000040H(下面如果直接在数值后加"H"的即表示为16进制)是指向他末尾的指针,也就是说明,我们把"DOS块"的部分给去掉了.

接下来是"PE信息部分",他的结构可以用下面的图来表示:

引用

+++++++++++++++++++++++++++++++++++++++++++++
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[PE标志][0x04]                      +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          + <==PE信息部分
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[PE文件头][0x18]                    +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          +
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[自定义数据结构][0x0e]              +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+++++++++++++++++++++++++++++++++++++++++++++


整个"PE信息部分"结构是这样的:

代码

typedef struct _IMAGE_NT_HEADERS {
 DWORD Signature;                      //"PE标志"段,总是"PE00"
 IMAGE_FILE_HEADER FileHeader;         //"PE文件头"段,指向IMAGE_FILE_HEADER结构
 IMAGE_OPTIONAL_HEADER OptionalHeader; //"自定义数据"段,指向IMAGE_OPTIONAL_HEADER结构
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;


IMAGE_FILE_HEADER结构(PE文件头)和IMAGE_OPTIONAL_HEADER结构如下:

代码

typedef struct _IMAGE_FILE_HEADER {
 WORD Machine;                         //运行平台,386的话是104CH
 WORD NumberOfSections;                //文件节数目,最少为2
 DWORD TimeDateStamp;                  //文件创建时间,随便设置(不过为了最后生成方便,随便设置的地方最好都设置为0)
 DWORD PointerToSymbolTable;           //这里两项记用于调试,也随便设置
 DWORD NumberOfSymbols;
 WORD SizeOfOptionalHeader;            //下面那个IMAGE_OPTIONAL_HEADER结构的长度,一般为000EH(包括16个IMAGE_DATA_DIRECTORY结构),我们只要2个结构,所以设置为0070H
 WORD Characteristics;                 //文件属性,PE文件是010H,DLL的话是210H
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;


IMAGE_FILE_HEADER说明了PE文件的基本运行信息,可是光靠这短短的结构并不能满足我们的需要,毕竟微软设计的东西还是考虑的很周全的,于是在它下面跟上了一个长长的结构(OptionHeader结构)来作为附加信息供给系统.

代码

OptionHeader结构(自定义数据结构)定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
 WORD    Magic;                        //EXE文件的话这里是10B
 BYTE    MajorLinkerVersion;           //连接器版本,随便
 BYTE    MinorLinkerVersion;
 DWORD   SizeOfCode;                   //所有代码节总大小,我们就一个节,所以是512,也就是200H
 DWORD   SizeOfInitializedData;        //....未初始化数据节....没有这个,设置为0
 DWORD   SizeOfUninitializedData;      //....已................................
 DWORD   AddressOfEntryPoint;          //代码执行起始地址,注意,这个是你代码存放的位置,[这里注意点1]
 DWORD   BaseOfCode;                   //代码段......(这里三个都是内存地址),这里是0
 DWORD   BaseOfData;                   //数据段......(并非硬盘文件地址),这里是0
 DWORD   ImageBase;                    //建议加载位置,通常是00400000H,9X的系统可能略小于这个值,记不得了..:(
 DWORD   SectionAlignment;             //内存中对齐大小,一般为1000H,也就是NT的一个内存片,4KB
 DWORD   FileAlignment;                //文件..........,这里设置最小的,200H,兼容全部系统
 WORD    MajorOperatingSystemVersion;  //一下几个都是系统版本相关的,随便设置
 WORD    MinorOperatingSystemVersion;
 WORD    MajorImageVersion;
 WORD    MinorImageVersion;
 WORD    MajorSubsystemVersion;        //这里要设置为04H
 WORD    MinorSubsystemVersion;
 DWORD   Win32VersionValue;            //未用
 DWORD   SizeOfImage;                  //PE文件占用的内存空间,我们设置为3000H
 DWORD   SizeOfHeaders;                //PE文件头大小(含节表),这里是200H
 DWORD   CheckSum;                     //效验和(我不知道用来干嘛,PE几乎都是000000000,可能和其他方面有关,比如调试?)
 WORD    Subsystem;                    //文件子系统,子系统的含义大家可以去参考NT内核,这里设置为02,03均可(控制台和窗口子系统)
 WORD    DllCharacteristics;          
 DWORD   SizeOfStackReserve;           //一下几个是有关堆和栈的设置,基本上随便,不过最好设置够用就行(不是0啊!)
 DWORD   SizeOfStackCommit;
 DWORD   SizeOfHeapReserve;
 DWORD   SizeOfHeapCommit;
 DWORD   LoaderFlags;                  //未用
 DWORD   NumberOfRvaAndSizes;          //下面的IMAGE_DATA_DIRECTORY结构的数量,原来是16个,最少为2个
 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;


有了这个IMAGE_OPTIONAL_HEADER结构,PE文件的作用和包含了什么资源都一目了然了.

IMAGE_DATA_DIRECTORY结构如下,PE文件中包含了很多数据类型,比如导出,导入函数,资源,重定位,调试和版权信息等等,这个结构最多可以有16个,就是用来定位这些数据的:

代码

typedef struct _IMAGE_DATA_DIRECTORY {
 DWORD   VirtualAddress;        
 DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;


IMAGE_DATA_DIRECTORY结构就是指出了你每个数据类型的在内存中的装载位置和长度.注意,这个结构和下面要说道的节表不同,虽然他们可能指向的是同一个地址,但是,IMAGE_DATA_DIRECTORY区分的是严格的数据类型,而节表只是根据人为的定义来划分数据的种类,如果是正常的EXE,通常把各个数据种类分开存放,而这些数据通常又和数据类型用一样的方法分类,所以IMAGE_DATA_DIRECTORY结构和节表指向的地址可能是一样的,但是本文这篇例子不同,因为我们手写的PE必须尽可能的小,所以我吧几个节表的数据全部放在了一个节,这样,节表就只有一个,而IMAGE_DATA_DIRECTORY结构要从混和的数据中指向正确的数据类型地址,就和节表指向的不一样了.

综上所述,"PE信息部分"对应框架的代码为:

代码

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000040   50 45 00 00 4C 01 02 00  00 00 00 00 00 00 00 00   PE..L...........
00000050   00 00 00 00 70 00 0F 01  

                                    0B 01 00 00 00 02 00 00   ....p...........
00000060   00 00 00 00 00 00 00 00  79 01 00 00 00 00 00 00   ........y.......
00000070   00 00 00 00 00 00 40 00  00 10 00 00 00 02 00 00   ......@.........
00000080   00 00 00 00 00 00 00 00  04 00 00 00 00 00 00 00   ................
00000090   00 30 00 00 00 02 00 00  00 00 00 00 02 00 00 00   .0..............
000000A0   00 01 00 00 00 00 00 00  00 01 00 00 00 10 00 00   ................
000000B0   00 00 00 00 02 00 00 00  

                                    00 00 00 00 00 00 00 00   ................
000000C0   28 11 00 00 28 00 00 00  


这上面的数据大多都解释过了,这里要看地址"000000C0"处的"28 11 00 00 28 00 00 00",这个是IMAGE_DATA_DIRECTORY结构的第二个,也就是导入表的地址,"00 00 00 28"这个是长度,不比多说,"00 00 11 28"这个又为何?带着这个问题看下去...[这里算作注意点2]


最后要介绍的是"数据部分":

引用

+++++++++++++++++++++++++++++++++++++++++++++
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[数据节表][0x24*N+1]                +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+                                          + <==PE数据部分
+  +++++++++++++++++++++++++++++++++++++++  +
+  +[数据节][不定]                      +  +
+  +++++++++++++++++++++++++++++++++++++++  +
+++++++++++++++++++++++++++++++++++++++++++++


其中IMAGE_SECTION_HEADER结构(数据节表)如下:

代码

typedef struct _IMAGE_SECTION_HEADER {
 BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];//这个8字节的空间就是给你来定义这个节的名称,比如大家常见的".text .data .code"等等,我这里为了以后的填充方便,设置了空白..(00000000H),其实这里是可以随便写的,比如你定义".zvrop"也可以
 union {
     DWORD   PhysicalAddress;          //这是个联合结构,说明了该节的大小,我们整个PE文件就是一个节,所以是200H
     DWORD   VirtualSize;
 } Misc;
 DWORD   VirtualAddress;               //定位该节在内存中的地址(相对于加载位置的偏移地址)我们这里是先不说这些.[这里算作注意点3]
 DWORD   SizeOfRawData;                //文件中的尺寸,这里和上面的联合结构不同,这里是对齐后的地址,我们设置为200H
 DWORD   PointerToRawData;             //该节在文件中的位置,相对于文件头,这里可以随便设置,不过设置了后面的代码指针也要跟着变动,我们这里设置100H
 DWORD   PointerToRelocations;         //下面四个是给连接器用的参数,随便
 DWORD   PointerToLinenumbers;
 WORD    NumberOfRelocations;
 WORD    NumberOfLinenumbers;
 DWORD   Characteristics;              //节的属性,自己区查表,基于篇幅,这张表我就不提供了,需要的可以PM我,一般代码节为60000020H(40000000&2000000&00000020),即是可执行,可读的代码段,我们设置为60000060H,因为我们既包含了数据又包含了代码.
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


可以看到这个结构的数量是不定的,也就是你下面有多少个节,就有多少个IMAGE_SECTION_HEADER+1的结构,因为系统需要一个全0的IMAGE_SECTION_HEADER结构来标识已经结束.另外,XP最少要两个IMAGE_SECTION_HEADER结构,,不然会报非法32位程序的(这个熟悉的提示我在完成这个东西的时候不知道出现了NN次,从此深恶痛绝!),2K则没有这个限制(引自watercloud的研究,我没多少时间去深挖这个哈...).

下面就是具体的"数据节"的内容了(我们这篇文档整个PE文件就是一个节),整个PE文件结构内容大概就是这么多.


综上所述,"数据部分"对应框架的代码为:

代码

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

                                    00 00 00 00 00 00 00 00   (...(...........
000000D0   00 02 00 00 00 10 00 00  00 02 00 00 00 01 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 60 00 00 60   ............`..`

000000F0   00 00 00 00 00 00 00 00  02 00 00 00 00 20 00 00   ............. ..
00000100   00 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 60 00 00 60  00 00 00 00 00 00 00 00   ....`..`........



[THIS IS JMP S1]

绕了这么大个圈子才回到正题....(一_一....其实我是想把问题写的详细,这样大家看了就没有态度的疑虑嘛..),还记得上面说过的数据类型吗,其中最重要的就是导入表,我们的URLDownloadToFile小朋友已经在板凳上坐了很久了.......这个导入表就是为他量身定做的.我们的目的就是让PE文件执行URLDownloadToFile的功能,自然得把URLDownloadToFile这个函数加入导入表.

说道导入表的定义呢?就不得不先说说WINDOWS加载可执行程序时候对IAT(IMPORT ADDRESS TABLE,导入地址表)的修改,我们知道,各个系统的每个函数在内存中的位置都是不同的(至少2K,XP,2003基本上都不一样),所以才有很多写人SHELLCODE的时候,位置计算个半天..这样来说的话,在我们编译EXE的时候就不可能确定某个函数的地址.要执行这个函数,必须找到他的入口地址,而这个地址就由系统在加载PE文件的时候帮你"填空",这动态的完成函数地址的填充也就是"动态连接"这个名词的由来.

现在我简单的模拟一下系统转载PE文件并给出函数地址的步骤,首先,我们给出一个PE文件中的导入表:

代码

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000120   58 11 00 00 00 00 00 00  
                                    50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00

                                                00 00 00 00   ....n... .......
00000140   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

00000150   58 11 00 00 00 00 00 00



可以看到,这个表被分为四个部分,其中中间两个等长为0x14的两段就是导入表中的IMAGE_IMPORT_DESCRIPTOR结构,该结构如下:

代码

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
   union {
       DWORD   Characteristics;            
       DWORD   OriginalFirstThunk;             //指向一个"函数列表的指针结构".
   };
   DWORD   TimeDateStamp;                      //暂时可以看作没用,0
   DWORD   ForwarderChain;                     //暂时可以看作没用,0
   DWORD   Name;                               //指向一个DLL,这个结构里面的函数必须都是这个DLL里面的
   DWORD   FirstThunk;                         //指向一个IAT表,最后操作系统修改的就是这个
} IMAGE_IMPORT_DESCRIPTOR;


注意,该结构也必须有N+1个,因为我们只需要一个函数"URLDownloadToFile",所以我们只有这一个结构,第二个结构是全0的.表示结束.

这个"函数列表指针结构"就是IMAGE_THUNK_DATA32结构:

代码

typedef struct _IMAGE_THUNK_DATA32 {
   union {
       PBYTE   ForwarderString;
       PDWORD  Function;
       DWORD  Ordinal;
       PIMAGE_IMPORT_BY_NAME  AddressOfData;
   } u1;
} IMAGE_THUNK_DATA32;


他只有一个双字类型的值,这个值如果是1XXXXXXXH的,那么说明该函数是一序号方式导入的,序号就是除了1外的剩下的7位,如果是0XXXXXXXH的,那么这个除了0外的7位就是作为一个虚拟地址指向这个函数的名字.

关于什么是序号导入什么是名字导入,我就不说了,这些涉及到导出表的概念.本文不需要.

假设我是WINDOWS操作系统的PE装载器,我从这个PE文件格式的某些参数中定位到了这个00000128H的地址是导入表地址,现在我的目的是要把"58 11 00 00"这个地址替换为正确的函数地址(注意,是00000120H处的,00000150H处的那个"58 11 00 00"是给系统提供"URLDownloadToFile"这个字符串位置的指针,这个地址不会变动,会变的是00000120H处的"58 11 00 00",其实00000120H处的"58 11 00 00"可以随便设置的.).

我开始定位到了:

代码

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

                                    50 11 00 00 00 00 00 00   X.......P.......
00000130   00 00 00 00 6E 11 00 00  20 11 00 00


的地方,发现这个函数的位置是"50 11 00 00",相关的DLL是"6E 11 00 00",于是我找到PE文件的这个位置(是内存中的相对位置),发现"50 11 00 00"位置处的IMAGE_THUNK_DATA32结构的值是"58 11 00 00",这个值不是1开头的,于是我用这个值作为地址查找,发现这个值指向的位置的内容是"31 00 URLDownloadToFile",除去前面的两个序号,找到了这个函数的名称,接下来我根据在"6E 11 00 00"位置找到的字符串"URLMON.DLL",用LoadLibrary()和GetProcAddress()找到了函数"URLDownloadToFile"在内存中的位置,假设是"XX XX XX XX",然后把"XX XX XX XX",填入到"20 11 00 00"指向的位置中...完毕.

这样来说,大家就明白了,URLDownloadToFile这个函数的存放位置应该根据"50 11 00 00"(确切的说应该是"50 11 00 00"指向的位置的指针)和"6E 11 00 00"来确定(确定这个函数存在的DLL).

你可能感兴趣的:(闲来无事之--记用BAT(批处理脚本)实现文件下载功能(续))