Pwn ret2libc in x86
原理
共享目标文件是 PIC (地址无关代码) 的, 对其中的符号进行寻址是 相对寻址, 这也说明了整个 .so 装载后是一个不可分割的整体, 各个符号之间的相对距离 (或者各个指令的相对距离) 并不会变. 这样, 即使程序开启了 ASLR (地址随机化, 动态链接 .so 的时候会装载到随机的地址上), 我们只要知道程序用的是哪一个版本的 libc.so (进而知道各个符号之间的相对距离), 且知道某一个符号当前装载在内存中的地址, 那么理论上来说, 我们就可以计算出任意符号 (或者直接就是 .so 的某个地址)当前装载在内存中的地址. libc.so 中有 system 函数, “/bin/sh” 字符串等可供我们利用的符号或数据.
那么现在的问题在于:
- 怎么知道程序使用的是什么版本的 libc.so
- 怎么知道某一个符号当前装载在内存中的地址
先来看第一个问题.
虽然 .so 在装载后的地址是随机化的, 但是地址的低 12 位是固定的 (随机了, 但没完全随机). 因为程序装载的时候, 会涉及到虚拟内存向物理内存的映射, 而这种映射是以页为单位的, 页需要对齐, 而一个页的大小为 0x1000 (4096) 字节. 程序装载的时候, 每个节的对齐粒度都是 0x1000, 这也就说明了为什么 .so 中指令, 符号或者数据的低 12 位是固定的. 关于页映射, 目前没有搞得很明白, 详细请参考 «程序员的自我修养».
如果知道某一个符号的低 12 位是什么, 那么直接拿它和现有的所有版本的 libc.so 去对比, 就可以找到程序用的是什么版本的 libc.so 了. 对没错就是暴力枚举. 当然可能匹配到多个版本的 libc.so, 这时就得提供更多的信息了, 比如拿两个甚至多个符号的低 12 位去爆搜, 直到确定某一个版本的 libc.so. 或者暴力把所有符合条件的 libc.so 都跑一遍, 总有一个是对的.