1

コンソールにメッセージが表示されるはずのアセンブリのみで作成されたこの PE ファイルを完成させようとしています。後で簡単に追加できるように整理したいと思います(コード、データ、インポートされた関数を追加する場所を知っています)。
今のところ、codedatauninitiated data、およびimport elementsの 4 つのセクションを作成しました。この段階での私の主な問題は次のとおりです。

  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

ここでの主な問題は、コード セクションからのポインタが間違っているために実行可能ファイルがクラッシュすることです。からの hello メッセージへのポインタと、セクション.sdataからのインポートされた関数へのポインタについて話しているのです。.sidatahello 変数とコンテンツ全体の両方.sidata.scode(ベローret) にコピーすると機能しますが、それぞれを適切なセクションにコピーするとすぐに、exe が壊れます。
そのため、住所の計算が間違っているようです。ここから始めて、セクションヘッダーまたは他の場所に間違った値がある可能性があります。どう思いますか?

更新:
以下の変更を実装した後、現在問題があります。.dataセクションが 512 バイト未満である限り、すべて正常に機能します。それを超えると、「奇妙な無効なwin32アプリケーション」エラーが発生します。

したがって、ここではPEInfoによってエクスポートされた 2 つの HTML ファイルがあります。この最初のものには、作業ファイルの情報が含まれています (.dataセクションが 512 バイト未満の場合): Working EXE PEInfoセクションに 512 バイト以上が含まれている
場合、2 番目のファイルには破損した EXE の情報が含まれます: Corrupt EXE PEInfo.data

誰かがクラッシュの違いと理由を見つけることができるかもしれません。

4

1 に答える 1

5

コードを詳細に見て、実際に実行する機会がありました。だからここに私が見つけたすべての問題があります。

まず、サイズの計算がどれもうまくいかないようでした。次のようにします。

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

NumberMultipleの倍数であることを確認する必要があるため、除算と乗算のシーケンスになります。Multiple-1元の Number に加算して、強制的に切り上げる必要もあります。

次の大きな問題は、RVA の計算、またはその欠如です。ファイル構造には、メモリ内の相対オフセットである相対仮想アドレス (RVA) としてオフセットを指定する必要がある場所がたくさんあります。ラベルの値をそのまま取得すると、disk上のオフセットが得られます。

セクション オフセットの場合、基本的に、そのオフセットをファイル アラインメントで割り、セクション アラインメントで乗算する必要があります。また、コード ブロックは 1 つのセクション アラインメント オフセットでロードされるため、コード ブロックを基準にしてすべてを計算し、結果に 1 つのセクション アラインメントを追加する必要があります。

%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

ファイル形式全体を手動で構築しているため、それらは何の役にも立たず、セクションの境界を横切るラベルを使用してオフセット計算を行うことができなくなります (ほとんどどこでも必要なものです)。

すべてが正しく調整され、2 つの RVA マクロが作成されたので、RVA を使用する必要があるコードのさまざまな部分の修正を開始できます。

最初のオプション ヘッダーには、コード 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

これは、実際には違いがないものの 1 つです。コードはどちらの方法でも動作しますが、それでも間違っているので修正する必要があると思います。

同様に、最初の回答で指摘したように、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

また、IDATA セクションに関連する RVA を使用するように I_TABLE オフセットが更新されていることにも注意してください。

次のセクション テーブルでは、すべてのオフセットが間違っています。たとえば、コード セクション ヘッダーの先頭は次のようになります。

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 セクションがなくなった場合、値がどこから来るのか不思議に思うかもしれBSS_SIZEません。その値を手動で定義する必要があると思います。また、そのセクション内の変数のオフセットの定数を手動で定義する必要があります。前に述べたように、ディスク上のスペースを占有したくないため、データ定義を使用することはできません。

次に、インポート テーブルに移動します。これに使用しているレイアウトは少し奇妙ですが、それは問題ではないようですので、そのままにしておきます。ただし、RVA を使用するには、すべてのアドレスを更新する必要があります。

最初に 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パディングの後です。

times 20 db 0    
I_TABLE_END:

これは、大きな違いはないと思うものの 1 つですが、それでも修正することをお勧めします。

また、このレイアウトは 1 つの DLL からインポートする場合には問題ありませんが、それ以上のものが必要な場合は、より多くの記述子と IAT セクションが必要になります。kernel32_iatしたがって、この場合のように、各 IAT の前にラベルを追加することをお勧めします。次に、最初のサンクを次のように初期化します。

.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が定義された後以外の場所で使用される前です。

于 2013-07-04T05:03:02.293 に答える