如何解码 ELF 中的 table 部分?
How to decode the section table in an ELF?
我正在分析这个小 ELF 文件:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 78 00 40 00 00 00 00 00 |..>.....x.@.....|
00000020 40 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....@.8...@.....|
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 7e 00 00 00 00 00 00 00 7e 00 00 00 00 00 00 00 |~.......~.......|
00000070 00 00 20 00 00 00 00 00 31 c0 ff c0 cd 80 00 2e |.. .....1.......|
00000080 73 68 73 74 72 74 61 62 00 2e 74 65 78 74 00 00 |shstrtab..text..|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000d0 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00 |................|
000000e0 06 00 00 00 00 00 00 00 78 00 40 00 00 00 00 00 |........x.@.....|
000000f0 78 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 |x...............|
00000100 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00 |................|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 7e 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |~...............|
00000140 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 |........|
00000158
我找到了有关 ELF header 和程序 header 的文档并对它们进行了解码,但我在解码之后的内容(从 31 c0 ff c0 cd 80 00 2e
开始)时遇到了问题。从 "shstrtab" 文本来看,我正在查看 table 部分,但 31 c0 ff c0 cd 80 00 2e
是什么意思?这部分记录在哪里?
OK,根据header前16个字节的信息判断:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
E L F | | '--- Pudding :) ---'
| '--- Little-endian (ELFDATA2LSB)
'------ 64-bit (ELFCLASS64)
我们正在处理 64 位 ELF,little-endian 编码 multi-byte 数字。所以 ELF header 是十六进制编辑器中的 前 4 行。我们对它的最后两行中的这些字段感兴趣:
Prog Hdr Tab offset Sect Hdr Tab offset
.----------^----------. .----------^----------.
00000020 40 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....@.8...@.....|
'-.-' '-.-' '-.-' '-.-' '-.-'
PHT entry size ---' | | | '-- Sect names in #2
PHT num entries ----------' | '-- SHT num entries
'-------- SHT entry size
所以我们知道 程序 Headers Table 从文件中的偏移量 0x40
开始(就在 header) 并包含 1
个大小为 0x38
的条目(56 字节)。所以它在偏移量 0x40 + 1*0x38 = 0x78
处结束(这是 table 之后的第一个字节,这也是你的 "mysterious data" 开始的地方,所以请记住这一点)。
节 Headers Table 从文件中的偏移量 0x98
开始,包含 3
个大小为 0x40
(64字节),即SHT中的每一项在十六进制编辑器中取连续4行,整个table就是3*4 = 12
这样的行,所以偏移量0x158
为table 之后的第一个字节。但这只是文件的结尾,所以在 SHT 之后没有更多内容。
索引 2
处的 SHT 条目(第三个=最后一个)应该是一个字符串 table,其中包含部分的名称。
现在让我们看看这些部分,好吗?
第 2 部分
让我们从第 2 部分开始,因为它应该包含字符串 table 和所有部分的名称,所以它在进一步分析中非常有用。这是它的 header(table 中的最后一个):
Name index Type=SHT_STRTAB (bingo!)
Flags .----^----. .----^----.
00000118 .----------^----------. 01 00 00 00 03 00 00 00 |........|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 7e 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |~...............|
'----------.----------' '----------.----------'
Starting offset Size
00000140 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 |........|
00000158
所以这确实是一个字符串table (0x03 = SHT_STRTAB
)。它从文件中的偏移量 0x7E
开始,并占用 0x11
(17
) 个连续字节。因此,字符串 table 之后的第一个字节是 0x8F
。此字节不属于任何部分(垃圾)。
字符串table
让我们看看包含字符串 table 的部分中有什么,以便我们可以命名我们的部分:
0000007E 00 2e |..|
00000080 73 68 73 74 72 74 61 62 00 2e 74 65 78 74 00 |shstrtab..text.|
0000008F
这是字符串 table,其地址相对于其开头:
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
00: 00 2E 73 68 73 74 72 74 61 62 00 2e 74 65 78 74
10: 00
或ASCII相同,NULL字符标记为∎
:
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
00: ∎ . s h s t r t a b ∎ . t e x t
10: ∎
所以我们只有 3 个完整的字符串,具有以下相对偏移量:
00: "" (Just the empty string)
01: ".shstrtab" (Name for this section)
0B: ".text" (Name for the section that contains the executable code)
(但请记住,如果这些部分共享相同的结尾,则这些部分还可以寻址这些字符串中的子字符串。)
我们现在可以验证此部分 (#2) 确实命名为 .shstrtab
:它的名称索引毕竟是 0x01
,不是吗? ;)
第 1 部分
现在让我们拆开第 1 部分 header:
Name index Type=SHT_PROGBITS
Flags .----^----. .----^----.
000000d8 .----------^----------. 0b 00 00 00 01 00 00 00 |........|
000000e0 06 00 00 00 00 00 00 00 78 00 40 00 00 00 00 00 |........x.@.....|
000000f0 78 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 |x...............|
'----------.----------' '----------.----------'
Starting offset Size
00000100 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 |........|
00000118
所以这个部分被命名为.text
(注意名称索引0x0B
)并且它是SHT_PROGBITS
类型,所以它包含一些program-defined数据; executable code 在这种情况下。它从文件中的偏移量 0x78
开始,并占用接下来的 6
个字节,因此此部分之后的第一个字节位于偏移量 0x7E
处(字符串 table 开始的位置) .这是它的内容:
00000070 31 c0 ff c0 cd 80 |1.....|
0000007E
但是等等!还记得你的 "mysterious data" 是从哪里开始的吗?是的!这是 0x78
偏移量! :) 所以这个 "mysterious data" 实际上是你的 executable 有效负载 :) 在将它解码为 Intel x86-64 操作码后,我们得到这个小程序:
31 C0 xor %eax,%eax ; Clear the EAX register to 0 (the short way).
FF C0 inc %eax ; Increase the EAX, so now it contains 1.
CD 80 int [=19=]x80 ; Interrupt 0x80 is the system call on Linux.
这基本上等同于在 C 中调用 exit(0)
;) 因为系统调用中断需要 EAX 中的操作编号,在本例中为 sys_exit
(操作编号 1)。
所以,是的,谜团解开了 :) 但是不管怎样,让我们继续,学习更多的东西,这样我们就能找到这段代码将被加载到内存中的什么地方。
第 0 部分
最后是#0 部分。它缺少一些部分,但我认为它是所有 0
s,因为毕竟第一部分始终是 NULL 部分。这是它的(屠宰)header:
00000098 00 00 00 00 00 00 00 00 | ........|
*
000000d0 00 00 00 00 00 00 00 00
但这对我们没有用。这里没什么有趣的。
计划 Headers Table
最后要解码的是程序 Headers Table,根据来自 ELF header 的信息,它从偏移量 0x40
开始并占用 56
个字节,第一个字节位于偏移量 0x78
之后。这是转储:
Type=PHT_EXEC Flags=RX Starting offset in file
.----^----. .----^----. .----------^----------.
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
'----------.----------' '----------.----------'
Virtual address Physical address
Size in file Size in memory
.----------^----------. .----------^----------.
00000060 7e 00 00 00 00 00 00 00 7e 00 00 00 00 00 00 00 |~.......~.......|
00000070 00 00 20 00 00 00 00 00
00000078 '----------.----------'
Alignment
所以说我们把文件的前126
(0x7E
)个字节加载到一个同样大小的内存段中,这个内存段应该是从虚拟地址开始的0x400000
。我们的代码从文件中的偏移量 0x78
和偏移量 0x7E
之后的第一个字节开始,所以它基本上加载了文件的整个开头,ELF header 和将 header table 程序写入内存,以及我们的 executable 有效负载在它的末尾,然后停止加载,忽略文件的其余部分。
所以如果文件的开头是在地址 0x400000
加载的,而我们的程序从开头开始 120
(0x78
) 字节,它将位于地址0x400078
在内存中 :>
现在让我们看看在 ELF header 中为我们的程序指定了什么入口点:
Executable x86-64 Version=1 Program's entry point
.-^-. .-^-. .----^----. .----------^----------.
00000010 02 00 3e 00 01 00 00 00 78 00 40 00 00 00 00 00 |..>.....x.@.....|
宾果! :> 它'0x400078
,因此它指向我们在内存映像中的一小段代码的开头。
就这些了,伙计们! ;)
我正在分析这个小 ELF 文件:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 78 00 40 00 00 00 00 00 |..>.....x.@.....|
00000020 40 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....@.8...@.....|
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 7e 00 00 00 00 00 00 00 7e 00 00 00 00 00 00 00 |~.......~.......|
00000070 00 00 20 00 00 00 00 00 31 c0 ff c0 cd 80 00 2e |.. .....1.......|
00000080 73 68 73 74 72 74 61 62 00 2e 74 65 78 74 00 00 |shstrtab..text..|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000d0 00 00 00 00 00 00 00 00 0b 00 00 00 01 00 00 00 |................|
000000e0 06 00 00 00 00 00 00 00 78 00 40 00 00 00 00 00 |........x.@.....|
000000f0 78 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 |x...............|
00000100 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 01 00 00 00 03 00 00 00 |................|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 7e 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |~...............|
00000140 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 |........|
00000158
我找到了有关 ELF header 和程序 header 的文档并对它们进行了解码,但我在解码之后的内容(从 31 c0 ff c0 cd 80 00 2e
开始)时遇到了问题。从 "shstrtab" 文本来看,我正在查看 table 部分,但 31 c0 ff c0 cd 80 00 2e
是什么意思?这部分记录在哪里?
OK,根据header前16个字节的信息判断:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
E L F | | '--- Pudding :) ---'
| '--- Little-endian (ELFDATA2LSB)
'------ 64-bit (ELFCLASS64)
我们正在处理 64 位 ELF,little-endian 编码 multi-byte 数字。所以 ELF header 是十六进制编辑器中的 前 4 行。我们对它的最后两行中的这些字段感兴趣:
Prog Hdr Tab offset Sect Hdr Tab offset
.----------^----------. .----------^----------.
00000020 40 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....@.8...@.....|
'-.-' '-.-' '-.-' '-.-' '-.-'
PHT entry size ---' | | | '-- Sect names in #2
PHT num entries ----------' | '-- SHT num entries
'-------- SHT entry size
所以我们知道 程序 Headers Table 从文件中的偏移量 0x40
开始(就在 header) 并包含 1
个大小为 0x38
的条目(56 字节)。所以它在偏移量 0x40 + 1*0x38 = 0x78
处结束(这是 table 之后的第一个字节,这也是你的 "mysterious data" 开始的地方,所以请记住这一点)。
节 Headers Table 从文件中的偏移量 0x98
开始,包含 3
个大小为 0x40
(64字节),即SHT中的每一项在十六进制编辑器中取连续4行,整个table就是3*4 = 12
这样的行,所以偏移量0x158
为table 之后的第一个字节。但这只是文件的结尾,所以在 SHT 之后没有更多内容。
索引 2
处的 SHT 条目(第三个=最后一个)应该是一个字符串 table,其中包含部分的名称。
现在让我们看看这些部分,好吗?
第 2 部分
让我们从第 2 部分开始,因为它应该包含字符串 table 和所有部分的名称,所以它在进一步分析中非常有用。这是它的 header(table 中的最后一个):
Name index Type=SHT_STRTAB (bingo!)
Flags .----^----. .----^----.
00000118 .----------^----------. 01 00 00 00 03 00 00 00 |........|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 7e 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |~...............|
'----------.----------' '----------.----------'
Starting offset Size
00000140 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 |........|
00000158
所以这确实是一个字符串table (0x03 = SHT_STRTAB
)。它从文件中的偏移量 0x7E
开始,并占用 0x11
(17
) 个连续字节。因此,字符串 table 之后的第一个字节是 0x8F
。此字节不属于任何部分(垃圾)。
字符串table
让我们看看包含字符串 table 的部分中有什么,以便我们可以命名我们的部分:
0000007E 00 2e |..|
00000080 73 68 73 74 72 74 61 62 00 2e 74 65 78 74 00 |shstrtab..text.|
0000008F
这是字符串 table,其地址相对于其开头:
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
00: 00 2E 73 68 73 74 72 74 61 62 00 2e 74 65 78 74
10: 00
或ASCII相同,NULL字符标记为∎
:
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
00: ∎ . s h s t r t a b ∎ . t e x t
10: ∎
所以我们只有 3 个完整的字符串,具有以下相对偏移量:
00: "" (Just the empty string)
01: ".shstrtab" (Name for this section)
0B: ".text" (Name for the section that contains the executable code)
(但请记住,如果这些部分共享相同的结尾,则这些部分还可以寻址这些字符串中的子字符串。)
我们现在可以验证此部分 (#2) 确实命名为 .shstrtab
:它的名称索引毕竟是 0x01
,不是吗? ;)
第 1 部分
现在让我们拆开第 1 部分 header:
Name index Type=SHT_PROGBITS
Flags .----^----. .----^----.
000000d8 .----------^----------. 0b 00 00 00 01 00 00 00 |........|
000000e0 06 00 00 00 00 00 00 00 78 00 40 00 00 00 00 00 |........x.@.....|
000000f0 78 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 |x...............|
'----------.----------' '----------.----------'
Starting offset Size
00000100 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 |........|
00000118
所以这个部分被命名为.text
(注意名称索引0x0B
)并且它是SHT_PROGBITS
类型,所以它包含一些program-defined数据; executable code 在这种情况下。它从文件中的偏移量 0x78
开始,并占用接下来的 6
个字节,因此此部分之后的第一个字节位于偏移量 0x7E
处(字符串 table 开始的位置) .这是它的内容:
00000070 31 c0 ff c0 cd 80 |1.....|
0000007E
但是等等!还记得你的 "mysterious data" 是从哪里开始的吗?是的!这是 0x78
偏移量! :) 所以这个 "mysterious data" 实际上是你的 executable 有效负载 :) 在将它解码为 Intel x86-64 操作码后,我们得到这个小程序:
31 C0 xor %eax,%eax ; Clear the EAX register to 0 (the short way).
FF C0 inc %eax ; Increase the EAX, so now it contains 1.
CD 80 int [=19=]x80 ; Interrupt 0x80 is the system call on Linux.
这基本上等同于在 C 中调用 exit(0)
;) 因为系统调用中断需要 EAX 中的操作编号,在本例中为 sys_exit
(操作编号 1)。
所以,是的,谜团解开了 :) 但是不管怎样,让我们继续,学习更多的东西,这样我们就能找到这段代码将被加载到内存中的什么地方。
第 0 部分
最后是#0 部分。它缺少一些部分,但我认为它是所有 0
s,因为毕竟第一部分始终是 NULL 部分。这是它的(屠宰)header:
00000098 00 00 00 00 00 00 00 00 | ........|
*
000000d0 00 00 00 00 00 00 00 00
但这对我们没有用。这里没什么有趣的。
计划 Headers Table
最后要解码的是程序 Headers Table,根据来自 ELF header 的信息,它从偏移量 0x40
开始并占用 56
个字节,第一个字节位于偏移量 0x78
之后。这是转储:
Type=PHT_EXEC Flags=RX Starting offset in file
.----^----. .----^----. .----------^----------.
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
'----------.----------' '----------.----------'
Virtual address Physical address
Size in file Size in memory
.----------^----------. .----------^----------.
00000060 7e 00 00 00 00 00 00 00 7e 00 00 00 00 00 00 00 |~.......~.......|
00000070 00 00 20 00 00 00 00 00
00000078 '----------.----------'
Alignment
所以说我们把文件的前126
(0x7E
)个字节加载到一个同样大小的内存段中,这个内存段应该是从虚拟地址开始的0x400000
。我们的代码从文件中的偏移量 0x78
和偏移量 0x7E
之后的第一个字节开始,所以它基本上加载了文件的整个开头,ELF header 和将 header table 程序写入内存,以及我们的 executable 有效负载在它的末尾,然后停止加载,忽略文件的其余部分。
所以如果文件的开头是在地址 0x400000
加载的,而我们的程序从开头开始 120
(0x78
) 字节,它将位于地址0x400078
在内存中 :>
现在让我们看看在 ELF header 中为我们的程序指定了什么入口点:
Executable x86-64 Version=1 Program's entry point
.-^-. .-^-. .----^----. .----------^----------.
00000010 02 00 3e 00 01 00 00 00 78 00 40 00 00 00 00 00 |..>.....x.@.....|
宾果! :> 它'0x400078
,因此它指向我们在内存映像中的一小段代码的开头。
就这些了,伙计们! ;)