DoubleLi

qq: 517712484 wx: ldbgliet

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、程序编译,链接后生成二进制可执行程序。二进制可执行文件以elf格式实现排列。可以通过readelf -S xxxx查看具体section的划分,粗略划分如下图所示。

在这些section中,代码段是只读的,自然也就不存在代码(指令)被改写的情况。数据段,堆,栈区具有读写的属性,但是数据段和堆一般存放的是数据,不涉及到指令的问题。剩下的栈区,存放的既有数据,又有代码(指令),可能存在代码(指令)被改写,即内存被飞踩现象。

2、栈空间数据被修改情况

2.1、先做个实验(ARM 32环境)

2.1.1、栈区空间

 
 
#include <unistd.h>
#include <stdio.h>
int do_nothing(int a)
{
return a;
}
 
 
int max(int a,int b)
{
int c = 0;
do_nothing(c);
return a + b;
}
 
 
 
int main()
{
 
int res = 0;
 
int a = 1;
 
int b = 2;
 
res = max(a, b);
 
return res;
 
}
 
 
 
 
arm-linux-gcc func.c -o func
 
arm-linux-objdump -d func > func.txt
 
 

查看反汇编结果

 
 
00010490 <max>:
 
10490: e92d4800 push {fp, lr} ;把bl <max>的下一条指令地址lr保存到栈区
 
10494: e28db004 add fp, sp, #4
 
10498: e24dd010 sub sp, sp, #16 ;确定好栈区大小
 
1049c: e50b0010 str r0, [fp, #-16]
 
104a0: e50b1014 str r1, [fp, #-20] ; 0xffffffec
 
104a4: e3a03000 mov r3, #0
 
 

增加max函数的局部变量个数,再查看相对应的反汇编

 
 
int max(int a,int b)
{
 
int c = 0;
 
int d = 0;
 
int e = 0;
 
c = d;
 
do_nothing(c);
 
return a + b;
 
}
 
 
 
 
00010490 <max>:
 
10490: e92d4800 push {fp, lr}
 
10494: e28db004 add fp, sp, #4
 
10498: e24dd018 sub sp, sp, #24 ;栈区空间增大
 
1049c: e50b0018 str r0, [fp, #-24] ; 0xffffffe8
 
104a0: e50b101c str r1, [fp, #-28] ; 0xffffffe4
 
104a4: e3a03000 mov r3, #0
 
 

由此可见,栈区的大小在程序编译的时候就已经确定。存放着函数退出后下一条指令的地址和局部变量的数值。

2.1.2、栈区数据

 
 
#include <stdio.h>
#include <string.h>
 
int do_nothing()
{
printf("nothing\n");
}
 
 
 
int func(int i)
{
int a = 6;
 
int b = 0;
 
int *p = NULL;
 
char num[3] = {0x6d, 0x04, 0x01};
 
p = &b;
 
printf("b addr=0x%x, func=0x%x\n", p, do_nothing);
 
printf("stack value:0x%x, 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", *(p + 1), *(p + 2), *(p + 3), *(p + 4), *(p + 5), *(p + 6), *(p + 7), *(p + 8));
 
// memcpy((char *)(p+8), num, 3);
 
return a * i;
}
 
 
 
int main(void)
{
 
int i = 1;
 
 
func(i);
 
i++;
 
return i;
 
}
 
 

从局部变量中,获取栈区地址,打印栈区里面的部分数据。

第一个红框存放着局部变量的数据,第二个红框存放着退出函数后下一条指令的地址,可以从反汇编中确认。

2.1.3、修改栈区数据,其中存放的下一条指令的地址0x10545被改写,程序运行是不是就会出现异常。

上图截图中绿框所示,对应着是另外一个函数do_nothing的地址,假如把栈区0x10545的数据改写成do_nothing的地址,应该就会执行do_nothing的函数。

释放2.1.2代码的memcpy处注释,运行程序,程序果然执行了do_nothing的函数

 2.2、上述假如被踩的数据是其他值,程序就可能会直接崩溃。而当程序出现崩溃时,数据可能在崩溃前的某一时刻就被修改,出现崩溃的现场并不是第一现场,很难定位。如下面的代码:

 
 
void fun(void) {
 
char arr[5];
 
strcpy(arr, "are you ok.are you ok.");
 
return;
 
}
 
 

2.3、可以在编译的时候添加-fstack-protector-all编译选项,减少定位难度(哪位大侠有更好的定位手段,在评论区求赐教),不至于代码跑飞,艰难查找到第一现场。

gcc stack.c -fstack-protector-all -o stack
 

 没有添加-fstack-protector-all编译选项出现异常时的日志

此core_dump很难查找到问题原因,因为出现异常的地方不是第一现场。 

2.4、栈区被踩问题的定位手段

栈区数据被飞踩问题定位手段_sydyh43的博客-CSDN博客

3、GOT表被修改情况

3.1、可以先看看下面的这篇分析

动态库*.so的延时绑定分析_sydyh43的博客-CSDN博客

打开git源码中的Makefile屏蔽和main函数的#if 0既可。

3.2、如以下的代码

 
 
int arr[2]; //arr数组是全局变量
void fun(void) {
arr[-8] = 0xFF;
return;
}
 
 

3.3、这种内存被踩的情况一般不会出现,因为编译器默认会把relacation后的GOT表设置成只读。

4、综上所述,关于内存被踩的问题,需要编译的时候做好准备。在编译的时候添加编译选项,出现问题的时候可以保留第一现场。编译选项添加情况可以通过checksec.sh脚本校验,详见:

linux程序的常用保护机制 | 上善若水

 

参考:(224条消息) 内存飞踩问题的几点思考_sydyh43的博客-CSDN博客

 

 
posted on 2023-04-20 09:51  DoubleLi  阅读(134)  评论(0编辑  收藏  举报