哎呀妈呀,太肝了,感觉我都要升仙了。
GLIBC 2.31
这个题目主要考察的其实是TCTF2020中duet技术相关的部分。作者提出了一个House of Pig的新的利用方式,但是这中方式之前已经见到过了,就是kirin在duet中使用到的技术
官方的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,感觉没有道理啊。。。