栈区数据被飞踩问题定位手段

您所在的位置:网站首页 如何定位栈溢出的数据来源 栈区数据被飞踩问题定位手段

栈区数据被飞踩问题定位手段

2024-05-28 08:14| 来源: 网络整理| 查看: 265

1、栈溢出或者栈数据被踩时,继续运行就会出现segmentation fault。可以尝试着接管SIGSEGV信号,在信号处理函数中保存一些出现异常时候的信息。

2、基于栈溢出场景,栈空间被破坏,也就没法使用栈区,当然就没法执行SIGSEGV信号的处理函数。因此需要开辟额外的空间用于栈空间使用,系统提供了sigaltstack接口。

2.1、开辟第二栈区的代码

void create_alt_stack(void) { stack_t sigstack; memset(&sigstack, 0x00, sizeof(stack_t)); sigstack.ss_sp = malloc(SIGSTKSZ); if( !sigstack.ss_sp) { printf("Err: malloc error\n"); exit(EXIT_FAILURE); } sigstack.ss_size = SIGSTKSZ; sigstack.ss_flags = 0; if(sigaltstack(&sigstack, NULL) == -1) { printf("Err: sigaltstack error\n"); exit(EXIT_FAILURE); } printf("Now the alternate signal stack is successfully allocated\n"); printf("The address of signal stack is : %10p - %10p\n",sigstack.ss_sp,(char*)sbrk(0)-1); return; }

2.2、接管SIGSEGV信号

void sig_action(void) { struct sigaction act; memset(&act, 0x00, sizeof(struct sigaction)); act.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; sigemptyset(&act.sa_mask); act.sa_sigaction = handler; //须挂在sa_sigaction 不应该挂在sa_handler sigaction(SIGSEGV, &act, NULL); // sigaction(SIGABRT, &act, NULL); return; }

信号处理函数handler不应该挂接到sa_handler,应该挂接到sa_sigaction,这样处理函数可以通过参数获取额外的信息,同时sa_flags需要增加SA_SIGINFO标志。

struct sigaction { union { __sighandler_t _sa_handler; void (*_sa_sigaction)(int, struct siginfo *, void *); } _u; sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void); };

2.3、当出现异常时,会运行到处理函数handler

static void handler(int sig, siginfo_t *info, void *cntxt) { int i = 0; int *paddr = NULL; paddr = (int *)((((ucontext_t *)(cntxt))->uc_mcontext).gregs[REG_RSP]); printf("Waoh, caught signal %s, Stack addr = %p\n",strsignal(sig), paddr); for (i = -20; i < 20; i++) { printf("0x%x\n", *(paddr + i)); } sleep(2); _exit(EXIT_FAILURE); }

其中ucontext_t结构体定义如下:

typedef struct ucontext { unsigned long int uc_flags; struct ucontext *uc_link; // 当前上下文执行完了,恢复运行的上下文 stack_t uc_stack; // 该上下文中使用的栈 mcontext_t uc_mcontext; // 保存当前上下文,即各个寄存器的状态 __sigset_t uc_sigmask; // 保存当前线程的信号屏蔽掩码 struct _libc_fpstate __fpregs_mem; } ucontext_t; typedef struct { gregset_t gregs; //用于装载寄存器 /* Note that fpregs is a pointer. */ fpregset_t fpregs; //所有寄存器的类型 __extension__ unsigned long long __reserved1 [8]; } mcontext_t;

2.4、打印出现异常时,异常栈区地址附近的数据,分析数据。根据被踩空间大小,适当调整打印范围。

其中红框的数据就是被踩的数据,黄框的数据保存着函数的地址。通过cat /proc/$pid/maps查看代码段的基地址,进而使用addr2line命令查看函数在代码中的位置。

从而可以缩小范围,在main->stack_funcAx这层调用路径下。同时通过红框中被踩的数据grep反推这些数据在代码中是否存在。

当然被踩的数据区域大小场景不一致,需要具体问题具体分析。

3、正常情况下会打开栈保护功能,-fstack-protector-all。注释demo程序sleep(8)前面初始化这几行,Makefile中增加-fstack-protector-all编译选项。

3.1、gdb挂载程序

3.2、直接运行程序,程序出现错误,查看当前的堆栈信息

查看__stack_chk_fail函数的上一层函数test_stack_overflow,应该是这一层的函数出现了问题。

3.3、退出gdb,重新进入gdb,给test_stack_overflow打断点,运行。查看附近的汇编指令

其中绿框中显示的汇编指令就是为了栈保护功能在栈区添加的canary魔鬼数字,在函数即将退出时,会比较canary魔鬼数字是否发生改变。假如被改写,会跳转到__stack_chk_fail函数运行。

3.4、使用n命令运行至绿框汇编指令后面,打印%rbp寄存器的值,获取存放canary魔鬼数字的地址。

3.5、查看地址的数据,同时watch命令对这一地址的数据监控起来,当地址数据被改写时,gdb会停留在改写的位置点。 对被改后的值1820209125数值转换成十六进制0x6F792065,对应着e yo字符串,被改后的值也是定位问题的参考证据。

3.6、查看此处的汇编指令和堆栈信息 

从中可以看出是test_stack_overflow函数中strcpy代码处出现的问题。

4、增加-fstack-protector-all编译参数后,在函数的栈区会添加canary值,函数即将退出时通过判断canary的值是否被修改确定栈区数据有没有被破坏。

然而,栈区数据存在被破坏,但是偏偏没有在存放canary值的位置,此时也是无法通过上述方法定位。

可以周期性地打印栈区数据,比较栈区数据的差异,综合定位栈区数据被修改问题。

测试demo程序

GitHub - dyh-git/stack_overflow: 模拟栈空间被修改问题代码



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3