2013-07-03 45 views
1

我正在嘗試完成這個僅用程序集製作的PE文件,它應該在控制檯中顯示一條消息。我想以這種方式組織它,我可以稍後添加更多的東西(知道在哪裏添加代碼,數據,導入的函數)。
我創建4個部分現在,爲代碼數據外行數據導入的元素。我在這個階段的主要問題是:爲程序集中的PE文件創建和使用部分(NASM)

  1. 節頭有些值使可執行文件無效(沒有有效的win32)
  2. 指針從數據部分元素是錯誤的
  3. 一些涉及計算的首選的絕對地址,部分對齊和文件對齊可能是錯誤的

首先,我將顯示所有我的代碼在下面。有些事情,真的並不重要不會爲了節省時間,並使其更容易閱讀 這是NASM代碼被添加

; Constants (use '$' as prefix) 
$SECTION_ALIGNMENT equ 4096  ; Each section is aligned to 4096 in memory 
$FILE_ALIGNMENT equ 512  ; Each section is aligned to 512 on disk 
$PREFERRED_ADDRESS equ 4194304 ; Preffered address for EXE is 4 MB 
$TOTAL_PE_SECTIONS equ 4  ; Code, Data, Bss and IData 
; Image size = headers aligned to section alignment + sections size aligned 
; to next multiple of section alignment, everything aligned, too 
$IMAGE_SIZE  equ $SECTION_ALIGNMENT + (HEADERS_SIZE/$SECTION_ALIGNMENT) + \ 
         $TOTAL_PE_SECTIONS * $SECTION_ALIGNMENT 


; Will help us align some of the values to the next specified multiple 
%define Round(Number, Multiple) Multiple+(Number/Multiple) 

section .header progbits vstart=0 

    ; This is the MZ header 
    DOS_HEADER: 
     db    "MZ"   ; MZ signature 

     ; ... 
     ; Here we have all the members of the DOS header, in 4 paragraphs 
     ; ... 

     db    PE_HEADER ; The last one is pointing to the PE header 


    DOS_STUB: 
     ; ... 
     ; A small DOS program to display a simple message in DOS (64 bytes) 
     ; ... 

    ; This is the PE header 
    PE_HEADER: 
     db    "PE", 0, 0 ; PE signature 
     dw    0x014C  ; Platform Intel I386 
     dw    $TOTAL_PE_SECTIONS 
     dd    1371668450 ; Creation timestamp 
     dd    0   ; No symbols table 
     dd    0   ; No symbols 
     dw    SECTIONS_TABLE - OPT_HEADER ; Optional header size 
     dw    0x0002|0x0004|0x0008|0x0100|0x0200 ; Characteristics 

    ; Optional header 
    OPT_HEADER: 
     dw    0x010B  ; Signature 
     db    0   ; Linker version 
     db    0   ; Minor linker version 
     dd    CODE_SIZE 
     dd    DATA_SIZE ; Initialized data size 
     dd    BSS_SIZE ; Uninitiated data size 
     dd    CODE  ; Entry point 
     dd    CODE  ; Code RVA 
     dd    DATA  ; Data RVA 
     dd    $PREFERRED_ADDRESS ; Preferred address in memory 
     dd    $SECTION_ALIGNMENT 
     dd    $FILE_ALIGNMENT 
     dw    4   ; OS version 
     dw    0   ; Minor OS version 
     dw    0   ; Image version 
     dw    0   ; Minor image version 
     dw    3   ; Subsystem version 
     dw    10   ; Minor subsystem version 
     dd    0   ; WIN32 version 
     dd    $IMAGE_SIZE ; Image size calculated above 
     dd    Round(HEADERS_SIZE, $SECTION_ALIGNMENT) ; Headers size 
     dd    0   ; Checksum 
     dw    3   ; System interface CUI 
     dw    0   ; DLL characteristics 
     dd    4096  ; Reserved stack 
     dd    4096  ; Still not  ?? 
     dd    65536  ; sure about  ?? 
     dd    0   ; these   ?? 
     dd    0 
     dd    2   ; Data directory entries 
     dd    0   ; Export table pointer 
     dd    0   ; Export table size 
     dd    I_TABLE  ; Import table pointer 
     dd    I_TABLE_S ; Size of import table 
     dq    0   ; Reserved 

    SECTIONS_TABLE: 
     CODE_SECTION_HEADER: 
      db   ".code", 0, 0, 0 
      dd   Round(CODE_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   CODE 
      dd   Round(CODE_SIZE, $FILE_ALIGNMENT) ; Size on disk 
      dd   Round(0, $FILE_ALIGNMENT) ; Real start address 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000020|0x20000000|0x40000000|0x80000000 

     DATA_SECTION_HEADER: 
      db   ".data", 0, 0, 0 
      dd   Round(DATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   $SECTION_ALIGNMENT * 2 
      dd   Round(DATA_SIZE, $FILE_ALIGNMENT) ; Size on disk 
      dd   Round(0, $FILE_ALIGNMENT) ; Real start address 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000040|0x40000000|0x80000000 

     BSS_SECTION_HEADER: 
      db   ".bss", 0, 0, 0, 0 
      dd   Round(BSS_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   $SECTION_ALIGNMENT * 3 
      dd   0 
      dd   0 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000080|0x40000000|0x80000000 


     IDATA_SECTION_HEADER: 
      db   ".idata", 0, 0 
      dd   Round(IDATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   $SECTION_ALIGNMENT * 4 
      dd   0 
      dd   Round(0, $FILE_ALIGNMENT) ; Real start address 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000040|0x40000000|0x80000000 

    HEADERS_SIZE equ $$ - DOS_HEADER 

    align 512 ; Align to 512 bytes in memory 

section .scode vstart=$SECTION_ALIGNMENT align=16 

    use32 

    CODE: 
     push -11 
     call dword [$PREFERRED_ADDRESS + F_GetStdHandle] 
     push 0 
     push 0x402000 
     push 6 
     push $PREFERRED_ADDRESS + hello 
     push eax 
     call dword [$PREFERRED_ADDRESS + F_WriteConsole] 
     push -1 
     call dword [$PREFERRED_ADDRESS + F_Sleep] 
     ret 

    CODE_SIZE equ $$ - CODE 

section .sdata vstart=$SECTION_ALIGNMENT*2 progbits align=4 
     DATA: 
      hello: db 'Hello!' 
     DATA_SIZE equ $$ - DATA 

section .sbss vstart=$SECTION_ALIGNMENT*3 align=4 
    BSS: 
     dd 5 
    BSS_SIZE equ $$ - BSS 

section .sidata vstart=$SECTION_ALIGNMENT*4 align=4 
    IDATA: 
     F_Sleep:   dd I_Sleep 
     F_WriteConsole: dd I_WriteConsole 
     F_GetStdHandle: dd I_GetStdHandle 
     dd    0 

     I_TABLE: 
      .originalfthk  dd 0 
      .timedate   dd 0 
      .forwarder  dd 0 
      .name    dd kernel32 
      .firstthunk  dd IDATA 

     I_TABLE_S equ $$ - I_TABLE 

     times 20 db 0 

     kernel32:    db 'kernel32.dll', 0 
     I_Sleep:   
      dw    0 
      db    'Sleep', 0 
      align    2 
     I_WriteConsole:  
      dw    0 
      db    'WriteConsoleA', 0 
      align    2 
     I_GetStdHandle: 
      dw    0 
      db    'GetStdHandle', 0 

    IDATA_SIZE equ $$ - IDATA 

這裏的主要問題是,可執行崩潰,因爲從指針代碼段是錯誤的。我正在討論指向.sdata的hello消息的指針以及來自.sidata部分的指向導入函數的指針。如果我將的hello變量和整個內容都複製到.scode(下面的ret)中,但它可以工作,但只要我將所有東西都複製到適當的部分,exe就會中斷。
所以,看起來地址是錯誤的計算。從這裏開始,在部分標題或其他地方可能有錯誤的值。你怎麼看?

更新:
執行下面的更改後,我現在有一個問題。只要.data節小於512字節,一切正常。一旦超過了,我會得到'奇怪的無效win32應用程序'錯誤。

所以,在這裏我有2個HTML文件導出PEInfo。這第一個包含工作文件(其中.data截面小於512個字節)的信息: Working EXE PEInfo
第二個包含腐敗EXE,當.data部分包含多於512個字節的信息:Corrupt EXE PEInfo

也許有人可以發現墜機的不同和原因。

+1

如何將您的文件加載到PE Explorer或其他類似的工具?這應該給你一個關於標題中哪些字段可能不正確的提示。 – Michael

+0

我很久沒有做Windows了!有一件事我看到......在你的dos頭文件末尾:'db PE_HEADER'。當然這應該是'dd',不是嗎?我懷疑這是否是您的問題(?),我同意部分地址可能無法正確計算。我認爲邁克爾有一個好主意! (FWIW,使用鏈接器更容易!) –

回答

4

我現在已經有機會詳細瞭解代碼並實際運行它。所以這裏是我發現的所有問題。

首先,您的尺寸計算都不起作用。你做這樣的事情:

CODE_SIZE equ $$ - CODE 

但你嘗試,並說明CODE_SIZE它的定義,所以它只是作爲計算零前行。

我的解決方案是添加結束標籤,例如, CODE_END:,無論你通常執行那些計算之一。然後在代碼的一開始,在使用這些值之前,計算每個塊的結束標籤和開始標籤之間的差值。

HEADERS_SIZE equ HEADERS_END - DOS_HEADER 
CODE_SIZE  equ CODE_END - CODE 
DATA_SIZE  equ DATA_END - DATA 
IDATA_SIZE equ IDATA_END - IDATA 
I_TABLE_SIZE equ I_TABLE_END - I_TABLE 

下一個大問題是你Round宏,它是這樣的:

%define Round(Number, Multiple) Multiple+(Number/Multiple) 

我不知道你想什麼,你那裏幹什麼,但是這更像是你所需要的:

%define Round(Number, Multiple) (Number+Multiple-1)/Multiple*Multiple 

你要確保的倍數,因此是分倍乘序列。您還需要將Multiple-1添加到原始數字中,以強制它收起來。

下一個大問題是RVA計算或缺乏。文件結構中有很多地方需要您指定偏移量作爲相對虛擬地址(RVA),這是內存中的相對偏移量。如果您只是將標籤的值取爲原來的值,則會給您磁盤上的偏移量。

對於段偏移量,您基本上需要將該偏移量除以文件對齊,然後將其乘以段對齊。另外,代碼塊將在一個段對齊偏移量上加載,所以應該相對於代碼塊計算一切,然後將一個段對齊添加到結果中。

%define RVA(BaseAddress) (BaseAddress - CODE)/$FILE_ALIGNMENT*$SECTION_ALIGNMENT+$SECTION_ALIGNMENT 

現在,它適用於部分邊界上的地址。對於其他任何事情,您需要計算它們相對於其部分基地址的內部偏移量,然後將其添加到該部分的RVA中。

%define RVA(Address,BaseAddress) RVA(BaseAddress)+(Address-BaseAddress) 

上述計算假設各個部分已經與$FILE_ALIGNMENT值一致,但實際上並不是這樣。你有一個align代碼前節是這樣的:

align 512 ; Align to 512 bytes in memory 

但是,你的每一個部分,以及一個在文件結束前需要這一點。我也建議使用$FILE_ALIGNMENT不變,否則沒有意義。

align $FILE_ALIGNMENT ; Align to 512 bytes in memory 

除此之外,你需要擺脫所有section聲明。例如,所有這些行都需要刪除。

section .header progbits vstart=0 
section .scode vstart=$SECTION_ALIGNMENT align=16 
section .sdata vstart=$SECTION_ALIGNMENT*2 progbits align=4 
section .sbss vstart=$SECTION_ALIGNMENT*3 align=4 
section .sidata vstart=$SECTION_ALIGNMENT*4 align=4 

既然你手動構建整個文件格式,他們沒有任何用處,他們阻止你跨段邊界(我們需要的幾乎無處不在的東西)標籤做偏移計算。

現在我們已經正確對齊了所有東西,並且有了兩個RVA宏,我們可以開始修復需要使用RVAs的代碼的各個部分。

首先在可選標題中,我們有代碼RVA,數據RVA和入口點。另外,雖然我們在那裏,但我相信各種尺寸值應該指定爲段對齊的倍數。

dd Round(CODE_SIZE, $SECTION_ALIGNMENT) 
dd Round(DATA_SIZE, $SECTION_ALIGNMENT) ; Initialized data size 
dd Round(BSS_SIZE, $SECTION_ALIGNMENT) ; Uninitiated data size 
dd RVA(CODE)       ; Entry point 
dd RVA(CODE)       ; Code RVA 
dd RVA(DATA)       ; Data RVA 

此外,在可選的頭,你必須四捨五入到節對齊時,我相信它應該四捨五入到文件對準頭部大小。

dd Round(HEADERS_SIZE, $FILE_ALIGNMENT) ; Headers size 

這是那些東西,其實沒有任何區別之一 - 代碼將工作無論哪種方式 - 但我仍然認爲這是錯誤的,應該予以糾正。

同樣,正如我在第一次回答指出,數據目錄表的大小應始終設置爲16,即使你不使用全部16個條目。它似乎工作,如果你不這樣做,但我會建議你做正確的。

dd 16     ; Data directory entries 
dd 0     ; Export table pointer 
dd 0     ; Export table size 
dd RVA(I_TABLE,IDATA) ; Import table pointer 
dd I_TABLE_SIZE  ; Size of import table 
times 14 dq 0   ; Space the other 14 entries 

此外,請注意I_TABLE偏移已更新爲使用相對於IDATA部分的RVA。

接下來在您的章節表中,您的所有偏移都是錯誤的。例如,該代碼段頭標開始應該是這樣的:

db ".code", 0, 0, 0 
dd Round(CODE_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(CODE)       ; Start address in memory 
dd Round(CODE_SIZE, $FILE_ALIGNMENT) ; Size on disk 
dd CODE         ; Start address on disk 

類似地,對於數據部分:

db ".data", 0, 0, 0 
dd Round(DATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(DATA)       ; Start address in memory 
dd Round(DATA_SIZE, $FILE_ALIGNMENT) ; Size on disk 
dd DATA         ; Start address on disk 

而idata段:

db ".idata", 0, 0 
dd Round(IDATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(IDATA)       ; Start address in memory 
dd Round(IDATA_SIZE, $FILE_ALIGNMENT) ; Size on disk 
dd IDATA         ; Start address on disk 

bss段是雖然略有不同。 bss部分的重點在於它不佔用磁盤空間,但它佔用了內存空間。這意味着您實際上不能爲您的bss數據包含任何數據定義。所以這個代碼必須去:

BSS: 
    dd 5 

但這意味着磁盤上的部分將不匹配內存中的部分。爲了簡化RVA計算,我建議的解決方法是將bss部分作爲文件中的最後一項。當它的大小從磁盤上的0擴展到內存中不會影響任何其他偏移量的內容時。

所以我會在文件的結尾處呼籲IMAGE_END:添加標籤,然後定義BSS部分是這樣的:

db ".bss", 0, 0, 0, 0 
dd Round(BSS_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(IMAGE_END)      ; Start address in memory 
dd 0         ; Size on disk 
dd 0         ; Start address on disk 

注意,這部分一定要來,因爲在部分表中的idata段後地址需要按升序排列。

你可能想知道其中BSS_SIZE值來自如果你沒有在代碼中的BSS部分了。我擔心你將不得不手動定義該值。你也將不得不手動定義該部分中任何變量的偏移量的常量。正如我之前所說的,你不能使用數據定義,因爲我們不希望它佔用磁盤上的任何空間。

接下來我們進入導入表。你使用的佈局有點奇怪,但這似乎不成問題,所以我會保持原樣。你確實需要更新所有的地址才能使用RVAs。

首先IAT:

F_Sleep:   dd RVA(I_Sleep,IDATA) 
F_WriteConsole: dd RVA(I_WriteConsole,IDATA) 
F_GetStdHandle: dd RVA(I_GetStdHandle,IDATA) 

然後導入描述:

.originalfthk dd 0 
.timedate  dd 0 
.forwarder  dd 0 
.name   dd RVA(kernel32,IDATA) 
.firstthunk  dd RVA(IDATA,IDATA) 

我還要提到的是你這個描述後,立即設置I_TABLE_S變量,如果你還記得,我說:你應該用結束標籤替換這些尺寸計算。但是,在這種情況下,描述符表的大小應該也包含最終的零條目。因此,放置該結束標籤的正確位置不在此處,而是在填充後。

times 20 db 0  
I_TABLE_END: 

這是另一個我認爲沒有多大區別的東西,但我仍然建議修復。

另外,當你從一個DLL導入時,這種佈局是很好的,但當你需要更多的時候,你將需要更多的描述符和更多的IAT部分。因此,我建議在每個IAT之前添加一個標籤,例如在這種情況下就像kernel32_iat。然後你初始化你的第一個thunk。

.firstthunk  dd RVA(kernel32_iat,IDATA) 

最後,我想處理$IMAGE_SIZE的計算。您正在使用的計算假定每個部分的固定大小。但由於我們在文件末尾有一個IMAGE_END標籤和一個RVA宏,我們可以很容易地計算出確切的圖像尺寸爲RVA(IMAGE_END)

但是,這並沒有考慮到bss部分,一旦將圖像加載到內存中,圖像就會變大。因此,對於圖像尺寸的正確定義應該是:

$IMAGE_SIZE equ RVA(IMAGE_END) + Round(BSS_SIZE,$SECTION_ALIGNMENT) 

注意,這附近的文件的開頭被定義 - 它在任何地方使用過,但在RVA宏和BSS_SIZE後已經確定。

+0

感謝您花費時間使它變得如此詳細。我終於知道如何創建一個PE文件「手動」 – ali

+0

現在,PEExplorer仍然顯示我這些:05.07.2013 22:10:22:警告!部分<.data>(1)超出了部分<.bss>(2)的原始文件偏移量。 05.07.2013 22:10:22:警告!部分<.bss>(2)超出了部分<.idata>(3)的虛擬地址。 – ali

+0

您是否在每一節之間添加了「對齊$ FILE_ALIGNMENT」?您是否在節表中的idata節後移動了bss節? –