译文声明
本文是翻译文章,文章原作者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
橙色为syscall函数名称及其ID,绿色为syscall存根
:::
提取存根
我编写了一个GetSyscallStub函数,它负责在概述部分的步骤3和步骤4。
它可以实现从ntdll.dll中找到任意给定代码的位置,并划分出syscall存根(前23个字节):
::: hljs-center
:::
举个例子,如果我想检索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
:::
在解析了所有函数名称后,将我们给定的syscall存根存储在syscall stub变量中。
在下面的GIF中,在反汇编窗口查看syscallStub变量,我们可以看到mov eax,0x55指令。既然我们知道NtCreateFile syscall ID为0x0055,这意味着我们已经成功提取了syscall存根:
::: hljs-center
:::
调用syscall存根
我们能够调用syscall,我们根据NtCreateFile的定义来声明一个相同类型的变量myNtCreateFile,将其指向syscallStub并执行syscallStub。
::: hljs-center
:::
现在我们可以调用: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
:::
代码实例
```
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;
}
```
CTFHub 布尔盲注 题目已经表明是布尔盲注,发现没有闭合,也木有过滤,本来也是道基础题,但是发现回显不对头: payload:?id=1 and 's'='p' 回显是query_success 这道题和普通的布尔盲注不一样,一般布尔盲注(我做的也不多)是…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论