Stack overflow攻击是一种很常见的代码攻击,armcc和gcc等编译器都实现了stack protector来避免stack overflow攻击。虽然armcc和gcc在汇编代码生成有些不同,但其原理是相同的。这篇文章以armcc为例,看一看编译器的stack protector。

armcc提供了三个编译选项来打开/关闭stack protector。

  • –no_protect_stack 关闭stack protector
  • –protect_stack 为armcc认为危险的函数打开stack protector
  • –protect_stack_all 为所有的函数打开stack protector

armcc如何防止stack overflow攻击?

armcc在函数栈中的上下文和局部变量之间插入了一个数字来监控堆栈破坏,这个值一般被称作为canary word,在armcc中将这个值定义为__stack_chk_guard。当函数返回之前,函数会去检查canary word是否被修改,如果canary word被修改了,那么证明函数栈被破坏了,这个时候armcc就会去调用一个函数来处理这种栈破坏行为,armcc为我们提供了__stack_chk_fail这个回调函数来处理栈破坏。

因此,在armcc打开- –protect_stack之前需要在代码中设置__stack_chk_guard和__stack_chk_fail。我从ARM的官网上摘抄了一段它们的描述。

void *__stack_chk_guardYou must provide this variable with a suitable value, such as a random value. The value can change during the life of the program. For example, a suitable implementation might be to have the value constantly changed by another thread.void __stack_chk_fail(void)It is called by the checking code on detection of corruption of the guard. In general, such a function would exit, possibly after reporting a fault.

armcc stack protector产生了什么代码来防止stack overflow?

首先来看一下写的一个c代码片段, 代码很简单,__stack_chk_guard 设置为一个常数,当然这只是一个例子,最好的方法是设置这个值为随机数。然后重写了__stack_chk_fail这个回调接口。test_stack_overflow这个函数很简单,仅仅在函数栈上分配了i和c_arr这两个局部变量,并对部分成员赋值。

void __stack_chk_fail()
{print_uart0("__stack_chk_fail()\n");while(1);
}void *__stack_chk_guard = (void *)0;int test_stack_overflow(int a, int b, int c, int d, int e)
{int i;int c_arr[15];int *p = c_arr;i = 15;c_arr[0] = 2;c_arr[1] = 3;return 0;
}

OK,首先看一下在–no_protect_stack情况下armcc产生的汇编代码,仅仅只是在栈上分配c_arr这个局部数组,而i这个变量则使用r1寄存器来保存。

60010044 <test_stack_overflow>:
60010044:   e92d4070    push    {r4, r5, r6, lr}
60010048:   e24dd03c    sub sp, sp, #60 ; 0x3c
6001004c:   e1a04000    mov r4, r0
60010050:   e1a05001    mov r5, r1
60010054:   e1a06002    mov r6, r2
60010058:   e59dc04c    ldr ip, [sp, #76]   ; 0x4c
6001005c:   e1a0200d    mov r2, sp
60010060:   e3a0100f    mov r1, #15
60010064:   e3a00002    mov r0, #2
60010068:   e58d0000    str r0, [sp]
6001006c:   e3a00003    mov r0, #3
60010070:   e58d0004    str r0, [sp, #4]
60010074:   e3a00000    mov r0, #0
60010078:   e28dd03c    add sp, sp, #60 ; 0x3c
6001007c:   e8bd8070    pop {r4, r5, r6, pc}

其栈上的内存map如下图所示

然后看一看使用–protect_stack_all选项编译后产生的汇编代码

600100a0 <test_stack_overflow>:
600100a0:   e92d47f0    push    {r4, r5, r6, r7, r8, r9, sl, lr}
600100a4:   e24dd040    sub sp, sp, #64 ; 0x40
600100a8:   e1a07000    mov r7, r0
600100ac:   e1a08001    mov r8, r1
600100b0:   e1a09002    mov r9, r2
600100b4:   e1a0a003    mov sl, r3
600100b8:   e59d6060    ldr r6, [sp, #96]   ; 0x60
600100bc:   e59f0094    ldr r0, [pc, #148]  ; 60010158 <c_entry+0x58>
600100c0:   e5904000    ldr r4, [r0]
600100c4:   e58d403c    str r4, [sp, #60]   ; 0x3c
600100c8:   e1a00000    nop         ; (mov r0, r0)
600100cc:   e1a00000    nop         ; (mov r0, r0)
600100d0:   e3a00002    mov r0, #2
600100d4:   e58d0000    str r0, [sp]
600100d8:   e3a00003    mov r0, #3
600100dc:   e3a05000    mov r5, #0
600100e0:   e58d0004    str r0, [sp, #4]
600100e4:   e59d003c    ldr r0, [sp, #60]   ; 0x3c
600100e8:   e1500004    cmp r0, r4
600100ec:   0a000000    beq 600100f4 <test_stack_overflow+0x54>
600100f0:   ebffffc2    bl  60010000 <__stack_chk_fail>
600100f4:   e1a00005    mov r0, r5
600100f8:   e28dd040    add sp, sp, #64 ; 0x40
600100fc:   e8bd87f0    pop {r4, r5, r6, r7, r8, r9, sl, pc}

两段代码主要的差异在于如下

600100bc:   e59f0094    ldr r0, [pc, #148]  ; 60010158 <c_entry+0x58>
600100c0:   e5904000    ldr r4, [r0]
600100c4:   e58d403c    str r4, [sp, #60]   ; 0x3c

这段代码很简单,就是从60010158 这个地址取出一个值,再将这个值作为地址取出他的值,将它保存到了sp, #60这个位置,这个位置就是位于上下文的下方和c_arr数组的上方。可以看一下此时的函数栈内存map是什么样子,如下图

还有一段差异代码如下,很简单就是在函数return之前拿出这个stack_chk_guard比较了一下,如果这个值被修改了就证明函数栈被破坏,如果没被修改就说明函数可以正常返回。

600100e4:   e59d003c    ldr r0, [sp, #60]   ; 0x3c
600100e8:   e1500004    cmp r0, r4
600100ec:   0a000000    beq 600100f4 <test_stack_overflow+0x54>
600100f0:   ebffffc2    bl  60010000 <__stack_chk_fail>

armcc中stack protector的作用

这个段落中写了一段代码来模拟这个stack overflow攻击,大部分代码与之前没有什么差异,在test_stack_overflow中*(p + 15) = 1234这一句表示修改stack_chk_guard,从图2中可以看到c_arr是15个整形变量数组,那么p+15就正好位于c_arr上方,即stack_chk_guard。同样根据上图推算出p+23就是栈中保存的返回地址,在这里将返回地址修改为attack_attack这个函数地址,来模拟栈被攻击后跳转到黑客想去运行的地址。attack_attack只是打印了一句话而已。

int test_stack_overflow(int a, int b, int c, int d, int e)
{int i;int c_arr[15];int *p = c_arr;i = 15;c_arr[0] = 2;c_arr[1] = 3;*(p + 15) = 1234;            /* modify the guard word, see fig.2*/*(p + 23) = (int)attack_attack;     /* modify return address as the attack function, see fig.2*/return 0;
}
int c_entry()
{print_uart0("befroe test_stack_overflow\n");test_stack_overflow(1, 2, 3, 4, 5);print_uart0("after test_stack_overflow\n");return 0;
}
void attack_attack()
{print_uart0("attack attack!\n");
}

将代码编译后,运行于qemu-system-arm上,得到打印如下,正如我们所预期的一样,由于stack_chk_guard被修改了,证明函数栈已经被破坏,所以并没有运行到attack_attack函数,而是跳转到了__stack_chk_fail进行处理。

不过需要注意的是,如果攻击者能够绕过stack_chk_guard而去直接修改pc值,那么stack protector是没有效果的,任何编译器都是一样的。但其实由于stack overflow的特性,攻击者是很难绕过stack_chk_guard这个值而去直接修改pc。假设他能绕过stack_chk_guard,那么实际上他可以去任意修改栈中的数据,也就不需要使用stack overflow来进行攻击。还是以上面那段代码做一个实验,在test_stack_overflow函数中将 *(p + 15) = 1234注释到,那么最终还是能够运行到attack_attack函数。代码如下:

int test_stack_overflow(int a, int b, int c, int d, int e)
{int i;int c_arr[15];int *p = c_arr;i = 15;c_arr[0] = 2;c_arr[1] = 3;//*(p + 15) = 1234;          /*no modify the guard word, see fig.2*/*(p + 23) = (int)attack_attack;     /* modify return address, see fig.2*/return 0;
}
int c_entry() {print_uart0("befroe test_stack_overflow\n");test_stack_overflow(1, 2, 3, 4, 5);print_uart0("after test_stack_overflow\n");return 0;
}

实验结果

小结

armcc中stack protector通过一些简单的设置就能实现,其他编译器的实现的原理也是大同小异,至少我看过gcc的stack protector,它的实现方式是跟armcc一样的。

ARMCC/GCC下的stack protector相关推荐

  1. 编译驱动模块时,出现“stack protector enabled but no compiler support”[解决办法]

    写驱动程序,编译驱动模块时,出现 "make[1]: Entering directory `/usr/src/linux-headers-2.6.32-5-amd64' /usr /src ...

  2. Stm32H7XX GCC下分散加载实现

    Stm32H7XX GCC下分散加载实现 STM32H750XBHT这块单片机,里面有五块内存区: TCM 区 速度: 400MHz. DTCM 地址: 0x2000 0000, 大小 128KB. ...

  3. 【Linux】关于三角函数、反三角函数在VC和linux gcc下的编译

    写法: 1.都要包含 <math.h> 2.三角函数,比如计算sin(30),应写 double  a; a = sin(30 * 3.1415926 /180): 3.反三角函数,比如a ...

  4. 输出10000以内的第M到第N个素数(Eclipse gcc下)

    偶然发现网易公开课的在线课程居然有测试,兴致大发随手做了几个,结果尼玛,一段时间不接触了手生了,测试平台采用的是gcc gcj,这让我很无语啊,gcj提交老是报错,始终无法解决,为了一劳永逸,搞了个E ...

  5. [转]gcc下程序调用静态库编译命令:主文件必须在静态库前面!

    很容易犯的错误,转载以备注! 转载请注明文章地址,尊重作者赖半仙的劳动成果,谢谢支持: http://hi.baidu.com/mgqw/blog/item/0969c4230a2508559922e ...

  6. gcc下strstream使用时报错

    源码  #include <strstream>  #include <iostream>  using namespace std;  int main()  {  strs ...

  7. 一、CentOS7.4下Elastic Stack 6.2.4日志系统搭建

    Elasticsearch是一个高度可扩展的开源全文搜索和分析引擎.它允许您快速,近实时地存储,搜索和分析大量数据.它通常用作支持具有复杂搜索功能和需求的应用程序的底层引擎/技术.         L ...

  8. Linux GCC下strstr的实现以及一个简单的Kmp算法的接口

    今天做了一道题,要用判断一个字符串是否是另一个字符串的子串,于是查了一下strstr的实现. 代码如下: 1 char *strstr(const char*s1,const char*s2) 2 { ...

  9. [转]浅谈缓冲区溢出之栈溢出

    浅谈缓冲区溢出之栈溢出 By 浅墨 发表于 2012-12-02 有段时间没有用windows了,刚一开机又是系统补丁更新.匆匆瞥了一眼看到了"内核缓冲区溢出漏洞补丁"几个字眼.靠 ...

最新文章

  1. keyset(),entryset() 遍历 (转)
  2. 【数字信号处理】基本序列 ( 正弦序列 | 数字角频率 ω | 模拟角频率 Ω | 数字频率 f | 模拟频率 f0 | 采样频率 Fs | 采样周期 T )
  3. mysql建表语句主键自增_MYSQL索引-上
  4. Docker 运行的 应用程序无法连接Oracle数据库的解决办法
  5. Redis的常用命令——String的常用命令
  6. facebook海量图片存储系统与淘宝TFS系统比较
  7. mugen4g补丁如何使用_如何搜索下载游戏
  8. 有哪些你踏入社会才明白的道理
  9. 飞机上的氧气面罩有什么用_第2部分—另一个面罩检测器……(
  10. 前台文件_欧木瑾怎么定制办公前台?
  11. 面试题:各大公司Java后端开发面试题总结 已看1 背1 有用 链接有必要看看
  12. opencv BRIEF角检测
  13. sql 2000與sql 2005互遷移的問題
  14. 简单RAM存储器分析
  15. 计算机中官方文档阅读方法,中国知网-帮助中心
  16. php求1到100的素数之和,php 质数计算 求100以内质数和
  17. 获取select选中的值php,js如何获取select标签选中的值
  18. inno setup 初次使用
  19. echart图表-刷新界面.初始化时默认显示hover提示内容
  20. 电商直播元年 微媒云播打造私域流量火爆商业新模式

热门文章

  1. 计算机控制基础冯勇,研究生导师冯勇:哈尔滨工业大学
  2. 为什么你的简历没人看?7份案例分析(收藏)
  3. 【知识兔】多个工作表数据的快速求和,简单到爆
  4. 马云告诉你在农村做什么赚钱
  5. ContextCapture User Guide V4.4.11 ContextCapture(Smart3D 帮助文档 第三章 认识软件)
  6. Android中的友盟(微信、QQ、新浪)第三方登录分享
  7. C++ 旅馆顾客统计(静态成员)
  8. Odoo 16 企业版手册 - 采购之供应商管理
  9. 荣誉时刻丨Linkflow入选《2022中国新品牌服务商手册》
  10. 【转】初学入门:如何有效编写软件的75条建议