你如何在C中正确实现一个强大的堆栈

时间:2013-02-07 04:02:02

标签: c stack heap buffer-overflow

先验知识

我已经阅读并理解了一些有关堆栈和数据结构的内容,但无法找到这个特定问题的答案。我知道任何有价值的程序员都会实现超出程序默认值的安全异常处理。

情况

我想了解一个程序如何用C编写,它建立了一个相对强大的堆栈,并对某些任意情况进行异常处理。 我的目标是从这个非常具体的信息中辨别出为什么(根据我的理解),总是可以在程序中利用SEH并执行任意代码。

问题是,我不理解溢出缓冲区的概念 - 我不明白为什么(在非常具体的,CPU架构相关的原因)堆栈上实现的安全性(金丝雀等)无法充分解决这些问题(如堆溢出无法停止?)。

参考信息

“没有理智的方法来改变结构中的数据布局;模块之间的结构应该是相同的,特别是对于共享库。缓冲区后的结构中的任何数据都不可能用金丝雀保护;因此程序员必须非常小心他们如何组织变量并使用他们的结构。在C和C ++中,带缓冲区的结构应该是malloc()ed或者是new。 - 通过http://en.wikipedia.org/wiki/Buffer_overflow_protection#Implementations

此外:

http://blogs.msdn.com/b/michael_howard/archive/2006/08/16/702707.aspx

如果有人知道用于理解此材料的资源,或者可以提供代码段,我将非常感激。

3 个答案:

答案 0 :(得分:1)

堆栈的安全性可以足够安全以应对堆栈溢出:

http://msdn.microsoft.com/en-us/library/9a89h429(VS.80).aspx

关于为什么需要注册异常处理程序(安全 -SEH)以及正常异常处理程序不会删除它的原因的问题是因为你会遇到非常大的堆栈溢出。

假设我有一个开始的功能

try {
   char buffer[N];
   strcpy(&buffer, &attacker);
} __except(...) { }

这可能会转化为汇编代码

push ebp
mov ebp, esp
; GS if you want to here

; install the exception handler:
push lbl_Exceptionhandler
push dword ptr [fs:0]
mov dword ptr[fs:0], esp

; setup the locals inside the stack
sub esp, LOCALS
; GS if you want to here

; call strcpy
lea ecx [ebp + offset_to_buffer];
push ecx
lea edx, [ebp + offset_to_attacker]
push edx
call _strcpy
add esp, 8

; uninstall the locals
mov esp, ebp

; uninstall the exception handler
pop dword ptr [fs:0]

; return
pop ebp

; optionally check GS cookies that we might have also inserted at any point in this function.
call _checksecuritycookie
ret 

或者换句话说,堆栈看起来像这样:

RET PTR 
/GS1
SAVED EBP
/GS2
SAVED FS:0
/GS3
LOCAL char buffer[N]

GS1,GS2和GS3是堆栈canaries可能选择写入堆栈cookie的位置。请注意,cookie仅在函数末尾进行检查(这在计算机安全性中很重要。当您引入检查时,您不仅要考虑检查是否会检测到溢出,而且是否会在检测到之前检测到溢出已经太晚了;这需要考虑检查将在何处进行。对于堆栈cookie,cookie仅在功能退出时进行检查,因为堆栈cookie通常只用于保护返回地址,而不是保护本地变量。) p>

如果攻击者缓冲区非常庞大,那么正常异常处理程序的问题就会发生。我们假设它如此巨大它会破坏整个堆栈,写入线程的保护页面并触发故障?

好吧,内核回调到ntdll并告诉它对它的进程进行排序,而ntdll的第一个调用端口是查看是否有任何已注册的异常处理程序。现在如何找到要调用的异常处理程序?好吧,它查看fs:0,它指向堆栈上的异常处理程序,并调用异常处理程序指针。除了攻击者刚刚销毁的堆栈上的异常处理程序。

糟糕。现在攻击者控制了EIP,你输了。

Safe-SEH通过注意您可能想要调用的异常处理程序列表实际上是在编译时完全确定的有限列表来解决此问题。通过将此列表刻录到PE文件本身,ntdll有机会仔细检查它应该跳转到的异常处理程序实际上是一个真正的异常处理程序,而不是导致某个恶意攻击者接管你的EIP的原因。

Safe-SEH有成本(因此它选择加入),但是成本是捕获异常变得更加昂贵,因为ntdll现在会在异常处理程序接管之前做更多的工作。

尽管如此,我的建议是SafeSEH应始终开启。让您更容易丢失客户的信用卡详细信息,因为您的应用程序严重依赖于抛出异常的速度,这表明在开发人员心中如此骇人听闻的心态应该立即放入大炮并向太阳射击以避免他们的破坏社会的可怕代码。

答案 1 :(得分:0)

在C中实现堆栈的常规方法是使用链表。例如:

struct stack_entry {
    struct stack_entry *previous;
    /* other fields for the actual data */
}

struct stack_entry *stack_top = NULL;

void push(struct stack_entry *entry) {
    entry->previous = stack_top;
    stack_top = entry;
}

struct stack_entry *pop(void) {
    struct stack_entry *entry;

    entry = stack_top;
    if(entry != NULL) stack_top = entry->previous;
    return entry;
}

这与任何其他普通代码一样强大且难以利用。

答案 2 :(得分:0)

如果您没有在C中实现堆栈,但正在实现C编译器(使用任何语言),那么..

可以创建一个C编译器来检测编程错误并生成安全的代码。例如,对于每次读取或写入,编译器都可以插入检查以确保读取或写入包含在一个存储区域内(例如,您并不是要尝试在char数组的末尾写入4个字节);如果其中一个检查失败,则发出信号(例如“SIGSEGV”)。

由于C的性质,这些检查涉及扫描诸如堆之类的东西,并且需要插入更多代码来跟踪堆栈中事物的大小。

没有实现的主要原因是它会产生大量的性能问题,因此会破坏使用C开始的目的。

但是,有一些调试工具(例如valgrind)通过在虚拟机内运行应用程序来执行此类检查(虚拟机跟踪存储区域的大小,并且能够在它们出现之前检查读/写)执行)。