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