Java教程

修改进程常量的几种方法-如何修改进程只读数据段的访问控制属性

本文主要是介绍修改进程常量的几种方法-如何修改进程只读数据段的访问控制属性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1. 引言

本文笔者在研究某著名软件的安全性时,碰到要修改进程中只读数据段(.rdata)中的常量

值的场景。笔者将这方的方法技巧及相关的经验向广大读者朋友进行分享,同时由于笔者技术水平有限,本文中若有错误恳请不吝赐教()。

       为了简单的说明问题,笔者使用delphi写了一个小示例程序(其实,使用什么语言并不重要,本示例程序只是为了分析问题,并验证问题解决思路是否正确,从严格意义来说该示例也有许多不严谨之处,例如因为编译器不同对字符串常量的处理也是不同的,但这些细节问题我们暂时可以忽略),在本示例程序中,声明了一个全局常量,在程序中动态修该常量值,看看运行下过如何。

  图-1 修改常量值的程序代码

      

图-2 运行程序修改常量值报写内存错误

        如图-2所示,运行示例程序修改常量值,会报写内存错误。当然,本示例只是修改本进程中的常量值,在实际的生产环境没有太大的实际意义,笔者使用该示例只是聚焦问题本质,简化实际场景。在软件安全研究领域,经常需要修改第三方进程注入常量的只读数据中的数据,分析思路和方法是相同的,笔者接下来,将围绕该示例对报错原因进行深入分析,并且给出该问题的解决方法。

2. 问题分析

        本小节笔者对在进程运行期间修改常量值报写内存错误的原因进行分析。笔者曾看到过一篇博客博客,其作者认为常量的只是在编译期间进行检查其值是否发生变化,在运行期间常量的值是可以修改的。这个观点是错误的(至少来说是不准确的)。在程序运行期间常量的值也是不可以修改的,那么这是如何做到的呢?

        在进程中一般都含有三个段(section),代码段(.text),数据段(.data),只读数据段(.rdata),段的名称会因为编译器不同而有差异,微软的C,C++编译器一般将代码段命名为“.text”,数据段名为“.data”,只读数据段命名为”.rdata”。Borlad的pascal和C++编译器将代码段名为”CODE”,数据段命名为”DATA”,只读数据段名为”.rdata”。

         代码段(.text)的访问控制属性是可执行,可读,不可写的。数据段的访问控制属性是可读写。只读数据段(.rdata)中的访问控制属性是只读的,资源文件,动态链接库导入表,常量一般都存储在该段。如果本进程或者外部进程中的代码尝试写不可读内存,则会触发异常。常量具体存储在哪个数据段,会因编译器的不同而有差异,但其所在段的访问控制属性应该是不可写的。该结论的分析过程如下。

2.1 使用CE查看示例程序中全局变量内存

        在图-2示例程序运行的日志中可以得知全局常量G_Name的内存地址为0x00452700,使用CE(Cheat Engine是一款内存分析、修改的工具,和IDA,OD并称为逆向分析的三剑客)查看到全局变量G_Name所在页的基地址为0x0045200,内区页的访问控制属性是可执行/只读,故写内存操作会触发异常。那么,全局量G_Name在哪个段(Section)呢?根据其访问控制属性来看其应该是在代码段,该推论是否正确呢?

图-3 使用CE查看示例程序内存

2.2 分析示例程序的PE头

        在PE文件头中会对程序的段信息详细的描述,如图-4和图-5所示,使用UE打开示例程序RDataSectionDemo.exe所呈现PE文件头。关于PE文件结构细节,本文不再展开叙述,后续如果时间允许,笔者会写一篇专本关于PE文件分析的文章。

       如图-4所示,在PE文件的IMAGE_NT_HEADER中可以看出示例程序共分为8个段。如图-5所示可看出这8个段分别为CODE段、DATA段、BSS段、.idata段、.tls段、.rdata段、.reloc段、.rsrc段。每个段头40字节,包括段名,段大小,段RAV地址,段属性信息。笔者不再对每个段进行一一分析,重点分析代码段(CODE,在其它编译器编译的目标程序中通常叫.text)。

       如图-5所示,代码段的RAV地址0x00001000,根据该地址加上exe的基地址0x00400000(默认是该基地址,在程序编译时可以设置为不同的值)计算出代码段的虚拟基地址(VA)是0x00401000。代码段的大小(未页对齐)0x00052054,win32默认的内存页的大小4K,内存页对齐后大小为0x00053000,据此可以推算出代码段的虚拟地址(VA)的范围是0x00401000~0x00454000

       同样如图-5所示,可以看出代码段的属性为0x60000020=0x40000000 or 0x20000000 or 0x00000020。其中0x40000000表示据段可读,0x20000000表示段可执行,0x0000002表示段中包含代码。

       根据2.1节所分析的全局变量的地址(虚拟地址VA)为0x00452700可以的得出全局常量G_Name是在代码段(CODE段)中的结论,同时根据段属性的分析也佐证了这一点。由于代码段访问控制属性是可执行/只读,所以在修改全局常量的值时会触发异常。

 图-4 示例程序PE文件 DOS和NT_HEADER头

 图-5 示例程序PE文件段头

3. 改变段属性的方法

        通过第2节的分析,我们已经清楚的知道,为什么在程序运行期间修改常量值会触发异常的原因。那么有没有方法可以绕过该限制,使得程序运行期间可以修改常量值呢?答案时肯定的,并且方法有多种,笔者在本文中重点介绍两种方法。

3.1 静态修改PE文件段属性

        在第2节的图-5中,我们得知代码段的属性0x60000020=0x40000000 or 0x20000000 or 0x00000020,该值属性值控制了代码段是只读的。那么我们能否通过修改代码段的属性值赋予其读写属性呢。0x80000000表示段可写,我们将代码段属性修改0xE0000020=0x80000000 or 0x40000000 or 0x20000000 or 0x00000020。

       如图-6所示,修改示例程序PE文件中数据段属性赋予可写属性,将其另存为新的示例程序(exe)。

图-6 修改代码段属性

       运行修改过代码段属性的示例程序,不出所料,在程序运行期间可以动态修改常量值了,运行结果如图-7所示。

图-7 修改代码段属性后运行结果

       在现实的生产环境中,通过该方法修改段属性,使常量值在运行期间可以修改有很大的局限性。为什么这么说呢?因为软件病毒和反病毒,调试于与调试双方一直进行斗志斗勇,激烈的较量,这也促进了软件技术的发展。在生产环境中软件都会加壳,而且有很多的先进的壳能到容易加壳但去壳却是不可逆的(关于加壳与脱壳的技术细节笔者在本文中不再展开叙述)。在实际的生产环境中应用一旦加壳,该方法就很难实施,是否有别的方法可以起到同样的作用呢?这就是笔者要介绍的第二种方法,在程序运行期间动态修改数据段的属性。

3.2 动态修改虚拟内存访问控制属性

        在程序运行期间动态修改常量值的方法有很多种,例如修改CR0寄存器停用内存禁用内存保护模式,或者通过DML修改虚拟内存的访问控制属性,DML表地址存储在CR3寄存器。这两种方法都需要有系统内核权限(R0),也就是需要开发驱动程序才能达到该目的。另外这两种方法很底层,不同的操作系统,32位和64位等相差都比较大,所以笔者不建议使用该方法。

       有一种简单易行的方法也可以达到该目的,使用VirtualQuery查询要修改的变量所在的虚拟内存页,然后通过调用VirtualProtect来修改目标内存页的访问控制属性,这样即可实现我们的目标。关于这两个函数的细节笔者在此不再赘述,直接贴出相关的代码片段如下图所示。

图-8 修改虚拟内存页访问属性

 图-9 修改虚拟内存访页问控制属性运行效果

       下载本文中示例程序源代码

这篇关于修改进程常量的几种方法-如何修改进程只读数据段的访问控制属性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!