根据ntdll.dll 反编译可以得知,其ntdll.dll的各种NT/ZW类函数实现汇编指令如下
https://github.com/jthuraisamy/SysWhispers,该工具在github上有接近600的Star,最后生成的汇编示例如下:
通过收集各个系统的调用号,结合系统版本,进行条件判断,最后执行syscall
该方式优点:
能够绕过核心函数监控,应该是最早的一代Direct System Call
该方式缺点
生成代码较为臃肿;
由于系统号全部写死,不能适用于后面发布的最新Windows系统
该方式优点:
通过读取ntdll.dll的实际代码方式,将ntdll的真实代码进行调用,避免ntdll.dll已被HOOK;
其它的函数也可以参考,很好的一种绕过HOOK的方法。
该方式缺点:
方法一:通过PEB的方式读取已经加载函数地址,其代码如下:
// Redefine PEB structures. The structure definitions in winternl.h are incomplete.
typedef struct _MY_PEB_LDR_DATA {
ULONG Length;
BOOL Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} MY_PEB_LDR_DATA, * PMY_PEB_LDR_DATA;
typedef struct _MY_LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
} MY_LDR_DATA_TABLE_ENTRY, * PMY_LDR_DATA_TABLE_ENTRY;
// TODO: 可以使用FuncHash方式,避免字符串
BYTE* GetFunctionStubFromMemory(const CHAR* pszFuncName)
{
PPEB PebAddress;
PMY_PEB_LDR_DATA pLdr;
PMY_LDR_DATA_TABLE_ENTRY pDataTableEntry;
PVOID pModuleBase;
PIMAGE_NT_HEADERS pNTHeader;
DWORD dwExportDirRVA;
PIMAGE_EXPORT_DIRECTORY pExportDir;
PLIST_ENTRY pNextModule;
DWORD dwNumFunctions;
USHORT usOrdinalTableIndex;
PDWORD pdwFunctionNameBase;
PCSTR pFunctionName;
UNICODE_STRING BaseDllName;
DWORD i;
#if defined(_WIN64)
PebAddress = (PPEB)__readgsqword(0x60);
#else
PebAddress = (PPEB)__readfsdword(0x30);
#endif
pLdr = (PMY_PEB_LDR_DATA)PebAddress->Ldr;
pNextModule = pLdr->InLoadOrderModuleList.Flink;
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pNextModule;
// unicode str
WCHAR wszNTDLL[] = { L'n', L't', L'd', L'l', L'l', L'.', L'd', L'l', L'l', L'\0' };
while (pDataTableEntry->DllBase != NULL)
{
pModuleBase = pDataTableEntry->DllBase;
BaseDllName = pDataTableEntry->BaseDllName;
pNTHeader = (PIMAGE_NT_HEADERS)((ULONG_PTR)pModuleBase + ((PIMAGE_DOS_HEADER)pModuleBase)->e_lfanew);
dwExportDirRVA = pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
// Get the next loaded module entry
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pDataTableEntry->InLoadOrderLinks.Flink;
// If the current module does not export any functions, move on to the next module.
if (dwExportDirRVA == 0)
{
continue;
}
if (wcsicmp(wszNTDLL, (WCHAR*)BaseDllName.Buffer) != 0) {
continue;
}
pExportDir = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)pModuleBase + dwExportDirRVA);
dwNumFunctions = pExportDir->NumberOfNames;
pdwFunctionNameBase = (PDWORD)((PCHAR)pModuleBase + pExportDir->AddressOfNames);
for (i = 0; i < dwNumFunctions; i++) {
pFunctionName = (PCSTR)(*pdwFunctionNameBase + (ULONG_PTR)pModuleBase);
if (stricmp(pFunctionName, pszFuncName) == 0)
{
usOrdinalTableIndex = *(PUSHORT)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfNameOrdinals) + (2 * i));
return (BYTE*)((ULONG_PTR)pModuleBase + *(PDWORD)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfFunctions) + (4 * usOrdinalTableIndex)));
}
pdwFunctionNameBase++;
}
}
return NULL;
}
方法二:通过读取文件,进行PE文件格式转换,读取到响应的函数地址。
#define NOT_FOUND_SYSCALL_ID -1 #define IS_NOT_FUND(x) (x == NOT_FOUND_SYSCALL_ID) /* 0x4c,0x8b,0xd1, //mov r10,rcx 0xb8,0xb9,0x00,0x00,0x00, //mov eax,0B9h 0x0f,0x05, //syscall0xc3 //ret */ unsigned char SYS_CALL_START_MAGIC[] = { 0x4c, 0x8b, 0xd1, 0xb8 }; #define SYS_CALL_START_MAGIC_LENGTH 4 #define MAX_SEARCH_LENGTH 24 DWORD MatchSyscallId(BYTE* pData) { // 通过内存搜索的方式绕过HOOK // HOOK一般会在函数开始处插入调整指令,通过内存搜索的方式查找到真实的函数位置,并提取SyscallId DWORD syscallId = NOT_FOUND_SYSCALL_ID; for (int item = 0; item < MAX_SEARCH_LENGTH; item++) { if (memcmp((pData + item), &SYS_CALL_START_MAGIC, SYS_CALL_START_MAGIC_LENGTH) == 0) { memcpy(&syscallId, (pData + item + 4), sizeof(DWORD)); break; } } return syscallId; }
; syscall
.DATA
syscallId DWORD 000h
.CODE
SetSyscallId PROC
mov syscallId, 000h
mov syscallId, ecx
ret
SetSyscallId ENDP
DynamicSyscall PROC
mov r10, rcx
mov eax, syscallId
syscall
ret
DynamicSyscall ENDP
END
定义两个函数和一个变量,SetSyscallId函数对系统调用号进行赋值,DynamicSyscall函数提供统一的Syscall调用。
extern "C" { VOID SetSyscallId(DWORD syscallId); NTSTATUS WINAPI DynamicSyscall(); }
#include <Windows.h>
#include "SyscallIdFinder.h"
/*
0x4c,0x8b,0xd1, //mov r10,rcx
0xb8,0xb9,0x00,0x00,0x00, //mov eax,0B9h
0x0f,0x05, //syscall
0xc3 //ret
*/
#define SYS_CALL_START_MAGIC_LENGTH 4
unsigned char SYS_CALL_START_MAGIC[] = {
0x4c, 0x8b, 0xd1, 0xb8
};
#define MAX_SEARCH_LENGTH 24
PVOID RVAtoRawOffset(DWORD_PTR RVA, PIMAGE_SECTION_HEADER section) {
return (PVOID)(RVA - section->VirtualAddress + section->PointerToRawData);
}
SyscallIdFinder::SyscallIdFinder()
{
_m_bImageInit = InitializeImage();
}
SyscallIdFinder::~SyscallIdFinder()
{
if (_m_pFileData)
{
::HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, _m_pFileData);
_m_pFileData = NULL;
}
if (_m_hFile != INVALID_HANDLE_VALUE && _m_hFile != NULL)
{
::CloseHandle(_m_hFile);
_m_hFile = NULL;
}
}
DWORD SyscallIdFinder::GetSyscallIdFromMemeory(const CHAR* pszFuncName)
{
HMODULE hModule = ::GetModuleHandleA("ntdll.dll");
unsigned char* pFuncAddr = (unsigned char*)::GetProcAddress(hModule, pszFuncName);
if (pFuncAddr == NULL) {
return NOT_FOUND_SYSCALL_ID;
}
return MatchSyscallId(pFuncAddr);
}
DWORD SyscallIdFinder::GetSystemIdFromImage(const CHAR* pszFuncName)
{
if (!_m_bImageInit) {
return NOT_FOUND_SYSCALL_ID;
}
PDWORD addressOfNames = (PDWORD)RVAtoRawOffset((DWORD_PTR)_m_pFileData + *(&_m_pExportDirectory->AddressOfNames), _m_pRDATASection);
PDWORD addressOfFunctions = (PDWORD)RVAtoRawOffset((DWORD_PTR)_m_pFileData + *(&_m_pExportDirectory->AddressOfFunctions), _m_pRDATASection);
BOOL stubFound = FALSE;
for (size_t i = 0; i < _m_pExportDirectory->NumberOfNames; i++)
{
DWORD_PTR functionNameVA = (DWORD_PTR)RVAtoRawOffset((DWORD_PTR)_m_pFileData + addressOfNames[i], _m_pRDATASection);
LPCSTR functionNameResolved = (LPCSTR)functionNameVA;
if (strcmp(functionNameResolved, pszFuncName) == 0)
{
DWORD_PTR functionVA = (DWORD_PTR)RVAtoRawOffset((DWORD_PTR)_m_pFileData + addressOfFunctions[i + 1], _m_pTEXTSection);
DWORD syscallId = MatchSyscallId((unsigned char*)functionVA);
if (syscallId > 0) {
return syscallId;
}
}
}
return NOT_FOUND_SYSCALL_ID;
}
BOOL SyscallIdFinder::InitializeImage()
{
_m_pFileData = NULL;
_m_hFile = CreateFileA("c:\\windows\\system32\\ntdll.dll",
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (_m_hFile == NULL || _m_hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
DWORD fileSize = ::GetFileSize(_m_hFile, NULL);
_m_pFileData = ::HeapAlloc(GetProcessHeap(), 0, fileSize);
::ReadFile(_m_hFile, _m_pFileData, fileSize, &fileSize, NULL);
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)_m_pFileData;
PIMAGE_NT_HEADERS imageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)_m_pFileData + dosHeader->e_lfanew);
DWORD exportDirRVA = imageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(imageNTHeaders);
_m_pTEXTSection = section;
_m_pRDATASection = section;
for (int i = 0; i < imageNTHeaders->FileHeader.NumberOfSections; i++)
{
if (strcmp((CHAR*)section->Name, (CHAR*)".rdata") == 0) {
_m_pRDATASection = section;
break;
}
section++;
}
_m_pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)RVAtoRawOffset((DWORD_PTR)_m_pFileData + exportDirRVA, _m_pRDATASection);
return TRUE;
}
DWORD SyscallIdFinder::MatchSyscallId(unsigned char* pData)
{
// bypass inline hook and iat hook
// Inline hook usually occupies 5 bytes or 7 bytes at the beginning of the function
// eg. jump xxxx
DWORD syscallId = NOT_FOUND_SYSCALL_ID;
for (int item = 0; item < MAX_SEARCH_LENGTH; item++) {
if (memcmp((pData + item), &SYS_CALL_START_MAGIC, SYS_CALL_START_MAGIC_LENGTH) == 0) {
memcpy(&syscallId, (pData + item + 4), sizeof(DWORD));
break;
}
}
return syscallId;
}
从ntdll.dll中根据函数名称动态获取syscalId
OBJECT_ATTRIBUTES oa;
HANDLE fileHandle = NULL;
UNICODE_STRING fileName;
RtlInitUnicodeString(&fileName, (PCWSTR)L"\\??\\C:\\test.log");
IO_STATUS_BLOCK osb;
ZeroMemory(&osb, sizeof(IO_STATUS_BLOCK));
InitializeObjectAttributes(&oa, &fileName, 0x00000040, NULL, NULL);
// 通过PEB的方式获取NTDLL.DLL的函数代码段
BYTE* pFuncStub = GetFunctionStubFromMemory((CHAR*)"NtCreateFile");
// 从函数代码段中匹配到系统调用号
DWORD syscallId = MatchSyscallId(pFuncStub);
// 设置系统调用号,此时的汇编代码就是NT函数的实现
SetSyscallId(syscallId);
// 将DynamicSyscall函数指针赋值给定义NtCreateFile函数指针变量
fnNtCreateFile fNtCreateFile = (fnNtCreateFile)DynamicSyscall;
// 进行函数参数传递并调用
NTSTATUS status = fNtCreateFile(&fileHandle,
FILE_GENERIC_WRITE,
&oa,
&osb,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE,
0x00000005,
0x00000020,
NULL, 0);
有如下优点:
兼容性强,不需要将系统调用号写死,兼容性可以有保障;
通过asm实现代码段,通过改变值进行函数调用;
从已经加载的DLL中获取系统号,并且考虑了已经被简单HOOK的场景。
待优化点:
由于汇编指令中全局变量,目前线程不安全,后期可以通过引入外部函数的方式进行加锁或者用户态自行实现;
函数名称的方式可使用函数Hash的方式,如果自己搞工具,可以自定义一套FastHash算法。
本文作者:穿云箭安全实验室
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/151343.html