第0部分。需要一个elf在Matrix中工作。 可以搬迁

注意:包含系统编程。 是的,实质上,它不包含任何其他内容。


假设您被赋予编写幻想游戏的任务。 好吧,关于精灵。 关于虚拟现实。 从小开始,您就梦想着写这样的东西,并且毫不犹豫地同意。 很快,您就会意识到,您已经从古老的bashorgh和其他不同来源的笑话中了解了大多数精灵。 糟糕,是个问题。 好吧,我们的生活并没有消失……凭借丰富的编程经验,您可以去Google,输入“ Elf规范”,然后点击链接。 喔! 这导致了某种PDF ...因此我们在这里拥有了... Elf32_Sword精灵之剑-看起来像您所需要的。 32显然是角色的等级,并且以下各列中的四个四分之一可能是损坏的。 正是您需要的,以及系统化的方式!


正如在一项奥运会编程任务中所述,在有关日本,武士和艺妓的主题的详细文本的几段之后:“正如您已经了解的那样,该任务根本不会与之有关。” 哦,是的,比赛当然是有一段时间了。 总的来说,我宣布坚持5分钟。


今天,我将尝试讨论解析64位ELF格式的文件。 原则上,它们只是不存储在其中,它们是本机程序,静态库,动态库,特定于实现的所有内容,例如崩溃转储...例如,在Linux和许多其他类似Unix的系统上使用它们,是的,他们说,甚至在电话上也是如此。以前,它的支持被积极地填充到修补的固件中。 似乎很难支持用于存储来自严重操作系统的程序的格式。 所以我想。 是的,可能是。 但是我们将支持一个非常特定的用例:从.o文件中加载eBPF字节码。 为什么这样 只是为了进行进一步的实验,我需要一些严肃的(即不是膝盖高的 )跨平台字节码,可以从C获取而不是手​​动编写,因此eBPF很简单,并且有一个LLVM后端。 我只需要将ELF解析为编译器将此字节码放入其中的容器。


为了以防万一,我将澄清一下:该文章是探索性编程,并不声称是详尽的指南。 最终目标是制作一个引导加载程序,使您可以阅读使用Clang(我拥有的Clang)在eBPF中编译的C程序,其数量足以继续进行实验。


标题


标头位于ELF的零偏移处。 它包含字母E,L,F,如果您尝试使用文本编辑器打开它,可以看到它们,以及一些全局变量。 实际上,标头是文件中位于固定偏移量处的唯一结构,并且它包含查找结构其余部分的信息。 (此后,我受32位格式文档elf.helf.h了解,他们了解64位。因此,如果您发现错误,请随时进行纠正)


在文件中遇到的第一件事是unsigned char e_ident[16] 。 还记得“以下所有陈述都是错误的”系列中的这些有趣的文章吗? 两者大致相同:ELF可以包含32位或64位代码,Little Endian或Big Endian,甚至是十几种处理器体系结构。 您将在Little endian下将其阅读为Elf64-好吧,祝您好运...这个字节数组是内部内容以及如何解析它的一种签名。


对于前四个字节,一切都很简单-它是[0x7f, 'E', 'L', 'F'] 。 如果它们不匹配,则有理由相信它们是某种错误的蜜蜂。 下一个字节包含类 性格 文件: ELFCLASS32ELFCLASS64位深度。 为简单起见,我们仅处理64位文件(是否有32位eBPF?)。 如果该类最终是ELFCLASS32 ,我们将简单地退出并返回一个错误:都一样,结构将“浮动”,并且进行健全性检查不会有任何伤害。 在此结构中,我们感兴趣的最后一个字节指示文件的字节序-我们将仅使用处理器的本机字节顺序。


为了以防万一,我将澄清一下:在C语言中使用ELF格式时,您不应该通过巧妙地计算出的偏移量减去每个int- elf.h包含必要的结构,甚至在e_ident包含字节数: EI_MAG0EI_MAG1EI_MAG2EI_MAG3EI_CLASSEI_DATA ...指向从文件读取或映射到内存的数据的指针,从文件到指向结构的指针并读取。


除了e_ident标头还包含其他字段,我们将仅检查其中一些字段,而某些字段将用于进一步分析,但稍后。 即,我们检查e_machine == EM_BPF (也就是说,它是“在eBPF处理器的体系结构之下”), e_type == ET_RELe_shoff != 0 。 最后的检查具有以下含义:文件可以包含用于链接(节表和节),启动(程序表和段)或两者的信息。 经过最后两次检查,我们确认文件中需要我们所需要的信息(好像用于链接)。 还要检查格式的版本是否为EV_CURRENT


立即进行预订,假设我们将其加载到进程中,那么我们将不信任该文件的有效性。 在处理不受信任文件的内核或其他程序的代码中,自然不可能在任何情况下都这样做


截面表


如我所说,我们对文件的链接视图(即节表和节本身)感兴趣。 标头中包含有关在何处查找节表的信息。 它的大小以及一个元素大小也都在sizeof(Elf64_Shdr) -它可以大于sizeof(Elf64_Shdr) (因为它影响格式的版本号,老实说,我不知道)。 一些主要的节号是保留的,实际上不在表中。 引用它们具有特殊含义。 显然,我们只对SHN_UNDEF感兴趣(零也保留-缺少的部分;顺便说一句,如您所知,表中的标题仍然存在) SHN_ABS 。 字符“在SHN_UNDEF部分中定义”实际上是未定义的,而在SHN_ABS它实际上具有绝对值并且不会重定位。 但是,似乎也不SHN_ABS


行表


在这里,我们第一次遇到字符串表-文件中使用的字符串表。 实际上,如果const char *strtab是一个字符串表,则名称sh_name只是strtab + sh_name 。 是的,它只是一行,从某个索引开始,一直到零字节。 线可能相交(更确切地说,一条可能是另一条的后缀)。 节可以有名称,然后在ELF标题中, e_shstrndx字段将指向行表的节(如果有多个,则为节名称),节标题中的sh_name字段指向特定行。


行表的第一个(零)和最后一个字节包含空字符。 后者是可以理解的原因:价值小时,结束最后一行。 但是零偏移量指定了一个不存在或为空的名称-取决于上下文。


载入部分


每个节的标头中都有两个地址:一个, sh_addr是加载地址(该节将放置在内存中),另一个, sh_offset是此节所在文件的偏移量。 我不知道这两个值是多少,但是每个值都可以分别为0:在一种情况下,“保留在磁盘上”部分是因为存在某种服务信息。 在另一部分中,该部分未从磁盘加载 ,例如,您只需要选择该部分并用零( .bss )对其打分即可。 老实说,虽然我不必处理下载地址-上传地址,但上传地址是:)但是,坦率地说,我们也有特定的程序。


搬迁


现在有趣的事情是:根据安全措施,如您所知,如果没有操作员留在基地,它们就不会进入矩阵。 由于这里我们仍然有幻想,因此与运营商的联系将是心灵感应。 哦,是的,我宣布了五分钟的坚韧期。 通常,我们将简要讨论链接过程。


对于我的实验,我需要将一段代码编译成一个常规的so-boot,并用常规的libdl加载。 在这里,我什至不详细介绍-只是打开dlopen ,通过dlsym提取字符,在程序dlclose时用dlclose将其关闭。 但是,即使这些是与我们的 ELF文件加载器都不相关的实现细节。 仅存在一些上下文 :按名称获取指针的能力。


通常,eBPF指令集是对齐的机器代码的胜利:一条指令始终占用8个字节,并具有一个结构


 struct { uint8_t opcode; uint8_t dst:4; uint8_t src:4; uint16_t offset; uint32_t imm; }; 

此外,可能不会使用每个特定指令中的许多字段-为“机器”代码节省空间与我们无关。


实际上,第一条指令可以紧跟第二条指令,第二条指令不包含任何操作码,而只是将立即数字段从32位扩展到64位。 这是称为R_BPF_64_64的复合指令的R_BPF_64_64


为了执行重定位,我们将再次查看sh_type == SHT_REL的节表。 标头的sh_link字段将指示我们正在修补的部分,以及sh_link从哪个表获取字符的描述。


 typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; } Elf64_Rel; 

实际上,有两种类型的重定位部分: RELRELA第二部分显式包含一个附加术语,但是我还没有看到它,因此我们仅对断言不符合这一事实添加断言,然后对其进行处理。 接下来,我将在指令中写入的值添加符号的地址。 在哪里得到呢? 正如我们已经知道的,在这里可以选择:


  • 该符号指的是SHN_ABS部分。 然后就取st_value
  • 字符指的是`SHN_UNDEF部分。 然后拉外线符号
  • 在其他情况下,只需将链接修补到同一文件的另一部分即可。

如何自己尝试


首先,要读什么? 除了已经指定的规范外,读取该文件也是有意义的,iovisor团队在其中收集了通过eBPF从Linux内核提取的信息。


其次,实际上每个人应该如何处理? 首先,您需要从某处获取ELF文件。 如StackOverfow所述,团队将为我们提供帮助。


 clang -O2 -emit-llvm -c bpf.c -o - | llc -march=bpf -filetype=obj -o bpf.o 

其次,您需要以某种方式对文件进行参考分析。 在正常情况下, objdump命令将帮助我们:


 $ objdump : objdump <> <()>     <()>.          : -a, --archive-headers Display archive header information -f, --file-headers Display the contents of the overall file header -p, --private-headers Display object format specific file header contents -P, --private=OPT,OPT... Display object format specific contents -h, --[section-]headers Display the contents of the section headers -x, --all-headers Display the contents of all headers -d, --disassemble Display assembler contents of executable sections -D, --disassemble-all Display assembler contents of all sections --disassemble=<sym> Display assembler contents from <sym> -S, --source Intermix source code with disassembly -s, --full-contents Display the full contents of all sections requested -g, --debugging Display debug information in object file -e, --debugging-tags Display debug information using ctags style -G, --stabs Display (in raw form) any STABS info in the file -W[lLiaprmfFsoRtUuTgAckK] or --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames, =frames-interp,=str,=loc,=Ranges,=pubtypes, =gdb_index,=trace_info,=trace_abbrev,=trace_aranges, =addr,=cu_index,=links,=follow-links] Display DWARF info in the file -t, --syms Display the contents of the symbol table(s) -T, --dynamic-syms Display the contents of the dynamic symbol table -r, --reloc Display the relocation entries in the file -R, --dynamic-reloc Display the dynamic relocation entries in the file @<file> Read options from <file> -v, --version Display this program's version number -i, --info List object formats and architectures supported -H, --help Display this information 

但是在这种情况下,它是无能为力的:


 $ objdump -d test-bpf.o test-bpf.o:   elf64-little objdump:      UNKNOWN! 

更确切地说,它将显示各个部分,但是拆卸是个问题。 在这里,我们回想起使用LLVM收集的内容。 LLVM具有来自binutils的实用程序扩展类,其名称形式为llvm-< > 。 例如,他们了解LLVM位代码。 而且他们也了解eBPF-可以肯定的是,它取决于编译选项,但是由于它已经编译,因此应该始终对其进行解析。 因此,为方便起见,我建议创建一个脚本:


 vim test-bpf.c #     clang -Oz -emit-llvm -c test-bpf.c -o - | llc -march=bpf -filetype=obj -o test-bpf.o llvm-objdump -d -t -r test-bpf.o 

然后对于这样的来源:


 #include <stdint.h> extern uint64_t z; uint64_t func(uint64_t x, uint64_t y) { return x + y + z; } 

会有这样的结果:


 $ ./compile-bpf.sh test-bpf.o: file format ELF64-BPF Disassembly of section .text: 0000000000000000 func: 0: bf 20 00 00 00 00 00 00 r0 = r2 1: 0f 10 00 00 00 00 00 00 r0 += r1 2: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll 0000000000000010: R_BPF_64_64 z 4: 79 11 00 00 00 00 00 00 r1 = *(u64 *)(r1 + 0) 5: 0f 10 00 00 00 00 00 00 r0 += r1 6: 95 00 00 00 00 00 00 00 exit SYMBOL TABLE: 0000000000000000 l df *ABS* 00000000 test-bpf.c 0000000000000000 ld .text 00000000 .text 0000000000000000 g F .text 00000038 func 0000000000000000 *UND* 00000000 z 

代号


第1部分。QInst:最好先输掉一天,然后在五分钟内飞起来(编写文书非常琐碎)

Source: https://habr.com/ru/post/zh-CN452592/


All Articles