PE 文件压缩
通过砍去 DOS 头来压缩 PE 文件
2006-08-05

简介

从 16 位系统时就想把程序做到最小,总是喜欢写单个段的 .com 程序,而不是 .exe。 到了 Windows 后也是一样,软盘依然只有 1.44MB,最初的网络贵且慢,尽可能压缩最后一个字节成为了一种乐趣。 对于别人写的程序,可以用这个小工具删去其 DOS Stub 部分。

后记:写完后发现有更彻底的删头方案,将真正Image部分提前。

代码

  1 /*
  2 =============================================
  3 | Remove the DOS Stub in PE                 |
  4 | ~~~~~~~~~~~~~~~~~~~~~~~~                  |
  5 | How:                                      |
  6 |       After removing the dos program,     |
  7 |   fix the last DWORD in MZ Head           |
  8 |       use the space before the Section    |
  9 |   Headers to align with FILEALIGN         |
 10 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
 11 | Compiled with VC6.0                       |
 12 =============================================
 13 */
 14 #include <stdio.h>
 15 #include <stdlib.h>
 16 #include <windows.h>
 17 /*
 18 Switches:
 19 ConsoleMode: printf or not
 20 DllMode:     whether to compile it as a DLL
 21 */
 22 #define ConsoleMode     1
 23 #define DllMode         0
 24 
 25 #if DllMode
 26 #pragma comment(linker,"/DLL")
 27 #define ConsoleMode 0
 28 #endif
 29 
 30 #pragma comment(linker,"/merge:.data=.text")
 31 #pragma comment(linker,"/merge:.rdata=.text")
 32 #pragma comment(linker,"/SECTION:.text,ERW")
 33 #if ConsoleMode
 34 #pragma comment(linker,"/subsystem:console")
 35 #else
 36 #pragma comment(linker,"/subsystem:windows")
 37 #endif
 38 #ifndef NDEBUG
 39 #if ConsoleMode
 40 #define debughex(hex) printf("\nDEBUG:%X\n",hex);
 41 #endif
 42 #endif
 43 
 44 #define WcrMAX_PATH                 100
 45 #define MAX_BUFFER                  0x200
 46 #define SIZEOFMZHEAD                0x40
 47 #define SIZEOFCONSTHEAD             0xF8
 48 #define SIZEOFSECTIONHEAD           0x28
 49 #define FILEOFFSETFROMSECTIONHEAD   0x14
 50 #define FILEALIGN                   0x200       /*you can read this data from PE file, but 0x200 by default*/
 51 #define macromin(MX,MY) ((MX)<(MY)?(MX):(MY))
 52 
 53 int
 54 #if !ConsoleMode
 55 __declspec(dllexport)
 56 #endif
 57 perform(char* nameinput){   /*if it is a dll, export ConsoleMode(char*)*/
 58 FILE *fin,*fout;
 59 register long i;
 60 unsigned long currentl,templ;
 61 unsigned short currents,temps;
 62 unsigned char currentc,tempc;
 63 unsigned char is_success;
 64 unsigned char mzhead[SIZEOFMZHEAD];
 65 unsigned short numofsections;
 66 unsigned long peoffset;
 67 long foutoffset,foutsectionheads,foutminaddress,finminaddress;
 68 long dosprogramlength;
 69 char filename[WcrMAX_PATH],newname[WcrMAX_PATH],blank[]="";
 70 unsigned char *buf;
 71 fin=NULL;
 72 fout=NULL;
 73 buf=NULL;
 74 is_success=0;
 75 if(nameinput&&strlen(nameinput)){
 76  buf=malloc(MAX_BUFFER);
 77  if(!buf){
 78     #if ConsoleMode
 79     printf("\nNot enough memoery!\n");
 80     #else
 81     MessageBox(NULL,"Not enough memoery!",blank,0);
 82     #endif
 83     goto end;
 84  }
 85  strcpy(filename,nameinput);
 86  fin=fopen(filename,"rb");
 87  if(!fin){
 88   #if ConsoleMode
 89   printf("\nError when reading the file: %s\n",filename);
 90   #else
 91   MessageBox(NULL,"Error when reading the file!",blank,0);
 92   #endif
 93   goto end;
 94  }
 95  fout=fopen("TEMP.WCR","wb");
 96  if(!fout){
 97   #if ConsoleMode
 98   printf("\nError when creating TEMP.WCR\n");
 99   #else
100   MessageBox(NULL,"Error trying to create TEMP.WCR",blank,0);
101   #endif
102   goto end;
103  }
104  /*
105  Init:
106     1. Change the last DWORD in MZ head to 0x40
107     2. Copy the PE,Optional and DataDirent head directly
108     3. Init the various
109  */
110  #if ConsoleMode
111  printf("\nAnalysis with: %s\n",filename);
112  #endif
113  fread(&mzhead[0],SIZEOFMZHEAD,1,fin);
114  fwrite(&mzhead[0],SIZEOFMZHEAD-4,1,fout);
115  templ=0x40;
116  fwrite(&templ,4,1,fout);
117  #if ConsoleMode
118  printf("\tMZ header copied.\n");
119  #endif
120  fseek(fin,SIZEOFMZHEAD-4,SEEK_SET);
121  fread(&currentl,4,1,fin);
122  peoffset=currentl;
123  fseek(fin,currentl,SEEK_SET);
124  fread(buf,SIZEOFCONSTHEAD,1,fin);
125  fwrite(buf,SIZEOFCONSTHEAD,1,fout);
126  #if ConsoleMode
127  printf("\tCOFF and Optional headers copied.\n");
128  #endif
129  /*
130  Fix the Section Head
131     1. Find out the minimum Section offset
132     2. Then align the next Section
133  */
134  fseek(fin,peoffset+0x6,SEEK_SET);
135  fread(&currents,2,1,fin);
136  numofsections=currents;
137  fseek(fin,peoffset+SIZEOFCONSTHEAD,SEEK_SET);
138  foutoffset=0x138;
139  foutsectionheads=foutoffset;
140  finminaddress=0xFFFFFFF;
141      for(i=0;i<numofsections;i++){
142         fread(buf,SIZEOFSECTIONHEAD,1,fin);
143         if(*(long*)(buf+FILEOFFSETFROMSECTIONHEAD)) /*my Patch 1*/
144             finminaddress=macromin(*(long*)(buf+FILEOFFSETFROMSECTIONHEAD),finminaddress);
145         foutoffset+=SIZEOFSECTIONHEAD;
146         #ifndef NDEBUG
147         debughex(*(long*)(buf+FILEOFFSETFROMSECTIONHEAD));
148         #endif
149      }
150      dosprogramlength=(peoffset-SIZEOFMZHEAD);/*The Length of code, that is deleted*/
151      foutminaddress=finminaddress-dosprogramlength;
152      while(foutoffset%FILEALIGN)foutoffset++;
153      if(foutoffset<foutminaddress){/* Check whether it is disturb by FILEALIGN*/
154 /*
155     Modify the PointerToRawData in the Section Head
156     Copy the whole Section Head, copy the whole file
157 */
158         fseek(fin,peoffset+SIZEOFCONSTHEAD,SEEK_SET); /*seek to the Section Head*/
159         templ=finminaddress-foutoffset;
160         foutminaddress=foutoffset;
161         for(i=0;i<numofsections;i++){
162             fread(buf,SIZEOFSECTIONHEAD,1,fin);
163             if(*(long*)(buf+FILEOFFSETFROMSECTIONHEAD)){
164                 (*(long*)(buf+FILEOFFSETFROMSECTIONHEAD))-=templ;
165             }
166             fwrite(buf,SIZEOFSECTIONHEAD,1,fout);
167             #ifndef NDEBUG
168             #if ConsoleMode
169             printf("After Modify:");
170             #endif
171             debughex(*(long*)(buf+FILEOFFSETFROMSECTIONHEAD));
172             #endif
173         }
174         foutoffset=ftell(fout);
175         buf[0]=0;
176         while(foutoffset<foutminaddress){
177             foutoffset++;
178             fwrite(buf,1,1,fout);
179         }
180         fseek(fin,finminaddress,SEEK_SET);
181         while(1){
182             fread(buf,1,1,fin);
183             if(!feof(fin))fwrite(buf,1,1,fout);else break;
184         }
185         /*#ifndef NDEBUG*/
186         is_success=1;
187         /*#endif*/
188      }else{
189         #if ConsoleMode
190         printf("The DOS section of this program cannot be deleted!");
191         #else
192         MessageBox(NULL,"Operation Failed!",blank,0);
193         #endif
194      }
195 /*
196     Copy the file, free the memory
197 */
198 end:
199  free(buf);
200  fclose(fin);
201  fclose(fout);
202  if(is_success){
203  strcpy(newname,filename);
204  strcat(newname,"~");
205  unlink(newname);
206  rename(filename,newname);
207  rename("TEMP.WCR",filename);
208  }else{
209     #ifndef NDEBUG
210     unlink("TEMP.WCR");
211     #endif
212  }
213 }else{
214  #if ConsoleMode
215  printf(
216   "\nTool to remove DOS Stub :\n\n"
217   "\tusage:\t<pe.exe/dll> <output>\n\n"
218  );
219  #else
220  MessageBox(NULL,"Please Enter the Argument!",blank,0);
221  MessageBox(NULL,
222  "Tip:\n\nTry it also on DLL/OCX files\n\n      C. Wu 2006 summer",blank,0);
223  #endif
224 }
225  #ifndef NDEBUG
226  system("pause");
227  #endif
228  return is_success;
229 }
230 
231 #if DllMode
232 int __stdcall DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved){
233 return 1;
234 }
235 
236 #else
237 #if ConsoleMode
238 int main(int arg1,char *arg2[]){
239     if(arg1>1)return perform(arg2[1]);
240         else
241             return perform(NULL);
242 }
243 #else
244 int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int nShowCmd){
245     if(lpCmdLine[0]=='\"'){
246         lpCmdLine[strlen(lpCmdLine)-1]=0;
247         strcpy(&lpCmdLine[0],&lpCmdLine[1]);
248     }
249     return perform(lpCmdLine);
250 }
251 #endif
252 #endif