0

先验知识

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

情况

我想了解如何用 C 编写一个程序,该程序建立一个相对健壮的堆栈,并对某些任意情况 S 进行异常处理。我的目标是从这个非常具体的信息中辨别为什么(据我所知)总是有可能在程序中利用 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

如果有人知道理解此材料的好资源或可以提供代码片段,我将不胜感激。

4

3 回答 3

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 是堆栈金丝雀可能选择写入堆栈 cookie 的位置。注意cookie只会在函数结束时被检查(这在计算机安全中很重要。当你引入检查时,你不仅需要考虑检查是否会检测到溢出,还要考虑它是否会在溢出之前检测到它已经太晚了;这需要考虑检查将在哪里进行。对于堆栈cookie,cookie仅在函数退出时检查,因为堆栈cookie通常只是为了保护返回地址,而不是保护局部变量)。

正常异常处理程序的问题是如果攻击者缓冲区非常大会发生什么。假设它太大了,它会破坏整个堆栈,写入线程的保护页面并触发错误?

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

哎呀。现在攻击者控制了 EIP 而你输了。

Safe-SEH 通过指出您可能想要调用的异常处理程序列表实际上是一个完全在编译时确定的有限列表来解决这个问题。通过将此列表写入 PE 文件本身,ntdll 有机会再次检查它应该跳转到的异常处理程序实际上是一个真正的异常处理程序,而不是某些邪恶攻击者阴谋接管您的 EIP 的原因。

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

尽管如此,我的建议是 SafeSEH 应该始终打开。由于您的应用程序的性能严重依赖于抛出异常的速度,因此更容易丢失客户的信用卡详细信息,这表明开发人员的心态如此糟糕,以至于他们应该立即投入大炮并被发射到阳光下以避免他们的破坏社会的可怕代码。

于 2013-02-07T05:58:52.467 回答
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;
}

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

于 2013-02-07T04:21:11.040 回答
0

如果您没有在 C 中实现堆栈,而是在实现 C 编译器(以任何语言),那么..

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

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

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

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

于 2013-02-07T04:52:55.503 回答