在汇编(NASM)中创建和使用PE文件的部分

时间:2013-07-03 19:17:52

标签: assembly crash nasm portable-executable sections

我正在尝试完成仅使用程序集制作的PE文件,它应该在控制台中显示一条消息。我想以这样的方式组织它,以后我可以轻松添加更多内容(知道在哪里添加代码,数据,导入的函数)。
我现在创建了4个部分,包括代码数据未启动的数据导入的元素。我现阶段的主要问题是:

  1. 节标题中的某些值使可执行文件无效(无效win32)
  2. 指向数据部分中元素的指针错误
  3. 某些涉及首选绝对地址,部分对齐和文件对齐的计算可能不正确
  4. 首先,我将在下面显示我的所有代码。为了节省时间并使其更容易阅读,不会添加一些真正无关紧要的内容 这是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(下面是ret)它可以工作,但是只要我将每个东西复制到它应该是正确的部分中,exe打破了。
    所以,看起来地址错误估计。从这里开始,部分标题或其他地方可能存在错误的值。你觉得怎么样?

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

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

    也许有人能发现事故的不同和原因。

1 个答案:

答案 0 :(得分: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

您希望确保 Number 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

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

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

接下来我们进入导入表。你为此使用的布局有些奇怪,但这似乎不是一个问题,所以我会按原样离开。您确实需要更新所有地址以使用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填充之后。

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已定义。