RCTF2018 Note4 - heap overflow & hijack .strtab
利用总结:heap overflow导致可以控制chunk中的指针实现任意地址写,由于dynamic可写,修改.strtab可以劫持还未调用的函数。
题目信息
这题只开了NX和CANARY,提供了三个功能:增加chunk、修改chunk和删除chunk。
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char menu_choice; // [rsp+17h] [rbp-9h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
init_sub_400AD2();
if ( (signed int)a1 > 1 ) // dynamicly set alarm params
{
v3 = atoi(a2[1]);
alarm(v3);
}
menu_choice = 0;
while ( 1 )
{
read_sub_4007C6((__int64)&menu_choice, 1u); // read 1 byte
switch ( menu_choice )
{
case 1: // not '1' ; python input 1
add_sub_400849();
break;
case 2:
change_sub_400984();
break;
case 3:
delete_sub_400A32();
break;
case 4:
exit(0);
break;
}
}
}
增加chunk中首先calloc一个0x10的chunk1,再read size后申请calloc size大小的chunk2,并把&chunk2保存在chunk1[1]中,把size保存在chunk1[0]中。&chunk1保存在bss段一个s数组中。
unsigned __int64 sub_400849()
{
unsigned __int8 size_v1; // [rsp+Bh] [rbp-15h]
int i; // [rsp+Ch] [rbp-14h]
_QWORD *chunk1_v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( chunk_number_dword_6020A0 > 32 )
exit(-1);
size_v1 = 0;
chunk1_v3 = calloc(0x10uLL, 1uLL); // malloc 0x10*1 and memset to 0
if ( !chunk1_v3 )
exit(-1);
read_sub_4007C6((__int64)&size_v1, 1u);
if ( !size_v1 )
exit(-1);
chunk1_v3[1] = calloc(size_v1, 1uLL);
if ( !chunk1_v3[1] )
exit(-1);
read_sub_4007C6(chunk1_v3[1], size_v1);
*chunk1_v3 = size_v1;
for ( i = 0; i <= 31 && s[i]; ++i )
;
s[i] = chunk1_v3;
++chunk_number_dword_6020A0;
return __readfsqword(0x28u) ^ v4;
}
修改chunk,根据下标从s中取出&chunk1,拿到chunk1中保存的chunk2的地址后,read size,再read size大小的数据到chunk2中。由于size可控,堆溢出。
// modify chunk in s (must have ptr in s)
unsigned __int64 sub_400984()
{
unsigned __int8 choice_v1; // [rsp+Eh] [rbp-12h]
unsigned __int8 size_v2; // [rsp+Fh] [rbp-11h]
_QWORD *chunk_v3; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
choice_v1 = 0;
read_sub_4007C6((__int64)&choice_v1, 1u);
if ( !s[choice_v1] )
exit(-1);
chunk_v3 = s[choice_v1];
size_v2 = 0;
read_sub_4007C6((__int64)&size_v2, 1u); // heap overflow!
read_sub_4007C6(chunk_v3[1], size_v2);
return __readfsqword(0x28u) ^ v4;
}
删除chunk,read offset从s中取出&chunk1,先free chunk1[1],也就是先freechunk2,再free chunk1。如果offset不对,则会SIGSEGV,&chunk1为0,free(0x8)。
unsigned __int64 sub_400A32()
{
unsigned __int8 choice_v1; // [rsp+7h] [rbp-9h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
choice_v1 = 0;
read_sub_4007C6((__int64)&choice_v1, 1u);
if ( choice_v1 > 0x20u ) // read choice and free , choice only < 0x20 vuln!!!
exit(-1);
free(*((void **)s[choice_v1] + 1)); // free 0x10 chunk
free(s[choice_v1]); // free chunk
s[choice_v1] = 0LL;
return __readfsqword(0x28u) ^ v2;
}
堆溢出时,size的范围是1byte,0-0xff,可以溢出后一个0x10chunk1的chunk1[1],可以控制其中的指针,update后一个chunk时,可以实现任意地址写0-0xff。堆溢出转化为任意地址写。
没有开RELRO,gdb中可以看出.dynamic段是可写的,且没有开aslr,利用方式:写free的string table为system,在bss上写/bin/sh后,堆溢出chunk1[1]为/bin/sh的地址,调用free就可以getshell。
EXP
https://github.com/thinkycx/pwn/blob/master/20181102-RCTF2018/RNote4/exp.py
总结
heap overflow导致可以控制chunk中的指针实现任意地址写,由于dynamic可写,修改.strtab可以劫持还未调用的函数。