Sil*_*cer 26 c stack stackunderflow
我想在C函数中引发堆栈下溢,以测试我系统中的安全措施.我可以使用内联汇编程序来完成此操作.但是C会更便携.但是,我无法想到使用C引发堆栈下溢的方法,因为堆栈内存在这方面由语言安全地处理.
那么,有没有办法使用C激发堆栈下溢(不使用内联汇编程序)?
如注释中所述:堆栈下溢意味着使堆栈指针指向堆栈开头下方的地址(堆栈从低到高的架构"下方").
Jer*_*myP 45
有一个很好的理由说明在C中很难引发堆栈下溢.原因是符合标准的C没有堆栈.
阅读C11标准,你会发现它谈论的是范围,但它没有谈论堆栈.这样做的原因是标准尽可能地尝试避免强制执行任何设计决策.您可能能够找到一种方法在纯C中导致特定实现的堆栈下溢,但它将依赖于未定义的行为或特定于实现的扩展,并且不可移植.
Lun*_*din 16
你不能在C中做到这一点,因为C将堆栈处理留给了实现(编译器).类似地,你不能在C中编写一个错误,你在堆栈上推送东西但忘记弹出它,反之亦然.
因此,在C语言中不可能产生"堆栈下溢".你不能从C中的堆栈中弹出,也不能从C中设置堆栈指针.堆栈的概念比C更低.语言.要直接访问和控制堆栈指针,必须编写汇编程序.
你在C中可以做的是故意写出栈的边界.假设我们知道堆栈从0x1000开始并向上增长.然后我们可以这样做:
volatile uint8_t* const STACK_BEGIN = (volatile uint8_t*)0x1000;
for(volatile uint8_t* p = STACK_BEGIN; p<STACK_BEGIN+n; p++)
{
*p = garbage; // write outside the stack area, at whatever memory comes next
}
Run Code Online (Sandbox Code Playgroud)
为什么你需要在不使用汇编程序的纯C程序中测试它,我不知道.
如果有人错误地认为上面的代码调用了未定义的行为,那么这就是C标准实际上所说的,规范性文本C11 6.5.3.2/4(强调我的):
一元*运算符表示间接.如果操作数指向函数,则结果是函数指示符; 如果它指向一个对象,则结果是指定该对象的左值.如果操作数的类型为''指向类型'',则结果的类型为''type''.如果为指针分配了无效值,则unary*运算符的行为未定义102)
那么问题是"无效价值"的定义是什么,因为这不是标准定义的正式术语.脚注102(提供信息,而非规范性)提供了一些例子:
在由一元*运算符解除引用指针的无效值中,有一个空指针,一个与指向的对象类型不适当对齐的地址,以及一个对象在其生命周期结束后的地址.
在上面的例子中,我们显然没有处理空指针,也没有处理已经超过其生命周期结束的对象.代码可能确实导致访问错位 - 这是否是一个问题是由实现决定的,而不是由C标准决定的.
"无效值"的最后一种情况是特定系统不支持的地址.这显然不是C标准提到的,因为特定系统的存储器布局不是C标准所转换的.
关于已经存在的答案:我认为在开发缓解技术的背景下谈论未定义的行为是不恰当的.
显然,如果实现提供了针对堆栈下溢的缓解,则提供堆栈.实际上,void foo(void) { char crap[100]; ... }最终会将数组放在堆栈上.
这个答案的评论提示注意:未定义的行为是一个事情,原则上任何行使它的代码最终都会被编译成绝对的东西,包括一些不像原始代码的东西.但是,漏洞利用缓解技术的主题与目标环境以及实践中发生的情况密切相关.在实践中,下面的代码应该"正常"工作.处理这类东西时,你总是需要验证生成的装配.
这让我在实践中会给出一个下溢(添加volatile以防止编译器优化它):
static void
underflow(void)
{
volatile char crap[8];
int i;
for (i = 0; i != -256; i--)
crap[i] = 'A';
}
int
main(void)
{
underflow();
}
Run Code Online (Sandbox Code Playgroud)
Valgrind很好地报告了这个问题.
根据定义,堆栈下溢是一种未定义的行为,因此触发此类条件的任何代码都必须是UB.因此,您无法可靠地导致堆栈下溢.
也就是说,以下滥用可变长度数组(VLA)将导致许多环境中的可控堆栈下溢(使用Clang和GCC测试x86,x86-64,ARM和AArch64),实际上将堆栈指针设置为高于其初始值:
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
uintptr_t size = -((argc+1) * 0x10000);
char oops[size];
strcpy(oops, argv[0]);
printf("oops: %s\n", oops);
}
Run Code Online (Sandbox Code Playgroud)
这个分配具有"负"(非常非常大)的尺寸,这将绕到栈指针并导致堆栈指针向上移动一个VLA.argc并argv用于防止优化取出数组.假设堆栈增长(在列出的体系结构上默认),这将是堆栈下溢.
strcpy将在调用时触发对下溢地址的写入,或者如果strcpy内联则写入字符串.决赛printf不应该是可以达到的.
当然,这一切都假定编译器不仅使VLA成为某种临时堆分配 - 编译器完全可以自由地进行.您应该检查生成的程序集,以验证上面的代码是否符合您的实际预期.例如,在ARM(gcc -O)上:
8428: e92d4800 push {fp, lr}
842c: e28db004 add fp, sp, #4, 0
8430: e1e00000 mvn r0, r0 ; -argc
8434: e1a0300d mov r3, sp
8438: e0433800 sub r3, r3, r0, lsl #16 ; r3 = sp - (-argc) * 0x10000
843c: e1a0d003 mov sp, r3 ; sp = r3
8440: e1a0000d mov r0, sp
8444: e5911004 ldr r1, [r1]
8448: ebffffc6 bl 8368 <strcpy@plt> ; strcpy(sp, argv[0])
Run Code Online (Sandbox Code Playgroud)
小智 5
这个假设:
C会更便携
不是真的.C不会告诉堆栈以及实现如何使用堆栈.在典型的x86平台上,以下(可怕的无效)代码将访问有效堆栈帧之外的堆栈(直到它被OS停止),但它实际上不会从它"弹出":
#include <stdarg.h>
#include <stdio.h>
int underflow(int dummy, ...)
{
va_list ap;
va_start(ap, dummy);
int sum = 0;
for(;;)
{
int x = va_arg(ap, int);
fprintf(stderr, "%d\n", x);
sum += x;
}
return sum;
}
int main(void)
{
return underflow(42);
}
Run Code Online (Sandbox Code Playgroud)
因此,根据您对"堆栈下溢"的具体含义,此代码在某些平台上执行您想要的操作.但是从C的角度来看,这只是暴露了未定义的行为,我不建议使用它.它根本不是 "便携式"的.
| 归档时间: |
|
| 查看次数: |
6972 次 |
| 最近记录: |