其实不光是"The House of Mind",在学习各种堆溢出漏洞的利用方法之前,都必须对glibc malloc()/free()的逻辑,有相当程度的了解,《Glibc内存管理--Ptmalloc2源代码分析》这份文档,通过129页的篇幅,已经分析的非常深刻和详细(如果没有积分下载文档,也可以去看作者的博客:https://www.iteye.com/blog/user/mqzhuang ),也可以看看我发过的一个帖子:https://bbs.pediy.com/thread-271331.htm ,先从外围了解glibc malloc()/free()的本质和设计目标,瞄一眼宏观的地形,再深入到茫茫的内部实现中,应该可以少迷点路。 另外,本文是对phrack杂志中一篇神作的总结和补充,所以exploit code和更完整的分析过程,请阅读原文:http://phrack.org/issues/66/10.html 。
"The House of Mind"是一种堆溢出漏洞的利用方法(为什么叫这个名称我目前还不知道),可以通过构造输入数据,让漏洞程序执行攻击者期望的任意代码(不过,不是所有存在堆溢出漏洞的程序,都可以利用这种方法进行攻击,需要漏洞程序满足一定条件,稍后具体说明)。 再具体一点就是,在应用程序分配到的内存周围,很多都是glibc内部使用的内存,程序存在漏洞,攻击者就有机会通过构造输入数据,溢出glibc内部使用的变量,进一步控制malloc()/free()的执行逻辑,最终借glibc之手,修改某个函数对应的got表项(可以理解为函数指针,感兴趣也可以看看我的另外一个帖子:https://bbs.pediy.com/thread-246373.htm ),使其指向一段shell code(同样通过用户输入构造),这样,当漏洞程序后续执行该函数时(比如.dtors()函数,它会在main()函数结束后执行),就会触发shell code执行。
为了满足"The House of Mind"的利用条件,作者提供了一个用于演示的漏洞程序(现实中这类漏洞当然会隐蔽的多,几乎不会存在这么饥渴难耐的想被宰割的程序)。
fread(ptr, 1024 * 1024, 1, stdin)这行代码,使攻击者有机会往0x804a008之后的1M内存,写入任意数据,这块内存中,有很多地方保存的是chunk->size,由glibc内部使用,通过构造溢出数据,控制这些地方的值,就可以达到欺骗glibc的效果,甚至还可以进一步欺骗glibc,将部分user data也当作自己内部使用的内存,为此,作者构造出了上述内存布局图(右)中的数据,当漏洞程序执行free(ptr2)时,glibc就会按照攻击者欺骗的流程执行。
(3) fake_arena->bins[2] = DTORS_END-12 根据布局图可以看出,fake_arena开始的8个字节,都被构造为0x102,剩余部分全部构造为DTORS_END-12,根据malloc_state结构的定义可知,这样构造肯定可以使fake_arena->bins[2] = DTORS_END-12。
使fake_arena->bins[2] = DTORS_END-12,是为了欺骗glibc修改got[.dtros]:
关于unsorted_chunks()函数返回&fake_arena->bins[0]的设计意图,可以进入这篇帖子:https://bbs.pediy.com/thread-271331.htm ,看看其中的bin结构图。
上述已经将攻击流程介绍完毕,但是实际能否成功,还要看到底能不能将glibc欺骗到"fwd->bk = p;"这一行代码上。
随着各种攻击技术的出现,glibc其实一直都在改造,以上看到的这些判断,很多就是为了缓解攻击,但glibc的改造,是受限于两个因素的:
所以,glibc只能缓存攻击,根本避免被攻击,在业务层的源头就要开始防范。
/
*
*
K
-
sPecial's vulnerable program
*
/
int
main (void) {
char
*
ptr
=
malloc(
1024
);
/
*
First allocated chunk
*
/
char
*
ptr2;
/
*
Second chunk
*
/
/
*
ptr & ~(HEAP_MAX_SIZE
-
1
)
=
0x08000000
*
/
int
heap
=
(
int
)ptr &
0xFFF00000
;
_Bool found
=
0
;
printf(
"ptr found at %p\n"
, ptr);
/
*
Print
address of first chunk
*
/
/
/
i
=
=
2
because this
is
my second chunk to allocate
for
(
int
i
=
2
; i <
1024
; i
+
+
) {
/
*
Allocate chunks up to
0x08100000
*
/
if
(!found && (((
int
)(ptr2
=
malloc(
1024
)) &
0xFFF00000
)
=
=
\
(heap
+
0x100000
))) {
printf(
"good heap allignment found on malloc() %i (%p)\n"
, i, ptr2);
found
=
1
;
/
*
Go out
*
/
break
;
}
}
malloc(
1024
);
/
*
Request another chunk: (ptr2 !
=
av
-
>top)
*
/
/
*
Incorrect
input
:
1048576
bytes
*
/
fread (ptr,
1024
*
1024
,
1
, stdin);
free(ptr);
/
*
Free first chunk
*
/
free(ptr2);
/
*
The House of Mind
*
/
return
(
0
);
/
*
Bye
*
/
}
/
*
*
K
-
sPecial's vulnerable program
*
/
int
main (void) {
char
*
ptr
=
malloc(
1024
);
/
*
First allocated chunk
*
/
char
*
ptr2;
/
*
Second chunk
*
/
/
*
ptr & ~(HEAP_MAX_SIZE
-
1
)
=
0x08000000
*
/
int
heap
=
(
int
)ptr &
0xFFF00000
;
_Bool found
=
0
;
printf(
"ptr found at %p\n"
, ptr);
/
*
Print
address of first chunk
*
/
/
/
i
=
=
2
because this
is
my second chunk to allocate
for
(
int
i
=
2
; i <
1024
; i
+
+
) {
/
*
Allocate chunks up to
0x08100000
*
/
if
(!found && (((
int
)(ptr2
=
malloc(
1024
)) &
0xFFF00000
)
=
=
\
(heap
+
0x100000
))) {
printf(
"good heap allignment found on malloc() %i (%p)\n"
, i, ptr2);
found
=
1
;
/
*
Go out
*
/
break
;
}
}
malloc(
1024
);
/
*
Request another chunk: (ptr2 !
=
av
-
>top)
*
/
/
*
Incorrect
input
:
1048576
bytes
*
/
fread (ptr,
1024
*
1024
,
1
, stdin);
free(ptr);
/
*
Free first chunk
*
/
free(ptr2);
/
*
The House of Mind
*
/
return
(
0
);
/
*
Bye
*
/
}
((heap_info
*
)((unsigned
long
)(ptr) & ~(HEAP_MAX_SIZE
-
1
)))
(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)
-
>ar_ptr : &main_arena)
((heap_info
*
)((unsigned
long
)(ptr) & ~(HEAP_MAX_SIZE
-
1
)))
(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)
-
>ar_ptr : &main_arena)
struct malloc_state {
/
*
Serialize access.
*
/
mutex_t mutex;
/
/
Should we have padding to move the mutex to its own cache line?
/
*
Statistics
for
locking. Only used
if
THREAD_STATS
is
defined.
*
/
long
stat_lock_direct, stat_lock_loop, stat_lock_wait;
/
*
The maximum chunk size to be eligible
for
fastbin
*
/
INTERNAL_SIZE_T max_fast;
/
*
low
2
bits used as flags
*
/
/
*
Fastbins
*
/
mfastbinptr fastbins[NFASTBINS];
/
*
Base of the topmost chunk
-
-
not
otherwise kept
in
a
bin
*
/
mchunkptr top;
/
*
The remainder
from
the most recent split of a small request
*
/
mchunkptr last_remainder;
/
*
Normal bins packed as described above
*
/
mchunkptr bins[NBINS
*
2
];
/
*
Bitmap of bins
*
/
unsigned
int
binmap[BINMAPSIZE];
/
*
Linked
list
*
/
struct malloc_state
*
next
;
/
*
Memory allocated
from
the system
in
this arena.
*
/
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
struct malloc_state {
/
*
Serialize access.
*
/
mutex_t mutex;
/
/
Should we have padding to move the mutex to its own cache line?
/
*
Statistics
for
locking. Only used
if
THREAD_STATS
is
defined.
*
/
long
stat_lock_direct, stat_lock_loop, stat_lock_wait;
/
*
The maximum chunk size to be eligible
for
fastbin
*
/
INTERNAL_SIZE_T max_fast;
/
*
low
2
bits used as flags
*
/
/
*
Fastbins
*
/
mfastbinptr fastbins[NFASTBINS];
/
*
Base of the topmost chunk
-
-
not
otherwise kept
in
a
bin
*
/
mchunkptr top;
/
*
The remainder
from
the most recent split of a small request
*
/
mchunkptr last_remainder;
/
*
Normal bins packed as described above
*
/
mchunkptr bins[NBINS
*
2
];
/
*
Bitmap of bins
*
/
unsigned
int
binmap[BINMAPSIZE];
/
*
Linked
list
*
/
struct malloc_state
*
next
;
/
*
Memory allocated
from
the system
in
this arena.
*
/
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
}
else
clear_inuse_bit_at_offset(nextchunk,
0
);
/
*
Place the chunk
in
unsorted chunk
list
. Chunks are
not
placed into regular bins until after they have
been given one chance to be used
in
malloc.
*
/
bck
=
unsorted_chunks(av);
/
/
返回:&fake_arena
-
>bins[
0
]
fwd
=
bck
-
>fd;
/
/
fd位于malloc_chunk结构体
8
字节偏移处
/
/
所以fwd
=
bck
-
>fd
=
fake_arena
-
>bins[
2
]
=
DTORS_END
-
12
p
-
>bk
=
bck;
p
-
>fd
=
fwd;
bck
-
>fd
=
p;
/
/
fake_arena
-
>bins[
2
]
=
p
fwd
-
>bk
=
p;
/
/
bk位于malloc_chunk结构体
12
字节偏移处
/
/
所以这里会将p,写到DTORS_END指向的内存单元,即:got[.dtors]
=
p
set_head(p, size | PREV_INUSE);
set_foot(p, size);
check_free_chunk(av, p);
}
}
else
clear_inuse_bit_at_offset(nextchunk,
0
);
/
*
Place the chunk
in
unsorted chunk
list
. Chunks are
not
placed into regular bins until after they have
been given one chance to be used
in
malloc.
*
/
bck
=
unsorted_chunks(av);
/
/
返回:&fake_arena
-
>bins[
0
]
fwd
=
bck
-
>fd;
/
/
fd位于malloc_chunk结构体
8
字节偏移处
/
/
所以fwd
=
bck
-
>fd
=
fake_arena
-
>bins[
2
]
=
DTORS_END
-
12
p
-
>bk
=
bck;
p
-
>fd
=
fwd;
bck
-
>fd
=
p;
/
/
fake_arena
-
>bins[
2
]
=
p
fwd
-
>bk
=
p;
/
/
bk位于malloc_chunk结构体
12
字节偏移处
/
/
所以这里会将p,写到DTORS_END指向的内存单元,即:got[.dtors]
=
p
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!