3.1.3 栈溢出案例及分析
1.栈溢出原理
由于栈是向低地址方向增长的(如图3.5所示),因此将数据复制到局部数组缓冲区就有可能导致超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧的数据(如图3.6所示),根据淹没数据的内容不同,可能会有产生以下情况。
图3.5 系统栈示意图
图3.6 栈溢出示意图
(1)淹没了其他的局部变量。如果被淹没的局部变量是条件变量,那么可能会改变函数原本的执行流程。这种方式可以用于破解简单的软件验证。
(2)淹没了上一栈帧的ebp值。修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡。
(3)淹没了返回地址。通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程。
(4)淹没参数变量。修改函数的参数变量也可能改变当前函数的执行结果和流程。
(5)淹没上级函数的栈帧,情况与上述4点类似,只不过影响的是上级函数的执行。当然这里的前提是保证函数能正常返回。
如果在data本身的数据内就保存了一系列的指令的二进制代码,一旦栈溢出修改了函数的返回地址,并将该地址指向这段二进制代码的真实位置,那么就完成了基本的溢出攻击行为。
2.跳板攻击原理
上述过程虽然理论上能完成栈溢出攻击行为,但是实际上很难实现。操作系统每次加载可执行文件到进程空间的位置都是无法预测的,因此栈的位置实际是不固定的,通过硬编码覆盖新返回地址的方式并不可靠。为了能准确定位shellcode[2]的地址,需要借助一些额外的操作,其中最经典的是借助跳板的栈溢出方式。
根据之前所述,函数执行后,栈指针esp会恢复到压入参数时的状态,如图3.7所示即data参数的地址。如果我们在函数的返回地址填入一个地址,该地址指向的内存保存了一条特殊的指令jmp esp—跳板。那么函数返回后,会执行该指令并跳转到esp所在的位置—即data的位置。我们可以将缓冲区再多溢出一部分,淹没data这样的函数参数,并在这里放上我们想要执行的代码!这样,不管程序被加载到哪个位置,最终都会回来执行栈内的代码。
图3.7 jmp esp跳板实例
3.栈溢出实例
我们通过一个小程序来理解栈溢出的具体利用方法。首先,将如图3.8中代码建立工程编译运行(环境为Windows 2000)。
图3.8 栈溢出实例代码
运行程序,输入少于或等于7位的正常password,如图3.9所示。
图3.9 输入password正确
输入超过7位的password,程序发生错误,如图3.10所示。
图3.10 输入password错误
利用ollydbg打开程序可执行文件,找到调用strcpy函数的汇编代码段,设置断点,如图3.11所示。
图3.11 ollydbg调试片段(a)
单步运行程序,查看缓冲区数据的变化,如图3.12所示。
图3.12 ollydbg调试片段(b)
我们找到程序的跳转语句,发现如果跳转到地址0x0040112F就能绕过password判断,于是,下一步采用缓冲区溢出漏洞淹没返回值,如图3.13所示。
图3.13 ollydbg调试片段(c)
为了便于构造输入的password,我们修改程序为文件输入,建立password.txt文件,用UE对文件进行编辑,如图3.14所示。
图3.14 建立的password.txt文件
可见,修改后,程序直接返回“Congratulation!......”破解成功,如图3.15所示。
图3.15 攻击成功