【问题标题】:How to decode the section table in an ELF?如何解码 ELF 中的节表?
【发布时间】:2016-12-25 16:07:27
【问题描述】:

我正在分析这个小 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 是什么意思?这部分记录在哪里?

【问题讨论】:

标签: linux elf low-level


【解决方案1】:

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 编码。所以 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 开始(紧跟在此标头之后)并包含大小为 0x38(56 字节)的 1 条目。所以它以偏移量0x40 + 1*0x38 = 0x78 结束(这是该表之后的第一个字节,这也是您的“神秘数据”开始的地方,所以请记住这一点)。

Section Headers Table 从文件中的偏移量0x98 开始,包含大小为0x40(64 字节)的3 条目,即SHT 中的每个条目占用4 个连续行在十六进制编辑器中,整个表是3*4 = 12 这样的行,所以偏移量0x158 是该表之后的第一个字节。但这只是文件的结尾,所以在 SHT 之后就没有其他内容了。
索引2(第三个=最后一个)处的 SHT 条目应该是一个包含各节名称的字符串表。

现在让我们看看这些部分,好吗?

第 #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)。它从文件中的偏移量0x7E 开始,占用0x11 (17) 连续字节。因此,字符串表之后的第一个字节是0x8F。该字节不是任何部分(垃圾)的一部分。

字符串表

让我们看看包含字符串表的部分中有什么,以便我们可以命名我们的部分:

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

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

    +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 节的标题:

                                    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

但是等等!还记得你的“神秘数据”从哪里开始吗?是的!这是0x78 偏移量! :) 所以这个“神秘数据”实际上是你的可执行负载 :) 将其解码为 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    $0x80         ; Interrupt 0x80 is the system call on Linux.

这基本上相当于在 C 中调用 exit(0) ;) 因为系统调用中断需要 EAX 中的操作号,在本例中为 sys_exit(操作号 1)。

所以,是的,谜团解开了 :) 但无论如何,让我们继续学习更多内容,这样我们就可以找出这段代码将在内存中加载的位置。

第 #0 节

最后是第 0 节。它缺少一些部分,但我认为都是0s,因为第一部分毕竟总是一个NULL部分。这是它的(屠宰)标题:

00000098                           00 00 00 00 00 00 00 00  |        ........|
*
000000d0  00 00 00 00 00 00 00 00  

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

程序头表

剩下要解码的最后一件事是程序头表,根据来自 ELF 头的信息,它从偏移量 0x40 开始并占用 56 字节,它位于偏移量 @ 之后的第一个字节987654362@。这是转储:

       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

所以它说我们将文件的第一个1260x7E)字节加载到相同大小的内存段中,并且该内存段应该从虚拟地址0x400000开始。我们的代码从文件中的偏移量0x78开始,之后的第一个字节偏移量为0x7E,因此它基本上将文件的整个开头,以及ELF头和程序头表加载到内存中,以及作为我们的可执行有效载荷,然后停止加载,忽略文件的其余部分。

所以如果文件的开头被加载到地址0x400000,并且我们的程序从它的开头开始1200x78)字节,它将位于内存中的地址0x400078:>

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

    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,所以它指向我们在内存映像中的一小段代码的开头。

就是这样,伙计们! ;)

【讨论】:

  • 有这方面的在线工具吗?
猜你喜欢
  • 1970-01-01
  • 2020-09-27
  • 2018-07-28
  • 1970-01-01
  • 1970-01-01
  • 2018-06-19
  • 1970-01-01
  • 1970-01-01
  • 2011-10-29
相关资源
最近更新 更多