用dw做一个个人网站,做新网站不换域名,没有网站怎么做推广,松北区建设局网站什么是栈简单来说#xff0c;栈 是一种 LIFO#xff08;Last In Frist Out#xff0c;后进先出#xff09; 形式的数据结构。栈一般是从高地址向低地址增长#xff0c;并且栈支持 push#xff08;入栈#xff09; 和 pop#xff08;出栈#xff09; 两个操作。如下图所…什么是栈简单来说栈 是一种 LIFOLast In Frist Out后进先出 形式的数据结构。栈一般是从高地址向低地址增长并且栈支持 push入栈 和 pop出栈 两个操作。如下图所示push 操作先将 栈顶sp指针 向下移动一个位置然后将数据写入到新的栈顶而 pop 操作会从 栈顶 读取数据并且将 栈顶sp指针 向上移动一个位置。例如将 0x100 压入栈过程如下图所示我们再来看看 出栈 操作如下图所示栈帧栈帧也就是 Sack Frame其本质就是一种栈只是这种栈专门用于保存函数调用过程中的各种信息参数返回地址本地变量等。栈帧 有 栈顶 和 栈底 之分其中栈顶的地址最低栈底的地址最高。SP(栈指针) 就是一直指向栈顶的。在 x86 的 32 位 CPU 中我们用 %ebp 寄存器指向栈底也就是基址指针用 %esp 寄存器指向栈顶也就是栈指针。下面是一个栈帧的示意图一般来说我们将 %ebp 到 %esp 之间区域当做栈帧。并不是整个栈空间只有一个栈帧每调用一个函数就会生成一个新的栈帧。在函数调用过程中我们将调用函数的函数称为调用者(caller)将被调用的函数称为被调用者(callee)。在这个过程中调用者 需要知道在哪里获取 被调用者 返回的值一般存放到 %eax 寄存器。被调用者 需要知道传入的参数在哪里和调用完后的返回地址在哪里。我们需要保证在 被调用者 返回后%ebp 和 %esp 寄存器的值应该和调用前一致。函数调用现在我们来看看函数调用时栈帧是如何变化的。我们以一个函数调用的实例来解说代码如下// stack.cint add_func(int a, int b)
{int c, d;c a;d b;return c d;
}int main(int argc, char *argv[])
{int total;total add_func(1, 2);return 0;
}我们使用命令 gcc -S -m32 stack.c 来编译上面的代码获取的汇编代码如下所示去掉一些无关紧要的信息add_func:pushl %ebp // 保存ebp寄存器到栈movl %esp, %ebp // 把ebp进程设置为esp的值subl $16, %esp // 为局部变量申请空间movl 8(%ebp), %eax // 把参数a保存到eax寄存器中movl %eax, -8(%ebp) // 把eax寄存器的值保存到局部变量c中c amovl 12(%ebp), %eax // 把参数b保存到eax寄存器中movl %eax, -4(%ebp) // 把eax寄存器到值保存到局部变量d中d bmovl -8(%ebp), %edx // 把d的值保存到edx寄存器中movl -4(%ebp), %eax // 把c的值保存到eax寄存器中addl %edx, %eax // 将eax寄存器与edx寄存器的值相加保存到eax中返回值leaveret // 函数返回...可能汇编代码比较难看懂我们用下面的插图来说明这个调用过程如上图所示调用过程如下在 main() 函数调用 add_func() 函数前先将调用 add_func() 函数的参数压栈。在调用 add_func() 函数时会将 返回地址 压栈接着进入 add_func() 函数。add_func() 函数执行时会将原来的 ebp寄存器 的值压栈然后把 ebp寄存器 的设置为 esp寄存器 的值。接着 add_func() 函数会为局部变量申请空间也就是将 esp寄存器 向下移动。然后把局部变量 c 设置为参数 a 的值局部变量 d 设置为 参数 b 的值。最后将局部变量 c 和 d 的值相加放置到 eax寄存器 中C语言规定以 eax寄存器 传递返回值然后调用 ret 指令返回到 main() 函数。函数返回上面介绍了 函数调用 的过程现在我们来介绍一下函数调用完毕后从被调用函数返回到原来的函数过程是如何处理的。从 add_func() 函数的汇编代码可以看到当被调用函数执行完毕返回到调用函数前会执行 leave 指令这条指令等价于movl %ebp, %esp
popl %ebp这两条汇编指令的意思是将 esp寄存器 和 ebp寄存器 恢复到调用函数前的值。然后调用 ret 指令返回到原来的函数。ret 指令会从栈顶获取 返回地址然后跳转到jmp指令此地址继续执行。这时的 栈帧 的结构如下图所示栈溢出攻击前面说了那么都是为了 栈溢出攻击 这节作铺垫的。通过前面的学习我们知道调用函数的 参数 、执行完函数后的 返回地址 和被调用函数的 局部变量 都是存放在栈中的。如果在调用函数时不小心将 返回地址 覆盖了那么调用完函数后将不会跳转到原来的函数继续执行而是跳转到覆盖后的地址执行。如下图所示那么怎样才能把 返回地址 覆盖呢我们可以通过下面的例子来说明#include stdio.h
#include string.h
#include unistd.h
#include stdlib.h
#include stdint.h#define PTR_SIZE 8 // 指针的大小
#define EBP_SIZE 8 // ebp寄存器的大小void inject_callback()
{printf(inject_callback called...\n);exit(0);
}void func_call(char *addr, int len)
{char tmpBuf[16] {0xff};memcpy(tmpBuf 16 EBP_SIZE, addr, len);printf(func_call called...\n);
}int main(int argc, char** argv)
{uint64_t injectPtr (uint64_t)inject_callback;func_call(injectPtr, PTR_SIZE);printf(main exited...\n);return 0;
}我们使用以下命令编译上面代码并且执行$ gcc stack-overflow.c -fno-stack-protector -o stack-overflow
$ ./stack-overflow
func_call called...
inject_callback called...在编译上面程序时一定要加上 -fno-stack-protector 参数否则将会触发栈溢出保护导致执行失败。在上面的代码中我们并没有直接调用 inject_callback() 函数而是通过把 inject_callback() 函数的地址复制到 func_call() 函数的局部变量 tmpBuf 中。由于局部变量 tmpBuf 的类型为字符串数组而且大小为 16 个字节。但我们复制数据是从 2416 8处开始复制已经超出了局部变量 tmpBuf 的大小如下图所示从上图可以看出func_call() 函数在调用 memcpy() 函数复制数据时由于不小心用 inject_callback() 函数的地址覆盖了返回地址导致 func_call() 函数执行完毕后跳转到 inject_callback() 函数处执行。这就是 栈溢出攻击 的原理而导致 栈溢出攻击 的原因就是调用 memcpy()、strcpy() 等函数复制数据时没有对数据的长度进行验证从而 返回地址 被复制的数据覆盖了。黑客可以利用 栈溢出攻击 来把函数的返回地址修改成入侵代码的地址从而实现攻击的目的。