2013-07-03 18 views
1

我正在尝试完成这个仅用程序集制作的PE文件,它应该在控制台中显示一条消息。我想以这种方式组织它,我可以稍后添加更多的东西(知道在哪里添加代码,数据,导入的函数)。
我创建4个部分现在,为代码数据外行数据导入的元素。我在这个阶段的主要问题是:为程序集中的PE文件创建和使用部分(NASM)

  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变量和整个内容都复制到.scode(下面的ret)中,但它可以工作,但只要我将所有东西都复制到适当的部分,exe就会中断。
所以,看起来地址是错误的计算。从这里开始,在部分标题或其他地方可能有错误的值。你怎么看?

更新:
执行下面的更改后,我现在有一个问题。只要.data节小于512字节,一切正常。一旦超过了,我会得到'奇怪的无效win32应用程序'错误。

所以,在这里我有2个HTML文件导出PEInfo。这第一个包含工作文件(其中.data截面小于512个字节)的信息: Working EXE PEInfo
第二个包含腐败EXE,当.data部分包含多于512个字节的信息:Corrupt EXE PEInfo

也许有人可以发现坠机的不同和原因。

+1

如何将您的文件加载到PE Explorer或其他类似的工具?这应该给你一个关于标题中哪些字段可能不正确的提示。 – Michael

+0

我很久没有做Windows了!有一件事我看到......在你的dos头文件末尾:'db PE_HEADER'。当然这应该是'dd',不是吗?我怀疑这是否是您的问题(?),我同意部分地址可能无法正确计算。我认为迈克尔有一个好主意! (FWIW,使用链接器更容易!) –

回答

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 

你要确保的倍数,因此是分倍乘序列。您还需要将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 

注意,这部分一定要来,因为在部分表中的idata段后地址需要按升序排列。

你可能想知道其中BSS_SIZE值来自如果你没有在代码中的BSS部分了。我担心你将不得不手动定义该值。你也将不得不手动定义该部分中任何变量的偏移量的常量。正如我之前所说的,你不能使用数据定义,因为我们不希望它占用磁盘上的任何空间。

接下来我们进入导入表。你使用的布局有点奇怪,但这似乎不成问题,所以我会保持原样。你确实需要更新所有的地址才能使用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  
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后已经确定。

+0

感谢您花费时间使它变得如此详细。我终于知道如何创建一个PE文件“手动” – ali

+0

现在,PEExplorer仍然显示我这些:05.07.2013 22:10:22:警告!部分<.data>(1)超出了部分<.bss>(2)的原始文件偏移量。 05.07.2013 22:10:22:警告!部分<.bss>(2)超出了部分<.idata>(3)的虚拟地址。 – ali

+0

您是否在每一节之间添加了“对齐$ FILE_ALIGNMENT”?您是否在节表中的idata节后移动了bss节? –