1

我正在尝试完成这个仅用程序集制作的 PE 文件,它应该在控制台中显示一条消息。我想以这样的方式组织它,以便以后可以轻松地添加更多内容(知道在哪里添加代码、数据、导入的函数)。
我现在创建了 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

这里的主要问题是可执行文件崩溃,因为来自代码部分的指针是错误的。我说的是.sdata从该部分指向 hello 消息的指针和指向导入函数的指针.sidata。如果我将 hello 变量和整个内容都复制.sidata.scode(bellow ret) 中,它可以工作,但是一旦我将每个东西复制到应该是它的正确部分,exe 就会中断。
因此,看起来地址计算错误。从这里开始,节标题或其他地方可能会有错误的值。你怎么看?

更新:
在实施以下更改后,我现在遇到了一个问题。只要该.data部分小于 512 字节,一切正常。一旦超过了,我就会得到一个“奇怪的无效 win32 应用程序”错误。

所以,这里我有 2 个由PEInfo导出的 HTML 文件。第一个包含工作文件的信息(.data节小于 512 字节): Working EXE PEInfo
第二个包含损坏的 EXE 的信息,当.data节包含超过 512 字节时:Corrupt EXE PEInfo

也许有人可以发现崩溃的区别和原因。

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

您要确保Number是 Multiple 的倍数,因此是除-乘序列。您还需要添加Multiple-1到原始 Number 以强制它向上舍入。

下一个大问题是 RVA 计算,或者说缺乏。文件结构中有很多地方需要您将偏移量指定为相对虚拟地址 (RVA),即内存中的相对偏移量。当您按原样获取标签的值时,这就是为您提供disk 上的偏移量。

对于节偏移量,您基本上需要将该偏移量除以文件对齐方式,然后将其乘以节对齐方式。此外,代码块将以一个节对齐偏移量加载,因此应相对于代码块计算所有内容,然后将一个节对齐添加到结果中。

%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 宏,我们可以开始修复需要使用 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

这是实际上没有任何区别的事情之一 - 代码可以以任何方式工作 - 但我仍然认为这是错误的,应该更正。

同样,正如我在第一个答案中指出的那样,即使您不使用所有 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

请注意,此部分必须位于 section 表中的 idata 部分之后,因为地址需要按升序排列。

BSS_SIZE如果代码中不再有 bss 部分,您可能想知道值从何而来。我担心您将不得不手动定义该值。您还必须手动为该部分中任何变量的偏移量定义常量。正如我之前所说,您不能使用数据定义,因为我们不希望它占用磁盘上的任何空间。

接下来我们进入导入表。您为此使用的布局有些奇怪,但这似乎不是问题,所以我将保持原样。不过,您确实需要更新所有地址才能使用 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:

这是我认为没有太大区别的另一件事,但我仍然建议修复。

此外,当您从一个 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已定义之后。

于 2013-07-04T05:03:02.293 回答