elva

手工打造微型Win32可执行文件

文章来源:www.xfocus.net
文章提交:watercloud (watercloud_at_xfocus.org)

本文是我在学习PE文件格式时打造小型PE文件的学习历程的一个记录性文字,最终我的目标是完全手工打造一个小的EXE文件,第一个为1024字节,最终改造到512字节。

废话不多说,开始工作:

写个汇编程序:
start:
    xor ebx,ebx
    jz tcall

tjmp:  
    pop eax
    push ebx
    push eax
    add eax,7
    push eax
    push ebx
    push ExitProcess
    push MessageBoxA
    ret

tcall:   call tjmp
     db "ExeDIY",0
     db "Hello!",0

用masm32编译链接后生成Hello.exe


该exe为2k大小,为了把它大小裁减下来,用UltraEdit新建一个16进制文件,
然后我们参考原来的文件来手工创建一个新的hello.exe。

文件如附件最终为1k大小,已经在WinXp/2k/Nt上测试。

我们作的主要工作是:
  1.  把Dos Stub去掉;按照新的信息构造文件头。
  2.  新文件对齐值为0x200,在0x200处填入指令信息。
  3.  把数据段去掉。(但由于WinXp上section个数不能为一个,否则报不是Win32程序,所以仍
      然保留了数据段的section信息,但把它指向都改为了.text段)
  4.  由于数据段已经被去掉,需要将Imports相关信息搬入.text段0x300处。并从新计算
      输入表的地址和输入地址表的地址及Import结构相关链表和指针的地址。由于我们在0x300
      处,执行时内存映像中地址为0x401100,需要按照0x1100为参照修改链表的各个值。

在制作过程中发现文件对齐WinXp不能小于0x200,否则为非法win32程序,而NT可以时32字节倍数的对齐
,如果只针对NT的话可以把文件构造的更小.

虽然我们只用到1个段,但pe文件还是必须要有两个section!

修改Import相关地址后必须修改指令信息中的地址。因为每次对API的引用都是在操作编译器提供的一个地址,
该地址存放的时一条jmp [xxxx]  的指令, xxxx 地址即为Import的函数指针数组,Loader在加载程序时
会根据调用的DLL函数的具体加载地址来填充这里,但我们已经把这一地址移到了0x401100,要作修正。

整个文件dump出来的内容如下(WinHex真是强大呀!)。具体意义在后面描述。
你可以在这里下载它:http://watercloud.diy.163.com/others/Hello1.exe

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

00000000   4D 5A D5 E2 C0 EF BB F9  B1 BE B6 BC C3 BB D3 D0   MZ这里基本都没有
00000010   D3 C3 2C 44 4F 53 B1 A3  C1 F4 B5 C4 CD B7 B2 BF   用,DOS保留的头部
00000020   D0 C5 CF A2 2C CE D2 C3  C7 B6 BC B2 BB D3 C3 2E   信息,我们都不用.
00000030   D2 BB D6 B1 B5 BD D5 E2  C0 EF C0 B2 40 00 00 00   一直到这里啦@...
00000040   50 45 00 00 4C 01 02 00  00 00 00 00 00 00 00 00   PE..L...........
00000050   00 00 00 00 E0 00 0F 01  0B 01 00 00 00 02 00 00   ....?..........
00000060   00 00 00 00 00 00 00 00  00 10 00 00 00 00 00 00   ................
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   10 11 00 00 3C 00 00 00  00 00 00 00 00 00 00 00   ....<...........
000000D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000000F0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000100   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000110   00 00 00 00 00 00 00 00  00 11 00 00 10 00 00 00   ................
00000120   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
00000130   00 00 00 00 00 00 00 00  43 6C 6F 75 64 00 00 00   ........Cloud...
00000140   00 02 00 00 00 10 00 00  00 02 00 00 00 02 00 00   ................
00000150   00 00 00 00 00 00 00 00  00 00 00 00 60 00 00 60   ............`..`
00000160   4E 53 46 4F 43 55 53 00  00 02 00 00 00 20 00 00   NSFOCUS...... ..
00000170   00 02 00 00 00 02 00 00  00 00 00 00 00 00 00 00   ................
00000180   00 00 00 00 60 00 00 60  D5 E2 C0 EF BF AA CA BC   ....`..`这里开始
00000190   CA C7 B6 D4 C6 EB CC EE  B3 E4 D0 C5 CF A2 A3 AC   是对齐填充信息,
000001A0   C3 BB D3 D0 D3 C3 2E 20  D2 BB D6 B1 B5 BD 30 78   没有用. 一直到0x
000001B0   32 30 30 68 BF AA CA BC  CE AA 2E 74 65 78 74 28   200h开始为.text(
000001C0   D2 B2 BE CD CA C7 43 6C  6F 75 64 29 B6 CE BF AA   也就是Cloud)段开
000001D0   CA BC 2C CE D2 C3 C7 B0  D1 20 49 6D 70 6F 72 74   始,我们把 Import
000001E0   B1 ED D2 B2 B7 C5 B5 BD  D5 E2 B8 F6 B6 CE C0 EF   表也放到这个段里
000001F0   C0 B4 C1 CB 2F 2F BA C7  BA C7 20 3A 29 20 20 20   来了//呵呵 :)  
00000200   33 DB 74 13 58 53 50 83  C0 07 50 53 68 2A 10 40   3踭.XSP兝.PSh*.@
00000210   00 68 30 10 40 00 C3 E8  E8 FF FF FF 45 78 65 44   .h0.@.描?ExeD
00000220   49 59 00 48 65 6C 6C 6F  21 00 FF 25 00 11 40 00   IY.Hello!.%..@.
00000230   FF 25 08 11 40 00 00 00  00 00 00 00 00 00 00 00   %..@...........
00000240   B4 FA C2 EB BE CD D5 E2  C3 B4 D2 BB B5 E3 A3 AC   刖驼饷匆坏悖?
00000250   D5 E2 C0 EF D3 D6 CA C7  CC EE B3 E4 A3 AC 49 6D   这里又是填充,Im
00000260   70 6F 72 74 B1 ED CE D2  C3 C7 D2 B2 B7 C5 B5 BD   port表我们也放到
00000270   D5 E2 B8 F6 B6 CE C0 B4  C1 CB A3 AC CE AA C1 CB   这个段来了,为了
00000280   BC C6 CB E3 B5 D8 D6 B7  B7 BD B1 E3 CE D2 C3 C7   计算地址方便我们
00000290   B0 D1 CB FC B7 C5 B5 BD  30 78 33 31 30 68 B4 A6   把它放到0x310h处
000002A0   C1 CB C6 E4 B6 D4 D3 A6  C4 DA B4 E6 B5 D8 D6 B7   了其对应内存地址
000002B0   CE AA A3 BA 30 78 34 30  31 31 30 30 00 00 00 00   为:0x401100....
000002C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000002D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000002E0   00 00 00 00 00 00 00 00  B5 BD D5 E2 C0 EF BF AA   ........到这里开
000002F0   CA BC 49 6D 70 6F 72 74  B1 ED D0 C5 CF A2 A3 BA   始Import表信息:
00000300   5C 11 00 00 00 00 00 00  78 11 00 00 00 00 00 00   \.......x.......
00000310   4C 11 00 00 00 00 00 00  00 00 00 00 6A 11 00 00   L...........j...
00000320   00 11 00 00 54 11 00 00  00 00 00 00 00 00 00 00   ....T...........
00000330   86 11 00 00 08 11 00 00  00 00 00 00 00 00 00 00   ?..............
00000340   00 00 00 00 00 00 00 00  00 00 00 00 5C 11 00 00   ............\...
00000350   00 00 00 00 78 11 00 00  00 00 00 00 AB 00 45 78   ....x.......?Ex
00000360   69 74 50 72 6F 63 65 73  73 00 4B 45 52 4E 45 4C   itProcess.KERNEL
00000370   33 32 2E 64 6C 6C 00 00  BB 01 4D 65 73 73 61 67   32.dll..?Messag
00000380   65 42 6F 78 41 00 55 53  45 52 33 32 2E 64 6C 6C   eBoxA.USER32.dll
00000390   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000003A0   BA C3 C1 CB A3 AC BD E1  CA F8 A3 AC D5 E2 C0 EF   好了,结束,这里
000003B0   CA C7 D7 EE BA F3 D2 BB  B8 F6 B6 D4 C6 EB D3 C3   是最后一个对齐用
000003C0   B5 C4 CC EE B3 E4 D6 B5  C1 CB A1 A3 20 20 20 20   的填充值了。    
000003D0   77 61 74 65 72 63 6C 6F  75 64 40 6E 73 66 6F 63   watercloud@nsfoc
000003E0   75 73 2E 63 6F 6D 20 20  32 30 30 32 C4 EA 31 32   us.com  2002年12
000003F0   D4 C2 31 38 C8 D5 20 20  20 CD FB B8 AB D5 FD 21   月18日   望斧正!


PE文件整个结构如图:

    +-------------------+
    | DOS头             |
    +-------------------+
    | File-Header       |
    +-------------------+
    | Optional-Header   |
    |- - - - - - - - - -|
    |                   |----------------+
    | Data Directories  |                |
    |                   |                |
    | ( RVA,  size)     |-------------+  |
    |                   |
    |                   |---------+   |  |
    |                   |         |   |  |
    +-------------------+         |   |  |
    |                   |-----+   |   |  |
    | Section-Headers   |     |   |   |  |
    |                   |--+  |   |   |  |
    |                   |  |  |   |   |  |
    +-------------------+<-+  |   |   |  |
    |                   |     | <-+   |  |
    | Section Data 1    |     |       |  |
    |                   |     | <-----+  |
    +-------------------+<----+          |
    |                   |                |
    | Section Data 2    |                |
    |                   | <--------------+
    +-------------------+

相关结构摘录MSDN和Winnt.h如下:


typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header  //除了这项和e_magic基本都可以随便填
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

我们关心的为 e_lfanew,该地址指向了PE头结构在文件中的偏移.如我们的这个文件该值为
0x40,而0x40开始就是PE头的开始,标志为"PE"两个字符.

PE头部信息包括一个标志值 "PE\0\0",和FileHeader及OptionHeader,结构如下:
typedef struct _IMAGE_NT_HEADERS {
  DWORD Signature;                     //总是"PE\0\0"
  IMAGE_FILE_HEADER FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

FileHeader的类型定义如下:

typedef struct _IMAGE_FILE_HEADER {
  WORD Machine;
  WORD NumberOfSections;               //Section个数,WinXp不能少于2(即使真正数据段只有一个),其他平台未知.
  DWORD TimeDateStamp;                 //可以随便填
  DWORD PointerToSymbolTable;          
  DWORD NumberOfSymbols;
  WORD SizeOfOptionalHeader;
  WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

OptionHeader类型定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD    Magic;
  BYTE    MajorLinkerVersion;
  BYTE    MinorLinkerVersion;
  DWORD   SizeOfCode;
  DWORD   SizeOfInitializedData;
  DWORD   SizeOfUninitializedData;
  DWORD   AddressOfEntryPoint;          //入口地址,佷重要
  DWORD   BaseOfCode;                   //代码段起始地址
  DWORD   BaseOfData;                   //数据段起始地址.
  DWORD   ImageBase;                    //NT/2k/XP基本都是0x400000
  DWORD   SectionAlignment;
  DWORD   FileAlignment;
  WORD    MajorOperatingSystemVersion;
  WORD    MinorOperatingSystemVersion;
  WORD    MajorImageVersion;
  WORD    MinorImageVersion;
  WORD    MajorSubsystemVersion;
  WORD    MinorSubsystemVersion;
  DWORD   Win32VersionValue;
  DWORD   SizeOfImage;
  DWORD   SizeOfHeaders;
  DWORD   CheckSum;
  WORD    Subsystem;                    //这个别乱填,要不然可能又是非法Win32程序,MajorSubsystemVersion也是.
  WORD    DllCharacteristics;
  DWORD   SizeOfStackReserve;
  DWORD   SizeOfStackCommit;
  DWORD   SizeOfHeapReserve;
  DWORD   SizeOfHeapCommit;
  DWORD   LoaderFlags;
  DWORD   NumberOfRvaAndSizes;          //以下DataDirectory
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD   VirtualAddress;   //每个地址都是Virtual,就是程序加载后该地址在内存中相对于ImageBase的偏移,带virtual的地址是这样
                            //在文件中的偏移为 Addr - 对应Section的VirtualAddress + 对应Section的文件内起始偏移
  DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

其中各个项意义如下:
1  Export table address and size
2  Import table address and size
3  Resource table address and size
4  Exception table address and size
5  Certificate table address and size
6  Base relocation table address and size
7  Debugging information starting address and size
8  Architecture-specific data address and size
9  Global pointer register relative virtual address  
10 Thread local storage (TLS) table address and size
11 Load configuration table address and size
12 Bound import table address and size
13 Import address table address and size
14 Delay import descriptor address and size
15 Reserved
16 Reserved


各Section-Header结构如下:
typedef struct _IMAGE_SECTION_HEADER {
  BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
      DWORD   PhysicalAddress;
      DWORD   VirtualSize;
  } Misc;
  DWORD   VirtualAddress;            
  DWORD   SizeOfRawData;      
  DWORD   PointerToRawData;          //该节起始地址对于于文件的偏移
  DWORD   PointerToRelocations;
  DWORD   PointerToLinenumbers;
  WORD    NumberOfRelocations;
  WORD    NumberOfLinenumbers;
  DWORD   Characteristics;           //属性,控制内存空间的执行,写,读等相关权限.
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


对应这一结构我们的这个文件各重要字段为:
46  Section个数 : 2
5c  代码大小 : 200
68  OEP : 1000
6c  BaseOfCode : 1000
74  ImageBase: 400000
78  Section数据内存对齐: 1000
7c  Section数据文件对齐: 200
90  Image在内存整个空间大小: 3000 /2000也行,起始我们只用了1000,但填这个值提示非法Win32程序:(
94  头部大小: 200,好像填400,800也没关系.
B4  DataDirectory中的数目 : 10
c0  输入表地址: 1110
11c 输入地址表地址: 1100

B8-138 为 DataDirectory数组

138 开始为.text节头部  ,在我们这里名字就叫Cloud啦:)
    140  size :36
    144  Vaddr:1000
    148  RawSize: 200
    14c  PointToRawData: 200
    15c  属性 : 60000060 可读写执行段

160 开始为第二个节,这个节我们没有用,但是系统不允许只有一个节,放到这里作样子的了,骗骗加载器.
200 开始为真正的代码开始
300 开始为Import相关链表.



程序加载后内存中情况为
400000开始: MZ . . .  文件头部信息
401000开始: 对应为文件0x200开始的内容
401100开始: 对应为文件0x300开始的Import表 ,但此时加载器已经把各外部函数地址填好了.

ExitPorcess函数的地址填在 401100处
MessageBoxA函数的地址填在 401108处



--------------------改造----------------------


打开UltraEdit再次重写,这次的目标是512字节:
你可以在这里下载它:http://watercloud.diy.163.com/others/Hello2.exe

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

00000000   4D 5A 33 DB 74 13 58 53  50 83 C0 07 50 53 68 2C   MZ3踭.XSP兝.PSh,
00000010   00 40 00 68 32 00 40 00  C3 E8 E8 FF FF FF 45 78   .@.h2.@.描? Ex
00000020   65 44 49 59 00 48 65 6C  6C 6F 21 00 FF 25 20 11   eDIY.Hello!.% .
00000030   40 00 FF 25 28 11 40 00  00 00 00 00 40 00 00 00   @.%(.@.....@...
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  00 00 00 00 00 00 00 00   ................
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   30 11 00 00 3C 00 00 00  2E 74 65 78 74 00 00 00   0...<....text...
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   2E 72 64 61 74 61 00 00  02 00 00 00 00 20 00 00   .rdata.........
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   ....`..`........
00000120   7C 11 00 00 00 00 00 00  98 11 00 00 00 00 00 00   |.......?......
00000130   6C 11 00 00 00 00 00 00  00 00 00 00 8A 11 00 00   l...........?..
00000140   20 11 00 00 74 11 00 00  00 00 00 00 00 00 00 00   ...t...........
00000150   A6 11 00 00 28 11 00 00  00 00 00 00 00 00 00 00   ?..(...........
00000160   00 00 00 00 00 00 00 00  00 00 00 00 7C 11 00 00   ............|...
00000170   00 00 00 00 98 11 00 00  00 00 00 00 AB 00 45 78   ....?......?Ex
00000180   69 74 50 72 6F 63 65 73  73 00 4B 45 52 4E 45 4C   itProcess.KERNEL
00000190   33 32 2E 64 6C 6C 00 00  BB 01 4D 65 73 73 61 67   32.dll..?Messag
000001A0   65 42 6F 78 41 00 55 53  45 52 33 32 2E 64 6C 6C   eBoxA.USER32.dll
000001B0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001E0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
000001F0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................


加载后内存空间布局如下:

ImageBase: 400000
   程序入口地址 : 400000  即从文件偏移000开始, 我们把代码56字节放到了dos头部
里了.
   .text : 401000  -- 对应 文件 off 100 (但程序加载时从000开始加载的)
   .data : 没有用
   Import表信息在401130处(对应为文件130)
        Import1
           Org -> 40116c  -> 40117c -> ExitProcess
           Name -> 40118a -> Kernel32.dll
           First -> 401120

        Import2
           Org -> 401174 -> 401198 -> MessageBoxA
           Name -> 4011a6 -> User32.dll
           First -> 401128

dll加载后在内存 401120 处存放了ExitProcess的地址
           内存401128 处存放了MessageBoxA的地址

程序代码的jmp表就是:
    40002c  : jmp 401120
    400032  : jmp 401128
然后程序里调用API就使用了40002c和400032这两个地址


对应文件最终被加载了两分,
    一个在400000 系统自身要使用的表结构,但我们同时把执行代码也
  放到了这里,并修改了程序入口指向这里.

    另一个在401000处,这里我们主要利用了这个地址除了了Import表.
  本来想使用400000处的导入表信息,但我把相关指针修改为000后系统报
  非法win32程序,就没有办法了.



文件布局如下:
00-40  : dos头,但我们把代码也放到这里了.
40-58  : PE-File-Header
58-C8  : PE-Option-Header,其中B8-C8为DataDirectory (ExportDirectory和
ImportDirectory)
          我们把DataDirectory已经减到不能在小了,无论如何要ImportDirectory.
c8-F0  : .text Section-Header
F0-118 : .rdata Section-Header (没有用处的节信息,但系统不允许只有一个节只好
装模作样放一个.)
         好了真正的PE头部信息结束,后面本来是 文件对齐的填充,我们利用这里存放
ImportTable.
120-1B0: Import-Table


而且从中可以看到,就算把DataDirectory都去掉(WinXp去掉后会被认为非法Win32程
序),把Section减到只有
1个(Winxp最低也要两个才是合法文件) 文件头部信息也需要 0x88个字节. 如果支持
100文件对齐,并且不使用
Import表那么可以将PE文件做到256字节 ,但对于让xp也能运行512字节应该是极限了.




最后提供一个512字节的小工具:锁屏(Win2k/Xp)
http://watercloud.diy.163.com/others/LockWin.exe


参考:http://watercloud.nease.net/others/pe.txt  英文 作者不详
      http://watercloud.diy.163.com/others/PE文件格式分析.pdf   中文  作者不详
      MicroSoft MSDN


本文只是一个记录性文字,且本人水平很差,望大家斧正。

                             watercloud

                           2002 - 12 - 21

posted on 2007-05-14 00:45 叶子 阅读(418) 评论(0)  编辑 收藏 引用 所属分类: 技术研究


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理