在运行时从磁盘检索ntdll Syscall存根

admin 2021年5月10日01:26:20评论89 views字数 5611阅读18分42秒阅读模式

译文声明
本文是翻译文章,文章原作者Red Teaming Experiments,文章来源:https://ired.team/
原文地址:https://ired.team/offensive-security/defense-evasion/retrieving-ntdll-syscall-stubs-at-run-time#references

总览

本次实验对象是syscall,目标是在运行时(在AV/EDR进行hook之前)从磁盘中检索ntdll syscall的stub。整个过程不能简单的通过硬编码来实现,因为在不同的Windows中stub名称可能会不同。

实验中代码执行流程如下:
1.从磁盘读取ntdll.dll文件(在AV/EDR进行hook之前),并将其写入到某个内存位置m1。
2.解析ntdll.dll中.text段和.rdata段
     1..rdata 包含ntdll导出的函数名称
     2..text 用于存放程序代码的区域
3.在m1中寻找指定的函数代码(syscall),在本实验中,我们需要找到NtCreateFile的存根代码的位置。
4. 提取NtCreateFile的存根(23字节)并将其写入某个内存位置m2
5.跟据NtCreateFile的定义来声明一个相同类型的函数指针
6.将函数指针v1指向内存位置m2,m2中是NtCreateFile的syscall存根。
7. 通过调用syscall v1的方式,来调用NtCreateFile syscall,它实际上指向存储NtCreateFile syscall存根的m2
8.执行NtCreate

## 提示
我们可以通过WinDBG或其它任意一款调试器来查看NT函数的系统调用ID号。

Syscall ID长度为2个字节,在函数中占四个字节。例如NtCreateFile的syscall ID为0x0055,NtQueryEvent为0x0056。

在下图中,绿色部分的字节是NtCreateFile的syscall存根。这部分正是我们在运行时需要检索的NT函数的字节。
::: hljs-center

在运行时从磁盘检索ntdll Syscall存根
橙色为syscall函数名称及其ID,绿色为syscall存根

:::

提取存根

我编写了一个GetSyscallStub函数,它负责在概述部分的步骤3和步骤4。
它可以实现从ntdll.dll中找到任意给定代码的位置,并划分出syscall存根(前23个字节):
::: hljs-center

在运行时从磁盘检索ntdll Syscall存根

:::

举个例子,如果我想检索NtCreateFile的syscall存根,我可以这样去构造
GetSyscallStub函数:
GetSyscallStub(
// function name for which the syscall stub is to be retrieved
"NtCreateFile",
// ntdll export directory
exportDirectory,
// ntdll file bytes
fileData,
// ntdll .text section descriptor - contains code of ntdll exported functions. Required for locating NtCreateFile syscall stub
textSection,
// ntdll .rdata section descriptor - contains name of ntdll exported functions.
rdataSection,
// NtCreateFile stub will be written here
syscallStub
);

调用GetSyscallStub后,它将循环遍历所有ntdll导出的函数名(最终被解析为functionNameResolved),以及模拟导出的函数地址,并查找我们要提取的syscall存根的函数。在我们的例子中,目标函数是NtCreateFile(通过functionName传递给GetSycallStub):
::: hljs-center

在运行时从磁盘检索ntdll Syscall存根

:::

在解析了所有函数名称后,将我们给定的syscall存根存储在syscall stub变量中。

在下面的GIF中,在反汇编窗口查看syscallStub变量,我们可以看到mov eax,0x55指令。既然我们知道NtCreateFile syscall ID为0x0055,这意味着我们已经成功提取了syscall存根:
::: hljs-center

在运行时从磁盘检索ntdll Syscall存根

:::

调用syscall存根

我们能够调用syscall,我们根据NtCreateFile的定义来声明一个相同类型的变量myNtCreateFile,将其指向syscallStub并执行syscallStub。
::: hljs-center

在运行时从磁盘检索ntdll Syscall存根

:::

现在我们可以调用:NtCreateFile
NtCreateFile(
&fileHandle,
FILE_GENERIC_WRITE,
&oa,
&osb,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE,
FILE_OVERWRITE_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);

下图展示了c:temppw.log文件调用了NtCreateFile并打开了文件句柄。这就说明了程序成功检索并调用了NtCreateFile syscall存根:
::: hljs-center

在运行时从磁盘检索ntdll Syscall存根

:::

代码实例

```

include

include "Windows.h"

include "winternl.h"

pragma comment(lib, "ntdll")

int const SYSCALL_STUB_SIZE = 23;
using myNtCreateFile = NTSTATUS(NTAPI*)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

PVOID RVAtoRawOffset(DWORD_PTR RVA, PIMAGE_SECTION_HEADER section)
{
return (PVOID)(RVA - section->VirtualAddress + section->PointerToRawData);
}

BOOL GetSyscallStub(LPCSTR functionName, PIMAGE_EXPORT_DIRECTORY exportDirectory, LPVOID fileData, PIMAGE_SECTION_HEADER textSection, PIMAGE_SECTION_HEADER rdataSection, LPVOID syscallStub)
{
PDWORD addressOfNames = (PDWORD)RVAtoRawOffset((DWORD_PTR)fileData + (&exportDirectory->AddressOfNames), rdataSection);
PDWORD addressOfFunctions = (PDWORD)RVAtoRawOffset((DWORD_PTR)fileData +
(&exportDirectory->AddressOfFunctions), rdataSection);
BOOL stubFound = FALSE;

for (size_t i = 0; i < exportDirectory->NumberOfNames; i++)
{
    DWORD_PTR functionNameVA = (DWORD_PTR)RVAtoRawOffset((DWORD_PTR)fileData + addressOfNames[i], rdataSection);
    DWORD_PTR functionVA = (DWORD_PTR)RVAtoRawOffset((DWORD_PTR)fileData + addressOfFunctions[i + 1], textSection);
    LPCSTR functionNameResolved = (LPCSTR)functionNameVA;
    if (std::strcmp(functionNameResolved, functionName) == 0)
    {
        std::memcpy(syscallStub, (LPVOID)functionVA, SYSCALL_STUB_SIZE);
        stubFound = TRUE;
    }
}

return stubFound;

}

int main(int argc, char* argv[]) {
char syscallStub[SYSCALL_STUB_SIZE] = {};
SIZE_T bytesWritten = 0;
DWORD oldProtection = 0;
HANDLE file = NULL;
DWORD fileSize = NULL;
DWORD bytesRead = NULL;
LPVOID fileData = NULL;

// variables for NtCreateFile
OBJECT_ATTRIBUTES oa;
HANDLE fileHandle = NULL;
NTSTATUS status = NULL;
UNICODE_STRING fileName;
RtlInitUnicodeString(&fileName, (PCWSTR)L"\??\c:\temp\pw.log");
IO_STATUS_BLOCK osb;
ZeroMemory(&osb, sizeof(IO_STATUS_BLOCK));
InitializeObjectAttributes(&oa, &fileName, OBJ_CASE_INSENSITIVE, NULL, NULL);

// define NtCreateFile
myNtCreateFile NtCreateFile = (myNtCreateFile)(LPVOID)syscallStub;
VirtualProtect(syscallStub, SYSCALL_STUB_SIZE, PAGE_EXECUTE_READWRITE, &oldProtection);

file = CreateFileA("c:\windows\system32\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
fileSize = GetFileSize(file, NULL);
fileData = HeapAlloc(GetProcessHeap(), 0, fileSize);
ReadFile(file, fileData, fileSize, &bytesRead, NULL);

PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS imageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)fileData + dosHeader->e_lfanew);
DWORD exportDirRVA = imageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(imageNTHeaders);
PIMAGE_SECTION_HEADER textSection = section;
PIMAGE_SECTION_HEADER rdataSection = section;

for (int i = 0; i < imageNTHeaders->FileHeader.NumberOfSections; i++) 
{
    if (std::strcmp((CHAR*)section->Name, (CHAR*)".rdata") == 0) { 
        rdataSection = section;
        break;
    }
    section++;
}

PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)RVAtoRawOffset((DWORD_PTR)fileData + exportDirRVA, rdataSection);

GetSyscallStub("NtCreateFile", exportDirectory, fileData, textSection, rdataSection, syscallStub);
NtCreateFile(&fileHandle, FILE_GENERIC_WRITE, &oa, &osb, 0, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

return 0;

}
```

相关推荐: CTF布尔盲注(新题型)

CTFHub 布尔盲注 题目已经表明是布尔盲注,发现没有闭合,也木有过滤,本来也是道基础题,但是发现回显不对头: payload:?id=1 and 's'='p' 回显是query_success 这道题和普通的布尔盲注不一样,一般布尔盲注(我做的也不多)是…

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月10日01:26:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   在运行时从磁盘检索ntdll Syscall存根http://cn-sec.com/archives/246617.html