首页
社区
课程
招聘
[翻译] 通过Win32 API 写入可执行内存
2018-9-30 22:18 5685

[翻译] 通过Win32 API 写入可执行内存

2018-9-30 22:18
5685

这篇文章的内容有的读者可能有了解过,但对我来说这是一个全新的发现。而且我感觉它有些让人匪夷所思,所以这里来分享给大家,它可能对大家有用。我是在编写一个POC的时候发现的这种情况。
这篇文章的核心思想简单来说是,Win32 API函数:WriteProcessMemory可以写入可执行PAGE_EXECUTE和可执行可读PAGE_EXECUTE_READ属性的页面,当然前提是你有相应的权限(译注:按照常识来说,可执行的内存必定不可写,可写的内存必定不可执行)。我想在开头强调,这不用绕过任何内置安全功能,也不需要利用任何东西,这只是一个方便的技巧。
首先我会介绍它是如何工作的,最后来解释为什么这样。

第一部分-How

这是WriteProcessMemory函数在最新版本的Windows1803上的实现。
此处输入图片的描述
如图所示,在函数内部首先会调用NtQueryVirtualMemory获得region的属性。
然后检查该内存页是否具有以下某个属性

PAGE_NOACCESS(0x1) | PAGE_READONLY(0x2) | PAGE_EXECUTE (0x10) | PAGE_EXECUTE_READ (0x20)

此处输入图片的描述
这是按位进行检查的,可以得出0xcc就是对以上标志的检查

0xcc = 1100 1100
0x1  = 0000 0001
0x2  = 0000 0010
0x10 = 0001 0000
0x20 = 0010 0000

在执行test指令之后,如果其中存在任一flag,它将设置ZF标志。 否则会直接进入NtWriteVirtualMemory调用,也就是说内存页具有WRITE位。
此处输入图片的描述

 

如果前面设置了某一个flag值,则会进行接下来的检查
此处输入图片的描述

 

如果设置了PAGE_NOACCESS或PAGE_READONLY则会跳转,并且我们会按预期被拒绝访问:
此处输入图片的描述

 

否则,如果不具有这两个flag会进行另外两个检查
此处输入图片的描述

 

如果页面是MEM_IMAGE(0x1000000)和MEM_PRIVATE(0x20000)属性 它将设置一个EAX值。如果内存页这两个属性都不具有,那就会走到ACCESS_DENIED处理例程中了。

 

这个EAX值最终在RSI中传递给NtProtectVirtualMemory:
此处输入图片的描述

 

现在内存具有以下这些标志:
0x40 - PAGE_EXECUTE_READWRITE
0x80 - PAGE_EXECUTE_WRITECOPY
0x20000000 - MEM_LARGE_PAGES(大页面支持)

 

这意味着操作系统会将页面保护更改为可写,而不是直接拒绝访问。 如果它是一个图像,它会将其设置为写入复制标志,这意味着它将创建进程图像的私有副本,因此它不会覆盖共享内存。

 

在此之后,会同样调用NtWriteVirtualMemory,如上所示。 最后,页面保护将恢复为原始值。 因此我们通过win api获得了对EXECUTABLE页面的写访问权限, 但是只有当我们的进程有相应的权限时才会这样,所以它没有去绕过任何保护。

 

在旧版本的Windows 10上,该功能略有不同,但逻辑完全相同:
此处输入图片的描述

 

在Windows 7和8上,该行为同样存在,但功能逻辑不同。 它会尝试将内存设置为PAGE_EXECUTE_READWRITE,如果失败就尝试设置到PAGE_READWRITE:
此处输入图片的描述
然后它检查旧保护是否为PAGE_EXECUTE_READWRITE、PAGE_READWRITE或PAGE_WRITECOPY。
如果是,会继续并恢复原始保护(因为原属性就是可写)并写入它。
如果不是,会检查它是否是PAGE_NOACCESS |PAGE_READONLY。 具有这些标志的话,会返回ACCESS_DENIED。否则当页面保护设置为PAGE_EXECUTE_READWRITE / PAGE_READWRITE时,它将调用NtWriteVirtualMemory。又是一个可以对EXECUTABLE页面进行写访问的机会。

 

在ReactOS中也存在类似的实现:
https://github.com/mirror/reactos/blob/master/reactos/dll/win32/kernel32/client/proc.c
当然,我们也可以自己来设置页面的属性,但是操作系统的这种行为让我们开发EXP更方便了。但是根据MSDN的描述,这些操作并不合法,MSDN是这么说的:
PAGE_EXECUTE - 0x10 - 允许对COMMIT的内存页进行execute访问。 尝试write会导致访问冲突。
PAGE_EXECUTE_READ - 0x20 - 允许对COMMIT的内存页进行read和execute访问。尝试写入已提交的区域会导致访问冲突。

 

如果我们直接调用NtWriteVirtualMemory它会返回失败,因为页面保护没有被修改:
0x8000000D - STATUS_PARTIAL_COPY - 由于保护冲突,并非所有请求的字节都可以复制。

第二部分-Why

我联系了微软为什么会存在这种特性,微软的解释是这是为调试器提供的功能,如果调试器需要写入内存,他们可以简单地调用这个API,而不必每次都关心页面保护的情况。以下是详细信息:
https://blogs.msdn.microsoft.com/oldnewthing/20120808-00/?p=6913

 

简而言之就是:
有许多函数允许操作其他进程的地址空间,比如WriteProcessMemory和VirtualAllocEx。他们可能的合法使用途径是什么?为什么一个进程需要操作另一个进程的地址空间呢,这有什么好处?
这些函数是为调试器而设计的。例如,当你需要用调试器查看被调试的进程的内存时,它就会使用ReadProcessMemory来执行此操作。同样,当你需要用调试器更新进程中变量的值时,它使用WriteProcessMemory来执行此操作。当你需要用调试器设置断点时,它使用VirtualProtectEx函数将你的代码页从read-execute更改为read-write-execute,以便它可以将int 3加入到程序中。
如果需要调试器进入进程,可以使用CreateRemoteThread函数将一个线程注入到立即调用DebugBreak的进程中。 (随后添加了DebugBreakProcess以使其更简单。)
但对于通用编程,这些功能实际上没有多少有效用途。
因此它们往往被用于恶意目的,如DLL注入和游戏外挂作弊之中。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2018-12-29 14:21 被Ox9A82编辑 ,原因:
收藏
点赞0
打赏
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  junkboy   +1.00 2018/09/30
最新回复 (1)
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
junkboy 2018-9-30 22:35
2
0
图片需要挂梯子
游客
登录 | 注册 方可回帖
返回