常见二进制漏洞原理及分析
本文将介绍常见二进制漏洞类型及避免方法。一.虚拟内存的布局Linux中进程使用虚拟内存再由MMU转换成物理内存其虚拟内存布局如下其中堆区向高 地址生长栈区向低地址生长且代码段和rodata段为只读属性。栈区高地址共享库 / 内存映射区mmap 区堆区数据段.data/.rodata/.bss代码段.text低地址二.常见漏洞类型1.缓冲区溢出漏洞由于用户输入时未进行长度校验导致用户输入长度超过缓冲区长度因而覆盖其他内存int main() { char buffer[16]; printf(please input); read(0, buffer, 100);//这里有明显的溢出 printf(your content:%s\n, buffer); return 0; }上面的代码中read的长度明显超过buffer的长度会造成栈溢出。同理当使用malloc()申请堆内存时就会造成堆溢出。所以在开发中尽量避免使用gets()strcpy()read()等危险函数同时要对用户输入进行长度校验。2.整形漏洞由于对整数的取值范围符号隐式类型转换的忽略导致的安全问题。void check_length(int input_len) { unsigned int safe_len input_len;//触发漏洞 if (input_len 16) { char buffer[16]; printf(input:); read(0, buffer, input_len); printf(content:%s\n, buffer); } else { printf(overflow\n); } }上图输入长度校验函数中因为int类型默认为有符号将int类型的input_len转化为无符号的int类型就会产生问题。当用户输入一个负数会将这个负数的补码存在内存中当使用时会直接将补码当做超大正数首先绕过检测然后进行read函数时发生溢出。3.格式化字符串漏洞格式化字符串漏洞是指用户输入中含有格式化字符换导致的printf()函数被错误执行。void log_write(const char* user_input) { char log_buf[256]; strcpy(log_buf, 用户操作); strcat(log_buf, user_input); printf(log_buf);//触发漏洞 printf(\n); }上图的日志写入函数中想将用户输入进行拼接然后输出但是printf()函数有两个特殊的格式化字符串%p可以输出地址%n可以向内存写入数据这样就会导致地址泄露相关问题。4.指针相关漏洞下面一种典型的可能触发Use after free漏洞代码例子主要由于free()函数触发。typedef struct { int id; char* name; } Person; Person* create_Person(int id, const char* name) { Person* p (Person*)malloc(sizeof(Person)); p-id id; p-name (char*)malloc(strlen(name) 1); strcpy(p-name, name); return p; } int main() { Person* person create_Person(1001, ZhangSan); printf(ID%d, Name%s\n, person-id, person-name); free(person-name); free(person); return 0; }上面的代码就是可能触发UAF漏洞的高危代码程序首先定义一个新类型Person然后定义了创建该类型的函数main函数中创建后使用然后释放但是释放后未将指针置空造成野指针。如果下面的代码出现再次使用这个指针的时候就会触发漏洞。下面则是空指针解引用漏洞的典型例子。struct data{ int id; int value; }; int main(){ struct data* p NULL; p (struct data*)malloc(sizeof(struct data)); p-id 1; p-value 100; free(p); p NULL; return 0; }这段代码中使用的p指针进行对data对象的处理但是其实我们并不知道p指针是否创建成功。所以必须要对p指针进行是否为NULL的判断if(p NULL){ return 1; }5.多线程相关程序漏洞主要是由于程序使用了多线程但是多线程同时访问了同一个内存或文件从而造成竞争。int count 0; void* task(void* arg){ int i; for(int i 0; i 1000000; i){ count; } return NULL; } int main(){ pthread_t t[10]; for(int i0;i10;i){ pthread_create(t[i],NULL,task,NULL); } for(int i0;i10;i){ pthread_join(t[i],NULL); } printf(count %d\n,count); return 0; }上面这段程序中就是点典型的动态竞争漏洞程序中开启了十个线程同时给count变量进行自增的任务但是并没有加锁导致会出现两个线程同时对count做操作造成结果错误。5.类型混淆漏洞由于强制类型转换导致的内存作物修改致使数据出错。class father { public: virtual void func1() { std::cout father std::endl; } int a; }; class childA : public father { public: void func1() override { std::cout child1 std::endl; } int b; }; class childB : public father { public: void func1() override { std::cout child2 std::endl; } int c; }; int main() { childA* Ap new childA(); Ap-b 10; childB* Bp (childB*)Ap; Bp-c 20; std::coutAp-bstd::endl; return 0; }这个就是一个类型混淆的基本实例由于强制将chiildA类型转化为childB类型导致程序按照childB的方式解析childA的内存布局了导致修改出错。三.常见防范手段1.缓冲区溢出防护手段主要有内存地址随机化(PIE)Canary以及NX等可以使用下面命令查看checksec ./your_program如图就是开启了PIE和NX但是没有开启canary保护栈保护较弱。Canary就是一个标志位如果栈被篡改标志位也会被篡改就可以发现栈溢出问题。NX就是让栈上的数据不可执行PIE 是让整个程序的基地址包括代码段、数据段、栈、堆随机化从而大幅增加 offset的计算难度。2.对于由于多线程造成的漏洞可以采用加互斥锁或这个改用原子操作。互斥锁常用函数主要有三个初始化锁上锁和开锁//初始化锁 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //上锁 int pthread_mutex_lock(pthread_mutex_t *mutex); //开锁 int pthread_mutex_unlock(pthread_mutex_t *mutex);进入任务函数时要上锁防止其他线程竞争同时完成任务时也要开锁让其他线程继续使用。同时也可以使用原子操作原子操作就是将好几步完成的事强制一步干完了使得CPU不能打断其性能高于互斥锁但是操作比较简单。常用数据结构如下。atomic_int a 0; // 原子int atomic_short b 0; // 原子short atomic_long b 0; // 原子long atomic_char c x; // 原子char常见函数及示例如下1.原子读取返回原子变量count的当前值给valint val atomic_load(count);//示例2.原子赋值把 val 写入原子变量atomic_store(count, 0);3.原子加操作atomic_fetch_add(count, 1); // count4.原子减操作atomic_fetch_and(flag, 0x01);// count-25.比较并交换atomic_compare_exchange_strong(a,b,c) //当a b将a改成c返回true //当a ! b将b改成a返回false以上是一些常用二进制漏洞以及一些预防方式同时这些只是基础原理真实漏洞会更加复杂。本人新手如有错误欢迎各位大佬指点。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2409213.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!