根据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);#elsePebAddress = (PPEB)__readfsdword(0x30);#endifpLdr = (PMY_PEB_LDR_DATA)PebAddress->Ldr;pNextModule = pLdr->InLoadOrderModuleList.Flink;pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pNextModule;// unicode strWCHAR 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 entrypDataTableEntry = (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.DATAsyscallId DWORD 000h.CODESetSyscallId PROCmov syscallId, 000hmov syscallId, ecxretSetSyscallId ENDPDynamicSyscall PROCmov r10, rcxmov eax, syscallIdsyscallretDynamicSyscall ENDPEND
定义两个函数和一个变量,SetSyscallId函数对系统调用号进行赋值,DynamicSyscall函数提供统一的Syscall调用。
extern "C"
{
  VOID SetSyscallId(DWORD syscallId);  
  NTSTATUS WINAPI DynamicSyscall();
}
#include <Windows.h>#include "SyscallIdFinder.h"/*0x4c,0x8b,0xd1, //mov r10,rcx0xb8,0xb9,0x00,0x00,0x00, //mov eax,0B9h0x0f,0x05, //syscall0xc3 //ret*/#define SYS_CALL_START_MAGIC_LENGTH 4unsigned char SYS_CALL_START_MAGIC[] = {0x4c, 0x8b, 0xd1, 0xb8};#define MAX_SEARCH_LENGTH 24PVOID 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 xxxxDWORD 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
必填 您当前尚未登录。 登录? 注册
必填(保密)是山东大哥么?