从 t.obj 看 windows 的 COFF 文件格式

from: http://www.mouseos.com/assembly/07.html


7. 从 t.obj 看 windows 的 COFF 文件格式

t.obj 是我们例子中的 object 文件,COFF 格式的全称为:Common Object File Format

我们的 t.obj 是这样来的:

nasm -f win64 t.asm -o t.obj

将 t.asm 编译成 win64 的 COFF 文件格式。

7.1 t.obj 的结构图

开始处是 t.obj 的 COFF 文件头,它是前面提到过的 IMAGE_FILE_HEADER 结构,接下来的是 section 表,再接下来的是 t.obj 的数据

7.2 COFF 文件头

从 0x00 ~ 0x13 共 20 bytes 是 COFF 文件头,实际上它就是 IMAGE_FILE_HEADER 结构

00000000  64 86                               // Machine
00000002  02 00                               // NumberOfSections
00000004  FC AC DD 4B                         // TimeDateStamp
00000008  E7 00 00 00                         // PointerTosymbolTable
0000000C  0C 00 00 00                         // NumberOfSymbols
00000010  00 00                               // SizeOfOptionalHeader
00000002  00 00                               // Characteristics

t.obj 是一个 64 位的 COFF 文件,有 2 个 section,并且使用了 symbol table,symbol table 位置在 0xE7 处,这是个 offset 值,基于 t.obj 的开始。symbol 数是 0x0C

 t.exe 映像文件不同的是,object 文件使用了 symbol table,在 WinNT.h 定义了这个 symbols table 结构:

//
// Symbol format.
//

typedef struct _IMAGE_SYMBOL {
    union {
        BYTE    ShortName[8];
        struct {
            DWORD   Short;     // if 0, use LongName
            DWORD   Long;      // offset into string table
        } Name;
        DWORD   LongName[2];    // PBYTE [2]
    } N;
    DWORD   Value;
    SHORT   SectionNumber;
    WORD    Type;
    BYTE    StorageClass;
    BYTE    NumberOfAuxSymbols;
} IMAGE_SYMBOL;
typedef IMAGE_SYMBOL UNALIGNED *PIMAGE_SYMBOL;

#define IMAGE_SIZEOF_SYMBOL                  18

这个 symbol table 的大小为 18 个字节,共有 0x0C 个 symbol table,因此整个 symbol table 是 216 个字节,即:从 0x000000E7 ~ 0x000001BE 是整个 symbol table 所在。

7.3 t.obj 的 section table

节表的数据结构前面已经介绍过,这里再重新看看 IMAGE_SECTION_HEADER 结构:

//
// Section header format.
//

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

#define IMAGE_SIZEOF_SECTION_HEADER          40

由前面的 IMAGE_FILE_HEADER 结构中的 NumberOfSections = 2 得知,t.obj 有 2 个 section table,t.obj 的 section table 位置在 0x14 ~ 0x63 处,共 80 bytes,下面看看 t.obj 的 section table 的内容:

00000014  2E 64 61 74 61 00 00 00                     // name = ".data"
0000001C  00 00 00 00                                 // VirtualSize
00000020  00 00 00 00                                 // VirtualAddress
00000024  29 00 00 00                                 // SizeOfRawData
00000028  64 00 00 00                                 // PointerToRawData
0000002C  8D 00 00 00                                 // PointerToRelocation
00000030  00 00 00 00                                 // PointerToLinenumber
00000034  00 00                                       // NumberOfRelocation
00000036  00 00                                       // NumberOfLinenumbers
00000038  40 00 30 C0                                 // Characteristics

0000003C  2E 74 65 78 74 00 00 00                     // name = ".text"
00000044  00 00 00 00                                 // VirtualSize
00000048  00 00 00 00                                 // VirtualAddress
0000004C  3C 00 00 00                                 // SizeOfRawData
00000050  8D 00 00 00                                 // PointerToRawData
00000054  C9 00 00 00                                 // PointerToRelocation
00000058  00 00 00 00                                 // PointerToLinenumber
0000005C  03 00                                       // NumberOfRelocation
0000005E  00 00                                       // NubmerOfLinenumbers
00000060  20 00 50 60                                 // Characteristics

上面的红色标注部分是 2 个 section 的名称,第 1 个 section 是 .data 节,第 2 个是 .text 节。

注意的是:这 2 个 section 的 VirtualSize 和 VirtualAddress 都是 0,由于 object 文件是未经过 link,因此,VirtualSize 和 VirtualAddress 都是 0 值。

.data 节的 PointerToRawData 是 64,SizeOfRawData 是 29,说明 .data 节在 t.obj 文件中的位置是 0x64 ~ 0x8C 共 41 bytes

.text 节的 PointerToRawData 是 8D, SizeOfRawData 是 3C,说明 .text 节在 t.obj 文件中的位置是 0x8D ~ 0xC8 共 60 bytes。而 .text 使用了 Relocation 表,这个 Relocation 位置在 0xC9 处,NumberOfRelocation = 03 表明,.text 使用 3 个 Relocation 表

7.4 t.obj 链接后的重定位

这里我们来看一看 t.obj 在链接过程中是如何进行重定位的?

下面是 t.obj  .text 在未重定位之前的代码(摘自 t.obj 的 .text 节):

0000008D  48 81 EC 28 00 00 00             sub rsp, 0x28
00000094  48 B9 00 00 00 00 00 00 00 00    mov rcx, 0
0000009E  48 BA 1C 00 00 00 00 00 00 00    mov rdx, 0x1c  (1)
000000A8  49 B8 00 00 00 00 00 00 00 00    mov r8, 0  (2)
000000B3  49 B9 01 00 00 00 00 00 00 00    mov r9, 0x1
000000BC  E8 00 00 00 00                   call .+0x00  (3)
000000C1  48 81 C4 28 00 00 00             add rsp, 0x28
000000C8  C3                               ret

上面的红色标注部分显示了:有 3 个地方需要进行重定位的。

(1)我们的源代码是:mov rdx, text , nasm 将它编译为 48 BA 1C 00 00 00 00 00 00 00 ,那么在 link 时,这个地址值 0x1c 是一个 offset 值,需要被重新替换为真实的 text 常量的地址值。

(2)我们的源代码是:mov r8, caption ,nasm 将它编译为 49 B8 00 00 00 00 00 00 00 00,同样地址值 0x00 是不正确的,link 时需替换为真实的 caption 常量的地址值。

(3)源代码是:call MessageBoxA ,nasm 将它编译为 e8 00 00 00 00 ,同样我们需要的是 MessageBoxA 的地址。

于是在 t.obj 中就需要有 3 个 Relocation 表,也就是上面所说的 .text 节使用了 3 个 relocation 表。并指明了 relocation 表在 0xC9 处,从 0xC9 ~ 0xE6 共 30 bytes。

7.4.1 relocation 表结构

在 WinNT.h 定义了 IMAGE_RELOCATION 结构用来描述使用 relocation 表,如下:

//
// Relocation format.
//

typedef struct _IMAGE_RELOCATION {
    union {
        DWORD   VirtualAddress;
        DWORD   RelocCount;             // Set to the real count when IMAGE_SCN_LNK_NRELOC_OVFL is set
    } DUMMYUNIONNAME;
    DWORD   SymbolTableIndex;
    WORD    Type;
} IMAGE_RELOCATION;
typedef IMAGE_RELOCATION UNALIGNED *PIMAGE_RELOCATION;

这个结构共 10 bytes,这些域的含义是:

域 
size
描述
VritualAddress
DWORD
指出 section 中的哪个 RVA 地址需要进行 relocate 操作
SymbolTableIndex
DWORD
用来在 Symbol table 中进行索引
Type
WORD
表明 VirtualAddress 是个怎样类型的 address

在 WinNT.h 定义了 VirtualAddress 的 type 值,下面是 x64 平台下的定义:

//
// x64 relocations
//
#define IMAGE_REL_AMD64_ABSOLUTE        0x0000  // Reference is absolute, no relocation is necessary
#define IMAGE_REL_AMD64_ADDR64          0x0001  // 64-bit address (VA).
#define IMAGE_REL_AMD64_ADDR32          0x0002  // 32-bit address (VA).
#define IMAGE_REL_AMD64_ADDR32NB        0x0003  // 32-bit address w/o image base (RVA).
#define IMAGE_REL_AMD64_REL32           0x0004  // 32-bit relative address from byte following reloc
#define IMAGE_REL_AMD64_REL32_1         0x0005  // 32-bit relative address from byte distance 1 from reloc
#define IMAGE_REL_AMD64_REL32_2         0x0006  // 32-bit relative address from byte distance 2 from reloc
#define IMAGE_REL_AMD64_REL32_3         0x0007  // 32-bit relative address from byte distance 3 from reloc
#define IMAGE_REL_AMD64_REL32_4         0x0008  // 32-bit relative address from byte distance 4 from reloc
#define IMAGE_REL_AMD64_REL32_5         0x0009  // 32-bit relative address from byte distance 5 from reloc
#define IMAGE_REL_AMD64_SECTION         0x000A  // Section index
#define IMAGE_REL_AMD64_SECREL          0x000B  // 32 bit offset from base of section containing target
#define IMAGE_REL_AMD64_SECREL7         0x000C  // 7 bit unsigned offset from base of section containing target
#define IMAGE_REL_AMD64_TOKEN           0x000D  // 32 bit metadata token
#define IMAGE_REL_AMD64_SREL32          0x000E  // 32 bit signed span-dependent value emitted into object
#define IMAGE_REL_AMD64_PAIR            0x000F
#define IMAGE_REL_AMD64_SSPAN32         0x0010  // 32 bit signed span-dependent value applied at link time

其中,常用的是 ADDR64 类型和 REL32 类型。

IMAGE_REL_AMD64_ADDR64  类型表明:relocation 表中的 VirtualAddress 是个 64 位的 Virtual Address,也就是说:在 link 后,VirtualAddress 将会变成 64 位的地址值,它是直接 (绝对值 ) virtual address,而不是  RVA  值。 
IMAGE_REL_AMD64_ADDR32  类型表明:relocation 表中的 VirtualAddress 是个 相对值 ,是基于某个 base 值的  32 位的 signed offset  值。

那么下面看看 .text 节所使用的 relocation 表是怎样的?

000000C9  13 00 00 00              // VirtaulAddress
000000CD  02 00 00 00              // SymbolTableIndex
000000D1  01 00                    // Type ( 64bit VA)

000000D3  1D 00 00 00              // VirtualAddress
000000D7  02 00 00 00              // SymbolTalbeIndex
000000DB  01 00                    // Type (64bit VA)

000000DD  30 00 00 00              // VirtaulAddress
000000E1  07 00 00 00              // SymbolTableIndex
000000E5  04 00                    // Type (32bit relative)

● 第 1 个 relocation 表,它的 VirtualAddress 是 0x13,表明:.text 节中的 0x13 处的地址值需要进行 relocate 操作,它是个 64 位的 VA 值,重定位操作是根据SymbolTable[2] 提供的信息进行的,Symbol Table 是 0 索引开始。

● 第 2 个 relocation 表,意义完全和第 1 个 relocation 表一样。

● 第 3 个 relocation 表和上面两个表所不同的是,VirtualAddress 是个 REL32 类型的值它是个 32 位的 offset 值。

 

7.4.2 Symbol Table 结构

在 WinNT.h 里对 symbol table 的定义为:

//
// Symbol format.
//

typedef struct _IMAGE_SYMBOL {
    union {
        BYTE    ShortName[8];
        struct {
            DWORD   Short;     // if 0, use LongName
            DWORD   Long;      // offset into string table
        } Name;
        DWORD   LongName[2];    // PBYTE [2]
    } N;
    DWORD   Value;
    SHORT   SectionNumber;
    WORD    Type;
    BYTE    StorageClass;
    BYTE    NumberOfAuxSymbols;
} IMAGE_SYMBOL;
typedef IMAGE_SYMBOL UNALIGNED *PIMAGE_SYMBOL;

#define IMAGE_SIZEOF_SYMBOL                  18

这个结构域的含义是:

size
描述
Name
8 bytes
Symbol 的名称
Value
DWORD
这个 value 辅助性质,取决于 SectionNumber 和 StorageClass
SectionNumber
SHORT
指向哪个 section,以 section table 中 1-based 为索引
Type
WORD
symbol table 的类型
StorageClass
BYTE
存储类别,是定义如何进行重定位的关键
NumberOfAuxSymbols
BYTE
附加 symbol table 个数

NumberOfAuxSymbols 指出附加的 symbol table 个数,附加表紧跟着 symbol table 后面。下面看一看 t.obj 所有的 symbol table


000000E7  2E 66 69 6C 65 00 00 00                       // Name = ".file"
000000EF  00 00 00 00                                   // Value
000000F3  FE FF                                         // SectionNumber
000000F5  00 00                                         // Type
000000F7  67                                            // StorageClass
000000F8  01                                            //NumberOfAuxSymbols


000000F9  74 2E 61 73 6D 00 00 00                       // *** 辅助表 ****   name = "t.asm"
00000101  00 00 00 00                                  
00000105  00 00                                        
00000107  00 00                                        
00000109  00                                           
0000010A  00                                           

0000010B  2E 64 61 74 61 00 00 00                        // Name = ".data"
00000113  00 00 00 00                                    // Value
00000117  01 00                                          // SectionNumber
00000119  00 00                                          // Type
0000011B  03                                             // StorageClass
0000011C  01                                             // NumberOfAuxSymbols

0000011D  29 00 00 00 00 00 00 00                        // **** 辅助表 ****
00000125  00 00 00 00                                    
00000129  00 00                                          
0000012B  00 00                                          
0000012D  00                                             
0000012E  00                                            

0000012F  2E 74 65 78 74 00 00 00                        // Name = ".text"
00000137  00 00 00 00                                    // Value
0000013B  02 00                                          // SectionNumber
0000013D  00 00                                          // Type
0000013F  03                                             // StoreageClass
00000140  01                                             // NumberOfAuxSymbols


00000141  3C 00 00 00 03 00 00 00                       // *** 辅助表 ***
00000149  00 00 00 00 
0000014D  00 00 
0000014F  00 00 
00000151  00 
00000152  00


00000153  2E 61 62 73 6F 6C 75 74                      // Name = ".absolut"
0000015B  00 00 00 00 
0000015F  FF FF
00000161  00 00 
00000163  03 
00000164  00

00000165  00 00 00 00 04 00 00 00 
0000016D  00 00 00 00
00000171  00 00 
00000173  00 00 
00000174  02 
00000175  00

00000176  00 00 00 00 10 00 00 00 
0000017F  00 00 00 00
00000183  00 00 
00000185  00 00 
00000187  02 
00000188  00

00000189  63 61 70 74 69 6F 6E 00                   // Name = "caption"
00000191  00 00 00 00                               // Value            
00000195  01 00                                     // SectionNumber
00000197  00 00                                     // Type
00000199  03                                        // StorageClass
0000019A  00                                        // NumberOfAuxSymbols

0000019B  74 65 78 74 00 00 00 00                   // Name = "text"
000001A3  1C 00 00 00                               // Value
000001A7  01 00                                     // SectionNumber
000001A9  00 00                                     // Type
000001AB  03                                        // StoreageClass
000001AC  00                                        // NumberOfAuxSymbols

000001AD  6D 61 69 6E 00 00 00 00                  // Name = "main"
000001B5  00 00 00 00                              // Value
000001B9  02 00                                    // SectionNumber
000001BB  00 00                                    // Type
000001BD  02                                       // StoreageClass
000001BE  00                                       // NumberOfAuxSymbols

注意到,第 0 个 symbol table 的 name 是 ".file",它下面接着一个辅助表,结合这个辅助表得出 .file = "t.asm"

第 2 个 symbol table 是 ".data",它下面也接着一个辅助表,指出 .data 的 size 是 0x29

7.4.3 重定位过程

最后,我们来看一看具体的重定位操作,以 mov rdx, text 指令中 text 重定位为例。

    源码                             nasm 编译后

mov rdx, text   =====>    0000009E  48 BA 1C 00 00 00 00 00 00 00

link 需要将 0x00000000_0000001c 重新分配正确的地址值,怎么做?

1. link 首先看有哪个 relocation 表需要重定位的。所以先做 relocation 表1 的重定位。

000000C9  13 00 00 00              // VirtaulAddress
000000CD  02 00 00 00              // SymbolTableIndex
000000D1  01 00                    // Type ( 64bit RVA)

那么,位于 RVA 为 0x13 的地方的地址需要进行重定位,这个 0x13 的地方到底是哪里?答案是:

.text + 0x13 = 0x8d + 0x13 = 0xA0

需要重定位的地址在 0xA0 处,.text 节的 base 为 0x8D,这个 0x8D 的值由 .text 节结构的 PointerToRawData 域指出。

而 0xA0 处就是存放 1c 00 00 00 00 00 00 00 的地方。由于 relocation 表指向 SymbolTable[2] 并且指明 VirtualAddress 是个 64 位的 address 值。

2. 查找 SymbolTable[2] 表格

0000010B  2E 64 61 74 61 00 00 00                        // Name = ".data"
00000113  00 00 00 00                                    // Value
00000117  01 00                                          // SectionNumber
00000119  00 00                                          // Type
0000011B  03                                             // StorageClass
0000011C  01                                             // NumberOfAuxSymbols

SymbolTable[2] 指向 .data 节,最重要的是它的 StoreageClass 是 3,下面是 microsoft 对于 StorageClass 作用的解释:

IMAGE_SYM_CLASS_STATIC

3

The offset of the symbol within the section. If the Value field is zero, then the symbol represents a section name.

也就是说,VirtualAddess 是基于 section 的 offset 的值。即:0x1c 是基于 .data 的 offset 值

3. 生成最终的 virtual address 值

当 link 生成 PE 格式的 t.exe 时。从上一节得出:.data 被加载 virtual address 为 0x00000001_40003000

因此:指令 mov rdx, text 的值最终是

.data + 0x1C = 0x0000000140003000 + 0x1c = 0x000000014000301C

下面是摘录在 t.exe 映像的 .text 节的数据

0000000140001011:   48 BA 1C 30 00 40 01 00 00 00     mov rdx, 0x000000014000301C

指令 mov rdx, text 的 text 地址最终被重定位为 0x000000014000301C。

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值