一个栈溢出C++示例
Posted 李迟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个栈溢出C++示例相关的知识,希望对你有一定的参考价值。
本文主要演示、分析、测试函数内变量越界的问题,即栈溢出。
问题提出
很久前在测试某个工程时,发现一直能工作的模块出现了段错误。由于代码复杂,又有其它事耽搁,直到最近才集中时间调试。那个模块是循环vector,在其中计算数据再组装成字符串,最终将所有结果写到文件中。经测试发现,是在某次循环时出错。抽象化后的代码示例如下:
len = vPath.length();
for (int i = 0; i < len; i++)
// 处理逻辑
// 处理逻辑
// 若干次调用sprintf()组装字符串
// 循环第N次出错
开始以为是处理逻辑部分出错,后发现在某次调用sprintf之后,i
的值变得十分大。超过了vector容量,因此造成段错误。
后来确认,是sprintf组装的缓冲区越界,i
的值被覆盖了。因为当时加代码片段时,没有留意缓冲区大小问题,加大容量即可解决问题。
工程代码
先给出变量的设计,如下:
int ret = 0;
int type = 10;
int id = 0;
char buffer[32] = 0;
因为本文就是模拟栈溢出情况,而栈是向下(低地址)增长的,为了让缓冲区buffer溢出覆盖其它变量,因此将其放到最后定义,其大小为32(十六进制为0x20),这样一旦溢出,就会越界波及干扰到ret
、type
、id
这几个变量,它们均为int类型,指针大小为4字节。
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 打印buffer的内存数据
void dump(const char *buffer, int len)
int i, j, n;
int line = 16;
char c;
unsigned char* buf = (unsigned char *)buffer; // 必须是unsigned char类型
n = len / line;
if (len % line)
n++;
for (i=0; i<n; i++)
//printf("0x%08x: ", (unsigned int)(buf+i*line)); // linux ok
printf("0x%8p: ", buf+i*line); // windows ok
for (j=0; j<line; j++)
if ((i*line+j) < len)
printf("%02x ", buf[i*line+j]);
else
printf(" ");
printf(" ");
for (j=0; j<line && (i*line+j)<len; j++)
if ((i*line+j) < len)
c = buf[i*line+j];
printf("%c", c >= ' ' && c < '~' ? c : '.');
else
printf(" ");
printf("\\n");
int main(void)
int ret = 0;
int type = 10;
int id = 0;
char buffer[32] = 0;
//dump(buffer, 48);
for (int i = 0; i < 16; i++)
printf("---- type: %d id: %d i:%d \\n", type, id, i);
ret += sprintf(buffer+ret, "helloworld type: %d id: %d ", type, id);
printf("write total len: %d(0x%x)\\n", ret, ret);
printf("++++ type: %d(0x%x) id: %d(0x%x) i: %d(0x%x)\\n", type, type, id, id, i, i);
type ++;
id ++;
printf("ptr ret: %p type: %p id: %p\\n", &ret, &type, &id);
dump((char*)(buffer), 60);
return 0;
代码比较简单,循环组装字符串再保存到buffer中,注意,buffer的内容是累加的——这正是溢出的根本问题。为了方便观察,同时打印其它变量的值及地址。本文在 x86 平台测试,其为小端模式,因为打印的值需要倒着看。
测试
一次测试结果如下:
---- type: 10 id: 0 i:0
write total len: 26(0x1a)
++++ type: 10(0xa) id: 0(0x0) i: 0(0x0)
---- type: 11 id: 1 i:1
write total len: 824195711(0x31203a7f)
++++ type: 1887007776(0x70797420) id: 1684828783(0x646c726f) i: 1684611121(0x64692031)
ptr ret: 000000000022FE48 type: 000000000022FE44 id: 000000000022FE40
0x000000000022FE20: 68 65 6c 6c 6f 77 6f 72 6c 64 20 74 79 70 65 3a helloworld type:
0x000000000022FE30: 20 31 30 20 69 64 3a 20 30 20 68 65 6c 6c 6f 77 10 id: 0 hellow
0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ.: 12 id
0x000000000022FE50: 3a 20 31 20 00 00 00 00 c7 13 40 00 : 1 ......@.
下面分析执行情况:
- 循环开始,第一次一切正常。
- 循环到第二次时,缓冲区溢出,
ret
、id
变量的值十分大。i
亦然,故循环退出,由于代码没有用i
作索引,因为没有段错误。
溢出数值分析如下:
-
buffer
地址为0x000000000022FE20
,变量id
靠近buffer
,其地址buffer
后的32字节偏移处,为000000000022FE40
,接着是type
,偏移4字节,地址为000000000022FE44
,ret
地址是000000000022FE48
。 -
id
溢出后的值是1684828783(0x646c726f)
,观察对应打印的二进制:0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ.: 12 id:。如下:0x000000000022FE40: 70 72 6c 64 ... prld
应该是
hello world
最后4字节orld
,但有乱码。 -
type
溢出后的值是1887007776(0x70797420)
,观察对应打印的二进制:0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld !typ.: 12 id:。 -
ret
溢出后的值是824195711(0x31203a7f)
,观察对应打印的二进制:0x000000000022FE40: 70 72 6c 64 21 74 79 70 7f 3a 20 31 32 20 69 64 prld!typ .: 12 id:。
综合测试分析情况,那些变量溢出后的数值,基本上就是写到buffer越界后的数据。
扩展知识
栈、堆是不同的概念——因此前面提及的仅是栈,理论上栈、堆都有溢出的可能。
栈溢出一般有2种可能:无限递归,变量或数组定义很大。以linux系统为例,栈的大小为8MiB。可用ulimit -a
查看:
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 253387
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
其中stack size
为8192,即8MiB。另外也能从中知晓文件句柄数量最大为1024(open files
字段)。
文中的“溢出”是指写到buffer数组的内容超过其容量,占用了其它变量的空间。至于其它的溢出情况,就不再深究了。
小结
本文出现的问题,主要原因是缓冲区容量不足引起溢出的。幸好不是生产环境的,否则又得急忙救火了,但也给自己提了个醒。编码一定要注意细节,内存的操作,数组的索引,指针的判断,等,都要谨慎。所谓“小心驶得万年船”,作为一名编码工具人,对代码要常怀敬畏之心。
以上是关于一个栈溢出C++示例的主要内容,如果未能解决你的问题,请参考以下文章