DOS下的EXE文件格式


EXE的文件頭

小端格式:高位存在高地址

EXE文件頭的大小一般為200H,大致的格式如下:

00000000: 4d5a 5001 0200 0200 2000 0000 ffff 0500  MZP..... .......
00000010: 0001 e80e 2800 0200 1e00 0000 0100 0100  ....(...........
00000020: 0200 0d00 0200 0000 0000 0000 0000 0000  ................

接下來,我們來解釋文件頭中每一個字節的具體含義:

00000000: 4d5a 5001 0200 0200 2000 0000 ffff 0500  MZP..... .......
            |	|     |	   |	|   |	 |    |
	    |	|     |	   |	|   |	 |    `---[e-f]堆棧段的段值ss(相對值)=該段(stack)段地址-第一個段地址的差		
	    |	|     |	   |	|   |	 `--------[c-d]至多可以為EXE分配多少字節,幾乎都是ffffh
	    |	|     |	   |	|   `-------------[a-b]至少要為EXE分配多少字節,幾乎都是0000h
	    |	|     |	   |	`-----------------[8-9]文件頭的節長度,一節=10h,文件頭的字節長度=節長度*10H;該文件頭長度為0020*10h=200h										 
	    |	|     |	   `----------------------[6-7]重定位項的個數(后續說明)
	    |	|     `---------------------------[4-5]小端格式表示當前exe在硬盤中占幾個扇區,一個扇區200h=512 bytes, 0002h個扇區															 
	    |	`---------------------------------[2-3]小端格式表示最后一個扇區的字節數,如果為0,表示最后一個扇區是滿的,
            |                                          此時最后一個扇區的字節數是0150h   	
	    `-------------------------------------[0-1]標志“MZ”,操作系統在運行exe文件時,會檢查這兩個字節,判斷是否為exe文件---防止死機
									   
						
00000010: 0001 e80e 2800 0200 1e00 0000 0100 0100  ....(...........
            |	|     |	   |	|   |	 |    |
	    |	|     |	   |	|   |	 |    `---[1Eh-1Fh] 重定位的偏移地址=0001h		
	    |	|     |	   |	|   |	 `
	    |	|     |	   |	|   `
	    |	|     |	   |	`-----------------[18h-19h] 重定位表的偏移位置001eh(后續說明)										 
	    |	|     |	   `----------------------[16h-17h] delta CS,代碼段的段地址=該段(.text)-第一個段地址的差
	    |	|     `---------------------------[14h-15h]IP的值0028h,程序在載入運行時第一條指令地址,病毒必須修改此處															 
	    |	`---------------------------------[12h-13h] EXE文件頭的校驗值,在DOS中沒什么用,windows里的驅動文件.sys(格式其實上是EXE)中使用
	    `-------------------------------------[10h-11h] sp寄存器的值(絕對值)0100h
00000020: 0200 0d00 0200 0000 0000 0000 0000 0000  ................
	    |	|    |
	    |	|    |
	    |	|    |
	    |	|    |
	    |	|    `-------------------------------[24h-25h] Δ=0002h 首段地址+Δ=重定位的段地址
	    |	`------------------------------------[22h-23h]重定位的偏移地址=000Dh
	    `----------------------------------------[20h-21h]重定位Δ=0002h,首段地址+Δ=重定位的																									   段地址

了解完文件頭各個字節的含義之后,為了加深理解,我們來看如下的幾個問題:

如何根據文件頭計算文件大小?

  1. 從文件頭[2-3]字節中可得出EXE文件在硬盤中的最后一個扇區的大小

    從文件頭[4-5]字節中可得出EXE文件在硬盤中所占的扇區數

  2. 文件大小的計算公式如下:

    if 最后一個扇區的大小==0
    	文件大小=扇區數*200h bytes
    else if 最后一個扇區大小>0
      文件大小=(扇區數-1)*200h + 最后一個扇區的大小
    
  3. 具體的C語言代碼實現如下(copy from blackwhite):

     // 設ExeHead的類型是char *, 它指向文件頭的內容。
     if(*(short int *)(ExeHead+2) != 0)
          FileSize = (*(short int *)(ExeHead+4)-1)*0x200+ *(short int *)(ExeHead+2);
     else
          FileSize = *(short int *)(ExeHead+4) * 0x200;
    
  4. 根據此公式算出來的文件大小嚴格的講是EXE文件載入內存的部分長度

為什么要在文件內部對文件的長度進行記錄?

任何一個文件系統EXT,FAT...在儲存文件的時候會保存文件目錄+文件內容,文件目錄里面包含文件名,文件長度,創建時間,文件第一塊內容保存在何處等等,那么在EXE的文件頭中為什么還需要記錄文件大小呢?

  • 文件內部的文件長度可能小於文件目錄里面所記錄的長度:EXE在運行時末尾的一部分可以暫時不載入內存,這一部分代碼叫做覆蓋(overlay)

  • 當exe文件中存在覆蓋時,根據文件頭計算得出的exe長度(exe載入內存的長度)<在硬盤上存儲的物理長度

    overlay技術的作用(與demand paging相似):

    1. 在DOS中,把常用的函數變量載入內存,把不常用的數據/函數放在overlay中,在使用時再放入內存

    2. 利用覆蓋技術實現反跟蹤,把dll動態庫文件粘在文件末尾,當作覆蓋,在程序運行時動態載入函數

驗證文件頭ss, cs中記錄的是相對值

  1. 通過masmlink編譯hello2.asm文件,得到hello2.exe

    hello2.asm的具體代碼(from blackwhite)如下:

    data segment; 1000:0000
    abc db "Hello, abc!$"; 12字節=0Ch字節 
        db 10h dup(0); 16字節
    data ends
    
    code segment; 
    assume cs:code, ds:data, ss:stk
    begin:
       mov ax, data
       mov ds, ax
       mov ah, 9
       mov dx, offset abc
       int 21h
       mov ax, code; 編譯時code被設成以下值
                   ; (code段-首個段的距離)/10h
       mov ds, ax
       mov ah, 9
       mov dx, offset xyz
       int 21h
       mov ah, 4Ch
       int 21h
    xyz db "Hello, xyz!$"
    main:
       jmp begin
    code ends
    
    stk segment stack
    db 100h dup('$'); 為了方便查看,將堆棧中的初始值改為'$'
    stk ends
    end main
    
  2. 在xp環境下,通過qv打開exe文件,跳過前200h字節(文件頭),來到程序開始的地方

    • 數據段(data segment)位於的區域[200h-21Fh],數據段為該程序的第一個段

      abc db Hello, abc!$ 占0Ch個字節, db 10h dup(0); 占10h個字節

      由於下一個段開始的段地址需要合法(被10h整除),所以前一個段的段長度也需要被10h整除,

      在這個例子中,數據段最后的4個byte的作用是擴充數據段,使其長度可以被10h整除的

    • 代碼段(code segment)位於的區域[220h-24Fh]

    • 堆棧段的起始地址是250h, 長度為100h

    驗證文件頭中ss和cs的值

    數據段的開始是在00000200, 代碼段是在00000220, 堆棧段是在00000250

    • 在文件頭中 [e-f]位保存的是ss的段相對地址=(250-200)/10h=5=0005

    • 在文件頭中[10h-11h]位保存的是堆棧段的大小100h,sp的絕對值

    • 在文件頭中 [16h-17h]位保存的是cs的段相對地址=(220-200)/10h=2=0002

    為什么ss, cs是相對值,sp可以是絕對值?

    stack的大小是絕對的,但是stk的實際值在編譯的時候是不知道的

    編譯器在編譯時表示data,code,stk的時都采用相對值的形式,用該段的段地址-第一個段地址的差來表示

  3. 在Qview中使用Tab鍵,切換到assembler模式,觀察代碼段

以上內容是個人學習總結,如有錯誤,歡迎指出!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM