ARMCC/GCC下的stack protector

2024-06-03 17:48
文章标签 stack gcc armcc protector

本文主要是介绍ARMCC/GCC下的stack protector,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1027679

相关文章

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

bash: arm-linux-gcc: No such file or directory

ubuntu出故障重装了系统,一直用着的gcc使用不了,提示bash: arm-linux-gcc: No such file or directorywhich找到的命令所在的目录 在google上翻了一阵发现此类问题的帖子不多,后来在Freescale的的LTIB环境配置文档中发现有这么一段:     # Packages required for 64-bit Ubuntu

编译linux内核出现 arm-eabi-gcc: error: : No such file or directory

external/e2fsprogs/lib/ext2fs/tdb.c:673:29: warning: comparison between : In function 'max2165_set_params': -。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。 。。。。。。。。 host asm: libdvm <= dalvik/vm/mterp/out/Inte

【linux学习指南】Linux编译器 gcc和g++使用

文章目录 📝前言🌠 gcc如何完成🌉预处理(进行宏替换) 🌠编译(生成汇编)🌉汇编(生成机器可识别代码) 🌠链接(生成可执行文件或库文件)🌉函数库 🌠gcc选项🚩总结 📝前言 预处理(进行宏替换)编译(生成汇编)汇编(生成机器可识别代码)连接(生成可执行文件或库文件) 🌠 gcc如何完成 格式 :gcc [选项] 要编译的文件 [选项] [目标文

gcc编译常见问题

inux C gcc -lm     使用 math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项 ,因为数学函数位于 libm.so 库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。本书用到的大部分库函数(例如printf)位于 libc.so 库文件中,使用libc.so中的库函数在编译时不需要加-l

Java-数据结构-栈和队列-Stack和Queue (o゚▽゚)o

文本目录: ❄️一、栈(Stack):     ▶ 1、栈的概念:   ▶ 2、栈的使用和自实现:      ☑ 1)、Stack():       ☑ 2)、push(E e):      ☑ 3)、empty():         ☑ 4)、peek(E e):        ☑ 5)、pop(E e):       ☑ 6)、size(E e):  ▶ 3、栈自实现的总代

C++入门(05-2)从命令行执行C++编译器_GCC

文章目录 GCC编译器1. 下载MinGW-w64,安装(不推荐)2. 使用MSYS2安装MinGW-w64(推荐)2.1 安装MSYS22.2 初始化和更新2.3 安装MinGW-w64编译器2.3 在MSYS2 Shell中导航到代码目录2.4 使用 g++ 编译2.5 运行可执行文件 GCC编译器 GCC(GNU Compiler Collection)是一个开源编译器集

gcc 编译器对 sqrt 未定义的引用

man sqrt  Link with -lm. gcc -o test test.c -lm 原因:缺少某个库,用 -l 参数将库加入。Linux的库命名是一致的, 一般为 libxxx.so, 或 libxxx.a, libxxx.la, 要链接某个库就用   -lxxx,去掉头 lib 及 "." 后面的 so, la, a 等即可。 常见的库链接方法为

linux编译器——gcc/g++

1.gcc linux上先要安装, sudo yum install gcc gcc --version 可以查看当前的版本 ,我们默认安装的是4.8.5的版本,比较低, gcc test.c -std=c99 可以使他支持更高版本的c标准 -o 可以殖指明生成文件的名字,可以自己命名,比如 gcc test.c -o my.exe -std=c99 或者 gcc -o my.exe

gcc make cmake例程

main.cpp文件: #include <iostream>#include "utils.h"int main(void) {int a = 1;int b = 2;int c = AddFunc(a, b);std::cout<< c <<std::endl;return 0;} utils.h文件: #pragma onceint AddFunc(int a, int b);