首先ida
查看一下该程序,程序一共提供了四种功能,分别是add,delete,show,edit
四个函数,其中show
函数只能够调用一次,edit
函数只能调用两次。比较特殊的一个点就是该程序使用的GLIBC 2.32
。我们首先来分析一下所有的函数,首先是add
函数
<!-- more -->
__int64 add()
{
__int64 result; // rax
unsigned int i; // [rsp+8h] [rbp-18h]
unsigned int size; // [rsp+Ch] [rbp-14h]
void *size_4; // [rsp+10h] [rbp-10h]
puts("Size:");
size = myRead();
if ( size > 0x7E )
size = 0x7F;
size_4 = malloc(size);
for ( i = 0; i <= 0xF; ++i )
{
if ( !noteList[i] )
{
noteList[i] = size_4;
global_index = i;
break;
}
}
result = noteList[global_index];
if ( result )
{
puts("Content:");
read(0, size_4, size);
result = 0LL;
}
return result;
}
从这里可以看出,一共最多可以分配0x10
个堆块,并且每个堆块的大小要<=0x90
,将新申请的堆块的index
赋给了global index
。看一下delete
函数。
void del()
{
free((void *)noteList[global_index]);
}
函数很简单,删除当前global index
,并且这里没有清空列表中存储的堆块指针,也就是存在UAF
。但是需要注意的是这里只能删除了global index
指向的堆块。结合add
函数来看,只能删除新申请的堆块,之前的旧的堆块无法进行删除。再来看一下show
函数。
ssize_t show()
{
return write(1, (const void *)noteList[global_index], 8uLL);
}
也就是输出了当前global index
指向堆块的前8
字节。最后看一下edit
函数
ssize_t edit()
{
puts("Content:");
return read(0, (void *)noteList[global_index], 0x10uLL);
}
这里也是对global index
指向的堆块进行0x10
字节大小的edit
。
从上述的函数分析来看,这里的对于堆块的操作仅仅限于当前的堆块。程序中存在一个UAF
。
那么这里利用UAF
我们仅仅可以泄漏出堆地址,并且这还是由于2.32
特性的原因。其最主要的一个点就是进行了tcache->fd
指针的加密。
#define PROTECT_PTR(pos, ptr) \\\\
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
也就是进行了抑或加密。那么这里就和其他版本的tcache
不一样了。我们看一下释放一个堆块之后的堆块内容。
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555757310 (size : 0x20cf0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x80) tcache_entry[6](1): 0x5555557572a0 --> 0x555555757 (invaild memory)
pwndbg> x/20gx 0x5555557572a0
0x5555557572a0: 0x0000000555555757 0x0000555555757010
0x5555557572b0: 0x0000000000000000 0x0000000000000000
0x5555557572c0: 0x0000000000000000 0x0000000000000000
0x5555557572d0: 0x0000000000000000 0x0000000000000000
0x5555557572e0: 0x0000000000000000 0x0000000000000000
0x5555557572f0: 0x0000000000000000 0x0000000000000000
0x555555757300: 0x0000000000000000 0x0000000000000000
0x555555757310: 0x0000000000000000 0x0000000000020cf1
0x555555757320: 0x0000000000000000 0x0000000000000000
0x555555757330: 0x0000000000000000 0x0000000000000000
pwndbg>
也就是说如果我们此时调用show
函数就可以泄漏出一个堆地址。那么得到这个堆地址之后就可以利用两次edit
的机会构造double free
,覆写fd
指针,使得我们可以分配到pthread_tcache_struct
结构体所在的堆块进而控制tcache
的count
和entry
指针,从而实现任意的地址分配。