程序解剖:从零开始的ELF读者
Type: Build
Languages: Python (stdlib), C (fixture only)
Prerequisites: Phase 0 · 01 (Dev Environment) with clang installed, Python 3.10 or later, hexadecimal arithmetic
Time: ~120 minutes
学习目标
- 说明本轨道中每一个目标的授权规则以及17 U.S.C. 1201 (f) 的四个限制.
- 阅读ELF标题,部分标题表,以及两个字符串表
struct.unpack现在,我们要去. - 解码
.symtab报名.rela记录了来自gABI和x86-64 psABI的名称. - 返回一个命名函数的确切字节,并将每个被偏移的文件映射到它拥有的结构.
- 阅读程序标题,将虚拟地址映射到通过的文件中.
PT_LOAD现在,我们要去. - 读取一个马赫-O对象的载荷命令,并给每个对象匹配的ELF想法.
开始前:授权和法律
课程32到36中的每一个目标都是你写的和编译的程序.code/fixtures/ledger.c您拥有源头,编译器命令和它生成的对象文件.没有读取许可证,没有传输保护,没有第三方您打开代码.这条轨道从来不涵盖软件保护,许可证检查或您不拥有的程序.
法律认为拆除是复制. 在Sega Enterprises Ltd. 其他 美国"阿科莱德"公司, 977 F.2d 1510 (9th Cir. 1992年),法院裁定拆卸过程中制造的中间副本是版权法规的副本. 法律规定,在"拆除"是获得在版权保护的计算机程序中包含的思想和功能元素的唯一途径,并且有正当理由寻求此类访问,拆除是法律上对版权保护的作品进行公平使用.
第二个案例是索尼电脑娱乐公司诉康涅狄格公司,203 F.3d 596 (2000年9月9日).康涅狄格公司在建造模拟器时复制了PlayStation BIOS,法院认为这些副本是公平使用的.这两起案例都保护了研究.最终产品中没有保护复制代码.
两项法规设定了极限. 美国法规第17条 1201(f) 允许"合法获得使用计算机程序副本的权利"的人绕过访问控制"仅为了识别和分析程序的那些元素,是必要的,以实现独立创建的计算机程序与其他程序的互操作性". 标准:合法复制,互操作性目的,尚未"易于使用"的元素,以及其他不违反的分析. 只有1201 (f) 节是反规则的例外. 复制问题仍然是公平的.
在欧盟,2009/24/EC条款5 (3) 涵盖研究.拥有使用副本权的人可以"观察,研究或测试程序的运作,以确定基于程序的任何元素的想法和原则".该权利适用于加载,显示,运行,传输或存储程序.
根据第6条,在"获取实现独立创建计算机程序互操作性所需的信息是不可或缺的"情况下,未经权利持有人的许可,可拆译. 条件与1201相符. 许可证持有人执行这些行为,这些信息还没有很快获得,而这些行为仍然存在于所需部分范围内. 根据第6条 (2) 禁止使用结果用于任何其他目标或类似于表达的程序. 根据第8条,违反第6条或第5条 (3) 条的合同条款无效.
| Source | What it allows | Conditions |
|---|---|---|
| 17 U.S.C. 1201(f) | Circumventing an access control to analyze a program | Lawful copy, interoperability purpose, information not readily available, no infringement |
| Directive 2009/24/EC Art. 5(3) | Observing, studying, and testing a running program | A right to use the copy, acts within normal loading and running |
| Directive 2009/24/EC Art. 6 | Decompilation | Licensee, interoperability purpose, information not readily available, confined to the needed parts |
| Sega v. Accolade | Disassembly as fair use | The only way to reach the ideas and functional elements, a legitimate reason |
| Sony v. Connectix | Intermediate copies during reverse engineering | A non-infringing final product |
根据"反向工程"手册第101节,在申请申请时,请在开放文件之前获得书面授权,并将授权与证据保持在一起.
问题
你编译了ledger.c走了llvm-objdump -t在对象上.输出列表percent_change在价值0000000000000000具有尺寸0000000000000010列表还显示了1768字节文件中的哪些16字节是这个函数?R_X86_64_PC32 .rodata+0x10在抵消0x36文件中的四个字节是零.它们将成为什么,为什么是添加0x10当桌子开始时0x14现在,我们要去.
rea回答布局问题inspect_binary_layout只有Linux x64上使用, pwntools 4.15.0 后面.REA_PWNTOOLS_PYTHON在这个Mac上,它没有.address_to_file_offset需要一个Ghidra会话和原始文件字节的完整副本,并具有相匹配的SHA-256. inspect_artifact它们从未打开了对象文件中的表格.所以你自己用Python的950行写读器.struct.unpack测试将每一个表进行比较.llvm-objdump文字被捕获一次到灯具中.
文件是指表的表
系统V gABI,第4章,图4-1,绘制了一个文件的两个视图. 链接视图有ELF标题,可选的程序标题表,部分和所需的部分标题表. 执行视图包含一个ELF标题,一个要求的程序标题表,段落和一个可选的部分标题表. 盖比添加了一个句子, 统治整个读者:"只有ELF标题在文件中有固定位置. 其他结构都被另一个结构中存储的偏移发现.
并且表示"ELF不使用比特字段".Elf64_Addr其他Elf64_Off它们是8个字节,Elf64_Half两个是Elf64_Word是4,和Elf64_Xword根据x86-64 psABI,4.1.1节,要求ELFCLASS64现在ELFDATA2LSB现在e_machine EM_X86_64(62) 和e_flags0. 固定宽度的小子平均值struct.unpack具有一个<预写字符在一个电话中读取每一个记录.
它们的第64个字节ledger-x86_64-O1.o其他:
text00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0100 3e00 0100 0000 0000 0000 0000 0000 ..>.............
00000020: 0000 0000 0000 0000 2804 0000 0000 0000 ........(.......
00000030: 0000 0000 4000 0000 0000 4000 0b00 0100 ....@.....@.....阅读它们与GABI"ELF头条"页面相比.e_ident是16字节7f 45 4c 46系统的数据, 系统的数据, 系统的数据, 系统的数据,
那么e_type 0x0001是ET_REL它们是可移动的.e_machine 0x003e只有62.EM_X86_64现在,我们要去.e_entry是零,因为对象没有入口点. e_phoff是零,因为对象没有程序标题. e_shoff是0x428部分标题表的文件偏移.最后一行保留e_ehsize 0x40现在e_phentsize零e_phnum零e_shentsize 0x40现在e_shnum 0x0b其他问题e_shstrndx 0x0001现在,我们要去.
读者将这行转化为一个呼叫:
pythonfields = struct.unpack_from("<HHIQQQIHHHHHH", data, 16)格式字符串是从左到右读到的gABI数字:两个半字,一个字,三个8字节值,一个字,六个半字节.读者拒绝一个不同于魔术,类或字节顺序的文件,并且拒绝.e_shentsize或e_phentsize错误的宽度会使每一个后来的偏移都错误,所以查询位于顶部.
部分标题和两个弦表
每个部分标题都是一个Elf64_Shdr具有64字节的字节 (gABI图4-8): sh_name现在sh_type现在sh_flags现在sh_addr现在sh_offset现在sh_size现在sh_link现在sh_info现在sh_addralign其他sh_entsize格式字符串是<IIQQQQIIQQ现在,我们要去.sh_name是对该部分的偏移e_shstrndx在这个对象中,e_shstrndx是1,并且叫该部分的.strtab单个表提供了分类名称和符号名称.
读者打印了 - O1 对象的11个标题:
textIdx Name Type Flags Offset Size Addr Link Info Align
0 NULL 0x0 0x0 0x0 0 0 0
1 .strtab STRTAB 0x368 0xbe 0x0 0 0 1
2 .text PROGBITS AX 0x40 0x9f 0x0 0 0 16
3 .rela.text RELA I 0x2a8 0x48 0x0 10 2 8
4 .rodata PROGBITS A 0xe0 0x50 0x0 0 0 8
5 .rela.rodata RELA I 0x2f0 0x78 0x0 10 4 8
6 .rodata.str1.1 PROGBITS AMS 0x130 0xa 0x0 0 0 1
7 .comment PROGBITS MS 0x13a 0x2f 0x0 0 0 1
8 .note.GNU-stack PROGBITS 0x169 0x0 0x0 0 0 1
9 .llvm_addrsig LLVM_ADDRSIG E 0x368 0x0 0x0 10 0 1
10 .symtab SYMTAB 0x170 0x138 0x0 1 9 8索引0是保留的零标题. .text在文件的偏移中包含159字节的代码0x40标题后面,有旗.A(记忆中分配) 和X(可执行). .rodata包含80字节的仅可读数据. .rodata.str1.1具有五个标签字符串F\0D\0C\0B\0A\0标签:M(可合并) 和S现在,我们要去看看.
.comment具有47字节的编译器邮票\0Apple clang version 17.0.0 (clang-1700.6.3.2)\0现在,我们要去..llvm_addrsig具有类型0x6fff4c03系统特定范围内SHT_LOOS为了SHT_HIOS据了解,GABI的储备和LLVM的储备ELF.h它们的旗.E是SHF_EXCLUDE接连器将它放下.
其他sh_link其他sh_info列是表之间的指标. .rela.text没有sh_info两号sh_link10:它补丁了第2节,并从第10节取出其符号..symtab没有sh_link1,符号名称的字符串表,以及sh_info9,是第一个非本地符号的指数. sh_addr链接器将它们分配给
两个gABI规则允许读者绘制文件的完整地图.部分"连接且不会重叠",文件"可能包含不活跃的空间".读者按偏移排序每个结构,填补漏洞:
text 0x0 +64 header ELF header
0x40 +159 section .text
0xdf +1 gap inactive space
0xe0 +80 section .rodata
0x130 +10 section .rodata.str1.1
0x13a +47 section .comment
0x169 +7 gap inactive space
0x170 +312 section .symtab
0x2a8 +72 section .rela.text
0x2f0 +120 section .rela.rodata
0x368 +190 section .strtab
0x426 +2 gap inactive space
0x428 +704 table section header table它们的三个空白总共10字节,每个字节都是对齐..rodata没有sh_addralign8,所以它从0xe0而不是0xdf现在,我们要去..symtab没有使用的7字节..comment部分标题表本身与8相一致.
类型的部分SHT_NOBITS没有任何字节,所以地图会跳过它们..bss在一个链接的程序中,有,一个信任的读者sh_size文件是没有的.
标志:结合,类型,部分,值,大小
Elf64_Sym是24字节 (gABI图4-18): st_name现在st_info现在st_other现在st_shndx现在st_value现在st_size格式<IBBHQQ现在,我们要去.st_info包装两个字段:上方四位的绑定和下方四位的类型.STB_LOCAL零STB_GLOBAL其他STB_WEAK2,以及类型STT_NOTYPE零STT_OBJECT其他STT_FUNC两,STT_SECTION其他STT_FILE低的两个比特st_other保持可见性STV_DEFAULT在这里,全是零.
textIdx Value Size Bind Type Section Name
0 0x0 0 LOCAL NOTYPE *UND*
1 0x0 0 LOCAL FILE *ABS* ledger.c
2 0x0 20 LOCAL OBJECT .rodata labels.rel
3 0x0 0 LOCAL SECTION .rodata .rodata
4 0x2 2 LOCAL OBJECT .rodata.str1.1 .L.str.1
5 0x4 2 LOCAL OBJECT .rodata.str1.1 .L.str.2
6 0x6 2 LOCAL OBJECT .rodata.str1.1 .L.str.3
7 0x8 2 LOCAL OBJECT .rodata.str1.1 .L.str.4
8 0x0 0 LOCAL SECTION .rodata.str1.1 .rodata.str1.1
9 0x0 16 GLOBAL FUNC .text percent_change
10 0x10 46 GLOBAL FUNC .text grade
11 0x40 31 GLOBAL FUNC .text total
12 0x60 63 GLOBAL FUNC .text grade_label索引0是保留的未定义符号. st_shndx标志定义的部分,有三个保留值: SHN_UNDEF零SHN_ABS 0xfff1文件名称,以及SHN_COMMON 0xfff2部分符号有空名,所以llvm-objdump根据gABI规则, "所有具有STB_LOCAL绑定符号的符号都先于弱和全球符号"的第九行,.symtab记录在9年sh_info由于这个原因.
在可移动的物体中st_value是符号部分内的一种偏移,因为sh_addr函数的文件偏移是sh_offset + st_value - sh_addr它们的长度是st_size其他:
pythonsection = elf.sections[symbol.st_shndx]
file_offset = section.sh_offset + (symbol.st_value - section.sh_addr)
code = elf.data[file_offset : file_offset + symbol.st_size]为了percent_change这就是0x40 + 0并且16个字节是85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3尺寸和开头一样重要.grade结束在.text抵消0x3e两个字节66 90后者是nop编译器添加了这样的total开始于0x40它们不属于任何符号. 第33课使用st_size课程36在比较重建之前,掩盖了它们.
移动:零字节意味着什么
Elf64_Rela是24字节 (gABI图4-23): r_offset现在r_info签署的r_addend格式<QQq子就分开了r_info具有两个宏:ELF64_R_SYM(i)是i >> 32其他ELF64_R_TYPE(i)是i & 0xffffffff在可转移的文件中r_offset是表中命名的部分内的一种偏移sh_info类型名称和公式来自x86-64 psABI,表4.9.S是符号值,A附加条P修补的地点,L关于""的报道.R_X86_64_64是S + A现在R_X86_64_PC32是S + A - P其他R_X86_64_PLT32是L + A - P现在,我们要去.
子的 - O1 对象有8个记录:
text .rela.text 0x36 R_X86_64_PC32 .rodata+0x10
.rela.text 0x84 R_X86_64_PC32 .rodata+0x24
.rela.text 0x93 R_X86_64_PC32 .rodata-0x4
.rela.rodata 0x0 R_X86_64_PC32 .rodata.str1.1
.rela.rodata 0x4 R_X86_64_PC32 .L.str.1+0x4
.rela.rodata 0x8 R_X86_64_PC32 .L.str.2+0x8
.rela.rodata 0xc R_X86_64_PC32 .L.str.3+0xc
.rela.rodata 0x10 R_X86_64_PC32 .L.str.4+0x10拆掉第一张.0x36躺在里面grade它们的范围0x10为了0x3e关于 如何使用0x33是48 8d 0d 00 00 00 00其他lea它们的四字节移动率为0x36处理器将移动添加到下一个指令的地址,0x3a现在,我们要去.
编译器希望结果是.rodata + 0x14,其中一个5个入口表的32位值1, 2, 3, 4, 4存储值必须是(.rodata + 0x14) - 0x3a公式S + A - P随着P = 0x36给出.rodata + A - 0x36设置两个等数A = 0x14 - 4 = 0x10添加符包含了目标偏移减去移动和命令结束之间的四个字节.
语编译了这个表格switch在grade在 -O1 代码计算score / 10乘以 0x66666667并且两个转移,减6与4相比,并索引表. -O1 ELF对象没有表的符号.参考是节点符号.rodata另外一个添加,只有同一个源的 arm64 构建命名它l_switch.table.grade第二个记录,在0x84在grade_label,在第一个8字节值的第二表中点.rodata + 0x28第三个,在0x93其他地方.rodata + 0x0开始的labels.rel现在,我们要去.
五个.rela.rodata记录填写labels.rel每个入口都在抵消4i并且得到了补充.4i现在,S + A - P减去表的底部. grade_label阅读一个条目movslq首先,将表的标志命名为"节点".rodata.str1.1随附加0因为字符串F其他部分的名称.L.str.1为了.L.str.4编译器的本地标签D现在C现在B其他A现在,我们要去.
读者可以在没有拆卸器的情况下显示区别:
| Build | Where labels lives | How grade selects | How grade_label reaches grade |
|---|---|---|---|
| -O0 | .data, five 8-byte pointers, five R_X86_64_64 records | A jump table in .rodata: five R_X86_64_PC32 records pointing into .text | A call with R_X86_64_PLT32 grade-0x4 |
| -O1 | .rodata as labels.rel, five 32-bit relative entries | A value table at .rodata+0x14, one R_X86_64_PC32 | Inlined, no call |
| -O2 | Same as -O1 | Same as -O1 | Inlined, total grows to 140 bytes |
在 - 0 时switch五个进口指向回来..text在+0x91现在+0x8c现在+0x87现在+0x82其他+0x86电话给grade带着相同的东西-0x4由于同样的原因,lea在 -O1 中,优化器看到每个案例都返回一个常数,并用结果表替换跳跃. 第33课解了两个形状.
节目标题:载体的观点
转移的对象没有程序头,所以e_phnum执行式或共享对象拥有它们,而加载器没有读到其他内容.Elf64_Phdr是56字节 (gABI图5-1): p_type现在p_flags现在p_offset现在p_vaddr现在p_paddr现在p_filesz现在p_memsz现在p_align格式<IIQQQQQQ博会设定了三个规则PT_LOAD它们在升级中出现.p_vaddr命令,以及一个p_memsz超过p_filesz没有填充.p_vaddr是与 相一致的p_offset模块p_align现在,我们要去.
在这条轨道上,每个目标都是你构建的程序,ledger.o所以,这个装置tiny-x86_64来自code/fixtures/build_tiny_elf.py字母为736字节.struct.pack文件包含一个ELF标题,两个程序标题,一个36字节_start只有3字节..rodata标志表和六个部分标题._start写道ok\n在 Linux 上,它运行在 x86-64 上.这里是程序标题路径的固定.
textProgram headers:
PT_LOAD r-x offset=0x0 vaddr=0x400000 filesz=0x2e0 memsz=0x2e0 align=0x1000
PT_GNU_STACK rw- offset=0x0 vaddr=0x0 filesz=0x0 memsz=0x0 align=0x10
entry 0x4000b0 is file offset 0xb0 through PT_LOAD单身的PT_LOAD查看整个736字节文件的图0x400000读者通过找到 PT_LOAD包含它和计算p_offset + (address - p_vaddr)对于进入点,这是0 + (0x4000b0 - 0x400000) = 0xb0开始的.text现在,我们要去.
这就是REA的计算.address_to_file_offset对于一个Ghidra目标,该工具会返回{address, file_offset}霍普尔的供应商也回来了provider_file_offset其他image_base_file_offset工具需要文件的完整原始副本,该副本的SHA-256必须与目标匹配.inspect_binary_layout添加一个行,保持:"链接地址不是运行时间地址".一个位置独立的可执行负载在内核选择的基础,文件的偏移是唯一稳定的坐标.
程序标题还解释了为什么一个被剥离的执行式仍然运行. 链接器可以完全删除部分标题表,e_shoff工具在行走的部分,然后看不到任何东西.
马赫-奥的想法相同
果没有发布Mach-O的散文规格.XNU源头的标题是规格: EXTERNAL_HEADERS/mach-o/loader.h现在nlist.h其他reloc.h连接器ledger-arm64-O1.o现在,我们都在做什么?ledger.c采用clang -target arm64-apple-macos -O1 -fno-asynchronous-unwind-tables -c现在,我们要去.
mach_header_64是32字节: magic现在cputype现在cpusubtype现在filetype现在ncmds现在sizeofcmds现在flags现在reserved魔术就是0xfeedfacf现在MH_MAGIC_64现在,我们要去.cputype是0x0100000c现在CPU_TYPE_ARM64现在,我们要去.filetype是 1, MH_OBJECT现在,我们要去.ncmds是5和sizeofcmds是 616:负载命令在标题之后占据了 616 字节,每个字节都从 开始.{cmd, cmdsize}旗0x2000是MH_SUBSECTIONS_VIA_SYMBOLS现在,我们要去.
textLoad commands:
0 LC_SEGMENT_64 cmdsize=472 at 0x20
1 LC_BUILD_VERSION cmdsize=24 at 0x1f8
2 LC_LINKER_OPTIMIZATION_HINT cmdsize=16 at 0x210
3 LC_SYMTAB cmdsize=24 at 0x220
4 LC_DYSYMTAB cmdsize=80 at 0x238LC_SEGMENT_64对于两个ELF表的Mach-O答案.segment_command_64没有segname现在vmaddr现在vmsize现在fileoff现在filesize现在maxprot现在initprot现在nsects其他flags然后nsects类型记录section_64按相同的命令执行.loader.h评论MH_OBJECT据说这些部分"在一个无名的段落中,没有段落填充".fileoff其他国家filesize456个,还有五个部分.
text Idx Section Addr Size Offset Reloff Nreloc Type
1 __TEXT,__text 0x0 0xd0 648 1104 6 S_REGULAR
2 __DATA,__const 0xd0 0x28 856 1152 5 S_REGULAR
3 __TEXT,__cstring 0xf8 0xa 896 0 0 S_CSTRING_LITERALS
4 __TEXT,__const 0x108 0x40 912 0 0 S_REGULAR
5 __LD,__compact_unwind 0x148 0x80 976 1192 4 S_REGULAR每个部分都以其母分类为名称.segname两个部分叫做__const它们是不同的,因为它们是__DATA,__const另一个__TEXT,__const移动是每节分reloff其他nreloc其他类型的子.rela.X目标的部分.
每个relocation_info字符号是8字节.r_address接下来是32位的字段,r_symbolnum其他类型r_pcrel其他r_length其他r_extern其他r_type(4) 六个__text记录是三个对的ARM64_RELOC_PAGE21其他ARM64_RELOC_PAGEOFF12根据每张表的参考,每张表的参考一对.adrp另外add能做什么?lea需要两个记录.__DATA,__const记录是ARM64_RELOC_UNSIGNED随着r_length它们每一个都会补丁一个8字节的绝对指针.
在O1下,手臂64的构建保持.labels作为简单的指针, x86-64构建构建了相对表.
LC_SYMTAB没有任何其他方法.symoff其他国家nsyms其他地方stroff现在,我们要去做什么?strsize文件大小为1688,每个文件大小是168nlist_64是16字节: n_strx现在n_type现在n_sect现在n_desc现在n_value现在,我们要去.n_type只是一个小领域.N_TYPE子0x0e) 等于N_SECT(0xe) 定义在该节中,其1基定数是n_sect现在,我们要去.N_EXT(0x01) 标志着外部符号.
nlist_64没有任何尺寸的字段._percent_change读者在同一部分中取下一个符号地址,_grade在0x18标志C具有领先的标志_现在,这个函数是_percent_change现在,我们要去.
LC_DYSYMTAB(0xb) 记录与ELF相同的本地第一规则sh_info其他ilocalsym零nlocalsym其他地方iextdefsym其他地方nextdefsym其他地方LC_BUILD_VERSION(0x32) 记录platform其他技术minos其他sdk包装的样子XXXX.YY.ZZ整数.这命令是为什么 arm64 装置被提交为字节. 建立在 Mac 上使用另一个 SDK 生成不同的文件.
读者的compare_formats两份文件的位置:
| Idea | ELF, ledger-x86_64-O1.o | Mach-O, ledger-arm64-O1.o |
|---|---|---|
| File identity | e_ident 7f 45 4c 46, class 64, data LSB | magic 0xfeedfacf |
| Machine | e_machine 62, EM_X86_64 | cputype 0x100000c, CPU_TYPE_ARM64 |
| File kind | e_type 1, ET_REL | filetype 1, MH_OBJECT |
| Where the tables are | e_shoff 0x428, 11 section headers | 5 load commands, 616 bytes after the header |
| The code | .text at 0x40, size 0x9f, flags AX | __TEXT,__text at 648, size 0xd0, in the unnamed segment |
| Symbol names | .symtab (section 10), names in .strtab through sh_link 1 | LC_SYMTAB symoff 1240, nsyms 17, names at stroff 1512 |
| Symbol size | st_size: percent_change is 16 bytes | No field: next symbol minus this one, 24 bytes |
| Relocations | .rela.text, 3 records, R_X86_64_PC32 | __text reloff 1104, 6 records, PAGE21 and PAGEOFF12 |
| Function bytes | percent_change: 16 bytes at 0x40 | _percent_change: 24 bytes at 0x288 |
实际上,Mach-O 通过inspect_macho通过otool -h -l现在nm -gjU现在dyld_info -imports -exports其他dwarfdump --uuid通过xcrun你的读者不需要任何东西,而且它都读不到.symoff其他stroff这otool -l印记.open_binary通过魔术字节检测到ELF,MZ,PE和薄或通用Mach-O.e_machine对于一个没有标题的文件,你给了format: raw-binary德拉提供商拒绝了通用二进制,并要求一个薄片.0xcafebabe现在,我们要去.
建立它
运行标准图书馆阅读器:
bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch
python3 code/main.py演示内容是ledger-x86_64-O1.o然后打印了其标题,部分,符号,移动和文件偏移地图.ledger-arm64-O1.o图表的标题是" 图表"的标题.tiny-x86_64预期的行:
textpercent_change: 16 bytes at file offset 0x40: 85 ff 74 09 29 fe 6b c6 64 99 f7 ff c3 31 c0 c3
grade: 46 bytes at file offset 0x50: 48 63 c7 48 69 c0 67 66 66 66 ...
entry 0x4000b0 is file offset 0xb0 through PT_LOAD通过一个读取文件的路径: python3 code/main.py code/fixtures/ledger-x86_64-O0.o读者从前4个字节中选择ELF或Mach-O.
阅读code/main.py在此顺序:
ElfFile._read_header()检查了魔术,类别,字节顺序,输入尺寸,然后打开头标.ElfFile._read_sections()其他read_cstring()阅读部分标题表,并通过 解决名称e_shstrndx现在,我们要去.ElfFile._read_symbols()分裂st_info通过sh_link保持保留的部分索引可读.ElfFile._read_relocations()走着每一个SHT_RELA部分,分开r_info根据4.9表的名称类型.ElfFile.function_span()其他function_bytes()将一个符号变成一个文件范围,并将其中的字节.ElfFile.file_offset_map()排序每个结构按偏移,填补不动空间.ElfFile._read_program_headers()其他address_to_file_offset()处理执行器.MachOFile读取标题,载荷命令,LC_SEGMENT_64它们的部分,LC_SYMTAB现在LC_BUILD_VERSION其他地方的地区.compare_formats()两个文件中都会有相同的名称,每个文件中都会有相同的值.
用它
导入后课中的读者from main import ElfFile在这个课程的补充之后code文件目录sys.path第33课要求我们做.function_bytes其他relocations_for(".text")课35记录其输出作为观察证据.课36要求它在比较字节之前掩盖移动抵消.
编译自己的C成一个用相同的对象clang -target x86_64-unknown-linux-gnu -c命令并运行读者.只需在 x86-64 psABI 表 4.9 列出时添加一个移动名称.只需在 gABI 或生产工具链记录时添加一个部分类型.当读者遇到一个没有名称的值时,它会以六分之一打印数字,而不是猜测.
互动实验室
实验室A:手动进行头条
关于code开放字符串:
bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch/code
python3 -q运行:
pythonimport struct
from main import ElfFile, FIXTURES
data = (FIXTURES / "ledger-x86_64-O1.o").read_bytes()
data[:4], data[4], data[5]
struct.unpack_from("<HHIQQQIHHHHHH", data, 16)
elf = ElfFile(data)
elf.header.e_shoff, elf.header.e_shnum, elf.sections[elf.header.e_shstrndx].name轮显示e_type其他e_machine其他e_shoff现在,我们要做什么?0x428最后一行显示第1节,.strtab现在,我们要把这些部分命名.
实验室B:开关背后的值表
pythonrodata = elf.section_bytes(elf.section(".rodata"))
[int.from_bytes(rodata[i : i + 4], "little") for i in range(0x14, 0x28, 4)]
[int.from_bytes(rodata[i : i + 8], "little") for i in range(0x28, 0x50, 8)]
[r for r in elf.relocations_for(".text") if r.symbol == ".rodata"]两个表都读到了1, 2, 3, 4, 4检查它们的情况ledger.c其他grade(65)计算器65 / 10 - 6 = 0值为 1 个值.case 6现在,我们要去.grade(100)值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4, 值为 4,case 10其他case 9现在,我们要去.
实验室C:一个源的三个构建
pythonfor level in ("O0", "O1", "O2"):
obj = ElfFile.from_path(FIXTURES / f"ledger-x86_64-{level}.o")
print(level, [s.name for s in obj.sections if s.name in (".data", ".rodata")])
print(level, sorted({r.type_name for r in obj.relocations}))
print(level, {s.name: s.st_size for s in obj.symbols if s.type == "FUNC"}) - 0 对象有一个.data部分和三种转移类型..data它们是一个类型.total在 -O0 位是66字节,在 -O1位是31字节,在 -O2位是140字节.
实验室D:在Mach-O中相同的函数
pythonfrom main import MachOFile
macho = MachOFile.from_path(FIXTURES / "ledger-arm64-O1.o")
[(c.name, c.cmdsize) for c in macho.load_commands]
macho.function_span("_percent_change")
macho.function_bytes("_percent_change")[:4].hex()文件的时间范围为24字节,在文件的抵消 648.a0000034简单的教训形式0x340000a0哪个llvm-objdump -d图像cbz w0, 0x14这就是before == 0检查器,同样测试的X86-64构建编码为85 ff 74 09现在,我们要去.
实践实验室
在Linux x86-64机器上,建立clang -O1 -shared -fPIC code/fixtures/ledger.c -o libledger.so其他clang -O1 code/fixtures/ledger.c -o ledger_main具有一个main您将添加,然后将读者运行到两个.
要求:
- 阅读
.dynsym,,,,,,,,,,,,,,,,,,,Elf64_Sym由于SHT_DYNSYM现在的记录是不变的. - 阅读
.dynamic作为Elf64_Dyn记录d_tag其他d_val姓名DT_NEEDED现在DT_STRTAB现在DT_SYMTAB现在DT_RELA其他DT_JMPREL在gABI"动态部分"页面上. - 列出未定义的符号
st_shndxSHN_UNDEF作为进口和R_X86_64_JUMP_SLOT记录.rela.plt作为他们接到的通话站点. - 绘制每一个图
PT_LOAD按gABI规则将该段子分为包含的部分,即"对象文件段包含一个或多个部分". - 试试
address_to_file_offset对于每个定义函数的字节,function_bytes返回了. - 捕获
llvm-objdump -h -t -r -p其他llvm-objdump -T两个文件的文本code/fixtures/让读者与读者相比,
成功意味着进口列表,细节地图和每个函数范围都与捕获的工具输出一致,在测试期间没有工具运行.
教训艺术品
这一课产生了outputs/skill-program-anatomy-elf-from-scratch.md通过使用CLAUD,Cursor,Codex,OpenClaw,Hermes或任何读SKILL.md的代理.该技能将用户构建的编译对象或可执行的执行器变成布局报告.报告显示每个结构,符号和迁移的文件范围,与捕获的工具输出进行检查.
检查
运行演示和确定性套件:
bashcd phases/13-tools-and-protocols/32-program-anatomy-elf-from-scratch
python3 code/main.py
python3 -m unittest discover -s code/tests -v套房证明:
- 每个部分的索引,名称,大小,地址和类型都匹配捕获的内容.
llvm-objdump -h对于三个x86-64对象和小的执行器的文本 - 每个符号值,绑定,类型,部分,大小和名称都匹配捕获的符号.
llvm-objdump -t文本 - 每个移动的偏移,类型名称,符号和添加符都匹配捕获的
llvm-objdump -r文本,包括R_X86_64_PLT32其他R_X86_64_64在 -O0 - 两位项目负责人
tiny-x86_64匹配被俘的llvm-objdump -p它们没有任何文本, percent_change是16字节的文件偏移0x40并且每个函数都在内.text- 文件偏移地图一次覆盖每个固定的每字节,在 -O1 对象的三个排列空白位置
- 进入点
tiny-x86_64文件的地图0xb0通过PT_LOAD转移可的对象拒绝地址映射 - 马赫-奥头,每个载荷命令,部分,五个部分,
LC_SYMTAB其他LC_BUILD_VERSION匹配被俘的--private-headers文本 - 马赫-奥的符号和15个搬迁与被捕者相匹配
llvm-objdump文本,以及_percent_change是24字节在偏移 648 - 错误的魔力,一个32位类,一个大数据文件,一个缩小表,一个通用二进制,
FormatError build_tiny_elf.py复制了所承诺的tiny-x86_64字节对字节- 图形模块
site/figures-re-32.js包含了 字节的确切字节.ledger-x86_64-O1.o
生产失败模式
| Failure | What the weak reading reports | What the reader must prove |
|---|---|---|
st_value used as a file offset | "percent_change is at 0" | Offset is sh_offset + st_value - sh_addr, here 0x40 |
| Padding counted as code | "grade is 48 bytes" | st_size is 46 and the 66 90 after it belongs to no symbol |
| Section names read from the wrong table | Garbage names or an exception | e_shstrndx names the table, here section 1 |
sh_size trusted for SHT_NOBITS | A read past the end of the file | .bss has no file bytes and the map skips it |
| Zero bytes read as the operand | "lea 0(%rip)" | The .rela.text record at 0x36 names .rodata+0x10 |
| Addend read as the target offset | "the table starts at 0x10" | S + A - P with the instruction end gives 0x14 |
| Sections walked on a stripped executable | "the file has no code" | PT_LOAD maps every address the loader uses |
| A linked address treated as a runtime address | A wrong offset under a random base | The file offset from PT_LOAD is the stable coordinate |
A Mach-O section looked up by sectname alone | Two __const sections collapse into one | segname,sectname is the name |
A size field expected in nlist_64 | An exception or zero bytes | The next symbol in the same section ends the function |
| A universal binary read as one Mach-O | "magic 0xcafebabe is unknown" | Refuse it and ask for a thinned slice |
| The arm64 object rebuilt on another Mac | "the fixture changed" | LC_BUILD_VERSION records the SDK, so the committed bytes are the fixture |
运营规则
阅读您可以信任的标题,跟随其包含的偏移,并从定义的规范中命名每个字段. 保持您比较的工具输出为承诺的固定,因此比较没有工具运行. 当值在规范中没有名称时,打印数字.您可以检查字节字节的读者是每次课程都建立在于它.
进一步阅读
- System V gABI, chapter 4: Object Files其他ELF Header现在Sections现在Symbol Table其他Relocation页面
- System V gABI, chapter 5: Program Header其他Dynamic Linking
- System V ABI, AMD64 Architecture Processor Supplement转移类型
- Apple XNU
loader.h现在nlist.h其他reloc.h - 17 U.S.C. 1201其他Directive 2009/24/EC
- Sega v. Accolade, 977 F.2d 1510其他Sony v. Connectix, 203 F.3d 596
- rea 6.5.0
docs/binary-diagnostics.md其他docs/ghidra-provider.md
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.