使用Visual C++的防御功能保护你的代码

此为转贴文章,但对一些语句有修改。

 

 

使用Visual C++的防御功能保护你的代码

 

 

         许多代码都是使用CC++编写的,但遗憾的是,其中很多此类代码都有一些甚至连开发人员都不知道的安全漏洞。以任何语言编写的程序都存在可能会使其用户受到攻击的漏洞,但CC++语言在Internet历史上占有特殊的地位,原因是它们的许多安全漏洞都源自使其大受欢迎的功能:对计算机硬件无限制的访问以及随之而来的性能优势。在阅读有关安全和CC++方面的资料时,通常很容易看到“缓冲”和“溢出”等术语,因为缓冲通常就是直接访问内存的一个示例。这种类型的直接访问虽然功能非常强大,但也非常非常危险。

 

         对于在生成CC++代码过程中出现的许多缓冲区溢出问题,其原因有多种。第一个原因在上面已提到过:编程语言提供对易受攻击内存的直接访问;第二个原因是开发人员出错;第三个原因是编译器通常不提供防御功能。对于第一个问题很容易提供补救措施,但自此CC++开始成为不同的语言。

 

         开发人员出错问题可通过培训来部分加以解决,但是还没有真正看到在这方面教育机构的兴起。行业中也确实有一些进行安全培训的机构,但是我们只能提供部分解决方案或解决部分问题,非常希望看到各所大学能对学生进行更多有关软件安全方面的教育。你可能要问“为什么教育机构不尝试培训这一非常重要的主题?”说实话,我也不知道为什么。不过这确实非常让人郁闷。

 

         最后,即使有一流的培训,但有些安全问题真的非常复杂,就算是受过良好教育的工程师也不能完全解决。我们人类并不是完美的。

 

         在编译器中建立更多防御措施是 Microsoft Visual C++ 团队多年以来一直试图解决的问题,在我们安全团队的协助下目前正逐渐得到改进。本专栏将概述一些在 Visual C++® 2005 中可用的缓冲区溢出防御措施及其他内容。请注意,一些其他编译器也提供防御措施,但 Visual C++ 有两个主要优势超越了gcc等编译器。首先,所有这些防御措施都默认集成在工具集中,无需下载某些稀奇古怪的加载项。其次,这些选项易于使用。

 

         Visual C++ 工具集提供的防御措施包括(不分先后顺序):

 

Ø  基于堆栈的缓冲区溢出检测(/GS

Ø  安全异常处理(/SafeSEH

Ø  数据执行保护(DEP)兼容性(/NXCompat

Ø  映像随机化(/DynamicBase

Ø  自动使用更安全的函数调用C++ operator::new

 

 

         在详细讨论每一项之前,需要指出的是这些防御措施并不能弥补不安全的代码。你应该始终尽最大努力编写最安全的代码,如果不知道该怎样做,可以先阅读一些有关这一主题的专业书籍。

 

 

带有/GS选项的典型堆栈对比图

 

 

         另外我还要指出,这些都是 Microsoft 的“安全开发生命周期”(SDL)的要求,这意味着编写CC++代码时必须使用这些选项,否则不能交付。偶尔会有些例外,但是非常少,因此在这里不对其做详细讨论。

 

         最后必须要牢记的要点是:这些精心设计的防御措施也是可以被绕过的,具体视代码问题而定。代码使用的防御措施越多,解决起来就越困难,但是没有任何防御措施是尽善尽美的。它们都是为了减少被侵入的机会。您一定对此已经有所了解!唯一的应对方案就是使用更安全的函数调用,这是真正能够堵上漏洞的防御措施。让我们详细了解一下各个防御措施。

 

 

         基于堆栈的缓冲区溢出检测(/GS

 

         基于堆栈的缓冲区溢出检测是Visual C++中提供的最陈旧最常见的防御措施。/GS编译器标志的目标非常简单:减少恶意代码正确执行的机会。默认情况下,/GS选项在Visual C++ 2003及更高版本中处于启用状态,它在运行时检测特定种类的堆栈溢出。为了进行检测,它会在函数堆栈中包括一个随机数(在堆栈的返回地址之前),当函数返回时,函数收尾代码将检查此值以确保它未进行过更改。如果它调用的 cookie 发生了变化,则执行过程将被中止。

 

         用来设置 cookie 的函数导引代码如下所示:

 

sub    esp, 8

mov    eax, DWORD PTR ___security_cookie

xor    eax, esp

mov    DWORD PTR __$ArrayPad$[esp+8], eax

mov    eax, DWORD PTR _input$[esp+4]

 

 

         以下是用来检查 cookie 的函数收尾代码:

 

mov    ecx, DWORD PTR __$ArrayPad$[esp+12]

add    esp, 4

xor    ecx, esp

call    @__security_check_cookie@4

add    esp, 8

 

 

         Visual C++ 2005还会在堆栈上移动数据,使该数据更不容易被破坏,比如将缓冲区移到高于非缓冲区的内存中,此步骤有助于保护位于堆栈上的函数指针。此外,运行时将指针和缓冲区参数移至较低内存处可减轻各种缓冲区溢出攻击带来的危害。可参阅上面典型堆栈和/GS堆栈的比较图。

 

 

         /GS 编译器选项不能应用于下列任何情况:

 

Ø  函数不包含缓冲区。

Ø  优化未启用。

Ø  函数定义中有参数列表。

Ø  函数被标为nakedC++)。

Ø  函数在第一个语句中包含内嵌汇编代码。

Ø  缓存区不是8字节类型且大小小于4个字节。

 

 

         Visual C++ 2005 SP1中新增了一个选项,可使/GS试探法更积极主动,从而可以保护更多的函数。Microsoft 添加此选项是因为一些安全公告中也提到了基于堆栈的缓冲区溢出代码的问题,即使这些代码是使用 /GS 进行编译的,其也未受到cookie的保护。这一新增的选项大大增加了受保护的函数数量。

         要使用此选项,可将以下代码行放到需要添加保护的模块中,例如用来处理来自Internet数据的代码:

 

#pragma strict_gs_check(on)

 

 

         这只是一个用来说明Microsoft如何不断发展/GS功能的示例。Visual C++ 2003中的初始版本非常简单,随后在Visual C++ 2005 SP1中有所更新,在了解了一些新攻击以及一些绕过现有攻击的新方法后,又在Visual C++ 2008中对其再次进行了更新。在分析中发现,/GS会导致不真实的兼容性或性能问题。

 

 

         安全异常处理 (/SafeSEH)

 

         Internet Information Server (IIS) 4.0造成巨大影响的“红色代码”蠕虫是由基于堆栈的缓冲区溢出导致的。有意思的是,/GS并不能发现蠕虫侵入问题,因为代码并没有溢出到受影响函数的返回地址,而是破坏堆栈上的异常处理程序。这个示例就很好地说明了为什么必须要始终强调编写安全代码,而不能完全依赖于基于编译器的各类防御措施。

 

         异常处理程序是指出现异常情况(如除零)时执行的代码。处理程序的地址保存在函数堆栈帧中,因此会受到破坏。Visual Studio® 2003及更高版本中的链接器都包含一个选项,可以在编译时将有效异常处理程序的列表存储在映像的PE标头中。如果在运行时引发异常,操作系统会检查映像标头以确定异常处理程序的地址是否正确;如果不正确,应用程序将被终止。如果在链接代码时使用此技术,即可预防像“红色代码”这类的病毒。除了出现异常外,/SafeSEH选项不会导致性能降低,因此链接时应始终使用此选项。

 

 

         DEP 兼容性(/NXCompat

 

         目前几乎生产的每个CPU都支持no execute (NX)功能,这意味着CPU不会执行非代码页。想一下此功能的含意:几乎每个缓冲区溢出漏洞都属于数据错误;攻击者利用缓冲区溢出将数据注入进程,然后在恶意数据缓冲区中继续执行。CPU究竟为什么会运行数据呢?

 

         /NXCompat链接选项意味着可执行文件将受到CPUno execute功能的保护。根据经验,使用此选项后,Microsoft安全团队很少会接到兼容性问题报告,而且性能也没有降低。

 

         Windows Vista® SP1还新增了一个新的API,它可以为运行中的进程启用 DEP,它一经设置无法取消:

 

SetProcessDEPPolicy(PROCESS_DEP_ENABLE);

 

 

         映像随机化(/DynamicBase

 

         Windows VistaWindows Server® 2008支持映像随机化,这意味着当系统启动时,它会在内存中无序放置操作系统映像。此功能的目的只是消除一些来自攻击者的可预见性。它也称为“地址空间布局随机化”(ASLR)。请注意,不管怎样使用ASLR,都必须同时启用DEP

 

         默认情况下,Windows®将只调整相关的系统组件,如果希望由操作系统来移动程序映像(强烈建议),则应链接/DynamicBase 选项。(此选项可在 Visual Studio 2005 SP1 及更高版本中获得。)链接/DynamicBase后有一个有趣的副作用——操作系统还会对你的堆栈进行随机化,这也有助于降低可预见性,使攻击者破坏系统的企图更难以得逞。还要注意,在Windows VistaWindows Server 2008中堆也会被随机化,但这是默认设置,无需编译或链接任何特殊选项。

 

 

         更安全的函数调用

 

         请看以下几行代码:

 

void func(char *p) {

    char d[20];

    strcpy(d,p);

    // etc

}

 

 

         假定 *p 包含不受信任的数据,则此代码代表一个安全漏洞。现在,编译器可能会将对strcpy的调用提升为更安全的函数调用(此安全函数会确认复制操作的目标缓冲区大小的边界)。为什么呢?因为缓冲区大小是静态的,并且在编译时已知!

 

         利用 Visual C++,可以将下列代码行添加到 stdafx.h 头文件中:

 

#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1

 

 

         编译器随后将继续运行并生成以下代码(这些代码来自初始的、不安全的函数):

 

void func(char *p) {

    char d[20];

    strcpy_s(d,__countof(d), p);

    // etc

}

 

 

         如你所见,代码现在是安全的,开发人员只添加了#define,此外未进行任何处理。这是我个人最喜欢的Visual C++新增功能之一,因为大约50%的不安全函数调用都可以被自动转换为安全调用。

 

 

         C++ Operator::new

 

         最后,Visual C++ 2005 及更高版本还新增了一种防御功能,它可以在调用operator::new时检测整数溢出。请看以下代码:

 

CFoo *p = new CFoo[count];

 

 

         Visual C++ 将其编译为:

 

00004    33 c9           xor    ecx, ecx

00006    8b c6           mov    eax, esi

00008    ba 04 00 00 00  mov    edx, 4

0000d    f7 e2           mul    edx

0000f    0f 90 c1       seto    cl

00012    f7 d9           neg    ecx

00014    0b c8            or    ecx, eax

00016    51             push    ecx

00017    e8 00 00 00 00 call    ??2@YAPAXI@Z    ; operator new

 

 

         在计算了要分配的内存数量后(mul edx),将根据相乘后溢出标志的值来决定是否设置CL寄存器,因此,ECX将为0x000000000xFFFFFFFF。由于下一个操作(or ecx)之后,ECX寄存器将是0xFFFFFFFF或是EAX中包含的值,即最初相乘的结果。它随后被传递到 operator::new,在进行 2^N-1 次分配时它会失败。

         此防御功能无限制,没有编译器开关,它只是编译器所做的工作。

 

 

         如果失败会怎样?

         噢!这是一个棘手的问题!如果触发了上述列出的任何防御功能,都会产生非常令人讨厌的结果:应用程序会退出。这不是最理想的解决方案,但总要比运行攻击者的恶意代码好得多。

         SDL强制要求新代码使用所有这些防御功能,这完全是因为外部有太多的攻击,而且开发者永远不能确保代码100%没有漏洞。SDL的一个流行语是“代码失败该怎么办?”在现实生活中“该怎么办”意味着不屈不挠!不要让攻击者的代码肆意横行,要让攻击者寸步难行,决不能放弃!

         所以,用最新版本的C++编译器进行编译以获得更好的/GS,用最新链接器进行链接以利用CPU和操作系统提供的防御功能。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
众所周知,Java编译后的Jar包和Class文件,可以轻而易举的使用反编译工具(如JD-GUI)进行反编译,拿到源码。为了保护自己发布的Jar包和Class文件,采用的方式大多是混淆方式,这种方式对于Class文件的加密是不彻底的,还是能够通过分析得出核心算法。本工具是采用jvmti方式对Class文件进行加密,使用C++生成加密和解密库,先用加密库对Jar包进行加密,将加密后的Jar包及解密库文件发布出去,执行时候需要JVM引入解密库文件,解密后执行。c++的.dll文件和.so文件的破解难度是很大的,这就能有效的保护软件和代码的知识产权. 使用方法: 1.打开windows命令行(运行=>cmd=>回车),在命令行中 进入 EncryptJar目录 2.执行 java -jar encrypt.jar 3.输入h,然后回车,可以看到帮助菜单 4.输入3,然后按回车键,进入加入jar文件功能 5.输入要加密的jar文件的路径 6.提示输入秘钥(key)的时候,直接回车,不要输入任何字符(否则后面classhook将不可解密加密后的jar包) 7.输入目标路径(加密后的jar文件路径,此处要注意:jar文件名要保持相同,将加密后的文件保存到不同的目录) 8.将加密后的jar包,替换原来的没有加密的jar包,与要发布的程序一起进行发布.(一般替换lib目录下对应的jar包即可) 9.加密后的jar包运行方法: windows下: 拷贝libClassHook.dll文件到程序的根目录(通常为要执行的jar程序的根目录) 使用以下命令启动程序: java -agentlib:libClassHook -jar xxxxxxxxxxx.jar 则在运行过程中会自动进行解密操作(解密过程是运行过程中用c++的dll进行解密的,可以有效防止破解class文件) 如果执行过程报错,可将程序根目录添加到环境变量path中去 Linux下: 拷贝libClassHook.so到程序的根目录(通常为要执行的jar程序的根目录) 使用以下命令启动程序: java -agentlib:ClassHook -jar xxxxxxxxxxx.jar (这里要删除掉lib,linux系统下会自动补全) 则在运行过程中会自动进行解密操作(解密过程是运行过程中用c++的dll进行解密的,可以有效防止破解class文件) 如果执行过程报错,可以在程序根目录下执行以下语句:export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH 或将libClassHook.so 拷贝到/usr/lib目录中去。 支持操作系统:加密请在windows64位系统并安装了64位jdk环境下进行。 需要解密运行的程序支持LINUX(64位)和windows(64位)安装了JDK1.8以上的系统。 测试程序: (t_lib目录下的jar包为经过加密的jar包) java -agentlib:libClassHook -jar test.jar
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值