运行环境 win 10 企业版 1809 17763.194,MinGW V3.14 32位,Bundled V3.13.2,Bundled GDB V8.2。

在C语言中,栈的方向是从高地址向低地址延伸,而数组中数据在栈中的存储方向与此正好相反。字符串拷贝等数组操作是不对数据长度做审核的,如果实际的数据长度超过了栈中预留的空间,就会将栈中其他数据覆盖,这种现象被称为“栈溢出”。栈溢出可能导致一个不可预期的错误,也可能导致一个精心策划的执行流程发生改变。可见,是否能够对自己所写程序的运行时状态做到心中有数,是能否写出高质量、安全代码的前提保证。

以上两节介绍的运行时结构都是由C程序所对应的指令,在内存中执行,驱动数据变化而产生的。C程序只有经过编译,才能生成目标代码。目标代码将与指令和全局数据一一对应。编译的最终目标就是能让C程序的设计意图体现在运行时结构中,这也使得编译的每个阶段的中心任务都要为形成运行时结构着想。下一节我们将概述编译的过程。

1.2 更为复杂C程序的运行时结构(1)

在实际编程过程中会遇到更为复杂的问题。要解决这样的问题,更加依赖对运行时结构的了解。下面我们来看一个比较复杂的案例,案例的两个程序分别如下:

#include <stdio.h>
#include <string.h> void fun1() {
int m = ;
char num[];
strcpy(num, "bbbb");
} void fun2() {
printf("You were attacked!!!\n");
} int main() {
fun1();
printf("over");
return ;
}
#include <stdio.h>
#include <string.h> void fun1() {
int m = ;
char num[];
strcpy(num, "bbbbbbbbbbbb\x12\x13\x40\x00");
} void fun2() {
printf("You were attacked!!!\n");
} int main() {
int address = (int) fun2;
printf("%08x\n", address); fun1(); return ;
}

这个案例中的两个程序在代码上只有微小的差别,但执行结果却不同,尤其是左边的程序,执行结果如下所示:

这些字符显然是fun2函数被调用时才会输出的,但fun2这个函数在本程序中没有被调用过,这样的输出结果显得有些不可思议了,程序执行时到底发生了什么呢?下面我们一步一步地对比分析这个案例。我们先来看main函数调用fun1函数时的情景,fun1函数执行后的返回地址被压入栈中,跳转到fun1函数执行,此时两边程序的执行没有差异,情景如图1-29所示。

之后保存了main函数栈底的地址值,ebp被腾出来,指向fun1函数的栈底,此时两边也没有差异。情景如图1-30所示。

m入栈,初始化为10,为num数组开辟了栈空间,此时仍然没有差异,情景如图1-31所示。

下面差异产生了。调用strcpy函数,执行的目的是把指定的字符串拷贝到num数组中,指定多少,拷贝多少。我们先来看右边的程序。该程序会把指定的字符串拷贝给num数组,其长度刚好填满num数组,情景如图1-32所示。

再看左边程序,指定的字符串长度已经超出了num数组的长度,所以在拷贝的时候,会把栈中前面的数据覆盖掉,包括num的数组、main函数栈底地址值直至fun1函数执行后的返回地址,全部被覆盖,情景如图1-33所示。

覆盖的结果使得fun1函数在返回并恢复现场时出现了问题。

我们先来看右边的程序,跳转回main函数,正常恢复,情景如图1-34所示。

再看左边的程序,栈底地址值被覆盖了,ebp会得到一个乱值,不再指向main函数的栈底,另外,由于fun1函数执行后返回地址已经被覆盖,而且覆盖的数值正好是fun2函数的起始地址,将这个数据传递给eip,那么eip自然跳转到fun2函数执行,相当于调用了fun2函数,也就输出了fun2函数的打印信息。同时,ebp成了乱值,程序最终将产生段错误,情景如图1-35所示。

更为复杂C程序的运行时结构的更多相关文章

  1. 图解简单C程序的运行时结构

    程序在内存中的存储分为三个区域,分别是动态数据区.静态数据区和代码区.函数存储在代码区,全局变量以及静态变量存储在静态数据区,而在程序执行的时候才会在动态数据区产生数据.程序执行的本质就是代码区的指令 ...

  2. [快手(AAuto)学习笔记]如何让程序在运行时请求管理员权限(UAC)

    作者:ffsystem 作为(糟糕的)程序猿,习惯写代码解决一些简单事务.正常用批处理就能解决大部分工作,复杂一点用AutoIt 3. 有时候要分发给别人,就需要一个界面.外行你程序写得如何他看不懂, ...

  3. 如何让Qt程序在运行时获取UAC权限

    在pro文件中加入以下语句: QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\' ...

  4. Laravel 使用 Provider 为程序提供运行时配置服务

    需求: 配置参数存在数据库中,Model 是 aah,需要在每次运行时,程序可以在任何地方采用 config("aah.name") 的方式访问配置信息. 思路: 采用 Provi ...

  5. Java中获取类的运行时结构

    获取运行时类的完整结构 通过反射获取运行时类的完整结构 Field(属性).Method(方法).Constructor(构造器).Superclass(父类).Interface(接口).Annot ...

  6. 关于java程序在运行时出现a java exception has occured时解决方法

    错误截图: 出现情况原因分析: 1.环境没有配置好,配置java环境变量: 参考 检查是否正确,java javac,可以尝试重新 2.查看使用的jdk版本是否存在版本问题: 例如jdk1.7对中文的 ...

  7. c#+handle.exe实现升级程序在运行时自动解除文件被占用的问题

    我公司最近升级程序经常报出更新失败问题,究其原因,原来是更新时,他们可能又打开了正在被更新的文件,导致更新文件时,文件被其它进程占用,无法正常更新而报错,为了解决这个问题,我花了一周时间查询多方资料及 ...

  8. 通过编写c语言程序,运行时实现打印另一个程序的源代码和行号

    2017年6月1日程序编写说明: 1.实现行号的打印,实现代码的读取和输出,理解主函数中的参数含义. 2.对fgets函数理解不够 3.对return(1); return 0的含义理解不够 4.未实 ...

  9. 为程序指定运行时所在的CPU核

    internal class Program { [DllImport("kernel32.dll")] private static extern uint GetTickCou ...

随机推荐

  1. python之用unittest实现接口参数化示例

    示例中获取参数的方法有三种: 1. 从文件(txt)中读取参数 2. 从Excel中读取参数 3. 在代码中直接写参数 def login(username,password): return 'ok ...

  2. Django REST Framework API Guide 06

    本节大纲 1.Validators 2.Authentication Validators 在REST框架中处理验证的大多数时间,您将仅仅依赖于缺省字段验证,或在序列化器或字段类上编写显式验证方法.但 ...

  3. 网络学习day04_VLSM、子网划分

    IP子网划分 首先,在进行子网划分的学习之前,我们先来回顾一下IP地址的相关知识,同时了解一下公有和私有IP地址: 在Internet上有千百万台主机,为了区分这些主机,人们给每台主机都分配了一个专门 ...

  4. 分布式系列六: WebService简介

    WebSerice盛行的时代已经过去, 这里只是简单介绍下其基本概念, 并用JDK自带的API实现一个简单的服务. WebSerice的概念 WebService是一种跨平台和跨语言的远程调用(RPC ...

  5. Java基础10-集合

    作业回顾 蜜蜂和熊的生产消费关系,熊在蜂蜜满10斤吃掉.蜜蜂一次生产一斤蜂蜜,且蜜蜂生成一斤蜂蜜花费的时间是10s. 十只蜜蜂和两只熊. 蜜蜂 bag: 20 每次产1,耗时10ms 满5的时候给蜜罐 ...

  6. windows每天备份文件的bat脚本【原创】

    备份昨天文件的脚本 @echo off set yy=%DATE:~,% set mm=%DATE:~,% set ,% ::前一天的日期,格式化输出 ,date)>vbs.vbs for /f ...

  7. MVC中一般为什么用IQueryable而不是用IList?

    IList(IList<T>)会立即在内存里创建持久数据,这就没有实现“延期执行(deferred execution)”,如果被加载的实体有关联实体(associations),此关联实 ...

  8. linux安装selenium+chrome+phantomjs

    1. 安装 selenium pip3 install selenium pip3 安装参考 2. 安装 ChromeDriver yum install chromedriver.x86_64 3. ...

  9. 【转】Java Socket编程基础及深入讲解

    原文:https://www.cnblogs.com/yiwangzhibujian/p/7107785.html#q2.3.3 Socket是Java网络编程的基础,了解还是有好处的, 这篇文章主要 ...

  10. C++线程中的几种锁

    线程之间的锁有:互斥锁.条件锁.自旋锁.读写锁.递归锁.一般而言,锁的功能越强大,性能就会越低. 1.互斥锁 互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量.也就是说是为了避免多个线程在 ...