如何解码ELF中的节表?

时间:2016-12-25 16:07:27

标签: linux elf low-level

我正在分析这个小小的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标题和程序标题的文档,并对这两者进行了解码,但我在解码后的问题(从31 c0 ff c0 cd 80 00 2e开始)时遇到了问题。通过" shstrtab"判断文字,我正在查看节表,但31 c0 ff c0 cd 80 00 2e是什么意思?该部分在哪里记录?

2 个答案:

答案 0 :(得分:2)

好的,根据标头的前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)

我们正在处理具有 little-endian 编码的多字节数字的 64位ELF 。因此, ELF标头是十六进制编辑器中的前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

因此我们知道 Program Headers Table (程序头表)从文件中的偏移量0x40(此头之后)开始,并且包含大小为1的{​​{1}}项(56个字节)。因此它以偏移量0x38结尾(这是此表之后的第一个字节,也是“神秘数据”的开始位置,因此请记住这一点)。

节标题表从文件中的偏移量0x40 + 1*0x38 = 0x78开始,并包含大小为0x98(64字节)的3个条目,即每个条目SHT中的int在十六进制编辑器中需要连续4行,并且整个表都是0x40这样的行,因此偏移量3*4 = 12是该表之后的第一个字节。但这只是文件的结尾,因此SHT之后没有其他内容了。
索引0x158(第三个=最后一个)上的SHT条目应该是一个包含表段名称的字符串表。

现在让我们看看这些部分吧?

第2节

让我们从第2节开始,因为它应该包含带有所有节名称的字符串表,因此在进一步分析中将非常有用。这是它的标题(表中的最后一个):

2

所以这确实是一个字符串表( 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 )。它从文件中的偏移量0x03 = SHT_STRTAB开始,并占用0x7E0x11)个连续字节。因此,字符串表之后的第一个字节为17。该字节不属于任何节(垃圾)的一部分。

字符串表

因此,让我们看看包含字符串表的部分中的内容,以便我们为这些部分命名:

0x8F

这是字符串表,其地址相对于其开头:

0000007E                                             00 2e                |..|
00000080  73 68 73 74 72 74 61 62  00 2e 74 65 78 74 00     |shstrtab..text.|
0000008F

或相同的ASCII码,其中NULL字符标记为 +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

所以我们只有3个完整的字符串,具有以下相对偏移量:

    +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:  ∎

(但是请记住,如果各节具有共同的结尾,那么它们也可以处理这些字符串中的子字符串。)

我们现在可以验证此部分(#2)确实命名为00: "" (Just the empty string) 01: ".shstrtab" (Name for this section) 0B: ".text" (Name for the section that contains the executable code) :毕竟它的名称索引是.shstrtab,不是吗? ;)

第1节

现在让我们分解第1节的标题:

0x01

因此,此部分的名称为 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开始,并占用下一个0x78字节,因此,本节之后的第一个字节位于偏移量6(字符串表开始的位置)。这是它的内容:

0x7E

但是等等!还记得您的“神秘数据”从哪里开始吗?是!这是00000070 31 c0 ff c0 cd 80 |1.....| 0000007E 的偏移量! :)因此,“神秘数据”实际上是您的可执行有效载荷:)在将其解码为Intel x86-64操作码后,我们得到了这个小小的程序:

0x78

基本上等同于在C;中调用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 $0x80 ; Interrupt 0x80 is the system call on Linux. ,因为系统调用中断期望EAX中的操作号,在这种情况下为exit(0)(操作号1)。

是的,谜团解决了:)但是无论如何,让我们继续学习更多的知识,这样一来,我们将找出这段代码将在内存中的装载位置。

第0部分

最后是第0部分。它缺少某些部分,但是我认为它是所有sys_exit,因为第一节毕竟始终是NULL节。这是(屠宰的)标题:

0

但这对我们没有用。这里没什么有趣的。

程序标题表

最后剩下要解码的是程序标头表,根据ELF标头中的信息,该标头表从偏移量00000098 00 00 00 00 00 00 00 00 | ........| * 000000d0 00 00 00 00 00 00 00 00 开始并占用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)个字节加载到相同大小的内存段中,并且该内存段应该从虚拟地址{{ 1}}。我们的代码从文件中的偏移量0x7E开始,之后是偏移量0x400000后的第一个字节,因此它基本上将文件的整个开头以及ELF头文件和程序头文件表加载到文件中内存以及位于其末尾的可执行文件有效负载,然后在不考虑文件其余部分的情况下停止加载。

因此,如果文件的开头加载到地址0x78,并且我们的程序从其开头开始0x7E0x400000)个字节,则它将位于地址{{ 1}}在内存中:>

现在让我们看看在程序的ELF标头中指定了什么入口点:

120

宾果! :>它是0x78,因此它指向内存映像中我们那小段代码的开头。

仅此而已,伙计们! ;)

答案 1 :(得分:-2)

  

我正在分析这个小小的ELF文件:

为什么不简单地使用readelf --all(即为什么你坚持用手解码文件?)。

如果您 希望手动解码文件,那很好,但我们并不想浪费时间做同样的事情。因此,请将文件提供给某个地方,我们会在其上运行readelf,并告诉您其中偏移量为078的内容。