OpenFileMapping and KnownDlls

渗透技巧 1个月前 admin
199 0 0

A common method of unhooking user-land API hooks is to load a fresh copy of NTDLL from KnownDlls, a special object directory that’s used to essentially cache commonly used system DLLs. We can use WinObj to view the Object Manager namespace, where we can see the KnownDlls directory, and the mapped sections it contains for each system DLL.
解除用户土地 API 挂钩的常用方法是从 加载 KnownDlls NTDLL 的新副本,这是一个特殊的对象目录,用于缓存常用的系统 DLL。我们可以使用 WinObj 查看对象管理器命名空间,在其中我们可以看到 KnownDlls 目录以及它包含的每个系统 DLL 的映射部分。

OpenFileMapping and KnownDlls

Whilst working through the excellent Maldev Academy course material, it was pointed out that you can’t seem to use OpenFileMapping to retrieve a handle to the KnownDlls directory, despite it’s purpose being to open named file mapping objects. Attempting to use the function to open \KnownDlls\ntdll.dll, or any other DLL in that directory, will result in error 161 - ERROR_BAD_PATHNAME. Instead, most malware uses the native NtOpenSection instead.
在学习优秀的马尔德夫学院课程材料时,有人指出,您似乎不能用于 OpenFileMapping 检索 KnownDlls 目录的句柄,尽管其目的是打开命名文件映射对象。尝试使用该函数打开 \KnownDlls\ntdll.dll 该目录中的任何其他 DLL 将导致错误 161 - ERROR_BAD_PATHNAME 。相反,大多数恶意软件使用本机 NtOpenSection 。

I wanted to investigate why the function was failing in this manner, and this post is just a short walkthrough what I found.

OpenFileMapping and NtOpenSection  OpenFileMapping 和 NtOpenSection

As already mentioned, the OpenFileMapping function “Opens a named file mapping object”. It’s definition is as follows:
如前所述, OpenFileMapping 函数“打开命名文件映射对象”。它的定义如下:

HANDLE OpenFileMappingA(
    [in] DWORD  dwDesiredAccess,
    [in] BOOL   bInheritHandle,
    [in] LPCSTR lpName

These parameters are all pretty self-explanatory; dwDesiredAccess specifies the access level for the file mapping object and is checked against the security descriptor on the target object. bInheritHandle specifies whether the handle can be inherited by another process or not. The lpname obviously specifies the name of the file mapping object to be opened, and as noted in the documentation: “The name can have a “Global" or “Local" prefix to explicitly open an object in the global or session namespace.”.
这些参数都是不言自明的; dwDesiredAccess 指定文件映射对象的访问级别,并根据目标对象上的安全描述符进行检查。 bInheritHandle 指定句柄是否可以由另一个进程继承。显然 lpname 指定了要打开的文件映射对象的名称,并且如文档中所述:“名称可以具有”全局“或”本地“前缀,以显式打开全局或会话命名空间中的对象。

OpenFileMapping eventually calls the native function NtOpenSection, which is used to open a handle for an existing section object:
OpenFileMapping 最终调用本机函数,该函数 NtOpenSection 用于打开现有节对象的句柄:

    [out] PHANDLE            SectionHandle,
    [in]  ACCESS_MASK        DesiredAccess,
    [in]  POBJECT_ATTRIBUTES ObjectAttributes

The most relevant parameter here is the pointer to the OBJECT_ATTRIBUTES structure, which is what really holds the meat of what object it is we want to open a handle to:
这里最相关的参数是指向结构的 OBJECT_ATTRIBUTES 指针,它真正包含我们想要打开句柄的对象:

typedef struct _OBJECT_ATTRIBUTES {
    ULONG           Length;
    HANDLE          RootDirectory;
    ULONG           Attributes;
    PVOID           SecurityDescriptor;
    PVOID           SecurityQualityOfService;

So what’s causing the ERROR_BAD_PATHNAME when we call the function with \KnownDlls\ntdll.dll?
那么当我们调用函数时是什么原因造成的 \KnownDlls\ntdll.dll 呢 ERROR_BAD_PATHNAME ?

A safe assumption is that the issue can be found in the OBJECT_ATTRIBUTES struct that OpenFileMapping is constructing and passing to NtOpenSection. We’ll write a simple program that calls the function, and then set a debugger breakpoint on NtOpenSection to see what is passed in the ObjectAttributes parameter.
一个安全的假设是,可以在 OpenFileMapping 正在构造并传递给 的结构 NtOpenSection 中找到 OBJECT_ATTRIBUTES 问题。我们将编写一个简单的程序来调用该函数,然后设置调试器断点 NtOpenSection 以查看 ObjectAttributes 参数中传递的内容。

OpenFileMapping and KnownDlls

We know that the NtOpenSection function takes three parameters, and with WinAPI using fastcall, that means the ObjectAttributes pointer argument will be in the R8 register when we hit our breakpoint. Following the pointer in R8 in a memory dump section will lead us to the OBJECT_ATTRIBUTES object being passed:
我们知道该 NtOpenSection 函数采用三个参数,并且使用 fastcall 的 WinAPI,这意味着当我们命中断点时, ObjectAttributes 指针参数将位于 R8 寄存器中。跟随内存转储部分中的 R8 指针将引导我们到达正在传递 OBJECT_ATTRIBUTES 的对象:

  • Length - red | RootDirectory - green
    Length - 红色 | RootDirectory -绿
  • ObjectName - blue | Attributes - orange
    ObjectName - 蓝色 | Attributes -橙
  • SecurityDescriptor - pink | SecurityQualityOfService - purple
    SecurityDescriptor - 粉红 | SecurityQualityOfService -紫色

OpenFileMapping and KnownDlls

Both of the final parameters are NULL, which is expected - the first one being NULL means the object will receive default security settings, and the second is optional and used to ‘indicate the security impersonal level and context tracking mode,’ which isn’t likely to be causing our issue here. We can check the ObjectName field first and just make sure that the path we are passing to OpenFileMapping is actually what is being passed to NtOpenSection, and isn’t mangled somewhere along the way.
最后两个参数都是 NULL ,这是预期的 - 第一个是表示对象将接收默认安全设置,第二个是 NULL 可选的,用于“指示安全非个人级别和上下文跟踪模式”,这不太可能导致我们的问题在这里。我们可以先检查字段, ObjectName 并确保我们传递到的路径 OpenFileMapping 实际上是要传递到的 NtOpenSection 路径,并且在此过程中没有被破坏。

Following the pointer will lead us to a UNICODE_STRING structure which is defined as such:
跟随指针将引导我们进入一个 UNICODE_STRING 结构,该结构定义如下:

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;

OpenFileMapping and KnownDlls

We can see from debugger comment which has resolved the address of the string that the path is being passed as we expect, and there isn’t anything unusual about the Length or MaximumLength values. Returning to the OBJECT_ATTRIBUTES structure, we are left with two other offending values - the RootDirectory and the Attributes. We can quickly check that the argument passed for the Attributes is 0x80 which is the value for OBJ_OPENIF. This attribute has a kinda confusing explanation in Microsoft’s documentation, but seems to mean that if the object exists a handle to it should be opened, unless the routine is trying to create a new object with that name, in which case it will return an NTSTATUS of STATUS_OBJECT_NAME_COLLISION. If we actually step through the syscall with our debugger to see what is returned from NtOpenSection, we receive a STATUS_OBJECT_PATH_SYNTAX_BAD status, meaning this attribute is unlikely to be what is erroring.
我们可以从解决了字符串地址的调试器注释中看到,路径正在按预期传递, Length 并且 or MaximumLength 值没有任何异常。回到结构, OBJECT_ATTRIBUTES 我们只剩下另外两个有问题的值 - 和 RootDirectory Attributes .我们可以快速检查为 是 传递的参数是 Attributes 0x80 的值 OBJ_OPENIF 。此属性在 Microsoft 的文档中有一个有点令人困惑的解释,但似乎意味着如果对象存在,则应打开它的句柄,除非例程尝试创建具有该名称的新对象,在这种情况下,它将返回 NTSTATUS of STATUS_OBJECT_NAME_COLLISION .如果我们实际使用调试器单步执行以查看 syscall 从 返回 NtOpenSection 的内容,我们会收到一个 STATUS_OBJECT_PATH_SYNTAX_BAD 状态,这意味着此属性不太可能是错误所在。

That leaves us with the RootDirectory. This is an optional field, which if set to NULL means that the ObjectName field has to point to the fully qualified path to an object. If RootDirectory isn’t NULLObjectName will point to an object relative to the RootDirectory. So this quite obviously is what is causing us issues. We are passing in a fully qualified path to an object, \KnownDlls\ntdll.dll, which we are expecting to access at the root of the object manager namespace - but NtOpenSection is trying to open this path from presumably a different root. So what location is actually being passed as the RootDirectory? We can have a closer look at what OpenFileMapping is doing to find out:
这给我们留下了. RootDirectory 这是一个可选字段,如果设置为 , NULL 则表示该 ObjectName 字段必须指向对象的完全限定路径。如果不是 NULL , ObjectName 则将 RootDirectory 指向相对于 . RootDirectory 所以这很显然是给我们带来问题的原因。我们正在将一个完全限定的路径传递给一个对象, \KnownDlls\ntdll.dll 我们希望在对象管理器命名空间的根目录下访问该路径 - 但 NtOpenSection 正在尝试从可能不同的根打开此路径。那么实际上传递的位置是什么 RootDirectory ?我们可以仔细看看 OpenFileMapping 正在做什么来找出:

OpenFileMapping and KnownDlls

The BaseFormatObjectAttributes jumps out immediately. This function is what constructs our initial OBJECT_ATTRIBUTES structure. If we follow through the execution, we find that it later calls BaseGetNamedObjectDirectory, and this is the value that is set in the RootDirectory field. Some quick searching for this function returns some community documentation from The provided overview of the function is that it returns a handle to a named object directory for the current session, in the remarks stating that ’the returned handle may refer to the BaseNamedObject directory if the current user can gain full access to it, or the BaseNamedObjects\Restricted directory if not.’
立即 BaseFormatObjectAttributes 跳了出来。这个函数是构造我们初始 OBJECT_ATTRIBUTES 结构的函数。如果我们执行完,我们会发现它稍后调用 BaseGetNamedObjectDirectory ,这是 RootDirectory 在字段中设置的值。对此函数进行一些快速搜索会从 返回一些社区文档。提供的函数概述是,它返回当前会话的命名对象目录的句柄,在备注中指出“如果当前用户可以完全访问该目录,则返回的句柄可能引用该 BaseNamedObject 目录,如果不能,则引用 BaseNamedObjects\Restricted 该目录。

Returning to WinObj will give us a better visual image of the issue this causes:
返回到 WinObj 将为我们提供更好的视觉图像,了解由此导致的问题:

OpenFileMapping and KnownDlls

The RootDirectory we are passing is being set to \Sessions\1\BaseNamedObjects\, and it doesn’t seem possible to traverse back past the root directory and to \KnownDlls. This can be confirmed by using OpenFileMapping to successfully open a handle to a section included in this directory:
我们正在传递的 RootDirectory 被设置为 \Sessions\1\BaseNamedObjects\ ,并且似乎无法遍历根目录和 \KnownDlls 。这可以通过使用 成功 OpenFileMapping 打开此目录中包含的部分的句柄来确认:

OpenFileMapping and KnownDllsOpenFileMapping and KnownDlls

Conclusion + Workaround 结论 + 解决方法

So that’s it - that’s why you can’t use OpenFileMapping to open the KnownDlls mapped section. Is there a way around it? Yep but it’s a stupid amount of work in order to call OpenFileMapping when you could just call NtOpenSection, and also requires us importing functions from the hokoed version of ntdll.dll - which is exactly what we are trying to bypass. But we’ll do it anyway because who doesn’t love wasting time overengineering solutions to problems that they’ve made up 🙂
就是这样 - 这就是为什么你不能用来 OpenFileMapping 打开映射的部分 KnownDlls 。有没有办法解决它?是的,但是为了在您可以调用时调用 OpenFileMapping NtOpenSection ,这是一个愚蠢的工作量,并且还需要我们从hokoed版本导入函数 ntdll.dll - 这正是我们试图绕过的。但无论如何我们都会这样做,因为谁不喜欢浪费时间过度设计解决方案来解决他们编造的问题:)

The over-the-top workaround is symlinks, as inspired by James Forshaw in
过度的解决方法是符号链接,正如 年 James Forshaw 的启发。

We can create a symlink to to \GLOBAL?? and then use it in the path to the OpenFileMapping call:
我们可以创建一个指向 to 的 \GLOBAL?? 符号链接,然后在 OpenFileMapping 调用的路径中使用它:

#include <windows.h>
#include <stdio.h>
#include <winternl.h>

#define NT_SUCCESS(status) (((NTSTATUS)(status)) >= 0)

typedef VOID(NTAPI *_RtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
typedef NTSTATUS (WINAPI * _BaseGetNamedObjectDirectory)(HANDLE* phDir);
typedef NTSTATUS(NTAPI* _NtCreateSymbolicLinkObject)(PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PUNICODE_STRING TargetName);

HANDLE CreateSymlink(LPCWSTR linkname, LPCWSTR targetname) {
    HANDLE rootDir = NULL;
    HANDLE hNtdll = GetModuleHandleW(L"NTDLL");
    NTSTATUS status = NULL;
    _RtlInitUnicodeString fRtlInitUnicodeString = (_RtlInitUnicodeString) GetProcAddress(hNtdll, "RtlInitUnicodeString");
    _NtCreateSymbolicLinkObject fNtCreateSymbolicLinkObject = (_NtCreateSymbolicLinkObject) GetProcAddress(hNtdll, "NtCreateSymbolicLinkObject");
    _BaseGetNamedObjectDirectory fBaseGetNamedObjectDirectory = (_BaseGetNamedObjectDirectory) GetProcAddress(GetModuleHandleW(L"kernel32"), "BaseGetNamedObjectDirectory");
    if (!fRtlInitUnicodeString || !fNtCreateSymbolicLinkObject || !fBaseGetNamedObjectDirectory) {
        printf("[!] Error resolving functions:\n");
        printf("\tfRtlInitUnicodeString: %x\n", fRtlInitUnicodeString);
        printf("\tfNtCreateSymbolicLinkObject: %x\n", fNtCreateSymbolicLinkObject);
        printf("\tfBaseGetNamedObjectDirectory: %x\n",fBaseGetNamedObjectDirectory);
        return NULL;


	fRtlInitUnicodeString(&name, linkname);
	fRtlInitUnicodeString(&target, targetname);

    status = fBaseGetNamedObjectDirectory(&rootDir);
    if (!NT_SUCCESS(status)) {
        printf("[!] Error calling BaseGetNamedObjectDirectory: %0.8X\n", status);
        return NULL;

	InitializeObjectAttributes(&objAttr, &name, OBJ_CASE_INSENSITIVE, rootDir, NULL);	

	status = fNtCreateSymbolicLinkObject(&hLink, SYMBOLIC_LINK_ALL_ACCESS, &objAttr, &target);
	if (NT_SUCCESS(status)) {
		printf("[i] Created link %ls -> %ls: %p\n", linkname, targetname, hLink);
	} else {
		printf("[!] Error creating link: %ls -> %ls\n", linkname, targetname);


    return hLink;

INT main(VOID) {
    HANDLE hNtdll = NULL;
    HANDLE symlinkRedirector = NULL;

    puts("Starting execution. Press enter to continue...");

    if (!(symlinkRedirector = CreateSymlink(L"inbits", L"\\GLOBAL??"))) {
        printf("[!] CreateSymlink failed\n");
        return 1;

    hNtdll = OpenFileMappingW(FILE_MAP_READ, FALSE, L"inbits\\GLOBALROOT\\KnownDlls\\ntdll.dll");
    if (!hNtdll || hNtdll == INVALID_HANDLE_VALUE) {
        printf("[!] OpenFileMappingW failed with error: %d\n", GetLastError());
        return 1;

    printf("[i] Opened a handle to ntdll.dll: %x\n", hNtdll);

        Actually overwrite the hooked ntdll.dll with the clean one 


    return 0;

原文始发于inbits-sec:OpenFileMapping and KnownDlls

版权声明:admin 发表于 2023年8月23日 上午9:20。
转载请注明:OpenFileMapping and KnownDlls | CTF导航