路由器——缓冲区溢出漏洞
路由器——缓冲区溢出漏洞
一. MIPS架构堆栈
栈操作:MIPS32架构堆栈与x86架构一样,都是向低地址增长的。但在MIPS32架构中没有EBP(栈底指针),进入一个函数时,需要将当前栈指针向下移动n比特,这个大小为n比特的存储空间就是此函数的Stack Frame的存储区域。此后,栈指针便不再移动,只能在函数返回时将栈指针加上这个偏移量恢复栈现场。由于不能随便移动栈指针,所以寄存器压栈和出栈时都必须指定偏移量。
调用:调用:如果函数A调用函数B,调用者函数(函数A)会在自己的栈顶预留一部分空间来保存被调用者(函数B)的参数,我们称之为调用参数空间。
参数传递:参数传递方式:前4个传入的参数通过\(a0~\)a3传递。有些函数的参数可能会超过4个,此时,多余的参数会被放入调用参数空间。x86架构下的所有参数都是通过堆栈传递的。
返回地址:在x86架构中,使用call命令调用函数时,会先将当前执行位置压入堆栈,MIPS 的调用指令把函数的返回地址直接存入SRA寄存器而不是堆栈中。
二. 函数调用过程
- 在函数A执行到调用函数B的指令时,函数调用指令复制当前的\(PC寄存器的值到\)RA寄存器,即当前$RA的值就是当前函数执行结束的返回地址,然后跳转到函数B并执行。
- 程序跳转到函数B以后,如果函数B是非叶子函数,则函数B首先会把函数A的返回地址(此时返回函数A的地址存放在\(RA寄存器中)存入堆栈,否则返回函数A的地址仍然在\)RA中。
- 函数返回时,如果函数B是叶子函数,则直接使用“jr\(ra”指令返回函数A,这里的寄存器\)RA指向返回地址。如果函数B是非叶子函数,返回过程相对来说复杂一点,函数B先从堆栈中取出被保存在堆栈上的返回地址,然后将返回地址存入寄存器\(RA,再使用“jr\)ra”指令返回函数A。
三. MIPS架构下栈溢出的可行性
MIPS32架构下的函数调用指令不会把返回地址存入堆栈,而是直接存入寄存器$RA中。那么,在MIPS32架构下缓冲区溢出是否能够被利用?接下来我们就通过两个例子探讨在MIPS32架构下利用缓冲区溢出是否具有可行性。
1. 非叶子函数
案例1:非叶子函数has_stack()函数内调用了strcpy函数,因此,has_stack是非叶子函数,返回main()函数的地址会首先保存到寄存器ra中,进入has_stack()函数以后,has_stack会把返回main()函数的返回地址保存在has stack的堆栈中,在has stack()函数返回main()函数继续执行时,将保存在堆栈中的返回main()函数的返回地址写入ra并返回main()函数继续执行,代码如下。
has_stack()函数执行完第2条指令“swra,Ox38+saved_ra”后堆栈的情况如图6-7所示。虽然函数调用指令不再直接将返回main()函数的返回地址保存到堆栈中,而是将返回地址写入ra中,但由于has_stack()函数要调用sprintf函数,所以has_stack将返回main()函数的返回地址0x0040042C保存到has_stack栈帧底部(如代码中的0x00400394代码行)。当函数返回时,会先从堆栈中取出返回地址0x0040042C并将其存放到寄存器ra中(如代码中的0x004003E8代码行)。如果has_stack()函数的局部变量中国存在缓冲区溢出漏洞,就可能导致堆栈上返回main()函数的返回地址被覆盖,has_stack取出的返回main()函数的返回地址不再是0x0040042C,而是由攻击者精心构造的数据。因此,在这种情况下缓冲区溢出是可以被利用的。
2. 叶子函数
使用IDA加载subcall_nostack查看反汇编代码,其反汇编代码如图6-8所示。main()函数调用no_stack()函数时,返回main()函数的返回地址并不是直接存入堆栈中的,而是存储到寄
但是,这并不代表叶子函数中的缓冲区溢出就完全无法利用。如果缓冲区溢出覆盖的区域足够大,no_stack()函数中的缓冲区溢出是有可能覆盖main()函数的栈帧的,这样就可能覆盖main()函数栈帧中存放的上层函数的返回地址。因此,当非叶子函数中存在缓冲区溢出漏洞时,程序的执行流程是存在被劫持的可能性的。