栈溢出基础套路

换下口味,换下心情,默认环境是Linux X86-64

最简单的栈溢出

最简单的溢出程序:

gcc编译语句:gcc -o bof bof.c -no-pie -fno-stack-protector -z execstack

-no-pie: 关闭PIE

-fno-stack-protector: 关闭栈溢出保护机制

-z execstack: 关闭NX

定义一个16字节的缓冲区,读取0xff字节的数据,溢出覆盖返回地址为vul函数的地址,用IDA找到vul函数的地址,然后构造栈溢出:

用24字节覆盖缓冲区,然后输入vul函数的返回地址,即可拿到shell,那么为什么是24字节,因为在缓冲区和返回地址之间保存了RBP。

SHELLCODE

同样用上面的程序,但是这次去掉vul函数,同时打印出缓冲区地址:

同样的编译命令,-z execstack的开启可以使在栈中执行语句,因此我们可以把shellcode放进栈中然后把返回地址改为shellcode的起始地址,即可执行我们的shellcode。

一段x86-64的shellcode:

我们首先把shellcode放进栈中,然后填充栈和rbp的位置,最后把返回地址改为栈开始的地址:

关于shellcode的编写又是一门很深的学问了。

Return2Libc

NX开启后无法在栈上直接执行语句,可以使用return2libc技术,libc是linux的基础库,保存大量系统函数,linux下运行程序需要连接libc到内存,libc每次加载时地址都会变化,但是代码段的相互位置不变,只要知道一个函数的地址就可以根据偏移还原整个libc,GOT是表示保存函数在libc中实际地址的一个全局表,保存了程序使用的函数在libc中的地址,只要拿到GOT表的内容就可以拿到对应函数的真实地址从而根据偏移找出libc中敏感函数例如system的地址。

64位程序参数保存在寄存器中,无法通过布置栈来布置参数,需要使用gadgets。

格式化串

printf对参数的确定完全取决于第一个格式化字符串,从而导致如果用户能控制格式化参数,那么就能利用Printf操纵任意内存,拿ICSS2017的PWN200举例子,IDA F5得到:

在input name里有明显的格式化串漏洞,并且可以循环利用,因此可以打印出一个函数的地址从而根据libc的偏移找到system函数的地址,从而打开一个shell,或者直接覆盖当前已有函数为system然后传入 bin/sh。

ROP