-
-
[原创][writeup]CTFHUB-LargeBin Attack|House of Storm
-
发表于: 2023-3-17 01:54 18031
-
由于本次利用相当的绕,我的语言表达和作图也并不够直白人,会看着非常晕,但我感觉我应该比大部分都要写的详细,如果你也被这题难住了,耐心看吧:),可能按顺序无法看明白对_int_malloc
的分析部分,不先讲清楚原理也不方便直接说例如FakeChunk的构造,要结合上下文和exp进行理解,我也有画草图,推荐自己分析一遍源码,以加深理解
本程序中对 申请 释放 和 打印 操作并没有严格的管理和检查,导致了 Use After Free漏洞 和 Unsorted Bin Leak
要使用 House of Storm 进行攻击,条件十分苛刻,需要以下条件均达成才可进行利用
再此我先说明需要构造的FreeChunkU(UnortedBin) 和FreeChunkL(LargeBin),不理解可以先看一眼,后面会详细说明
由于在申请FakeChunk的时候要求攻击者将FakeChunk指向一个可用的FakeChunk Header已绕过一系列如针对size的检查,如果程序允许攻击者随意申请任意大小(相对),那么或许可以直接利用程序中的内存区作为FakeChunk Header,但是如果程序规定了攻击者所能申请的HeapSize,那么FakeChunk Header的条件就较为苛刻,需要经过特殊手段伪造或寻找,而 House Of Storm 正是针对这一情况,进行针对FakeChunk Header的伪造
当用户申请的heapSize不属于 SmallBins FastBins Tcache 中任何一个时,则会对Unsorted bin进行由后向前的遍历(FIFO),参与当前遍历的FreeChunk在源码中记作 victim ,而当前FreeChunk的 后一个FreeChunk被记作 bck,在该循环中按顺序分别存在以下处理分支
格局上节分析已知,触发漏洞处位于_int_malloc()函数在对Unsorted Bin的FreeChunk分类并排序入Large bin时候发生,我们先看看引发漏洞的关键代码
当 FreeChunk Size > largeBins最后一个FreeChunk的size时,将会执行以上操作
我们暂时重点关注第四句,它将当前FreeChunk的字段bk_nextsize
所指向的fd_nextsize
修改为了当前FreeChunk的地址,而经过调试我们知道我们申请的heap地址起始总是0x55/0x56
,而在程序的Backdoor
函数中可申请一个0x48经过对齐后size同为0x50
的heap,所以我们可以利用该语句使字节错位将FreeChunk地址的0x55/0x56
写出从而伪造FakeChunk
经过上面分析我们已知,在 分支1 之后有一个操作,既将当前FreeChunk的bk(后一个FreeChunk)作为UnsortedBin的bk使其准备进入下一次循环,同时要修改该bk->fd使其指向UnsortedBin(双向链表),由于FreeChunkU的bk指向我们伪造的FreeChunk
,所以它的fd极有可能指向一个不可访问的区域,或者为0;但是我们回到上一节的分析中,重点关注bck = fwd->bk;
,它将bck
指向了FreeChunkL的bk
,而该字段是我们所伪造的FreeChunk+8
处,而在排序的结尾,有这么一个操作
可以看到,它将bck->fd
指向了FreeChunkU,这样在进入下一次循环当FakeChunk
作为victim
时,它的fd
字段将不会引发异常;若是freeChunkU size < freeChunkL size
,bck
将指向FreeChunkL,FakeChunk->fd
将不会被修改,依旧是一个不安全的指针
也许大部分人会和我一样对这块相当懵B,为什么跟着别人的exp篡改完FreeChunkU和FreeChunkL的bk bk_next size
,再申请就能申到FreeChunk了呢,不明白是个怎么流程,以下我会详细说明
当用户申请新的heap在fastBin/smallBin
中无法找到时,会来到UnsortedBin
查找,以下是该部分开头的代码
victim
为本次循环的FreeChunk
av
指的是一个main_arena
,我们知道所有bins都是main_arena
结构下的成员,而unsortedBin
只有一个,不像其它bins那样根据大小分类拥有多个,所以unsorted_chunks(av)
即为main_arena->bins[0]
,关于bin的更多内容请自行补充
该循环从UnsortedBins>bk
也即从该双向链表的尾部开始,只要尾指针不指着头,既代表当前bin中有FreeChunk
bck
为当前FreeChunk的后一个FreeChunk,提前保存将用于后续的链表操作作为UnsortedBin->bk
准备进入下一次循环
到这里我们可以解释我们的第一个构造 freeChunkU->bk = FakeChunk;
,为什么要让freeChunkU的bk指向FakeChunk了,因为我们想让第二次循环的时候来到FakeChunk并检查能否将其分配给我们,而实际上来到第二次循环之前,我们的FakeChunk就已经被伪造好了,现在我们依旧在第一次循环,我们继续往下分析
我们先补充几点,此时我们调用了back_door
函数,我们申请的size经过对齐后是0x50
,根据上面章节的分支分析我们可以知道前面的分支我们并不会参与,会知道来到LargeBin的处理,而我们也解释了关于FreeChunkU和FreeChunkL的大小关系,所以我们会来到LargeBin分支内的第二个分支,以下是源码
可以看到我们的第一次循环实际上在跳过第一次分支将bck(FakeChunk)嵌入UnsortedBin
后就直接来到了上述代码中,我们再回想一遍上面讲过的内容,也在此处解释一下freeChunkL->bk_nextSize = FakeChunk-0x18-5;
的目的,在重点的第二行victim->bk_nextsize = freeChunkL->bk_nextsize;
,也即此时victim->bk_nextsize
已经指向了FakeChunk-0x18-5
,而第四行的操作既是将FreeChunkU的地址写入FakeChunk-0x18-5
,由于字节错位,最终我们将得到一个size字段0x0000000000000056
,这样我们的FakeChunk已经伪造完毕了
但是到这里还没有结束,别忘了我们的构造2:FreeChunkL->bk=FakeChunk+8
,看到最后一句操作了吗,它将bck指向了FreeChunkL->bk
,这将为我们进入第二次循环提供了保障,在问题一中已经说过了,在Largebin排序分支结束后将由如下操作,这里结合起来强调一次
根据上面我们已知bck被指向了FakeChunk+8处,所以它实际上将FakeChunk->bk
修改为FreeChunkU#0
,而在进入第二次循环时,我们知道程序首先第一件事就是保存了bck = victim->bk
,而在第一个分支不成立后的如下操作
此处将bck作为一个Unsorted bin Chunk结构体引用了它的字段fd
,但若是bck
无法被引用,此处将导致程序意外退出;而只有当 FreeChunkU > FreeChunkL 时才能避免这个问题
至此我们的第一次循环已经结束了,FakeChunk
已经被伪造好、FakeChunk已经作为UnsortedBin->bk
准备进入第二次循环、第二次循环可能导致的崩溃也已经解决
进入第二次循环后,简单粗暴,用户申请的大小对齐后为0x50
,而我们的FakeChunk去掉标志位后size也为0x50
,直接进入 分支2,申请空间与victim size
相等,分配,return,_int_malloc结束
简单画了个图,命名不是很规范,也画的没那么细,因为这个东西确实太绕了,我自己也总给绕进去
可能有很多人和我一样看到分析的地方什么bk bck victim fwd fd av
都看的挺懵B吧,没办法,源码里就是这么命名的,也可能是我不专业,所以看起来膈应,但是在分析里要详细讲清楚bck fwd
到底指哪一个FreeChunk
,该FreeChunk
到底是第几个真的很绕,就连我自己都很容易看晕,所以真的强烈建议自己分析一遍源码,我就是看不明白别人的分析才这么干的;懒得看可以结合我画的草图分析整个流程是怎么样的
这次写的总结和分析也是相对比较乱的,没有从头开始比如构造什么什么然后构造的东西做了什么什么开始,而是简单提了一下就直接从源码开始了,因为这次的内容真的非常非常绕,文中我也强调了很多次,至于有多绕,我写的东西有多绕它就有多绕,而且比我还绕:)
我留下的EXP在远程上无法打通,因为gadGet条件不符合,不过也简单,加上reallocHook就行了,这里就不拓展该内容了
补呗,我刚看也头大,先把Unsorted Bin和Large Bin具体看明白了,我的图比较潦草但我感觉还能看。还有main_arena的结构,如果你和我一样纠结av
到底是个什么玩意的话:),要分析源码不把这俩bin结构整明白了,一会bck一会fwd,一会还tmfwd=bck
,你晕不晕吧就:|
- 目录
- 前言
- 程序分析
- IDA静态分析
- 伪代码分析
- main函数
- add函数
- dele函数
- edit函数【UAF】
- show函数【Leak】
- back_door函数
- 分析总结
- 漏洞利用及原理
- 可利用漏洞
- 1. House Of Storm
- 利用条件
- 情景分析
- 漏洞发生原因
- 结合Glibc-2.27源码分析_int_malloc函数中引发House Of Storm攻击的漏洞
- 问题1:为什么要求freeChunkU size > freeChunkL size ?
- 问题2:怎么我篡改完两个FreeChunk的字段调用backDoor突然就申请完了?
- 程序执行流程和bins情况(图解)
- Exploit
- 总结
- 关于源码分析中的各种变量名膈应不理解的问题
- 关于本章
- 关于EXP
- Bins那块看的头大怎么办
int
__cdecl main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
int
choice[
67
];
/
/
[rsp
+
Ch] [rbp
-
114h
] BYREF
unsigned __int64 v5;
/
/
[rsp
+
118h
] [rbp
-
8h
]
v5
=
__readfsqword(
0x28u
);
init(argc, argv, envp);
while
(
1
)
{
menu();
/
/
打印菜单
_isoc99_scanf(
"%d"
, choice);
switch ( choice[
0
] )
{
case
1
:
add();
/
/
break
;
case
2
:
dele();
break
;
case
3
:
edit();
break
;
case
4
:
show();
break
;
case
5
:
back_door();
break
;
default:
exit(
0
);
}
}
}
int
__cdecl main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
int
choice[
67
];
/
/
[rsp
+
Ch] [rbp
-
114h
] BYREF
unsigned __int64 v5;
/
/
[rsp
+
118h
] [rbp
-
8h
]
v5
=
__readfsqword(
0x28u
);
init(argc, argv, envp);
while
(
1
)
{
menu();
/
/
打印菜单
_isoc99_scanf(
"%d"
, choice);
switch ( choice[
0
] )
{
case
1
:
add();
/
/
break
;
case
2
:
dele();
break
;
case
3
:
edit();
break
;
case
4
:
show();
break
;
case
5
:
back_door();
break
;
default:
exit(
0
);
}
}
}
unsigned __int64 add()
{
int
counter;
/
/
eax
unsigned
int
chunkIndex;
/
/
ebx
unsigned
int
sizeIndex;
/
/
[rsp
+
4h
] [rbp
-
1Ch
] BYREF
unsigned __int64 v4;
/
/
[rsp
+
8h
] [rbp
-
18h
]
v4
=
__readfsqword(
0x28u
);
counter
=
add_count
-
-
;
if
( counter !
=
-
1
)
/
/
第六次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &sizeIndex);
if
( sizeIndex >
=
0xA
)
/
/
index<
10
{
puts(
"error"
);
exit(
0
);
}
puts(
"size:"
);
_isoc99_scanf(
"%d"
, &chunk_size[sizeIndex]);
if
( chunk_size[sizeIndex] !
=
0x430
&& chunk_size[sizeIndex] !
=
0x440
&& chunk_size[sizeIndex] !
=
0x450
)
{
puts(
"error"
);
exit(
0
);
}
chunkIndex
=
sizeIndex;
chunk[chunkIndex]
=
malloc(chunk_size[sizeIndex]);
}
return
__readfsqword(
0x28u
) ^ v4;
}
unsigned __int64 add()
{
int
counter;
/
/
eax
unsigned
int
chunkIndex;
/
/
ebx
unsigned
int
sizeIndex;
/
/
[rsp
+
4h
] [rbp
-
1Ch
] BYREF
unsigned __int64 v4;
/
/
[rsp
+
8h
] [rbp
-
18h
]
v4
=
__readfsqword(
0x28u
);
counter
=
add_count
-
-
;
if
( counter !
=
-
1
)
/
/
第六次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &sizeIndex);
if
( sizeIndex >
=
0xA
)
/
/
index<
10
{
puts(
"error"
);
exit(
0
);
}
puts(
"size:"
);
_isoc99_scanf(
"%d"
, &chunk_size[sizeIndex]);
if
( chunk_size[sizeIndex] !
=
0x430
&& chunk_size[sizeIndex] !
=
0x440
&& chunk_size[sizeIndex] !
=
0x450
)
{
puts(
"error"
);
exit(
0
);
}
chunkIndex
=
sizeIndex;
chunk[chunkIndex]
=
malloc(chunk_size[sizeIndex]);
}
return
__readfsqword(
0x28u
) ^ v4;
}
unsigned __int64 dele()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
dele_count
-
-
;
if
( counter !
=
-
1
)
/
/
第四次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
0
);
}
free((void
*
)chunk[index]);
/
/
只做了释放操作但是没有将指针置零,Use After Free漏洞
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 dele()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
dele_count
-
-
;
if
( counter !
=
-
1
)
/
/
第四次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
0
);
}
free((void
*
)chunk[index]);
/
/
只做了释放操作但是没有将指针置零,Use After Free漏洞
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 edit()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
edit_count
-
-
;
if
( counter !
=
-
1
)
/
/
第三次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
0
);
}
puts(
"content:"
);
read(
0
, (void
*
)chunk[index], chunk_size[index]);
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 edit()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
edit_count
-
-
;
if
( counter !
=
-
1
)
/
/
第三次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
0
);
}
puts(
"content:"
);
read(
0
, (void
*
)chunk[index], chunk_size[index]);
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 show()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
show_count
-
-
;
if
( counter !
=
-
1
)
/
/
第二次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
1
);
}
puts((const char
*
)chunk[index]);
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 show()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
show_count
-
-
;
if
( counter !
=
-
1
)
/
/
第二次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
1
);
}
puts((const char
*
)chunk[index]);
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 show()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
show_count
-
-
;
if
( counter !
=
-
1
)
/
/
第二次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
1
);
}
puts((const char
*
)chunk[index]);
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 show()
{
int
counter;
/
/
eax
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
counter
=
show_count
-
-
;
if
( counter !
=
-
1
)
/
/
第二次操作会失败
{
puts(
"idx:"
);
_isoc99_scanf(
"%d"
, &index);
if
( index >
=
0xA
)
{
puts(
"error"
);
exit(
1
);
}
puts((const char
*
)chunk[index]);
}
return
__readfsqword(
0x28u
) ^ v3;
}
/
/
FakeChunk是我们要申请的目标地址上方(
-
0x20
或
-
0x30
)
freeChunkU
-
>bk
=
FakeChunk;
freeChunkL
-
>bk
=
FakeChunk
+
8
;
freeChunkL
-
>bk_nextSize
=
FakeChunk
-
0x18
-
5
;
/
/
FakeChunk是我们要申请的目标地址上方(
-
0x20
或
-
0x30
)
freeChunkU
-
>bk
=
FakeChunk;
freeChunkL
-
>bk
=
FakeChunk
+
8
;
freeChunkL
-
>bk_nextSize
=
FakeChunk
-
0x18
-
5
;
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
bck
=
fwd
-
>bk;
/
/
重点
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
bck
=
fwd
-
>bk;
/
/
重点
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
/
重点
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
/
重点
while
((victim
=
unsorted_chunks(av)
-
>bk) !
=
unsorted_chunks(av))
{
bck
=
victim
-
>bk;
while
((victim
=
unsorted_chunks(av)
-
>bk) !
=
unsorted_chunks(av))
{
bck
=
victim
-
>bk;
victim_index
=
largebin_index(size);
bck
=
bin_at(av, victim_index);
/
/
bck
=
largebins[
1
],既大小分类的第二个
bin
fwd
=
bck
-
>fd;
/
/
fwd
=
该分类下的第一个freeChunk
if
(fwd !
=
bck)
/
/
如果largeBins下有FreeChunk,则进行排序
{
size |
=
PREV_INUSE;
/
/
size
=
victim的大小
assert
(chunk_main_arena(bck
-
>bk));
if
((unsigned
long
)(size) < (unsigned
long
)chunksize_nomask(bck
-
>bk)){
......
/
/
这里我们不关注,已经解释过该分支为什么不可行了
}
else
{
assert
(chunk_main_arena(fwd));
/
/
对freeChunkL进行检查
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;
else
{
/
/
↓↓↓↓↓↓↓↓↓↓↓↓重点↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
bck
=
fwd
-
>bk;
/
/
重点
}
victim_index
=
largebin_index(size);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!