【问题标题】:DLL Injection with CreateRemoteThread使用 CreateRemoteThread 注入 DLL
【发布时间】:2014-05-10 02:46:19
【问题描述】:

如果您看一下以下简单 DLL 注入的工作代码:

  //Open the target process with read , write and execute priviledges
   Process = OpenProcess(PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, FALSE, ID); 

   //Get the address of LoadLibraryA
   LoadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); 

   // Allocate space in the process for our DLL 
   Memory = (LPVOID)VirtualAllocEx(Process, NULL, strlen(dll)+1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 

   // Write the string name of our DLL in the memory allocated 
   WriteProcessMemory(Process, (LPVOID)Memory, dll, strlen(dll)+1, NULL); 

   // Load our DLL 
   CreateRemoteThread(Process, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibrary, (LPVOID)Memory, NULL, NULL); 

   //Let the program regain control of itself
   CloseHandle(Process); 

让我困惑的是GetProcAddress返回当前进程LoadLibraryA函数地址,你怎么能把它作为参数传递给CreateRemoteThread并期望目标进程运行它?

【问题讨论】:

  • 因为 CreateRemoteThread 将LoadLibrary 作为参数并调用它。由于它也将Memory 作为参数,所以它传递给LoadLibrary 的参数。
  • 这仍然无法解释为什么你需要当前进程LoadLibrary函数地址。 Memory 是 dll 名称的地址,如果你只是想调用它,为什么不直接将 LoadLibrary 作为字符串传递?
  • 因为偏移量在其他进程中将完全相同。如果另一个进程是 x32 而你的进程是 x32,那么与 kernel32 的偏移量是相同的。如果您的进程是 x64 而另一个进程是 x64,则偏移量再次相同。如果另一个进程是 x32 而你的进程是 x64 或反之亦然,则偏移量将不同,注入将失败。我相信 User32.dll 也总是以相同的偏移量加载。类似于 Kernel32.dll

标签: c++ visual-studio visual-c++ dll dll-injection


【解决方案1】:

如果你启动 Visual Studio,创建一个空项目,添加新的 main.cpp 文件,记下:

#include <windows.h>
void main()
{
}

然后编译这个程序,不要指望创建的可执行文件什么都不做,它没有。

也许Visual C++编译器没有在目标文件中写下任何命令,因为源代码中没有命令,但是链接器确实在可执行LoadLibrary调用的主过程的开头写下了转至user32.dllkernel32.dllgdi32.dll

因此,使用 Visual Studio C++ 编写的每个应用程序最初都会以相同的顺序多次调用LoadLibrary,无论此可执行文件的源代码是什么。

源代码仅确定应在LoadLibrary 调用之后执行的命令。

因此,每个 Visual Studio C++ 应用程序在执行开始时都会加载 LoadLibrarykernel32.dll,因此 LoadLibrary 在相对于进程地址的所有进程中具有相同的入口点或地址。

理论上,如果您将一些未加载 kernel32.dll 的 LoadLibrary 过程的 kernel32.lib 链接到程序的内存中,您可以使注入器(试图注入您的程序的恶意程序)失败。

如果您的程序在执行期间无法动态加载 dll,则注入器无法使您的程序在执行期间动态加载某些 dll,因为应该执行此操作的过程 LoadLibrary 不存在于进程内存中。

因此注入器创建的远程线程无法执行受害者内存中不存在的LoadLibrary

但这有可能是攻击者会通过 VirtualAllocEx 在受害者的内存中创建一些块,WriteProcessMemory 一些可执行代码然后 CreateRemoteThread 来执行它。

【讨论】:

    【解决方案2】:

    地址空间布局随机化 (ASLR) 是一种由 Windows 处理的反漏洞利用缓解功能,它允许地址重定位以帮助防止攻击者确定地址以利用内存中的某些内容(停止地址/偏移的硬编码) .但是,Windows 模块仅在每个会话中更改其地址。

    如果您有一个使用 kernel32.dll 的进程(并非所有进程都使用 kernel32.dll,我将在几分钟内进一步解释这一点),例程的地址可能是 55AA1122 作为示例(这是一个无效示例地址)。现在,带有 kernel32.dll 的下一个进程,对于与前一个相同的例程,将具有相同的地址 55AA1122.... 仅当两个进程都具有相同的架构时。

    32 位进程将具有相同的 kernel32.dll 导出地址,以及其他 Windows 模块导出(例如 NTDLL、USER32 等)。 64 位进程与 32 位进程的地址不同,但是 64 位进程对于 Windows 模块的地址也都相同!

    远程线程创建不是“意外”,微软有意实现了它。为什么?微软自己在 Windows 中大量使用它,也用于异步过程调用。微软也经常为自己的例程做热补丁,作为一种防逆向技巧,或者如果他们将源代码丢失到自己的项目中,哈哈。

    现在关于 kernel32.dll 被加载到进程中,它只被加载到使用 Win32 API 的进程中。这包括世界上 99% 的程序,但是可以编译一个不会使用它的本机进程。然而,这将迫使您完全使用 Native API 而不是 Win32 API,而名为 smss.exe 的 Windows 进程正是这样做的。您还可以编译甚至没有正常 Win32 API DLL 入口例程的 Native DLL。

    简而言之,Windows 模块例程的地址每次启动都会更改一次。它将保持不变,直到下一次重新启动,依此类推。 32 位进程与 64 位进程一样,每个进程都有自己的 Windows 模块共享地址。因此,您不能在针对 32 位进程的 DLL 注入时使用 64 位进程的 LoadLibraryA 地址,除非您使用 32 位 Kernel32.dll LoadLibraryA 地址。一个更好的主意是无论如何都使用 LdrLoadDll,或者只使用反射 DLL 加载程序存根的 shell 代码注入。

    【讨论】:

      【解决方案3】:

      它的工作原理是偶然的。这是一个非常常见的意外,Microsoft 做了很多努力来确保操作系统 DLL,如 kernel32.dll,具有不与任何其他 DLL 冲突的基地址。由于 kernel32.dll 在进程初始化时很早就被加载,因此它得到进一步增强的可能性非常低,以至于它必须努力获得它的首选基地址。

      你会轻松逃脱。值得注意的是,这个已经过去出了问题,有一个 XP 安全更新 oops 导致 gdi32.dll 被重新定位并导致许多机器在启动时翻倒。正确的方法是相当痛苦的,CreateToolhelp32Snapshot() + Module32First/Next() 找到重定位偏移量并不是很开心。坦率地说,如果操作系统像那样“怪异”,您可能根本不应该这样做。

      【讨论】:

      • 等待...所以它可以工作,因为 LoadLibrary 满足 LPTHREAD_START_ROUTINE 签名并且恰好在所有进程中位于同一地址?所以 DllMain 在其他进程空间内被调用......我必须尝试一下。
      【解决方案4】:

      LoadLibraryA 位于kernel32.dll 中,这个模块总是被加载到每个进程中,并且恰好也被加载到每个进程的相同地址。

      【讨论】:

      • LoadLibraryA ... happens to also be loaded at the same address in every process这句话有没有文献或参考资料?
      • 可能,虽然我手边没有参考资料。由于 ntdll.dll 和 kernel32.dll 都是 Windows 自动加载的除主 exe 之外的前两个模块,因此这两个模块的首选加载地址没有机会被其他东西占用。
      • @500-InternalServerError: 这是错误的原因。 ASLR 将导致它们不会被加载到它们的首选地址。但是 ASLR 每个会话只更改一次加载地址。
      • @BenVoigt:我的立场是正确的。感谢您提醒我有关 ASLR 的信息。正如您所指出的,效果保持不变。
      • 仅供参考:上面提到的 Calvin Hsia 的博客现在位于 here(在 MSDN 博客存档之后)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-28
      • 1970-01-01
      • 2012-03-16
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 2017-04-28
      相关资源
      最近更新 更多