我正在阅读IDA Pro书,在第20章中,作者从调试版本中显示了以下代码:

push ebp
mov ebp, esp
sub esp, 0F0h
push ebx
push esi
push edi 
lea edi, [ebp+var_F0]
mov ecx, 3Ch
mov eax, 0CCCCCCCCh
rep stosd
mov[ebp+var_8], 0
mov [ebp+var_14], 1
mov [ebp+var_20], 2
mov [ebp+var_2C], 3


我们可以看到局部变量不相邻对彼此。克里斯·伊格尔(Chris Eagle)概述说,这使得从一个变量中检测可能溢出并破坏另一个变量的溢出变得更加容易,然后他就把它留了下来。这对我来说没有意义,难道只是在执行可能导致溢出的特定操作之后设置一个断点,然后检查变量的值,这会更容易吗?究竟有什么用呢?

评论

这涉及(var_8,var_14,var_20和var_2C)的值,如果它们之间的距离小于4个字节,则应更改3个变量。

#1 楼

最新的Visual Studio编译器使用运行时检查来检测溢出情况
使用各种运行时检查来执行溢出检查

您只能在未优化的版本中使用它们/ Od
(不适用于/ O1或/ O2或/ Ox的优化版本)

这些可以是#pragmas或/ RTC1 / RTCS | U | C命令行开关

通过分配比所需的更大的缓冲区并用已知的模式填充它来检测堆栈损坏,因为编译器知道多少应该使用空格,它可以检查边界是否已被未知模式践踏

(是的,一些聪明的模式匹配漏洞可能仍然会愚弄它,但是它可以用于真正的用法,在您无意间编写的情况下)溢出代码)

以下面的代码为例

#define CRT_SECURE_NO_WARNING
#include <string.h>
#include <stdio.h>
void foo(void){
    char flowoverme[0x10];
    strcpy(flowoverme,"yaddaaayadddaaafoo");
}
int main(void){
    foo();
    printf("checking overflows by pattern pasting \n");
}


(如果使用/ analyze编译器开关,它将吐出该代码将溢出

:\>cl /nologo /Zi /RTC1 /analyze /Od /EHsc rtcchk.cpp /link /nologo /debug
rtcchk.cpp
rtcchk.cpp(8) : warning C6386: Buffer overrun while writing to 'flowoverme':  the wr
itable size is '16' bytes, but '19' bytes might be written.: Lines: 7, 8


,但是假设您只是执行cl foo.cpp

:\>cl /nologo /Zi /RTC1 /Od /EHsc rtcchk.cpp /link /nologo /debug
rtcchk.cpp


,如果您执行此编译代码printf如果启用了运行时检查,将无法访问

:\>rtcchk.exe

:\>


我们可以反汇编并查看函数foo中发生了什么以及为什么不执行printf()
< br让我们打开双在windbg中,nary转到foo()的开头,然后要求windbg向上运行(返回到main()的gu),如下所示,您会注意到windbg不会返回main,但是会停止并显示错误消息

:\>cdb -c "g rtcchk!foo;gu" rtcchk.exe

Microsoft (R) Windows Debugger Version 10.0.16299.15 X86

0:000> cdb: Reading initial command 'g rtcchk!foo;gu'

rtcchk!failwithmessage+0x255:
013d75da cc              int     3


,如果您仍然想知道这些功能的运行方式或方式,调用堆栈将显示

0:000> kP
ChildEBP RetAddr
0028f544 013d72a9 rtcchk!failwithmessage(
                        void * retaddr = 0x013d698a,
                        int crttype = 0n1,
                        int errnum = 0n2,
                        char * msg = 0x0028f568 "Stack around the variable 'flowoverme' was corrupted.")+0x255
0028f96c 013d6c3d rtcchk!_RTC_StackFailure(
                        void * retaddr = 0x013d698a,
                        char * varname = 0x013d69b8 "flowoverme")+0x94
0028f98c 013d698a rtcchk!_RTC_CheckStackVars(
                        void * frame = 0x0028f9b8,
                        struct _RTC_framedesc * v = 0x013d69a4)+0x42
0028f9b8 013d69d8 rtcchk!foo(void)+0x4a
0028f9c0 013d6ecd rtcchk!main(void)+0x8
(Inline) -------- rtcchk!invoke_main+0x1c
0028fa08 76a9ed6c rtcchk!__scrt_common_main_seh(void)+0xf9
0028fa14 77cb37eb kernel32!BaseThreadInitThunk+0xe
0028fa54 77cb37be ntdll!__RtlUserThreadStart+0x70
0028fa6c 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000>


要么vs中的crt源代码,要么反汇编函数

编译器知道所需的大小以及边界在哪里

0:000> dx -r3 (_RTC_framedesc *) 0x013d69a4
(_RTC_framedesc *) 0x013d69a4  : 0x13d69a4 [Type: _RTC_framedesc *]
    [+0x000] varCount         : 1 [Type: int]
    [+0x004] variables        : 0x13d69ac [Type: _RTC_vardesc *]
        [+0x000] addr             : -24 [Type: int]
        [+0x004] size             : 16 [Type: int]
        [+0x008] name             : 0x13d69b8 : "flowoverme" [Type: char *]
0:000>


#2 楼

我已经检查了提到的章节,还有关于检测堆栈中指令执行情况的信息。我认为这是更常见的情况。

关于溢出检测,我只能推测,但是对我来说,在一个地方检查所有应为0xCC的值实际上是否仍比在每次检查时都完好无损是容易的可能溢出的操作之后的时间,并检查该值是否应为操作的结果。还考虑使用此方法可以检查数组是否超出范围的数组。

使用OxCC可以同时覆盖两个检查。

#3 楼

您粘贴的是Visual C ++的/GZ开关或更高版本的/RTC。它使用0xCC填充局部变量的堆栈。我找不到的是,是否有一个实际的函数在运行时检查是否有任何间隙被污染。

我想是因为编译器应该能够在编译时生成这些函数而知道堆栈布局,因此在某个时候(函数结束还是异常?),他可以自动验证间隙是否干净。

没有间隙,您将无法判断一个变量是否写入溢出,因为它只会在下一个中结束。我想这就是书的作者所隐含的意思。

很遗憾,我找不到这些标志的清晰文档,除了描述它确实填满了堆栈并使用它来验证完整性。