字符串操作下的Pwn
C风格的字符串是指由一个连续的字符序列组成,并且以一个空字符(null)作为结束。一个指向字符串的指针实际上就是指向该字符串的起始。字符串长度指的是空字符之前的字节数。
常见操作错误
无边界字符串复制
我们常见的缓冲区溢出一半就会发生在这里。
无边际字符串复制,实际上是读取数据,复制到一个定长定缓冲区中。如我们常见定栈溢出:
1 | int mian(){ |
这就是常见的无边界字符串复制,gets这个位置是一个很明显的栈溢出点。除了gets ,还有strcpy(),strcat()等等函数。
差一错误
其实差一错误,是我们通常所说等off-by-one。。看几个例子:
my_gets 函数导致了一个off-by-one漏洞,原因是for循环的边界没有控制好导致写入多执行了一次,这也被称为栅栏错误
1 | int my_gets(char *ptr,int size) |
my_gets 函数导致了一个off-by-one漏洞,原因是for循环的边界没有控制好导致写入多执行了一次,这也被称为栅栏错误
off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括
- 使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节。
- 字符串操作不合适
空结尾错误
这种又属于 off-by-one 的一个分支称为 NULL byte off-by-one程序乍看上去没有任何问题(不考虑栈溢出),可能很多人在实际的代码中也是这样写的。 但是 strlen 和 strcpy 的行为不一致却导致了off-by-one 的发生。 strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符1
2
3
4
5
6
7
8
9
10
11
12
13
14int main(void)
{
char buffer[40]="";
void *chunk1;
chunk1=malloc(24);
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
}
return 0;
}\x00
计算在内的,但是 strcpy 在复制字符串时会拷贝结束符\x00
。这就导致了我们向chunk1中写入了25个字节.
off-one-by 在堆上的利用
一般来说,单字节溢出被认为是难以利用的,但是因为 Linux 的堆管理机制 ptmalloc 验证的松散性,基于Linux堆的 off-by-one 漏洞利用起来并不复杂,并且威力强大。 此外,需要说明的一点是 off-by-one 是可以基于各种缓冲区的,比如栈、bss 段等等,但是堆上(heap based)的off-by-one 是 CTF 中比较常见的。我们这里仅讨论堆上的 off-by-one 情况。
demo
Asis CTF 2016的一道题目,考察点是null byte off-by-one
分析
题目漏洞主要是在create中myread里,read函数可以发现对于边界的考虑是不当的。
1 | signed __int64 __fastcall myread_9F5(_BYTE *ptr_buf, int size) |
利用
保护开启状况如下:
1 | ➜ off-one-by checksec b00ks |
为了方便调试,我将 系统随机化关闭,那么之后程序基址将保持不变。