• 有时候程序并没有直接提供返回/bin/sh的函数地址可供覆盖,这时候我们就需要去libc.so(C 语言的标准动态链接库)来找我们需要的函数。

    libc.so(C 语言的标准动态链接库)

  • 概念:它是 C 语言的标准动态链接库。里面包含了成千上万个编译好的函数实体代码,比如 printfreadputs,以及后门函数 system
  • 物理特性:它是一个极其庞大的独立文件
  • ASLR 机制:程序运行时,操作系统为了防攻击,会把 libc.so 加载到内存中的一个完全随机的基地址,导致我们不能简单的去泄漏想要的函数。

    GOT 表

  • 概念:全局偏移表。它存在于程序(ELF文件)内部,本质上是一个指针数组(一排内存格子)。
  • 作用:专门用来存放 libc.so 中各个函数的真实、绝对内存地址。
  • 物理特性:它被分配在程序的数据段。这意味着它是可读、可写的。
  • 结论:只要能查到 GOT 表里的值,就能破解 ASLR,泄露地址。

    PLT 表

  • 概念:过程链接表。它也存在于你的程序内部,是一排微小的代码片段(汇编指令)。
  • 作用:因为你的代码不知道 libc.so 的地址,所以当代码想调用 printf 时,它不能直接 call printf,而是 call printf@plt。PLT 表的作用就是去读取 GOT 表,然后跳过去。
  • 物理特性:它被分配在程序的代码段。这意味着它是只读、可执行的,而且在没有开启 PIE 保护的程序中,PLT 表的地址是固定死不变的。

Lazy Binding机制

阶段一:第一次调用 printf

  1. 代码跳转:主程序执行了一句 call printf@plt,跳到了 PLT 表里属于 printf 的那个小蹦床。
  2. 查看通讯录:PLT 蹦床的汇编指令会去读 printf@got 这个格子,想拿到真实地址。
  3. 发现没写:因为是第一次运行,GOT 表里还没有填入真实的 libc 地址,里面填的是一个指回 PLT 表的“默认假地址”。
  4. 呼叫系统解析:顺着这个假地址,程序跳到了系统的动态解析器(ld.so)。解析器去茫茫内存中找到了 libc.soprintf 的真实物理地址。
  5. 写入 GOT 表(关键):解析器不仅把程序引导到了 printf 去执行,还顺手把刚才找到的真实物理地址,死死地刻在了 printf@got 这个格子里

阶段二:第二次及以后调用 printf(光速直达)

  1. 代码跳转:主程序再次 call printf@plt
  2. 查看通讯录:PLT 蹦床再次去读 printf@got 这个格子。
  3. 直接起飞:这一次,GOT 表里已经赫然写着真实的绝对地址了!PLT 拿到地址,直接跳转到 libc.so 内部执行,再也不用找解析器了。

这里贴一道pwn的栈结构作为备忘

==== 低地址 (Low Address) | 栈顶方向 ==== 
                      |
                      |  <-- 你的输入 (buf) 从这里开始写入,像倒水一样往下流
                      V
+---------------------+  <-- ebp - 0x70
|                     |
|      buf[100]       |  (这里足足有 100 个字节的空间)
|                     |  (Payload 第一部分:b'A' * 100,刚好填满这个大坑)
|                     |
+---------------------+  <-- ebp - 0x0C
|     Canary (v3)     |  (金丝雀,占 4 个字节)
|                     |  (Payload 第二部分:p32(canary),悄悄把真密码放回去)
+---------------------+  <-- ebp - 0x08
|                     |
|  对齐缝隙 / 杂物区  |  (编译器为了对齐留下的空白,占 8 个字节)
|                     |  
+---------------------+  <-- ebp + 0x00
|  旧 EBP (Saved EBP) |  (保存着上一个函数的抽屉底,占 4 个字节)
|                     |  (Payload 第三部分:b'B' * 12,无情碾压过缝隙和旧EBP)
+---------------------+  <-- ebp + 0x04
|       返回地址      |  (也就是 Return Address,占 4 个字节)
|                     |  (Payload 第四部分:p32(system_addr))
+---------------------+  <-- ebp + 0x08
|     假的返回地址    |  (system 执行完去哪?我们随便填 b'AAAA')
+---------------------+  <-- ebp + 0x0C
|  传给 system 的参数 |  (也就是 p32(binsh_addr),指引它去执行 "/bin/sh")
+---------------------+
                      |
==== 高地址 (High Address) | 栈底方向 ====