ret2dl
前提
1 | 1、理解ELF |
这里以http://pwn4.fun/2016/11/09/Return-to-dl-resolve/中的bof文件为例
查找何处调用write函数
1 | objdump -d bof|grep write |
发现程序只在0x804859a处调用了一次write函数,且write函数在plt表中的地址为0x80483d0。
下面进行gdb调试
在0x804859a处下断点
1 | gdb-peda$ b *0x804859a |
查看plt表中write函数的内容,此时write函数不曾被调用
程序跳转到0x804a01c处,查看该地址内容
0x804a01c是write函数在plt表中的地址,因为write函数未被调用,所以此时got表中没有存放write函数的真实地址,而是存放write函数在plt表中的下一条地址,执行push 0x20。
接着程序跳转到0x8048380,这是plt表的入口地址,查看该地址即plt[0]内容
程序将0x804a004处的内容入栈,0x0804a004即got[1],因此该处的内容为link_map。
程序接着执行0x804a008即got[0],即调用动态装载器中 _dl_runtime_resolve(link_map,reloc_arg=0x20) 函数。
1 | 0x8048380: push DWORD PTR ds:0x804a004 --->0xf7ffd950 ---> link_map |
下面是_dl_runtime_resolve找到write函数的真实地址并写入got表的过程
首先通过计算JMPREL[0x20]找到.rel.plt
中的write函数,因为JMPREL映射到.rel.plt
。查看JMPREL地址
JMPREL[0x20] = 0x8048350
1 | r_offset=0x804a01c #即write的got表地址 |
根据r_info找到.dynsym
表中的st_name即计算SYMTAB[(r_info>>8)*0x10],SYMTAB映射到.dynsym
,其中0x10为.dynsym
每一项的大小。查看SYMTAB的地址
SYMTAB[(r_info>>8)*0x10] = 0x8048238
1 | st_name=0x4c #动态符号在 .dynstr 表(动态字符串表)中的偏移 |
根据st_name找到.dynstr
表中write符号即计算STRTAB[0x4c],STRTAB映射到.dynstr
STRTAB[0x4c] = 0x80482c4
找到write符号,后面就是系统调用的事了。按n
继续执行程序,查看got表中的write函数
got表中write函数更新为write的真实地址,以上便是延迟绑定技术
。
如果修改.dynstr
中write符号所在地址的值,结果又会如何。下面重新运行程序,将write
修改为read
按n
继续执行程序,查看got表中的write函数
成功修改!
因此我们可以修改.dynstr
表来执行我们想要的任意函数,然而.dynstr
表不可写。
加入我们在某一可写地址处写入system
,然后伪造.dynsym
使STRTAB[st_name]指向system
,接着伪造.ret.plt
使SYMTAB[r_info>>8 * 0x10]指向伪造的.dynsym
,然后想栈顶写入指向伪造.ret.plt
的偏移reloc_arg,最后控制eip指向plt[0],即可执行system函数。
exp如下:
1 | from pwn import * |
exp分析:
首先执行read函数:向base_stage中读入100字节
1 | base_stage :'AAAA' |
接着返回到ppp_ret,pop掉三个参数,返回ebp_pop,将base_stage传入ebp,接着返回leave,将ebp中的base_stage传入esp。此时esp指向base_stage,然后pop ebp
1 | base_stage :'AAAA' |
返回plt_0
1 | base_stage :'AAAA' |
plt_0将link_map入栈
1 | base_stage :'AAAA' |
接着调用_dl_runtime_solve()函数,执行system(‘/bin/sh’)。
参考链接:
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
https://ctf-wiki.github.io/ctf-wiki/executable/elf/elf-structure-zh/