恰好找到了这道题的bin文件,就来做一下。
这道题目是一个经典的选单程序但是具有三级选单,在bss段存在指针数组ptr,ptr中的值指向每个主结构,其中主结构如下所示。
[16] model [4] price [4] padding; [8] pointer
共32byte
其中pointer会指向一个子结构customer
[32] first_name [32] name [8] pointer 共72byte
这个pointer又会指向一个一块内存comment
[72] comment
第一个漏洞存在于sub_400BFB中,这是自己实现的Read函数,这个Read函数在读取长度限制的最后一个字节之后还会把指针加1,并且又符了0值,从而造成NULL byte off-by-one。代码如下
__int64 __fastcall sub_400CC9(__int64 a1, int a2) { int v2; // eax@3 __int64 result; // rax@6 int v4; // [sp+10h] [bp-10h]@1 __int64 v5; // [sp+18h] [bp-8h]@1 v5 = a1; v4 = 0; while ( v4 < a2 ) { v2 = _IO_getc(stdin); if ( v2 == ' ' ) { result = v5; *(_BYTE *)v5 = 0; return result; } if ( v2 != 0xD ) { *(_BYTE *)v5++ = v2; ++v4; } } result = v5; *(_BYTE *)v5 = 0; return result; }
这个漏洞可以说是比较难找的,主要是程序代码量比较大,很容易忽略这里。
拿到这个null byte off-by-one之后肯定就是围绕这个漏洞展开思考,首先想的是两种常用的利用方法,第一想到的肯定是去构造unlink,因为几乎是每次获取输入都使用了这个存在漏洞的Read函数,所以可以off-by-one的地方很多。想了一下可以在哪里unlink,因为unlink存在check,所以也就只有存在了指针指向的地方才可以用来构造unlink,经过一番查找缺发现这道题并没有提供这样的机会。
后来发现其实这道题的关键在于这里
__int64 __fastcall Add_custom(__int64 a1) { __int64 v1; // rdx@5 __int64 result; // rax@5 if ( *(_QWORD *)(a1 + 24) ) { if ( *(_QWORD *)(*(_QWORD *)(a1 + 24) + 64LL) )// [64] // [8] pointer { free(*(void **)(*(_QWORD *)(a1 + 24) + 64LL)); memset(*(void **)(a1 + 24), 0, 72uLL); } free(*(void **)(a1 + 24)); } v1 = Alloc_ZI_STRUCT(); result = a1; *(_QWORD *)(a1 + 24) = v1; return result; }
实际是通过控制内存块来控制free函数的值,实现free全局指针域。然后紧接着的分配会重用这个块,控制了指针以进行任意地址读写。感觉这道题还是很有难度的,洞比较好找,但是想出利用却比较难。