glibc版本查看_GLIBC: heap basic2

a9340748bfa656ed6c9f129337991a74.png

section 0 preface

本文主要内容是glibc heap的fastbin,使用glibc-2.27版本。

section I fastbin overview

在引入tcache之后,fastbin的优先级仅次于tcachebin。fastbin由arena进行直接维护,因此arena中,即malloc_state中专门有一个指针数组fastbinY[],大小是10,可以存放10个链表,每个链表对对应一个size。从0x20bytes开始,按0x10bytes递增。在同一个链表上的chunk的size都是相同的。一般就使用到0x80bytes为止。

6d45e27f5b4da09f74ad1490dd82a60f.png

fastbin,与tcachebin chunk使用的结构体相同,仅仅取用fd指针,bin中是单链表,且始终设置prev_inuse bit,不论前一个是否inuse,原因在于,glibc经常会整理堆的碎片,当遇到几个相邻的、空闲的chunk的时候,就会将这几个chunk合并,形成一个新的大chunk。因此fastbin中的chunk如果全都设置为prev_inuse,那么他们就不会被合并,相当于保留了内存的碎片。

由于避免了复杂的合并(以及分割),且fastbin的检查系统也相对较少。因此fastbin的处理速度较快。

fastbin与tcachebin同,都是用LIFO策略。“最近释放的 chunk 会更早地被分配,所以会更加适合于局部性”,觉得很有道理。

只有在调用malloc_consolidate() 的时候,才会对fastbin中的chunk进行合并。

section II 源码分析

先说malloc部分:

早在__libc_malloc()阶段,就已经找过tcache了,所以这里的代码是_int_malloc()中的,fastbin是第一个检查的 :

/*
     If the size qualifies as a fastbin, first check corresponding bin.
     This code is safe to execute even if av is not yet initialized, so we
     can try it without checking, which saves some time on this fast path.
   */

#define REMOVE_FB(fb, victim, pp)			
  do							
    {							
      victim = pp;					
      if (victim == NULL)				
	break;						
    }							
  while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) 
	 != victim);					

 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
       /*  nb是new block的size              等于global_max_fast,这个值可以通过设置改变
           由req计算                        64bit默认下,((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK)
                                                          128     8                15  --> 0x80
            */
    {
      idx = fastbin_index (nb);               //取得index,略
      mfastbinptr *fb = &fastbin (av, idx);   //取得对应链表
      mchunkptr pp;
      victim = *fb; //解引用链表,得到表头,即对应chunk

      if (victim != NULL)  //chunk是ok的
	{
	  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)); //### check ###
	      if (__builtin_expect (victim_idx != idx, 0)) /*要求链表中的victim的
                                                             index(由size计算)符合要求
                                                             否则报错 */
		malloc_printerr ("malloc(): memory corruption (fast)");
	      check_remalloced_chunk (av, victim, nb);                //### check ###
#if USE_TCACHE
	     /* While we're here, if we see other chunks of the same size,
		 stash them in the tcache. 如果开启了tcache机制,就把
                 fb中size对应的新表头block,放到tcachebin里 高效
             */
	      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;//fb上表头后移,原来的表头,放到tb里去
		      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); //返回的指针是指向userdata的
	      alloc_perturb (p, bytes);
	      return p;
	    }
	}
    }

逻辑上总结一下:

使用用户设定的request size计算出new block的size,然后判断其所属于的bin范围,发现其小于等于get_max_fast() ,则开始探索在fastbin中是否有适合的chunk可以直接满足这个request。计算出相应的fastbin index,查看对应链表,如果其为空,就说明fastbin中没有相应的chunk。如果找到了,在函数返回这个链表的表头节点,即我们的target之前,有一些事情要做:首先,进行一定的检查,这次malloc是否合法。具体检查的内容查看源码及注释;然后。在tcache机制下,有额外的事情要做,即将同一个链表中的之后所有节点,加入到对应size的tcache bin中去。这个操作也是可以理解的,tcachebin的速度比fastbin更加快,将同一个size的chunk加入到tcachebin中,一定是更加高效的。做完这两件事,返回我们的target。

此外,源码中有几个涉及SINGLE_THREAD_P的分支,大概都是和多线程相关的,尚未仔细阅读理解。

接下来看看free部分的源码,_int_free()部分包含了tcache部分,我们跳过这部分,且可以注意一下:static void _int_free (mstate av, mchunkptr p, int have_lock),第一个参数是我们的chunk所对应的arena,第二个参数是我们想要free的chunk,第三个则是一个有关锁的flag。

我们将识别指针p所指向的chunk,并提取其中的size字段。

 /*
    If eligible, place chunk on a fastbin so it can be found
    and used quickly in malloc.
  */

  if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
                  /* size of P */
#if TRIM_FASTBINS
      /*
	If TRIM_FASTBINS set, don't place chunks
	bordering top into fastbins
        默认关闭
      */
      && (chunk_at_offset(p, size) != av->top)
#endif
      ) {
                          //检查下一个chunk的size是否合法(准确的说是:是否过于夸张...)
    if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
			  <= 2 * SIZE_SZ, 0)
	|| __builtin_expect (chunksize (chunk_at_offset (p, size))
			     >= av->system_mem, 0))
      {
	bool fail = true;
	/* We might not have a lock at this point and concurrent modifications
	   of system_mem might result in a false positive.  Redo the test after
	   getting the lock. 拿锁 */
	if (!have_lock)
	  {
	    __libc_lock_lock (av->mutex);
	    fail = (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
		    || chunksize (chunk_at_offset (p, size)) >= av->system_mem);
	    __libc_lock_unlock (av->mutex);
	  }

	if (fail)
	  malloc_printerr ("free(): invalid next size (fast)");
      }

    free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);

    atomic_store_relaxed (&av->have_fastchunks, true);
    unsigned int idx = fastbin_index(size);
    fb = &fastbin (av, idx);//获得对应链表

    /* Atomically link P to its fastbin: P->FD = *FB; *FB = P;  */
    mchunkptr old = *fb, old2;//记录老表头

    if (SINGLE_THREAD_P)
      {
	/* Check that the top of the bin is not the record we are going to
	   add (i.e., double free). 检测double free */
	if (__builtin_expect (old == p, 0))
	  malloc_printerr ("double free or corruption (fasttop)");
	p->fd = old;   //p指向老表头
	*fb = p;       //p成为新表头
      }
    else
      do
	{
	  /* Check that the top of the bin is not the record we are going to
	     add (i.e., double free).  */
	  if (__builtin_expect (old == p, 0))
	    malloc_printerr ("double free or corruption (fasttop)");
	  p->fd = old2 = old;
	}
      while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2))
	     != old2);

    /* Check that size of fastbin chunk at the top is the same as
       size of the chunk that we are adding.  We can dereference OLD
       only if we have the lock, otherwise it might have already been
       allocated again.  */
    if (have_lock && old != NULL
	&& __builtin_expect (fastbin_index (chunksize (old)) != idx, 0))
      malloc_printerr ("invalid fastbin entry (free)");
  }

逻辑上进行总结:使用chunk指针p,获取对应偏移上的chunk p 的size,并以此进行检查、计算,获取对应的index,然后加入对应链表表头位置。期间还做了一些多线程方面的考虑。

section III fastbin attack

首先可以联想tcachebin中的几个攻击方式,总的来说都是通过劫持fd指针,来达到一次任意地址写。tcachebin与fastbin有很多相似之处,这种攻击思路在此也适用,只是增加了一些限制。

以double free为例,tcache由于没有任何限制,所以可以通过:

free(A);
free(A);

来形成tcache_perthread_struct中的链表指向chunk A,chunk A的fd指向自己的情况。此时malloc第一次可以获得chunk A,且chunk A依然在bin的链表里,因此我们可以修改chunk A的内容来劫持fd指针,设置其为target address。于是在下一次malloc的时候,我们又会获得chunk A,然后bin链表则指向了我们的target address。此时在进行一次malloc,就会从bin中获取到地址为target address的chunk了。于是一次任意地址写攻击完成。

fastbin中,由于多了一些检查,攻击在整体思路不变的前提下,多要求了一些准备:

free(A);
free(B);
free(A);

最直接的就是要绕过double free 的检查,之前在分析free部分也看到了,只会检查下一个chunk的size是否合法,以及表头部分与当前free的目标chunk是否等同,因此这种free121的方式,是对于正常分配的chunk的释放,因此第一个检测一定能过,又由于121的形式,也能成功绕过第二个检测。

从而达到“让一个chunk在bin链表中出现两次”的情况,在此基础上,我们可以类似以上的,劫持fd指针,从而让arena中的fastbin 链表指向我们的target address。

但此时我们的攻击还没有成功,fastbin在申请过程中也是有检测机制的。可以回顾一下malloc部分的代码,看这个检查:

size_t victim_idx = fastbin_index (chunksize (victim)); //### check ###
if (__builtin_expect (victim_idx != idx, 0)) /*要求链表中的victim的
                                               index(由size计算)符合要求
                                               否则报错 */
	malloc_printerr ("malloc(): memory corruption (fast)");

在我们的攻击中,victim已经被我们劫持修改为了target address,也就是说,我们需要让target address的地方,使用fastbin_index(chunksize(victim))计算出的index与该链表的index是吻合的。

#define fastbin_index(sz) 
  ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
/* 我们是64bit下,字长是8,因此就是除以16然后减二。 */

#define chunksize(p) (chunksize_nomask (p) & ~(SIZE_BITS))
/* 就是取chunk的mchunk_size字段,然后把3个flag给去掉 */

#define chunksize_nomask(p)         ((p)->mchunk_size)
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)

比方说,我们劫持的是index=0的fastbin链表。要求:mchunk_size / 16 - 2 = 0

即这个size可以是0x20~0x2F,又考虑到这个size最后3个bit都会被清零,因此在此处计算的时候传递到fastbin_index(sz)中的sz只能是0x20了。

则反推出,我们的[target address + 8] 的地方,必须是0x20~0x2F。否则将无法通过这个检查。之后我们会在demo中测试。

接下来是一个专门的检查函数:

check_remalloced_chunk (av, victim, nb);  

这是一个宏定义,这里包含细致的可怕的检查,多如牛毛,简直牛鼻,把我看的吓死了,好在这些检查仅仅会在debug状态下被启用,这么多检查实在是太耗费性能了,与fastbin的初衷是相背的:

/*
   Debugging support

   These routines make a number of assertions about the states
   of data structures that should be true at all times. If any
   are not true, it's very likely that a user program has somehow
   trashed memory. (It's also possible that there is a coding error
   in malloc. In which case, please report it!)
 */

#if !MALLOC_DEBUG //非debug状态下,以下宏定义全为空。

# define check_chunk(A, P)
# define check_free_chunk(A, P)
# define check_inuse_chunk(A, P)
# define check_remalloced_chunk(A, P, N)
# define check_malloced_chunk(A, P, N)
# define check_malloc_state(A)

#else

# define check_chunk(A, P)              do_check_chunk (A, P)
# define check_free_chunk(A, P)         do_check_free_chunk (A, P)
# define check_inuse_chunk(A, P)        do_check_inuse_chunk (A, P)
# define check_remalloced_chunk(A, P, N) do_check_remalloced_chunk (A, P, N)
# define check_malloced_chunk(A, P, N)   do_check_malloced_chunk (A, P, N)
# define check_malloc_state(A)         do_check_malloc_state (A)

不妨顺带看一看这里的检查吧:【不想看的直接到####继续正常阅读】

/*
   Properties of chunks recycled from fastbins
 */

static void
do_check_remalloced_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T s)
{
  INTERNAL_SIZE_T sz = chunksize_nomask (p) & ~(PREV_INUSE | NON_MAIN_ARENA);
                                              //将其中两个flag清掉了,留下了#IS_MAPPED# flag
  if (!chunk_is_mmapped (p))//非mmap
    {
      assert (av == arena_for_chunk (p));//p对应的arena就是传进来的arena
      if (chunk_main_arena (p))//如果是main arena对应的chunk
        assert (av == &main_arena);
      else
        assert (av != &main_arena);
    }

  do_check_inuse_chunk (av, p);//检查一些作为inuse chunk所具有的性质

  /* Legal size ... */
  assert ((sz & MALLOC_ALIGN_MASK) == 0);
  assert ((unsigned long) (sz) >= MINSIZE);
  /* ... and alignment */
  assert (aligned_OK (chunk2mem (p)));//要求user data部分0x10bytes 对齐
  /* chunk is less than MINSIZE more than request */
  assert ((long) (sz) - (long) (s) >= 0);
  assert ((long) (sz) - (long) (s + MINSIZE) < 0);
}

其中使用的宏定义:

#define arena_for_chunk(ptr) 
  (chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr)

/* Check for chunk from main arena.  */
#define chunk_main_arena(p) (((p)->mchunk_size & NON_MAIN_ARENA) == 0)

#define heap_for_ptr(ptr) 
  ((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))

#define aligned_OK(m)  (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)

接下来是一层检查调用的do_check_inuse_chunk()

/*
   Properties of inuse chunks
 */

static void
do_check_inuse_chunk (mstate av, mchunkptr p)
{
  mchunkptr next;

  do_check_chunk (av, p);//检查其作为普通chunk的性质

  if (chunk_is_mmapped (p))
    return; /* mmapped chunks have no next/prev */

  /* Check whether it claims to be in use ... */
  assert (inuse (p));// 要求inuse bit 为1

  next = next_chunk (p);

  /* ... and is surrounded by OK chunks.
     Since more things can be checked with free chunks than inuse ones,
     if an inuse chunk borders them and debug is on, it's worth doing them.
   */
  if (!prev_inuse (p))
    {
      /* Note that we cannot even look at prev unless it is not inuse */
      mchunkptr prv = prev_chunk (p);
      assert (next_chunk (prv) == p); //要求前一个chunk的next是本身
      do_check_free_chunk (av, prv);
    }

  if (next == av->top)
    {
      assert (prev_inuse (next));
      assert (chunksize (next) >= MINSIZE);
    }
  else if (!inuse (next))
    do_check_free_chunk (av, next);
}

其中使用的宏定义:

#define inuse(p)							      
  ((((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)
//这个inuse需要通过下一个chunk的prev_inuse来判断

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

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

接下来是二层检查中调用的第一个do_check_chunk()

/*
   Properties of all chunks
 */

static void
do_check_chunk (mstate av, mchunkptr p)
{
  unsigned long sz = chunksize (p);
  /* min and max possible addresses assuming contiguous allocation */
  char *max_address = (char *) (av->top) + chunksize (av->top);
  char *min_address = max_address - av->system_mem;

  if (!chunk_is_mmapped (p))
    {
      /* Has legal address ... */
      if (p != av->top)
        {
          if (contiguous (av))//arena的一个性质
            {
              assert (((char *) p) >= min_address);
              assert (((char *) p + sz) <= ((char *) (av->top)));
            }
        }
      else//top chunk
        {
          /* top size is always at least MINSIZE */
          assert ((unsigned long) (sz) >= MINSIZE);
          /* top predecessor always marked inuse */
          assert (prev_inuse (p));
        }
    }
  else if (!DUMPED_MAIN_ARENA_CHUNK (p))
    {
      /* address is outside main heap  */
      if (contiguous (av) && av->top != initial_top (av))
        {
          assert (((char *) p) < min_address || ((char *) p) >= max_address);
        }
      /* chunk is page-aligned */
      assert (((prev_size (p) + sz) & (GLRO (dl_pagesize) - 1)) == 0);
      /* mem is aligned */
      assert (aligned_OK (chunk2mem (p)));
    }
}

有些东西我看不懂,我有点自闭,以下为用到的宏定义:

#define contiguous(M)          (((M)->flags & NONCONTIGUOUS_BIT) == 0)

/* True if the pointer falls into the dumped arena.  Use this after
   chunk_is_mmapped indicates a chunk is mmapped.  */
#define DUMPED_MAIN_ARENA_CHUNK(p) 
  ((p) >= dumped_main_arena_start && (p) < dumped_main_arena_end)

#define initial_top(M)              (unsorted_chunks (M))

#define unsorted_chunks(M)          (bin_at (M, 1))

#define bin_at(m, i) 
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))			      
             - offsetof (struct malloc_chunk, fd))

接下来是二层检查中调用的第二个,即do_check_free_chunk()

static void
do_check_free_chunk (mstate av, mchunkptr p)
{
  INTERNAL_SIZE_T sz = chunksize_nomask (p) & ~(PREV_INUSE | NON_MAIN_ARENA);
  mchunkptr next = chunk_at_offset (p, sz);

  do_check_chunk (av, p);

  /* Chunk must claim to be free ... */
  assert (!inuse (p));
  assert (!chunk_is_mmapped (p));

  /* Unless a special marker, must have OK fields */
  if ((unsigned long) (sz) >= MINSIZE)
    {
      assert ((sz & MALLOC_ALIGN_MASK) == 0);
      assert (aligned_OK (chunk2mem (p)));
      /* ... matching footer field */
      assert (prev_size (next_chunk (p)) == sz);
      /* ... and is fully consolidated */
      assert (prev_inuse (p));
      assert (next == av->top || inuse (next));

      /* ... and has minimally sane links */
      assert (p->fd->bk == p);
      assert (p->bk->fd == p);
    }
  else /* markers are always of size SIZE_SZ */
    assert (sz == SIZE_SZ);
}

没有包含新的宏定义。

由于是仅仅在debug阶段使用的检查选项,性能无关紧要,所以发现一些重复检查的现象出现。同样是因为是debug时才会使用的关系...这些检查与我们的攻击无关hhhhhh

####

那么看起来我们需要应对就是malloc时的那个size检查了。通过了这个检查,我们就能在target address上分配一个chunk了,从而获取一次任意地址写的攻击。

堆相关数据结构 - CTF Wiki​wiki.x10sec.org

section IV demo

demo环境是glibc-2.27,由于开启了tcache,所以我们先往tcachebin里面塞7个chunk,然后再进行如下操作,且前面的7个chunk与chunk a的size都是相同的。

f89aa5c78a652ce63a977d58713d1e98.png

连续进行两次free,目标是放到fastbin里边。

第一次free后,可以看到fastbin中已经放了一个chunk了。

a7bcc1ef45e9031091df70e3c46665be.png

再走一步,报错了,与源码对应。

9e20c092b7a616967c38a9d24d3cd97f.png

然后尝试一下之前所说的free121的方式:

a284fd03eeaf1bbbf263bfef72a599b0.png

三次free完成后,查看bins,可以看到chunk a已经多次出现在了bin中,且gef也探测到了形成了一个loop。

4955cf30cd8ecbed91509dca0cd4ee41.png

继续进行一些有趣的探索:

d38a498196edb9b84e0ab665cfa2b099.png

申请10个chunk,然后依次free,得到如下结果:

82e710ff747617c90f4baf15df7ccdff.png

tcachebin中对应单链表已经被塞满了,fastbin中存了3个chunk。

此时申请8个chunk。下图为申请完第七个chunk时的情况,tcachebin已经被清空了,下一次malloc会从fastbin拿取chunk。

7147295347cef7db6eb9d3fc30701207.png

可以看到,从fastbin中拿取了一个chunk之后,其剩余的chunk,全都被转移到了tcachebin上去。

18178c463256896ef46a400e5f868f8a.png

原本的fastbin中:fastbin[0x1]-->0x602440-->0x602410-->0x6023e0-->NULL

我们申请到了userdata d = 0x602450,且剩余的chunk,被倒序插入到tcachebin中去了。

fab5e1029e524725e282753d669852f2.png

变成了这样:tcachebin[0x1]-->0x6023f0-->0x602420-->NULL

这里再重申一下,tcachebin中使用的指针指向userdata,而fastbin中的指针指向chunk address。

在此基础上,我们对fd进行劫持:

266509c2e9fb17b404a6d0917c0c4b32.png

申请了第11个chunk,size为0x110,设置target address为这个chunk f中间的一个地址,这个地址周围全是0。

当我们修改了在bin中的chunk a的fd字段之后,使用gef的heap bins功能,可以看到出现了一个新的chunk,userdata地址为0x602510,即我们劫持的target address的chunk。

且可以看到gef已经识别出了,使用0x602500这个地址识别出的chunk,计算出的fastbin_index是不合符的,即size是有问题的。

9896b34399ed10ccaf7115b0164a6a82.png

不过由于这是在tcache机制下的...所以...第一次malloc获得了userdata 0x6023f0之后,下一个不合法的chunk会被丢到tcachebin里面,于是就变成合法的了...

8c56f5aa90a8c2870a7416d7c502635a.png

然后在申请chunk g,得到target+0x10地址的指针:

1e3206715da08e78b7619d2fccb104c0.png

这是由于tcache机制。实际上可以看到这个地址对fastbin来说,是不合法的,如果这个chunk在fastbin里面被申请出来,一定会报错,报错为:malloc(): memory corruption (fast)

我吐了,这就是不装对应环境的下场,下次我一定好好装环境...

但是演示还是可以继续的,我们直接劫持malloc_state中的fastbinsY[]:

776f28ad66a65fc6bfcde412c41589f7.png

这个0x7ff的地址就是直接指向fastbinsY[1]的,由于gdb默认没有地址随机,所以可以直接hardcode进去。

free掉chunka之后,可以看到fastbin已经出来了:

94b2719614f991b7d2625135efb518e4.png

劫持fastbin之后:

0b99d00683742d0b650de007ec9aa010.png

错误的chunk现在的位置是fastbinsY[1]的表头,将会作为fast chunk被取出来,因此会进行相应的index检查,此时我们malloc这个chunk(已经省略tcache的7个chunk的malloc):

fc559a7bcd2214d615fe7b69af081de0.png

感激涕零。

此时target+8处的qword值为0,修改其为0x30,再跑一次,当我们修改完成时,再看gef,发现之前所说的incorrect fastbin_index已经没了:

28eb471919115411eb3bba0e8add624d.png

继续跑,成功申请到target chunk:

46abe4e980442b1946ab32d1b2fb7ff2.png

coda

《EVA》是我最爱的动漫之一,讲述一个自我的价值问题。

人如果看不到自己,就会迷失自己。那么如何看到自己呢?

其一,与他人交流。在与每一个人交流的时候,你就会看见别人心中的自己。于是看见了自己。

其二,与自己交流,冥想、写小作文自我反省,思考。

二者缺一不可。仅仅看见他人眼中的自己,积极一些的人会变成表现型人格,消极一点的则会失去自我。仅仅专注于与自己交流,则会出现自我定位的缺失,过于自卑或者过于自负。

我经常阻断自己与他人交流的通道,一个人活在自己的小小世界里,自负自卑都有,徘徘徊徊,跌跌撞撞。甚至有时直接连与自己的交流都放弃了,躺在床上,当一个废物。

只要看不见自己,就不会因为自己犯的错误而悲伤,就不会因为自己的弱小而愤怒。这种短暂的、虚妄的麻木感,叫做"逃避"。

可惜,问题只要出现了,那它就在那里,不解决,只会越来越严重,不要指望任何人来拯救你,只有自己能决定这一切。

一定要加油呀hhh

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值