https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c182a709-0367-48a5-bbcd-3ad80d5635c3/Untitled.png

哎呀妈呀,太肝了,感觉我都要升仙了。

House of Pig

GLIBC 2.31

这个题目主要考察的其实是TCTF2020中duet技术相关的部分。作者提出了一个House of Pig的新的利用方式,但是这中方式之前已经见到过了,就是kirin在duet中使用到的技术

0CTF/TCTF 2020 Quals PWN

官方的WP

稍等...

程序实现很复杂,实现了三种不同的操作方式即三种add,三种edit和三种show,三种功能delete,每种操作方式对应的buf_list是不同的。用户可以进行切换操作方式,在切换的时候程序会将当前操作方式所对应的buf_list等信息拷贝到一个map的地址空间中,我称之为备份。三种不同的操作方式的备份的地址空间是不同的。因此这里不存在备份冲突的情况。

直觉上来讲肯定是进行切换时候发生了问题,因此这里仔细的分析一下切换时候的操作。注意到这里的ida分析不出jmp eax的情况,因此这里使用ghrida来进行分析的。在进行状态切换的时候会check pass,这里的pass和切换的index是对应的,并且这里的pass是经过md5加密的。这里我没有看解密的操作。一开始是直接改check_pass函数的返回值来进行调试的。

case 5:
  iVar2 = checkpass();
  if ((iVar2 != 0) && (iVar2 != local_428)) {
    if (local_428 == 1) {
      save_array_2_map_1(local_420);
    }
    else {
      if (local_428 == 2) {
        save_array_2_map_2(local_420);
      }
      else {
        if (local_428 == 3) {
          save_array_2_map_3(local_420);
        }
      }
    }
    local_428 = iVar2;
    if (iVar2 == 1) {
      pbVar3 = std::operator<<((basic_ostream *)&std::cout,"This is Peppa Pig~");
      std::basic_ostream<char,std::char_traits<char>>::operator<<
                ((basic_ostream<char,std::char_traits<char>> *)pbVar3,
                 std::endl<char,std::char_traits<char>>);
      local_420 = local_418;
      copy_map_2_array_1(local_420);
    }
    else {
      if (iVar2 == 2) {
        pbVar3 = std::operator<<((basic_ostream *)&std::cout,"This is Mummy Pig~");
        std::basic_ostream<char,std::char_traits<char>>::operator<<
                  ((basic_ostream<char,std::char_traits<char>> *)pbVar3,
                   std::endl<char,std::char_traits<char>>);
        local_420 = local_2c8;
        copy_map_2_array_2(local_420);
      }
      else {
        if (iVar2 == 3) {
          pbVar3 = std::operator<<((basic_ostream *)&std::cout,"This is Daddy Pig~");
          std::basic_ostream<char,std::char_traits<char>>::operator<<
                    ((basic_ostream<char,std::char_traits<char>> *)pbVar3,
                     std::endl<char,std::char_traits<char>>);
          local_420 = local_178;
          copy_map_2_array_3(local_420);
        }
      }
    }
  }
}

可以看到这里首先将当前模式的相关的信息保存早map中,然后根据check_pass的结果将对应模式的信息拷贝到栈中,这样就完成了状态的切换。但是在进行拷贝的时候会出现一个问题,save和recover的信息不对等。

unsigned __int64 __fastcall save_to_map_3(__int64 a1)
{
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memcpy((char *)global_map + 0x2B0, (const void *)a1, 0xC0uLL);
  memcpy((char *)global_map + 0x370, (const void *)(a1 + 0xC0), 0x60uLL);
  memcpy((char *)global_map + 0x3E8, (const void *)(a1 + 0x138), 0x18uLL);
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 __fastcall copy_global_map_2_array_3(__int64 a1)
{
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memcpy((void *)a1, (char *)global_map + 0x2B0, 0xC0uLL);
  memcpy((void *)(a1 + 0xC0), (char *)global_map + 0x370, 0x60uLL);
  memcpy((void *)(a1 + 0x120), (char *)global_map + 0x3D0, 0x18uLL);
  memcpy((void *)(a1 + 0x138), (char *)global_map + 0x3E8, 0x18uLL);
  return __readfsqword(0x28u) ^ v2;
}

我们看到这里在进行保存的时候忘记了+0x120偏移位置的数据,而这部分的数据是用来标识当前模式下buf_list中的堆块是否被释放了的。因此这里如果没有进行保存的话,那么在恢复的时候这里的值都是0,而0代表的是buf_list对应的index处的buf未被释放,即仍在使用中,那么这里就出现了一个UAF的漏洞。即切换两次即可对buf进行UAF。

但是程序中都是calloc,并没有malloc,并且限制了堆块的大小最小为0xa0,也就是这里并不能直接使用tcache实现任意地址分配,而且这里并不能使用fastbin attack。因此这里唯一可以考虑的就是small bin attack和large bin attack。并且这里是GLIBC 2.31 unsorted bin attack已经不能使用了。

这里在做题的时候犯了一个错误就是没有看好small bin attack是否可行就去尝试了,结果构造完毕堆布局之后发现small bin attack不能使用,这就耗费了很长时间,这里吃一堑长一智吧。

if (__glibc_unlikely (bck->fd != victim))
	    malloc_printerr ("malloc(): smallbin double linked list corrupted");

不知道当时为什么要尝试这个smallbin attack,感觉没有道理啊。。。