【汇编杂项】关于_高级语言中 数组越界与汇编中 栈溢出的_联系的思考

  • 数组越界

    数组越界,是刚开始学习编程时,就不断被别人提醒的一个点,“相当可怕”。获取不合理数值,造成程序异常or操作计算机重要内存,造成威胁。。。原因是什么呢?数组在汇编中以栈机制实现,其中内存分配的机制与数组越界的风险有很大关系。今天做个小实验,来简单探讨下这个。

  • 代码

    先展示问题代码

1 #include<stdio.h>
2 int main(){
3     int a[3]={0,1,2};
4     for(int i=0;i<=3;i++){
5         a[i]=0;
6         printf("test");
7     }
8     return 0;
9 }    

    

    诸君很容易看出,第4行for循环内的结束条件设置的显然有问题,数组a长度位3,显然下标只能到2,而循环中却做了一个对所谓a[3]的赋0操作,这就是常说的数组下标越界问题。

    看一下,这个问题代码给我们带来了什么样的麻烦。。。

    我编译出可执行文件,运行。。。瞬间屏幕被 “test” 字符串填满。。。

    仅仅两三秒,就不知做了多少次循环了,计算机运算就是快/xyx/xyx/xyx,展示下页长。。。

    

    显然,由于这个数组越界的问题,我们陷入了死循环,(疯狂ctrl+c,终于停了,如图)

  • 思考

    那。。。为什么会死循环?

    汇编语言里找问题,用gcc拿出中间汇编文件,查看汇编代码(没有采用什么O1/O2的优化编译,所以以下仍含有栈帧的概念)。

 1         .file   "test.c"
 2         .intel_syntax noprefix
 3         .section        .rodata
 4 .LC0:
 5         .string "test"
 6         .text
 7         .globl  main
 8         .type   main, @function
 9 main:
10 .LFB0:
11         .cfi_startproc
12         push    rbp
13         .cfi_def_cfa_offset 16
14         .cfi_offset 6, -16
15         mov     rbp, rsp
16         .cfi_def_cfa_register 6
17         sub     rsp, 16
18         mov     DWORD PTR [rbp-16], 0
19         mov     DWORD PTR [rbp-12], 1
20         mov     DWORD PTR [rbp-8], 2
21         mov     DWORD PTR [rbp-4], 0
22         jmp     .L2
23 .L3:
24         mov     eax, DWORD PTR [rbp-4]
25         cdqe
26         mov     DWORD PTR [rbp-16+rax*4], 0
27         mov     edi, OFFSET FLAT:.LC0
28         mov     eax, 0
29         call    printf
30         add     DWORD PTR [rbp-4], 1
31 .L2:
32         cmp     DWORD PTR [rbp-4], 3
33         jle     .L3
34         mov     eax, 0
35         leave
36         .cfi_def_cfa 7, 8
37         ret
38         .cfi_endproc
39 .LFE0:
40         .size   main, .-main
41         .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)"
42         .section        .note.GNU-stack,"",@progbits

    

    (杂鱼懒得删了,全贴,这是intel风格64位汇编代码,带诸君看)

    开始分析,第11行到38行就是main函数了,.cfi_startproc和.cfi_endproc是调用框架指令,用来标记这是一段函数(这里涉及调用框架,诸君有兴趣自行探索)。

    

    12行起到16行:一系列操作据说是函数调用框架的规范步骤,成为前序,就是为了调用main函数和函数返回的正常做的工作,这里不做深究。

    

    我们看17行,将rsp(栈顶寄存器)减了16字节,这就是为下面的数组及变量开辟空间。接着四步就可以看出一次录入了 a[0],a[1],a[2],i 的值。接着jmp无条件跳转到 L2段。

    !!!L2里就涉及到for循环的控制了,我们开始接近问题的本源了。cmp 操作数,用来比较 [rbp-4] (我们知道这里放的是变量i) 与3的大小,接着jle(什么jump when less or equal,差不多这样),若结果为小于等于则跳转到L3。

    L3内,上来就把我们的i的值取了出来(因为后面依据下标取数组元素要用到),接着cdqe是将32为寄存器拓展为64为寄存器rax。我们就从出问题的时间点来排查,假设这时i的值已为3(即下标已经越界了),可是到了26行时 i 的值又被赋为了0,这一步其实对应 c 文件里for循环中 a[i] = 0; 这一步,但是这里由于栈帧中内存的分配导致越界后操作到了 i 的值。可想而知,程序的逻辑是for循环到 i==4(i<=3) 时结束,而每次 i一到 3 又被我们重置为 0,for循环又如何停止???所以就死循环了呗。

    (附一张main栈帧的简图,方便诸君理解)

    没错,分析到这里基本就没什么问题了,可是学习不止于此。。。

  • 拓展

    既然我们知道死循环是由于 i 变量被非法篡改了,导致无法满足 i>3 的截止条件,那么我们可不可以“将错就错”,使 i 的值被非法篡改为满足条件的值(比如4)

    即 a[i]=0; ==> a[i]=4; 那么汇编就变为 mov DWORD PTR [rbp-16+rax*4],4 。(虽然没啥意义,但从侧面印证了,的确是 i 的值被篡改导致问题)

  • 思考

    幸而这里只是一个不那么紧要的变量被改,导致这个小小的程序出错。然而更多时候,这样的问题威胁更大:堆栈溢出!这是缓冲区溢出中危害较大的一种了,原理就是我们设计的程序并没有对接受的数据做长度的检查,导致该程序分配到的内存空间(栈区/缓冲区)放不下这么多内容,从而,这些数据被写入到其他不合理的内存空间,比如上图中返回地址,一旦被修改,下一条被执行的保不齐就是一条shellcode,系统被别人拿了特权;又或者恶意导致计算机宕机。。。(懂得不多,差不多也就了解到写。意在引起诸君对栈溢出的关注,无论是以后走安全,还是走马农,有良好的“意识”)

  • 扯闲篇

    关于CFI(函数调用框架),还是想扯点东西的,毕竟专门去了解了一堆,但是看网上人家都说现在都不用栈帧这些了,不知诸君想看不。。。

原文地址:https://www.cnblogs.com/hackmylife/p/9781481.html

时间: 2024-10-02 05:38:56

【汇编杂项】关于_高级语言中 数组越界与汇编中 栈溢出的_联系的思考的相关文章

C编译器剖析_1.5 结合C语言来学汇编_指针、数组和结构体

让我们再来看一份C代码,及其经UCC编译器编译后产生的主要汇编代码,如图1.33所示,其中包含了数组.指针和结构体. 图1.33 数组.指针和结构体 按照C的语义,图1.33第9行的C代码是对局部数组number的初始化,需要把number[0]初始化为2015,而数组中的其他元素皆被初始化为0.UCC编译器采取的翻译方法是:先调用memset函数来把数组number所占的内存空间清0,然后再把number[0]设为2015,如图1.33的第17至24行所示.C库函数memset的API如下所示

Java中的数组越界问题

Java中数组初始化和OC其实是一样的,分为动态初始化和静态初始化, 动态初始化:指定长度,由系统给出初始化值 静态初始化:给出初始化值,由系统给出长度 在我们使用数组时最容易出现的就是数组越界问题,好了,下面来演示一下 int [][] array = {{1,2,3},{1,4}}; System.out.println(array[1][2]); 这是一个二维数组,很明显,数组越界了,控制台中会打印如下信息: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px;

逆向知识十三讲,汇编中数组的表现形式,以及还原数组

逆向知识十三讲,汇编中数组的表现形式,以及还原数组 讲解数组之前,要了解数组的特性 1.数据具有连续性 2.数据类型相同 比如: int Ary[3] = {0,1,2}; 我们可以看出,上面定义的数组,数据是连续的,其中每个数据类型大小都是int类型(类型也是一样的) 汇编中识别数组: 1.地址连续 2.带有比例因子寻址   (lea  reg32,[xxx  + 4 *xxxx]) 一丶一维数组在汇编中的表现形式 首先说下数组寻址公式,便于下面讲解 公式: 数组首地址 + sizeof(ty

在Visual C++中使用内联汇编

一.内联汇编的优缺点 因为在Visual C++中使用内联汇编不需要额外的编译器和联接器,且可以处理Visual C++中不能处理的一些事情,而且可以使用在C/C++中的变量,所以非常方便.内联汇编主要用于如下场合: 1.使用汇编语言写函数: 2.对速度要求非常高的代码: 3.设备驱动程序中直接访问硬件: 4."Naked" Call的初始化和结束代码. //(."Naked",理解了意思,但是不知道怎么翻译^_^,大概就是不需要C/C++的编译器(自作聪明)生成的

《数据结构与算法之美》 &lt;03&gt;数组:为什么很多编程语言中数组都从0开始编号?

提到数组,我想你肯定不陌生,甚至还会自信地说,它很简单啊. 是的,在每一种编程语言中,基本都会有数组这种数据类型.不过,它不仅仅是一种编程语言中的数据类型,还是一种最基础的数据结构.尽管数组看起来非常基础.简单,但是我估计很多人都并没有理解这个基础数据结构的精髓. 在大部分编程语言中,数组都是从 0 开始编号的,但你是否下意识地想过,为什么数组要从 0 开始编号,而不是从 1 开始呢? 从 1 开始不是更符合人类的思维习惯吗? 你可以带着这个问题来学习接下来的内容. 如何实现随机访问? 什么是数

05| 数组:为什么很多编程语言中数组都从 0 开始编号?

提到数组,我想你肯定不陌生,甚至还会自信地说,它很简单啊. 是的,在每一种编程语言中,基本都会有数组这种数据类型.不过,它不仅仅是一种编程语言中的数据类型,还是一种最基础的数据结构.尽管数组看起来非常基础.简单,但是我估计很多人都并没有理解这个基础数据结构的精髓. 在大部分编程语言中,数组都是从0开始编号的,但你是否下意识地想过,为什么数组要从0开始编号,而不是从1开始呢? 从1开始不是更符合人类的思维习惯吗? 你可以带着这个问题来学习接下来的内容. 如何实现随机访问? 什么是数组?我估计你心中

为什么很多编程语言中数组都是从 0 开始编号?

1.什么是数组? 数组(Array)是一种线性表数据结构.它用一组连续的内存空间,来存储一组具有相同类型的数据. 概念解析:       线性表:线性表就是数据排成像一条线一样的结构.每个线性表上的数据最多只有前和后两个方向.其实除了数组,链表.队列.栈等也是线性表结构. 连续的内存空间和相同类型的数据:所以数组根据下标具有随机访问特性,这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除.插入一个数据,为了保证连续性,就需要做大量的数据搬移工作. 2.数组是如何实现根据下标随机访问数

QRCode 数组越界异常

因为需求的缘故需要解析出ios二维码的地址,把解析的地址传到按钮上 把功能写好之后用几张二维码测试没问题后提交到svn上,第二天生产环境正好发版,发现有个应用的按钮点了一直没反应,看了下控制台发现报错 居然是数组越界...纠结了好久.最后点到那个二维码看看有什么特别之处 一切尽在无言中(我擦...居然是1000多像素的二维码,你逗我呢)...

私人定制javascript中数组小知识点(Only For Me)

先上笑话,1.刚看到一个游泳的,想起公司组织去三亚旅游,老板跳海里,各种挣扎,捞上来老板第一句话:我记得我会游泳的啊. 2.媳妇说:老公对不起,我把你新买的自行车撞散架了! 老公:没事宝贝,你若安好,便是晴天! 媳妇说:老公你太有诗意了. 老公:滚犊子,安不好我整死你! 数组的概念 javascript数组是值得有序集合,不过它实属一个javascript对象的特殊形式,这是一个很重点的定性. 创建数组 1.var a=new Array();//等同于[] 2.var a=new Array(