-
-
[原创][writeup]CTFHUB-FastBin Attack
-
发表于: 2023-3-13 15:49 18149
-
根据IDA静态分析已知存在堆溢出漏洞,且申请的堆大小固定,释放后会进入fastbins,所以考虑通过篡改fastbin->fd来申请fakeChunk,现查找可利用的fd
其中0x6020c0
是全局变量heapList
的地址,其中存着每一个heap的指针,如果可以将该指针修改就可以达到任意读/写,并且在heapList
上方存在可利用的内容,通过字节错位将fd
指针定于0x60209d
,内存如下
可以看到若chunk->fd=0x60209d
时,size
字段为0x7f
即0111 1111
,而其中末4位为标志位高到低分别是PREV_INUSE IS_MMAPPED NON_MAIN_ARENA SIZE_BITS
,既实际大小为0111 0000
即0x70
,由于我们申请的heap大小固定为0x60
,加上字段大小后即0x70
,最终的fastbins
大小分类一致,可用作构造FakeChunk
根据分析可以总结出一下三点:
根据分析已知
根据上述条件准备进行以下攻击
对于glibc堆管理的各类bins详细请参见
CTF竞赛权威指南(Pwn篇)->11.1.3章
以下为简述:
程序中申请的大小为0x60的heap释放后均会进入fastbins->0x70
分类中(由于glibc版本问题所以并不会进入tcache
,调试时请注意使用的glibc版本);
fastbins
是一个后进先出的单链表,除分类中第一个进入的chunk
外的每一个chunk->fd
字段都指向上一个进入fastbins
的chunk
,也即当前chunk#1
被弹出后下一个应该被弹出的chunk#0
,既然这样我们就可以通过申请连续的chunk#0 chunk#1 chunk2
并按顺序释放#chunk2、#chunk1
,此时的chunk1->fd
为#chunk2
,意为当我们再次申请大小为0x60
时会弹出chunk1
并将chunk->fd
作为下一个预备弹出的chunk
此时我们通过edit()
编辑chunk#0
并且使用堆溢出篡改chunk1->fd
使其指向一个fakeChunk
,以达到读写fakeChunk
的目的;注意:该FakeChunk->size需要符合fastbins的大小分类即0x70
,根据上述分析已知在全局变量heapList
上方存在符合条件的FakeChunk
内存区,所以我们将chunk#1->fd
指向该内存区,将其申请到手后可即可结合show() edit()
进行任意读写
可以任意读后即可泄露LibcBase
,将函数got
地址填入heapList[0]
后使用show()
函数即可达到泄露,而将&__malloc_hook
或&__free_hook
填入即可篡改此两个hook的指向,此两个hook的指向在分别在malloc()
和free()
函数调用时被调用,所以我们可以向其中填入oneGadget并再次调用add()
或者delete()
使其执行并且getShell
int
__cdecl __noreturn main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
init(argc, argv, envp);
interface();
}
void __noreturn interface()
{
int
choice;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v1;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v1
=
__readfsqword(
0x28u
);
while
(
1
)
{
while
(
1
)
{
menu();
/
/
打印菜单;输入
1
:申请heap;输入
2
:释放heap;输入
3
:打印Heap;输入
4
:编辑Heap
__isoc99_scanf(
"%d"
, &choice);
if
( choice !
=
1
)
break
;
add();
/
/
申请heap,固定大小
0x60
,最多申请
10
个
}
switch ( choice )
{
case
2
:
delete();
/
/
释放heap,不清空heap,但是heapList的指针会置
0
break
;
case
3
:
show();
/
/
printf(
"%s"
,heapList[index]
-
>heap)
break
;
case
4
:
edit();
/
/
编辑heap中内容,有长度检查,但不完全有:) !!!EXP Point!!!
break
;
default:
exit(
-
1
);
}
}
}
int
__cdecl __noreturn main(
int
argc, const char
*
*
argv, const char
*
*
envp)
{
init(argc, argv, envp);
interface();
}
void __noreturn interface()
{
int
choice;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v1;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v1
=
__readfsqword(
0x28u
);
while
(
1
)
{
while
(
1
)
{
menu();
/
/
打印菜单;输入
1
:申请heap;输入
2
:释放heap;输入
3
:打印Heap;输入
4
:编辑Heap
__isoc99_scanf(
"%d"
, &choice);
if
( choice !
=
1
)
break
;
add();
/
/
申请heap,固定大小
0x60
,最多申请
10
个
}
switch ( choice )
{
case
2
:
delete();
/
/
释放heap,不清空heap,但是heapList的指针会置
0
break
;
case
3
:
show();
/
/
printf(
"%s"
,heapList[index]
-
>heap)
break
;
case
4
:
edit();
/
/
编辑heap中内容,有长度检查,但不完全有:) !!!EXP Point!!!
break
;
default:
exit(
-
1
);
}
}
}
unsigned __int64 add()
{
unsigned
int
i;
/
/
[rsp
+
8h
] [rbp
-
1018h
]
unsigned
int
index;
/
/
[rsp
+
Ch] [rbp
-
1014h
]
char v3[
4096
];
/
/
[rsp
+
10h
] [rbp
-
1010h
] BYREF
unsigned __int64 v4;
/
/
[rsp
+
1018h
] [rbp
-
8h
]
v4
=
__readfsqword(
0x28u
);
memset(v3,
0
, sizeof(v3));
for
( i
=
0
; i <
=
9
;
+
+
i )
{
if
( !
*
(&heapList
+
i) )
/
/
检测heapList下哪个索引没有使用
{
index
=
i;
break
;
}
}
if
( i
=
=
11
)
/
/
最多只能申请
10
个
{
puts(
"wrong"
);
exit(
0
);
}
*
(&heapList
+
index)
=
malloc(
0x60uLL
);
/
/
固定申请
0x60
字节并存放于全局变量heapList中
Size[index]
=
96
;
/
/
没什么D用
puts(
"Done"
);
return
__readfsqword(
0x28u
) ^ v4;
}
unsigned __int64 add()
{
unsigned
int
i;
/
/
[rsp
+
8h
] [rbp
-
1018h
]
unsigned
int
index;
/
/
[rsp
+
Ch] [rbp
-
1014h
]
char v3[
4096
];
/
/
[rsp
+
10h
] [rbp
-
1010h
] BYREF
unsigned __int64 v4;
/
/
[rsp
+
1018h
] [rbp
-
8h
]
v4
=
__readfsqword(
0x28u
);
memset(v3,
0
, sizeof(v3));
for
( i
=
0
; i <
=
9
;
+
+
i )
{
if
( !
*
(&heapList
+
i) )
/
/
检测heapList下哪个索引没有使用
{
index
=
i;
break
;
}
}
if
( i
=
=
11
)
/
/
最多只能申请
10
个
{
puts(
"wrong"
);
exit(
0
);
}
*
(&heapList
+
index)
=
malloc(
0x60uLL
);
/
/
固定申请
0x60
字节并存放于全局变量heapList中
Size[index]
=
96
;
/
/
没什么D用
puts(
"Done"
);
return
__readfsqword(
0x28u
) ^ v4;
}
unsigned __int64 delete()
{
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v2;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v2
=
__readfsqword(
0x28u
);
puts(
"Index:"
);
__isoc99_scanf(
"%d"
, &index);
if
( index >
0xB
)
{
puts(
"wrong"
);
exit(
0
);
}
free(
*
(&heapList
+
index));
*
(&heapList
+
index)
=
0LL
;
/
/
heap指针被置零了,没法double free
Size[index]
=
0
;
return
__readfsqword(
0x28u
) ^ v2;
}
unsigned __int64 delete()
{
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v2;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v2
=
__readfsqword(
0x28u
);
puts(
"Index:"
);
__isoc99_scanf(
"%d"
, &index);
if
( index >
0xB
)
{
puts(
"wrong"
);
exit(
0
);
}
free(
*
(&heapList
+
index));
*
(&heapList
+
index)
=
0LL
;
/
/
heap指针被置零了,没法double free
Size[index]
=
0
;
return
__readfsqword(
0x28u
) ^ v2;
}
unsigned __int64 show()
{
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v2;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v2
=
__readfsqword(
0x28u
);
puts(
"Index:"
);
__isoc99_scanf(
"%d"
, &index);
if
(
*
(&heapList
+
index) )
printf(
"Content: %s\n"
, (const char
*
)
*
(&heapList
+
index));
/
/
从堆指针起始按字符串打印内容
return
__readfsqword(
0x28u
) ^ v2;
}
unsigned __int64 show()
{
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v2;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v2
=
__readfsqword(
0x28u
);
puts(
"Index:"
);
__isoc99_scanf(
"%d"
, &index);
if
(
*
(&heapList
+
index) )
printf(
"Content: %s\n"
, (const char
*
)
*
(&heapList
+
index));
/
/
从堆指针起始按字符串打印内容
return
__readfsqword(
0x28u
) ^ v2;
}
unsigned __int64 edit()
{
int
readLength;
/
/
[rsp
+
0h
] [rbp
-
10h
] BYREF
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
puts(
"Index:"
);
__isoc99_scanf(
"%d"
, &index);
/
/
用户输入要编辑的heap索引
puts(
"Size:"
);
__isoc99_scanf(
"%d"
, &readLength);
/
/
用户输入要编辑的长度
if
( readLength <
=
96
)
/
/
有长度检查,但不完全有,有符号数比较输入负数绕过
{
if
(
*
(&heapList
+
index) )
{
puts(
"Content:"
);
read(
0
,
*
(&heapList
+
index), (unsigned
int
)readLength);
/
/
read长度参数是无符号数
}
else
{
puts(
"wrong"
);
}
}
else
{
puts(
"wrong!"
);
}
return
__readfsqword(
0x28u
) ^ v3;
}
unsigned __int64 edit()
{
int
readLength;
/
/
[rsp
+
0h
] [rbp
-
10h
] BYREF
unsigned
int
index;
/
/
[rsp
+
4h
] [rbp
-
Ch] BYREF
unsigned __int64 v3;
/
/
[rsp
+
8h
] [rbp
-
8h
]
v3
=
__readfsqword(
0x28u
);
puts(
"Index:"
);
__isoc99_scanf(
"%d"
, &index);
/
/
用户输入要编辑的heap索引
puts(
"Size:"
);
__isoc99_scanf(
"%d"
, &readLength);
/
/
用户输入要编辑的长度
if
( readLength <
=
96
)
/
/
有长度检查,但不完全有,有符号数比较输入负数绕过
{
if
(
*
(&heapList
+
index) )
{
puts(
"Content:"
);
read(
0
,
*
(&heapList
+
index), (unsigned
int
)readLength);
/
/
read长度参数是无符号数
}
else
{
puts(
"wrong"
);
}
}
else
{
puts(
"wrong!"
);
}
return
__readfsqword(
0x28u
) ^ v3;
}
pwndbg> x
/
30gx
0x6020c0
-
0x50
0x602070
:
0x0000000000000000
0x0000000000000000
0x602080
<stdout@@GLIBC_2.
2.5
>:
0x00007ffff7bc4620
0x0000000000000000
0x602090
<stdin@@GLIBC_2.
2.5
>:
0x00007ffff7bc38e0
0x0000000000000000
0x6020a0
<stderr@@GLIBC_2.
2.5
>:
0x00007ffff7bc4540
0x0000000000000000
0x6020b0
:
0x0000000000000000
0x0000000000000000
0x6020c0
<heapList>:
0x0000000000000000
0x0000000000000000
0x6020d0
<heapList
+
16
>:
0x0000000000000000
0x0000000000000000
0x6020e0
<heapList
+
32
>:
0x0000000000000000
0x0000000000000000
0x6020f0
<heapList
+
48
>:
0x0000000000000000
0x0000000000000000
0x602100
<heapList
+
64
>:
0x0000000000000000
0x0000000000000000
0x602110
:
0x0000000000000000
0x0000000000000000
0x602120
<Size>:
0x0000000000000000
0x0000000000000000
0x602130
<Size
+
16
>:
0x0000000000000000
0x0000000000000000
0x602140
<Size
+
32
>:
0x0000000000000000
0x0000000000000000
pwndbg> x
/
30gx
0x6020c0
-
0x50
0x602070
:
0x0000000000000000
0x0000000000000000
0x602080
<stdout@@GLIBC_2.
2.5
>:
0x00007ffff7bc4620
0x0000000000000000
0x602090
<stdin@@GLIBC_2.
2.5
>:
0x00007ffff7bc38e0
0x0000000000000000
0x6020a0
<stderr@@GLIBC_2.
2.5
>:
0x00007ffff7bc4540
0x0000000000000000