【学习记录】Week2(一):深入 ELF 结构视图与 .got/.plt 节区作用详解
写在前面进入 Week2我们需要把目光从宏观的保护机制下沉到微观的文件结构中。在平时用readelf或objdump分析二进制时我们经常会看到“节区”和“段”这两个词。它们到底有啥区别我们天天喊的“打 GOT 表”GOT 表到底在文件结构的哪个位置本文将为你彻底讲透。 目录ELF 双视图节区 与 段 的本质区别核心节区速览动态链接的枢纽.plt 与 .got 详解PWN 视角为什么要区分 .got 和 .got.plt1. ELF 双视图节区 与 段 的本质区别ELF (Executable and Linkable Format) 文件可以从两个不同的维度来看待链接视图和执行视图。这也是节区和段的最根本区别。1.1 链接视图节区 Section服务对象链接器。作用链接器需要知道代码放在哪、数据放在哪、符号表在哪。因此 ELF 被划分成了无数个细分的“节区”。特点粒度极细。比如.text代码、.data已初始化全局变量、.bss未初始化全局变量都是独立的节区。1.2 执行视图段 Segment服务对象操作系统的程序加载器。作用加载器在把程序载入内存时不需要知道.text和.rodata的区别它只关心“这块内存需要可读可执行那块内存需要可读可写”。为了节约内存页的开销加载器会将多个属性相同的节区合并成一个“段”来加载。特点粒度较粗。 假设性说明模拟readelf输出假设我们在终端输入readelf -l vuln查看段信息你会在输出中看到类似这样的对照关系Section to Segment mapping: Segment Sections... 00 .interp 01 .interp .note.gnu.property .note.ABI-tag ... 02 .init .plt .plt.got .text .fini -- 合并成了可执行段 03 .rodata .eh_frame_hdr .eh_frame -- 合并成了只读段 04 .init_array .fini_array .dynamic .got .data .bss -- 合并成了可读写段从上面的模拟输出可以直观看到.plt、.text被打包在一起执行而.got、.data被打包在一起读写。节区是逻辑划分段是物理装载。2. 核心节区速览在 PWN 中我们最常打交道的几个节区如下节区名称说明PWN 关注度.text存放程序编译后的机器指令。通常只读可执行。⭐⭐⭐ (找 ROP Gadget、后门函数).bss存放未初始化的全局/静态变量。运行时在内存中分配并清零。⭐⭐ (ret2shellcode 或 堆利用占位).data存放已初始化的全局/静态变量。⭐⭐ (查数据).rodata只读数据比如字符串常量/bin/sh。⭐⭐⭐ (找/bin/sh地址).got/.plt动态链接跳板与地址表。⭐⭐⭐⭐⭐ (核心攻击目标)3. 动态链接的枢纽.plt 与 .got 详解在 Week1 我们讲过“懒绑定”机制程序在第一次调用外部函数时才去寻找真实地址。这个过程就是靠.plt和.got配合完成的。3.1 .plt (过程链接表)属性属于代码段可读可执行通常不可写。作用它是一段段极其精简的跳板代码。程序中call printf实际上汇编是call printfplt。内部结构每个外部函数在.plt中都有一个小项比如 16 字节。它的核心逻辑是“去.got表里查地址并跳转过去”。3.2 .got (全局偏移表)属性属于数据段可读可写除非开启了 Full RELRO。作用它本质上是一个指针数组。每个表项占 4 字节32位或 8 字节64位用来存放外部函数在 libc 中的真实绝对地址。4. PWN 视角为什么要区分 .got 和 .got.plt用readelf -S查看节区时你可能会发现有两个长得像 GOT 的东西.got和.got.plt。它们有什么区别4.1.got这个表主要用于存放全局变量的地址比如引用其他动态库中的全局变量。在 PWN 中较少直接作为主要攻击目标。4.2.got.plt(重点中的重点)这个表专门用于存放外部函数的真实地址如puts,system,gets。这就是我们常说的“打 GOT 表”的那个表。.got.plt表的特殊结构前 3 项是特殊数据第一项存放.dynamic节的地址动态链接器使用。第二项存放link_map结构的地址动态链接器使用。第三项存放动态链接解析函数_dl_runtime_resolve的地址。第四项及以后才是我们熟悉的putsgot、getsgot等。4.3 攻击逻辑复盘在 Partial RELRO部分保护下.got.plt是可写的。程序调用puts第一次执行时动态链接器找到puts在 libc 的地址比如0x7ffff7a649c0然后把这个地址写入.got.plt表中puts对应的表项里。下次再调用puts程序直接从.got.plt读出0x7ffff7a649c0并跳转。攻击发生如果我们通过栈溢出或格式化字符串漏洞任意地址写把.got.plt表中puts的表项内容篡改成system函数的地址0x7ffff7a4c440。程序再次执行puts(hello)时去.got.plt读地址读出的是system的地址于是实际执行了system(hello)。这就是经典的GOT Table OverwriteGOT 表覆写攻击。而开启 Full RELRO 的原理就是在程序启动时一口气解析完所有函数地址填入.got.plt然后直接把这块内存设为只读从根源上掐断覆写可能。5. 结语理解了 ELF 的双视图以及.got.plt表的精确结构我们就能在后续实战中精准地算出目标函数 GOT 表的绝对地址。下一步我们将学习如何利用泄露出来的地址反查libc版本并计算system函数的偏移量。如果本篇文章对您有帮助请点赞收藏支持一下感谢阅读我们下一部分见