assembly - 为程序集中的PE文件创建和使用节(NASM)

标签 assembly crash nasm portable-executable sections

我正在尝试完成仅用汇编制作的PE文件,该文件应该在控制台中显示一条消息。我希望以这种方式进行组织,以便以后可以轻松添加更多内容(知道在哪里添加代码,数据,导入的函数)。
我现在创建了4个部分,分别用于代码数据未初始化的数据导入的元素。我现阶段的主要问题是:

  • 部分标题中的某些值使可执行文件无效(无效的win32)
  • 指向数据部分中元素的指针是错误的
  • 某些涉及首选绝对地址,节对齐和文件对齐的计算可能是错误的

  • 首先,我将显示以下所有代码。为了节省时间并使其更易于阅读,将不会添加一些真正无关紧要的内容
    这是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就会中断。
    因此,似乎地址计算错误。从这里开始,section header 或其他地方可能有错误的值。你怎么看?

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

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

    也许有人可以发现崩溃的差异和原因。

    最佳答案

    现在,我有机会详细查看代码并实际运行它。所以这是我发现的所有问题。

    首先,您的尺寸计算似乎都不起作用。您可以执行以下操作:

    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是倍数的倍数,因此要除以乘数序列。您还需要在原始Number上添加Multiple-1以强制将其四舍五入。

    下一个大问题是RVA计算,或者缺少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宏,我们可以开始修复需要使用RVA的代码的各个部分。

    首先在可选 header 中,我们具有代码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
    

    同样在可选 header 中,当我认为 header 大小应四舍五入到文件对齐方式时, header 大小会四舍五入到部分对齐方式。
    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_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部分,一旦将其加载到内存中,bss部分就会使图像变大。因此,图像尺寸的正确定义应为:
    $IMAGE_SIZE equ RVA(IMAGE_END) + Round(BSS_SIZE,$SECTION_ALIGNMENT)
    

    请注意,应在文件开头附近定义它-在任何地方使用它之前,但要定义RVA宏和BSS_SIZE之后。

    关于assembly - 为程序集中的PE文件创建和使用节(NASM),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17456372/

    相关文章:

    assembly - “PUSH”指令的操作可以用其他指令执行吗?

    c - 如何将 Intel Assembly C 转换为 AT&T C++

    actionscript-3 - 如何检测 Adob​​e Flash 崩溃原因?

    assembly - 为什么在将 Int 0x21 与服务 0x2c 一起使用时,可编程间隔计时器不显示正确的时间值

    c++ - 无法在 VC++ 中从 NASM 调用 c 函数,除了 main,出现链接错误

    linux - x86_64 程序集 execve *char[] 系统调用

    c - 如何使用printf调试linux内核的汇编代码

    ios - 动画 UITableView 自动布局顶部约束导致崩溃,任何线索?

    crash - 虚幻引擎4出现0xc000007b错误

    linux - float 显示十六进制 x86-64 nasm linux