C和C++安全编码笔记:指针诡计
指针诡计(pointer subterfuge)是通过修改指针值来利用程序漏洞的方法的统称。
可以通过覆盖函数指针将程序的控制权转移到攻击者提供的外壳代码(shellcode)。当程序通过函数指针执行一个函数调用时,攻击者提供的代码将会取代原本希望执行的代码而得到执行。
对象指针也可以被修改,从而执行任意代码。如果一个对象指针用作后继赋值操作的目的地址,那么攻击者就可以通过控制该地址从而修改内存其它位置中的地址。
3.1 数据位置:
static int GLOBAL_INIT = 1; // 数据段,全局
static int global_uninit; // BSS段,全局
int test_secure_coding_3_1() // 栈,局部
{int local_init = 1; // 栈,局部int local_uninit; // 栈,局部static int local_static_init = 1; // 数据段,局部static int local_static_uninit; // BSS段,局部// buff_ptr的存储空间是栈,局部;分配的内存是堆,局部int* buff_ptr = (int*)malloc(32);free(buff_ptr);return 0;
}
UNIX可执行文件包含data段和BSS段。data段包含了所有已初始化的全局变量和常数。BSS段包含了所有未初始化的全局变量。将已初始化和未初始化变量分开是为了让汇编器不将未初始化的变量内容(BSS段)写入目标文件中。
3.2 函数指针:
void good_function(const char* str) {} // 栈
// 一个有漏洞的程序,其BSS段中的函数指针可以被覆写
void test_secure_coding_3_2(int argc, char* argv[]) // 栈
{const int BUFFSIZE = 10; // 栈static char buff[BUFFSIZE]; // BSS段static void(*funPtr)(const char* str); // BSS段funPtr = &good_function;// 当argv[1]的长度大于BUFFSIZE的时候,就会发生缓冲区溢出,这个缓冲区溢出漏洞// 可以被利用来将函数指针值覆写为外壳代码的地址,从而将程序的控制权转移到任意的代码// 当执行由funPtr标识的函数时,外壳代码将会取代good_function()得以执行strncpy(buff, argv[1], strlen(argv[1])); (void)(*funPtr)(argv[2]);
}
虽然栈溢出(连同很多基于堆的攻击)不可能发生于数据段(data segment)中,但是覆写函数指针在任何内存段中都会发生。
3.3 对象指针:
// 一个有漏洞的程序,可以被利用来实现任意内存写,修改对象指针
void test_secure_coding_3_3(void* arg, size_t len)
{char buff[100];long val = 1;long* ptr = &val;// 一个无界内存复制,在溢出缓冲区后,攻击者可以覆写ptr和val// 当执行*ptr=val时,就会发生任意内存写memcpy(buff, arg, len);*ptr = val;
}
C和C++中的对象指针用于指向动态分配的结构、函数的引用参数、数组以及其它对象。这些对象指针可能会被攻击者修改,比如当利用一个缓冲区溢出漏洞的时候。如果一个指针接下来被用作一个赋值操作的目的地址,那么攻击者就可以通过控制地址达到修改其它内存位置内容的目的,这种技术也称为”任意内存写”(arbitrary memory write)。
3.4 修改指令指针:攻击者要想在x86-32架构上成功地执行任意代码,必须利用某种方式修改指令指针,使其指向外壳代码。指令指针寄存器(eip)存储了将要执行的下一条指令在当前代码段内的偏移量。eip寄存器不能被软件直接访问。它在顺序执行代码时由一个指令边界步进到下一条指令,也可以由控制转移指令(例如jmp、jcc、call和ret等)、中断以及异常间接修改。
以call指令为例,它首先将返回信息存储于栈中,然后将控制权转移到由目标操作数指定的被调用函数处。目标操作数指定了被调用函数中的第一条指令的地址。该操作数可以是一个立即数(immediate value)、一个通用寄存器或一个内存位置。
3.5 全局偏移表:Windows和Linux在库函数的链接和控制转移方面使用了类似的机制。从安全的角度来看,二者主要的区别在于Linux使用的方法是可被利用的,而Windows则不然。
Linux使用的默认二进制格式称为可执行和链接格式(Executable and Linking Format, ELF)。ELF最初由UNIX系统实验室(UNIX System Laboratories, USL)作为二进制应用程序接口(Application Binary Interface, ABI)的一个部分开发并发布。后来将ELF标准作为多种x86-32操作系统上的可移植目标文件格式。
任何ELF的二进制文件的进程空间中,都包含一个称为全局偏移表(Global Offset Table, GOT)的区。GOT存放绝对地址,从而使得地址可用,并且不会影响位置独立性和程序代码的可共享性。要使得动态链接的进程能够工作,这个表是必不可少的。该表的实际内容和形式取决于处理器的型号。
程序使用的每一个库函数在GOT中都拥有一个入口项,GOT中包含有实际函数的地址。这使得很容易在进程内存中对库函数进行重定位。在程序首次使用一个函数之前,该入口项包含有运行时链接器(RunTime Linker, RTL)的地址。如果该函数被程序调用,则程序的控制权被转移到RTL,然后函数的实际地址被确定且被插入到GOT中。接下来就可以通过GOT中的入口项直接调用函数,而跟RTL就无关了。
在ELF可执行文件中GOT入口项的地址是固定的。这就导致对任何可执行进程映像而言GOT入口项都位于相同的地址。可以利用objdump命令查看某一个函数的GOT入口项的位置,如下图所示:为每一个R_X86_64_JUMP_SLOT重定位记录指定的偏移量,包含了指定函数(或RTL链接函数)的地址。
攻击者可以利用任意内存写将一个函数的GOT入口项覆写为外壳代码的地址。这样,当程序调用对应于被改写的GOT入口项的函数时,程序的控制权就被转移到外壳代码。例如,每一个编写良好的C程序最后都会调用exit()函数,因此,只要覆写了exit()的GOT入口项,就可以在exit()被调用时将程序的控制权转移到指定的地址。ELF过程链接表(Procedure Linkage Table, PLT)具有类似的问题。
Windows PE(Portable Executable, 可移植的可执行)文件格式扮演着与ELF格式相似的角色。PE文件中包含一个数据结构数组,每一项对应一个导入的DLL。每一项都包含有导入的DLL的名称以及一个指向函数指针数组的指针(即导入地址表,Import Address Table, IAT)。每一个被导入的API在IAT中都有自己的保留槽,由Windows载入器为其填充导入函数的地址。一旦一个模块被载入,IAT就保存了需要调用的导入函数的地址。IAT的入口项是写保护的,因此它们在运行时无需修改。
3.6 .dtors区:
#ifndef _MSC_VER
static void create(void) __attribute__((constructor));
static void destroy(void) __attribute__((destructor));static void create(void)
{fprintf(stdout, "create called.\n");
}static void destroy(void)
{fprintf(stdout, "destructor called.\n");
}
#endifvoid test_secure_coding_3_6()
{
#ifndef _MSC_VERfprintf(stdout, "create: %p.\n", create);fprintf(stdout, "destroy: %p.\n", destroy);exit(0);
#endif
}
任意内存写攻击的另外一个目标是覆写由GCC生成的可执行文件的.dtors区中的函数指针。GNU C允许程序员利用__attribute__关键字后跟一个包含于双括号中的属性修饰符来声明函数的属性。属性修饰符包括constructor和destructor。constructor属性指示函数在main()之前被调用,destructor属性则表示函数将在main()执行完成后或exit()被调用后进行调用。
构造函数和析构函数分布存储于生成的ELF可执行映像的.ctors和.dtors区中。.ctors和.dtors区映射到进程地址空间后,默认属性为可写。漏洞利用程序从未利用过构造函数,因为它们都在main()函数之前执行。结果,攻击者的兴趣都集中到了析构函数和.dtors区上。攻击者可以通过覆写.dtors区中的函数指针的地址从而将程序控制权转移到任意的代码。如果攻击者能够读取到目标二进制文件,那么通过分析ELF映像,很容易就能确定要覆写的确切位置。
注:在GCC高版本中好像用.init_array、.fini_array取代了.ctors、.dtors。如下图所示:
3.7 虚指针:在C++中可以定义虚函数(virtual function)。虚函数就是用virtual关键字声明的类成员函数。该函数可以由派生类中的同名函数重写。一个指向派生类对象的指针可以被赋给基类指针,并且通过该指针来调用函数。如果没有虚函数,则调用的是基类的函数,因为它和指针的静态类型相关联。当使用虚函数时,调用的则是派生类的函数,因为该函数和对象的动态类型相关联。
大多数C++编译器使用虚函数表(Virtual Function Table, VTBL)实现虚函数。VTBL是一个函数指针数组,用于在运行时派发虚函数调用。在每一个对象的头部,都包含一个指向VTBL的虚指针(Virtual Pointer, VPTR)。VTBL含有指向虚函数的每一个实现的指针。
覆写VTBL中的函数指针或者改变VPTR使其指向其它任意的VTBL都是可能的,可以通过任意内存写或者利用缓冲区溢出直接写入对象实现这一操作。通过对对象的VTBL和VPTR的覆写,攻击者可以使函数指针执行任意的代码。
3.8 atexit()和on_exit()函数:
char* glob;void test(void)
{fprintf(stdout, "%s", glob);
}int test_secure_coding_3_8()
{atexit(test);glob = "Exiting.\n";return 0;
}
atexit()是C标准定义的一个通用工具函数。atexit()可以注册无参函数,并在程序正常结束后调用该函数。C要求实现支持至少32个函数的注册。SunOS上的on_exit()函数具有类似的功能。libc4、libc5和glibc也提供了这样的函数。
atexit()通过向一个退出时将被调用的已有函数的数组中添加指定的函数完成工作。当exit()被调用时,数组中的每一个函数都以”后进先出”(Last-in, First-out, LIFO)的顺序被调用。由于atexit()和exit()都要访问该数组,因此它被分配为一个全局性的符号(在Linux操作系统中是__exit_funcs)。可以通过对__exit_funcs结构采用任意内存写或缓冲区溢出手段将程序的控制权转移到任意的代码。
3.9 longjump()函数:
int test_secure_coding_3_9()
{jmp_buf env;int val;val = setjmp(env);fprintf(stdout, "val is %d\n", val);if (!val) longjmp(env, 1);return 0;
}
C标准定义了setjmp()宏、longjmp()函数,以及jmp_buf类型,它们可以用来绕过正常的函数调用和返回规则。
setjump()宏为稍后将会调用的longjmp()函数保存其调用环境。longjmp()则恢复最后一次由setjmp()宏保存的调用环境。可以通过将jmp_buf缓冲区中PC(Program Counter, 程序计数器)的值覆写为外壳代码的起始地址的方法来利用longjmp()函数。任意内存写或者直接针对jmp_buf结构的缓冲区溢出都能达到这个目的。
3.10 异常处理:
int test_secure_coding_3_10()
{try {//throw 10;throw "overflow";}catch(int x) {fprintf(stderr, "exception value: %d\n", x);}catch (const char* str) {fprintf(stderr, "exception value: %s\n", str);}return 0;
}
异常就是函数操作中发生的意外情况。例如,被除0将会产生一个异常。很多程序员采取实现异常处理程序的方式来处理这些特殊情况,以避免非预期的程序中止。另外,异常处理程序被串在一起并以一定的顺序被调用,直到其中一个能够处理异常为止。
Microsoft Windows操作系统提供了三种形式的异常处理程序。操作系统按给定的顺序调用它们,直到其中某一个被成功执行:(1).向量化异常处理(Vectored Exception Handling, VEH):首先调用以重写结构化异常处理程序。(2).结构化异常处理(Structured Exception Handling, SEH):这种方式被实现为每函数(per-function)或每线程(per-thread)的异常处理程序,即每一个函数或每一个线程都有自己的异常处理程序。(3).系统默认异常处理:这是一个全局异常过滤器和处理器,用于处理整个进程的异常情况。如果上面两个异常处理程序都无法处理异常,那么它将会被调用。
结构化异常处理:SHE通常在编译器级别通过try…catch语句实现。try块中引发的任何异常都将被匹配的catch块处理。如果catch块无法处理异常,那么它将被传回之前的范围块。__finally关键字是微软对C/C++语言的扩展,用于表示一个代码块,该代码块被调用来清理由try块说明的任何东西。不管try块如何退出,该关键字都被调用。
对结构化异常处理而言,Windows为每线程的异常处理程序提供了特殊支持。编译器产生的代码将一个指向EXCEPTION_REGISTRATION结构的指针的地址,写入fs段寄存器所引用的地址。因为异常处理程序地址紧跟在局部变量之后,因此,如果一个栈变量发生缓冲区溢出,那么异常处理程序地址就可以被覆写为任意值。除了覆写单独的函数指针外,还可以替换线程环境块(Thread Environment Block, TEB)中的指针,已注册的异常处理程序的列表就是由该指针所引用的。
系统默认异常处理:未处理异常过滤器函数利用SetUnhandledExceptionFilter()函数进行设置。该函数作为进程的最后一级异常处理程序而被调用。然而,如果攻击者利用任意内存写技术覆写了某特定内存地址,则未处理异常过滤器可以被重定向去执行任意代码。
3.11 缓解策略:防止指针诡计的最佳方式就是消除”允许内存被不正确地覆写”的漏洞。覆写对象指针、常见的动态内存管理错误、字符串格式化漏洞都可能导致指针诡计的发生。消除这些漏洞来源是消除指针诡计的最佳方式。
栈探测仪:仅对那些预通过溢出栈缓冲区来覆写栈指针或者其它受保护区域的漏洞利用有效。栈探测仪并不能防止对变量、对象指针或者函数指针进行修改的漏洞利用。栈探测仪不能阻止包括栈段在内的任何位置发生缓冲区溢出。
W^X:此策略意思是说一段内存区域要么可写要么可执行,但不可同时两者兼备。这种策略不能防止类似于atexit()这样的同时需要运行时写入和可执行的目标覆写。
对函数指针编码和解码:程序可以存储一个指针的加密版本,而不是存储该指针。攻击者需要破解加密的指针才能重定向到其它代码。提议在C11标准中加入encode_pointer()和decode_pointer()函数,后未被采纳。这两个函数与Microsoft Windows的两个函数(EncodePointer()和DecodePointer())的目的类似,但细节略有不同,后者被Visual C++的C运行时库所使用。
3.12 小结:就像栈溢出攻击被用于覆写返回地址一样,缓冲区溢出可被用于覆写对象指针或函数指针。覆写函数指针或对象指针的能力取决于缓冲区溢出发生的地址和目标指针之间距离的远近,不过一般而言,同一个内存段内都存在这样的机会。
攻击函数指针使得攻击者能够直接将程序的控制权转移到由其提供的任意代码。对对象指针进行修改并赋值的能力创建了任意内存写技术。不管环境如何,任意内存写技术都有很多机会将程序的控制权转移给任意的用于任意内存写技术的代码。其中一些目标是C标准特性的结果,另外一些则特定于编译器或操作系统。
GitHub:https://github.com/fengbingchun/Messy_Test
相关文章:
runLoop和runtime的分析
一.RunLoop: Runloop是事件接收和分发机制的一个实现。 Runloop提供了一种异步执行代码的机制,不能并行执行任务。 在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。 (1).RunLoop的主要目的&#…
脑出血遇到深度学习,是否可以无所遁形?
近期大家对身体健康这个话题格外关注,而我们今天公开课的主题也恰巧与此不谋而合。我国脑卒的发病率已经超过心血管疾病,成为致死、致残率最高的疾病,并且发病率呈逐年上升的趋势,此外脑血管病和颅内肿瘤等脑部疾病也危害人们的健…

Cloudera Manager 5.3 和 CDH5.3.0 本地(离线)
为什么80%的码农都做不了架构师?>>> 声明一下:http://my.oschina.net/dataRunner/blog/369129 是本人所写,并非抄袭。 有部分内容来自 http://www.wangyongkui.com/hadoop-cdh5/ 这个文件是根据官网操作,翻译的不…
万字长文详解如何用Python玩转OpenGL | CSDN 博文精选
作者 | 天元浪子来源 | CSDN博文精选【编者按】OpenGL(开放式图形库),用于渲染 2D、3D 矢量图形的跨语言、跨平台的应用程序编程接口,C、C、Python、Java等语言都能支持 OpenGL。本文作者以 Python 语法为例,用两万字详…

模仿视频抓帧实现
路口或某些场所可能并不会把从摄像头获取到的视频全部存储下来或对所有的视频帧进行处理,即摄像设备是一直处于打开状态,可能会根据需要间隔性的抓取其中一帧,或当某事件触发时才会抓取当前的一帧数据进行处理。这里使用两个线程来模仿此场景…

iOS--MD5加密封装
#import <Foundation/Foundation.h> interface MD5 : NSObject /** * md5加密 * * param inPutText 需要加密的字符串 * * return 加密好的字符串 */ (NSString *)md5:(NSString *)inPutText; end #import "MD5.h" #import "CommonCrypto/CommonDiges…

Akka路由_RoundRobinRoutingLogic
2019独角兽企业重金招聘Python工程师标准>>> Akka路由_RoundRobinRoutingLogic 使用Round Robin算法的Router,代码中有注释,基本和上篇文章中的代码一样 http://my.oschina.net/xinxingegeya/blog/369721, 具体如下,关…

iOS ---网络请求封装(自动缓存与手动缓存)
#import <Foundation/Foundation.h> interface WNetworkCache : NSObject /** * 手动写入/更新缓存 * * param jsonResponse 要写入的数据 * param URL 请求URL * * return 是否写入成功 */ (BOOL)saveJsonResponseToCacheFile:(id)jsonResponse andURL:(NSStrin…

Windows下获取视频设备的一种改进实现
之前在https://blog.csdn.net/fengbingchun/article/details/102806822中介绍过在Windows下获取视频设备列表的方法。其实那种实现方法是有缺陷的,当PC机上连接多个视频设备,并且其中有设备处于启动运行状态时,再调用相关接口获取视频设备可能…
最新单步目标检测框架,引入双向网络,精度和速度均达到不错效果
作者 | Tiancai Wang等译者 | 路一直都在出品 | AI科技大本营(ID:rgznai100)one-stage的目标检测方法因其具有实时性强、检测精度高等特点,近年来受到广泛关注。目标检测包括分类和定位两个子任务,通常来说,one-stage目…

基于Sentinel的Redis3.2高可用方案
默认情况下,Redis node和sentinel的protected-mode都是yes,在搭建集群时,若想从远程连接redis集群,需要将redis.conf和sentinel.conf的protected-mode修改为no,若只修改redis node,从远程连接sentinel后&am…
从YARN迁移到k8s,滴滴机器学习平台二次开发是这样做的
整理 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】人工智能时代,机器学习已经渗透进每个领域,改变了这些领域的业务模式、技术架构以及方法论。随着深度学习技术近年来快速发展,高效、易用的机器学习平台对于互联…

最新 macOS Sierra 10.12.3 安装CocoaPods及使用详解
cocoapods官网:https://guides.cocoapods.org 一、什么是CocoaPods 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如 Java 语言的 Maven,nodejs 的 npm。随着 iOS 开发者的增多,业界也出现了为 iOS 程序提供…

libusb中的热插拔使用举例
以下为判断usb设备是插入还是拔出状态(热插拔)的测试代码: 在Windows下是不支持的,在Linux是支持的,下一个版本可能会支持Windows下的热插拔: #include <chrono> #include <thread> #include <iostream> #incl…

C++复制控制:拷贝构造函数
一、拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。与默认构造函数一样 ,拷贝构造函数可由编译器隐式调用。拷贝构造函数应用的场合为: (1࿰…

关于IOS获取本地通讯录信息(包含iOS9.0前后)
在ios开发当中,获取用户本地的通讯录功能愈加频繁的出现,七两自己也在自己公司的项目当中遇到的获取本地的通讯录信息的功能(俗称“种子用户功能”,太可怕了)。对此七两总结了自己使用本地通讯录时的注意点,…

C和C++安全编码笔记:动态内存管理
4.1 C内存管理: C标准内存管理函数: (1).malloc(size_t size):分配size个字节,并返回一个指向分配的内存的指针。分配的内存未被初始化为一个已知值。 (2).aligned_alloc(size_t alignment, size_t size):为一个对象…
作为一名程序员,数学到底对你有多重要?
最近在知乎上看到一个贴子,看完后我沉默了.....沉思后想想,其实每个行业都会分等级,程序员也不例外!说好听一点的叫工程师,普通一点的叫程序员,差一点的叫码农,更差的还会叫码畜,码奴…

经典SQL(sqlServer)
一、基础 1、说明:创建数据库CREATE DATABASE database-name 2、说明:删除数据库drop database dbname3、说明:备份sql server--- 创建 备份数据的 deviceUSE masterEXEC sp_addumpdevice disk, testBack, c:\mssql7backup\MyNwind_1.dat--- …

iOS UITextField输入框随键盘弹出界面上移
//点击输入框界面跟随键盘上移 - (void)textFieldDidBeginEditing:(UITextField *)textField { CGRect frame textField.frame; int offSet frame.origin.y 70 - (self.view.frame.size.height - 216.0); //iphone键盘高度为216.iped键盘高度为352 [UIView beginAnimations:…
IEEE分享 | 机器学习在领英的规模化应用
人工智能和机器学习仍然是全球持续增长的领域之一,近年来涌现出越来越多本科生或者非人工智能专业出身的工程师,他们努力学习和使用技术来改进产品,几乎每天都有新的机器学习技术和框架发布。这篇文章将讨论领英如何规模化利用技术࿰…

GitHub/GitLab/Gitee中项目互拷贝后仍保留历史提交记录的方法
GitHub、GitLab、Gitee等在同一个网站中执行复制或拷贝一个已有项目到一个新项目比较简单,因为它们在每一个项目上都有一个Fork按钮,直接点击此Fork按钮即可,Fork后的新项目会保留原有项目的历史提交记录。但是如果不在同一个网站上进行此操作…

基于mimeTex的数学公式Webservice的部署和实现
通过Latex语法,实现生成数学公式的解决方案也很多。这里介绍一种方法,使用开源的mimeTex。该项目的官网地址如下:http://www.forkosh.com/mimetex.html网站主页有一个声明。如果你的服务器上已经安装了latex,那么推荐使用mathTex&…

对称加密算法AES之GCM模式简介及在OpenSSL中使用举例
AES(Advanced Encryption Standard)即高级加密标准,由美国国家标准和技术协会(NIST)于2000年公布,它是一种对称加密算法。关于AES的更多介绍可以参考:https://blog.csdn.net/fengbingchun/article/details/100139524 AES的GCM(Galois/Counte…

iOS UITextField清空按钮
extField.clearButtonModeUITextFieldViewModeWhileEditing; 就可以了,表明编辑输入框的时候启动一键清空按钮。另外,clearButtonMode还有三个属性: UITextFieldViewModeNever, 清空按钮永不出现 UITextFieldViewModeUnlessEditing, 不编…
腾讯“疯狂”开源
作者 | 马超责编 | 胡巍巍出品 | CSDN(ID:CSDNnews)近日,腾讯自研的万亿级分布式消息中间件TubeMQ正式开源,并捐赠给Apache基金会,成为基金会官方认可的Incubator项目。我们知道与TubeMQ功能类似的kafka是领…

[Android]开发摇一摇分歧表决器过程
心血来潮,走进Android,准备开发一个摇一摇分歧表决器(PS:这个想法源自去年看的一个都市剧《约会专家》中主人公杭杭开发的分歧表决器APP)。简述:摇一摇分歧表决器是一款Android App,通过将传统的…

C和C++安全编码笔记:整数安全
5.1 整数安全导论:整数由包括0的自然数(0, 1, 2, 3, …)和非零自然数的负数(-1, -2, -3, …)构成。 5.2 整数数据类型:整数类型提供了整数数学集合的一个有限子集的模型。一个具有整数类型的对象的值是附着在这个对象上的数学值。一个具有整数类型的对象…
8.3折特惠票仅剩3天!「2019 嵌入式智能国际大会」全日程大公开!
8.3折特惠票仅剩3天立即抢购:https://t.csdnimg.cn/otBk还有5天,大伙期待的「2019嵌入式智能国际大会」正式开幕了!2019年12月6日-7日,我们在深圳市人才研修院见!大会以“万物互联泛在智能”为主题,邀请30位…

iOS点击空白收回键盘
//点击空白收回键盘 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; }