路由器漏洞挖掘(栈,危险函数,方法)
MIPS32架构堆栈:
- 和x86 架构一样,都是由高地址向低地址增长,无EBP。
- 进入函数调用时,把栈指针(sp)向下移动n比特,这个大小为n比特的存储空间为此函数的stack Frame。
- 此后栈指针不移动,只有在函数返回时,加上这个偏移量恢复现场。
- 由于不能随便移动栈指针,所以寄存器压栈和出栈使用偏移
- A调用B,会在A 的栈顶预留一部分空间保存b的调用参数,称为参数空间。
- 参数传递,前4个参数通过a0-a4传递,超出的会放入参数空间。
- 返回地址:把返回地址直接存入$ra寄存器。
- 函数执行的命令的取指从$PC中取
从某个地址到’jr $ra' 指令之间的二进制序列称为gadget
函数调用过程:
函数调用参数:
说明:在调用函数b前,参数使用a0-a3外加参数空间的参数,当B调用并分配了栈空间,b会把a0-a3的值存储到A的参数空间
函数调用栈数据情况:
说明:非叶子函数has_stack调用时,会把返回main 的地址放到自己的栈底部,如图:0x0040042c,
数据从低到高覆盖,有可能覆盖返回main的地址,造成缓冲区溢出。
危险函数:
1,sprintf
下面是 sprintf() 函数的声明。
int sprintf(char *str, const char *format, ...)
参数
- str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
- format -- 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:
#include <stdio.h> #include <math.h> int main() { char str[80]; sprintf(str, "Pi 的值 = %f", M_PI); puts(str); return(0); } 输出: Pi 的值 = 3.141593
strchr使用
char *strchr(const char *s, int c)
功能: 查找字符串s中首次出现c字符的位置
说明: 返回首次出现c的位置的指针,返回的地址是被查找的字符串指针开始的第一个与c相同字符的指针,若s中不存在c则返回NULL。。。。
返回值: 成功返回要查找的字符第一次出现的位置,否则返回NULL。。。。
strrchr使用
char *strrchr(const char *s, int c)
功能: 查找一个字符c在一个字符串s中最后一次出现的位置(也就是从s的右侧开始查找字符c首次出现的位置),并返回从字符串中的字符c所在的位置开始直到字符串s结束的所有字符。 若没有找到字符c,则返回NULL。
strstr函数使用
char *strstr(const char *haystack, const char *needle);
haystack -->被查找的目标字符串"父串" needle -->要查找的字符串对象"子串"
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char *res = strstr("xxxhost: www.baidu.com", "host"); if(res == NULL) printf("res1 is NULL!\n"); else printf("%s\n", res); // print:-->'host: www.baidu.com' res = strstr("xxxhost: www.baidu.com", "cookie"); if(res == NULL) printf("res2 is NULL!\n"); else printf("%s\n", res); // print:-->'res2 is NULL!' return 0; } 注:strstr函数中参数严格"区分大小写"
strcasestr函数
strcasestr函数的功能、使用方法与strstr基本一致。
strcasestr函数在"子串"与"父串"进行比较的时候,"不区分大小写"
#define _GNU_SOURCE // 宏定义必须有,否则编译会有Warning警告信息 #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char *res = strstr("xxxhost: www.baidu.com", "Host"); if(res == NULL) printf("res1 is NULL!\n"); else printf("%s\n", res); // print:-->'host: www.baidu.com' return 0; }
memset使用
将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,
块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作
用 法: void *memset(void *s, char ch, unsigned n);
#include <string.h> #include <stdio.h> #include <memory.h> int main(void) { char buffer[] = "Hello world/n"; printf("Buffer before memset: %s/n", buffer); memset(buffer, '*', strlen(buffer) );// //数组直接首地址传进去。 主要是对数组指针的修改!!因为可以被修改而const char int等这些不能被修改 和malloc 配套使用 printf("Buffer after memset: %s/n", buffer); return 0; } 输出结果: Buffer before memset: Hello world Buffer after memset: ***********
memcpy
C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字符到存储区 str1。
#include <stdio.h> #include <string.h> int main () { const char src[50] = "http://www.w3cschool.cc"; char dest[50]; printf("Before memcpy dest = %s\n", dest); memcpy(dest, src, strlen(src)+1); printf("After memcpy dest = %s\n", dest); return(0); } 让我们编译并运行上面的程序,这将产生以下结果: Before memcpy dest = After memcpy dest = http://www.w3cschool.cc
fgets函数使用
原型 char * fgets(char * s, int n,FILE *stream);
参数:
s: 字符型指针,指向存储读入数据的缓冲区的地址。
n: 从流中读入n-1个字符
stream : 指向读取的流。
返回值:
1. 当n<=0 时返回NULL,即空指针。
2. 当n=1 时,返回空串"".
3. 如果读入成功,则返回缓冲区的地址。
4. 如果读入错误或遇到文件结尾(EOF),则返回NULL.
漏洞挖掘方法
代码审计:
- 使用IDA对目标进行反汇编
- 收索可能造成安全漏洞的危险函数
- 跟踪危险函数如何获取和处理用户提供的数据过程,判断是否存在安全漏洞
危险函数分类:
1,部分用户提供数据来源的相关函数
- 命令行参数:argv操作
- 环境变量:getenv()
- 输入数据文件:read() fscanf()getc() fgetc() fgets() fscanf()
- 键盘输入/stdin: read, scanf getchar gets
- 网络数据:read recv recvfrom
2,部分数据操作的相关危险函数
- 字符串复制:strcpy(char *dest,char *src) strncpy
- 命令执行:system execve
- 字符串格式化:strcat
- 格式化字符串:sprintf snprintf
定位上诉危险函数,根据参数个数,类型跟踪个参数,分析缓冲区大小,判断是否存在漏洞
- 对于1,采用正向数据流跟踪,从输入点开始跟踪
- 对于2,采用逆向数据流跟踪,反向跟踪参数数据流向,找出缓冲区大小
自动化二进制文件审计工具
Bugscam:x86平台下,
R-bugscam: RISC指令审计工具,误报多,还需人工进一步分析