栈区数据被飞踩问题定位手段 |
您所在的位置:网站首页 › 如何定位栈溢出的数据来源 › 栈区数据被飞踩问题定位手段 |
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、直接运行程序,程序出现错误,查看当前的堆栈信息
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 |