【问题标题】:How to run unmanaged executable from memory rather than disc如何从内存而不是磁盘运行非托管可执行文件
【发布时间】:2010-11-16 11:39:28
【问题描述】:

我想在我的 C# 应用程序中嵌入一个命令行实用程序,这样我就可以将其字节作为数组获取并运行可执行文件,而无需将其作为单独的文件保存到磁盘(避免将可执行文件存储为单独的文件并避免需要能够在任何地方写入临时文件)。

我找不到仅从其字节流运行可执行文件的方法。 Windows 是否要求它位于磁盘上,或者有没有办法从内存中运行它? 如果 windows 要求它在磁盘上,.NET 框架中是否有一种简单的方法来创建某种虚拟驱动器/文件并将文件映射到可执行文件的内存流?

【问题讨论】:

  • 你可以使用像 imdisk 这样的工具

标签: executable unmanaged embedded-resource memorystream


【解决方案1】:

这在 Vista+ 中是明确不允许的。您可以在 XP 中使用一些未记录的 Win32 API 调用来执行此操作,但在 Vista+ 中它被破坏了,因为它是一个巨大的安全漏洞,并且唯一使用它的人是恶意软件编写者。

【讨论】:

  • 这太荒谬了。字节流是字节流,无论您如何看待它或它来自何处。强制这些字节来自 FILE 而不是 MEMORY 运行没有任何意义。恶意软件?严重地?让我休息一下......所以他们只需先将其写入文件即可运行它。您是说 API 中存在安全漏洞(好吧,我猜),还是说从内存字节流而不是字节文件流运行代码的想法存在安全漏洞(绝对不是)。
  • 这很重要,而且并不荒谬。例如;如果文件不在磁盘上,您如何正确且安全地验证可执行映像的签名?既然您是从内存映像执行的,那么过滤器驱动程序将如何工作?既然您输入的是内存流而不是文件名,那么支持图像将如何工作?这些只是其中的一小部分,但您仍有一百万个极端情况,例如处理图像文件执行选项、调试不一致等。仅写出文件确实是微不足道的,这是处理一个非常复杂的胆量操作系统。
  • 通过从内存块而不是磁盘块读取字节来验证签名!应该没有区别;这是糟糕的设计。过滤器驱动程序是无关紧要的,因为我们谈论的是已经在执行的代码,请求一些其他特定代码开始运行,并且没有过滤器应该干扰该代码。如果您真的需要过滤器,操作系统可以提供低级挂钩,以允许过滤器在内存受到保护和执行开始之前扫描内存。同样,磁盘上的数据并没有什么特别之处,都是糟糕的架构。
  • IFEO 是一个等待发生的漏洞利用,只不过是为使用特定配置启动进程提供的一种懒惰的便利。这是草率的设计。我看到注册表中有类似“HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\SearchIndexer.exe”的条目。所以现在如果我运行一个名为 SearchIndexer.exe 的文件,是否会出现这种隐藏的、系统发起的、用户不知道的特殊行为?这是一个等待发生的漏洞,IMO。再次,调试是在类似的船上。
  • 如果你遵循我的逻辑,我什至不会惊讶于搜索“exploit IFEO”会发现一个简单的漏洞,它允许人们从登录屏幕访问提升的命令提示符通过按 shift 5 次 (redmondpie.com/…) 激活粘滞键。我只需要在任何朋友的计算机上用 10 秒时间添加带有 .reg 文件的注册表项即可完成这项工作。可悲且明显糟糕的设计。
【解决方案2】:

您要求在高级托管环境中实现非常低级的、特定于平台的功能。一切皆有可能……但没有人说这很容易……

(顺便说一句,我不知道您为什么认为临时文件管理很繁重。BCL 为您做到了:http://msdn.microsoft.com/en-us/library/system.io.path.gettempfilename.aspx


  1. 分配足够的内存来保存可执行文件。当然,它不能驻留在托管堆上,因此与本练习中的几乎所有内容一样,您需要 PInvoke。 (实际上,我推荐 C++/CLI,以免让自己发疯)。请特别注意应用于分配的内存页的属性位:弄错它们,您将打开一个巨大的安全漏洞,或者让您的进程被 DEP 关闭(即,您将崩溃)。见http://msdn.microsoft.com/en-us/library/aa366553(VS.85).aspx

  2. 在程序集的资源库中找到可执行文件并为其获取pinned handle

  3. Memcpy() 从托管堆的固定区域到本机块的代码。

  4. 释放 GCHandle。

  5. 调用VirtualProtect 以防止进一步写入可执行内存块。

  6. 根据从 VirtualAlloc 获得的句柄和 DUMPBIN 或类似工具显示的文件中的偏移量,计算可执行文件的 Main 函数在进程的虚拟地址空间内的地址。

    李>
  7. 将所需的命令行参数放在堆栈上。 (Windows Stdcall convention)。当然,任何指针都必须指向原生或固定区域。

  8. 跳转到计算的地址。可能最容易使用 _call(内联汇编语言)。

  9. 向上帝祈祷,可执行映像中没有任何可以通过以正常方式调用 LoadLibrary 来修复的绝对跳转。 (当然,除非您想在第 3 步中重新实现 LoadLibrary 的大脑)。

  10. 从@eax 寄存器中检索返回值。

  11. 调用 VirtualFree。

第 5 步和第 11 步应该在 finally 块中完成和/或使用 IDisposable 模式。


另一个主要选项是创建一个 RAMdrive,在其中写入可执行文件,运行它并进行清理。这可能会更安全一些,因为您没有尝试编写自修改代码(这在任何情况下都很难,但尤其是当代码甚至不是您的代码时)。但我相当肯定它需要比动态代码注入选项更多的平台 API 调用——所有这些都需要 C++ 或 PInvoke,自然而然。

【讨论】:

  • 回顾所有这些步骤和祈祷,我希望很明显为什么“代码必须在磁盘上”规则如此繁重。由于可执行代码最终加载到内存中,我应该可以直接从内存中运行它,或者将它从磁盘加载到内存中然后运行它。当前的 .NET API 将要求我将代码放入内存,将其写入磁盘,使用临时文件管理系统,然后将其直接读回内存,它本来就在内存中!这只是展示了将旧技术包裹在新衣服中并为其命名时会发生什么。
  • 此外,这种“低级、特定于平台”的功能已经在高级托管 .NET 框架中作为“Process.Start”。问题是它接受的唯一指向可执行代码字节的指针是文件名,这意味着磁盘是代码应该起源的唯一地方。
  • 我只是认为根本不需要文件权限,不幸的是他们需要临时文件。请参阅 MSDN 上有关 Path.GetTempFilename 的评论:在低权限环境中,例如在某些 ASP.NET 配置中,此函数将失败并出现安全异常……因为它需要找到临时目录并且正在读取“环境”……它需要 EnvironmentPermission 才能不受限制地访问环境变量。关联枚举:PermissionState.Unrestricted
  • 没有。调用 Process.Start 与仅将 CS:IP 设置为可执行内存区域中的随机偏移量并跳转到那里非常不同。特别是来自托管代码。即使在本机 Win32 应用程序中,加载任意 .exe/.dll 模块也比您想象的要多得多。您在我上面的回答中看到的并不是繁琐的;这实际上是一个戏剧性的过度简单化。这是一篇 [开始] 为您处理 LoadLibrary 的所有内容的文章:msdn.microsoft.com/en-us/magazine/cc301727.aspx
  • 当我说该功能已经在 .NET 框架中时,我的意思是 Process.Start 可用。当我说它“将旧技术包裹在新衣服中”时,我承认它取决于本地方法(具有复杂功能),重点是新技术受到旧技术的限制。在这种情况下,我要做的就是跳过从磁盘加载数据的部分。我只是说,Process.Start 所依赖的现有原生 API 如此根深蒂固地要求磁盘/文件名作为起点而不是内存地址,这太荒谬了。
【解决方案3】:

查看本文的“内存中”部分。意识到它是从远程DLL注入的角度来看的,但概念应该是相同的。

Remote Library Injection

【讨论】:

  • 很有趣,但我正在寻找一种使用 Process.Start 之类的方法,传递一个字节数组而不是文件名。我不喜欢他们不从内存中启动进程的理由,因为“病毒扫描程序无法扫描磁盘上从未存在的程序”......真是个警察。如果他们要构建操作系统来安全地运行程序,那么“必须源自磁盘”的规则就没有必要了。
  • 什么意思?没有这样的规则。任何时候进程突然开始执行自我修改代码时,病毒扫描程序(不是由 MS 编写的)都会产生怀疑,这只是生活中的事实。无论扫描仪在什么操作系统上运行,这对我来说都是合适的行为。如果您不想依赖病毒扫描程序来提供这种保护,那么这就是 Windows 多年来内置 DPE 的原因。
  • .NET 框架通过提供 Process.Start 重载来强制执行“可执行代码必须源自磁盘”的隐含规则,该重载最终将仅接受文件名作为指向可执行代码的指针。它既不接受托管字节数组,也不接受指向包含可执行代码的非托管字节的指针。
  • 一个好的 API 应该主要接受字节,并且支持文件名来节省时间,而不是相反。代码最终会在内存中结束,但现有的 API 会强制将代码字节从内存复制到磁盘,然后再复制回内存,并在两者之间使用临时文件。这是不必要的工作,框架这样做只是因为它包装了一个底层平台 API(LoadLibrary 等),它坚持可执行代码始终源自磁盘的想法。代码是字节。操作系统需要文件这一事实确实是框架应该抽象出来的。想想吧。
  • 其他所有主要操作系统的安全模型也都基于用户帐户。这样做有很好的理由。 cmets 中没有足够的空间来列举你错的所有原因,所以我会让 Raymond 来说话:blogs.msdn.com/oldnewthing/archive/2006/08/18/705957.aspx
【解决方案4】:

创建 RAM 磁盘或将代码转储到内存中然后执行它们都是可能的,但解决方案极其复杂(在托管代码中可能更复杂)。

它需要是可执行文件吗?如果将其打包为程序集,则可以从内存流中使用 Assembly.Load() - 几行简单的代码。

或者如果它真的必须是可执行文件,那么编写临时文件实际上有什么问题?将它转储到临时文件需要几行代码,执行它,等待它退出,然后删除临时文件 - 在你删除它之前它甚至可能不会从磁盘缓存中取出!有时,简单明了的解决方案是最好的解决方案。

【讨论】:

  • 可执行文件只是一堆字节,我认为操作系统没有理由关心字节来自磁盘还是内存,只要这两种类型的位置都可以锁定(并且它们可以)。操作系统似乎依赖于位于文件系统中的可执行文件,为什么?如果我们可以内存映射文件,为什么不通过向仅由锁定的内存区域支持的虚拟文件注册唯一的 UNC 路径来轻松地做相反的事情呢?即使 Microsoft 自己的所有东西都是作为 ISO 映像分发的,Windows 7 仍然无法在没有第三方软件的情况下将 ISO 映像本地安装到虚拟驱动器。
  • 我认为我不能将用 c++ 编译的第三方非托管命令行实用程序打包为可与 Assembly.Load 一起使用的程序集。写临时文件唯一的问题是我觉得没必要,而且需要我找一个临时目录,保证写权限,这也是没必要的。只是所有这些不必要的步骤可能会导致我试图避免的问题,但操作系统和 .NET 框架使这样做变得困难。
  • 我同意你的看法。我只是认为你很难找到像编写临时文件这样容易实现和测试的东西。
  • 我同意没有比这更简单的了,这正是我想要避免的,因为我不想出于任何原因写入磁盘。
  • 甚至没有“因为你本可以在 2 个月前完成它并转向另一个问题”?或者“因为它是标准方法,并且只适用于任何可执行文件(托管或非托管等),因为它受到 .net 的特别支持”?
猜你喜欢
  • 1970-01-01
  • 2022-11-08
  • 1970-01-01
  • 2018-01-15
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-24
相关资源
最近更新 更多