红队技术:绕过用户模式Hook,直接进行系统调用(一)

匿名者  1442天前

引言

绕过用户模式hook的动机最初始于提高进程注入的成功率。有些时候,进程注入被用于合法用途,例如,UI Automation和Active Accessibility就使用该技术来读写GUI进程的内存,而Spy++则使用它来记录进程之间发送和接收的窗口消息。但在大多数情况下,进程注入却被用于以下用途:

  • 将代码隐藏在合法进程中以逃避检测,延长检测和删除时间
  • 在另一个用户的上下文中执行代码或提升权限
  • 修改内存,实现网络游戏作弊

然而,进程注入还有一个较少被提及的用途,那就是阻止上述所有操作。通常,从用户模式(UM)应用程序中实现进程注入需要完成以下步骤:

  • 打开目标进程
  • 分配新内存或使用现有内存来存储代码
  • 将包含可选数据的代码写入目标进程
  • 通过新线程或现有线程执行代码

尽管该技术实施起来相对简单,但红队队员、游戏作弊者和恶意软件开发人员如今却仍面临一些障碍:安全厂商安装的内核模式(KM)通知、微型过滤器驱动程序和UM hook。UM hook通常用于处理NTDLL内的系统调用,而NTDLL则是UM进程最接近内核的地方。通过获得对内核的完全访问权限,安全厂商就可以完全控制系统,以便轻松地阻止各种类型的恶意活动。但正如部分读者已经知道的那样,Windows自Vista版本以来就提供一种内置的安全功能,该功能称为PatchGuard(PG),可用于保护内核的关键区域免遭修改。这些内存区域包括:

  • 系统服务描述符表(SSDT)
  • 全局描述符表(GDT)
  • 中断描述符表(IDT)
  • 系统映像(ntoskrnl.exe、ndis.sys、hal.dll)
  •  处理器MSR(系统调用)

PG(让安全厂商和恶意软件开发人员都大失所望)禁止任何软件对Windows内核进行扩展(即使是那些出于合法原因的软件)。在该功能推出之前,安全厂商给SSDT打补丁是司空见惯的事情(Sysinternals的RegMon的早期版本也使用了这种手法)。然而,现在微软的立场是,凡是对内核进行修改软件(无论是否为恶意软件)都可能导致可靠性、性能以及安全性受损。PG发布后,安全厂商不得不完全重新设计其反恶意软件解决方案。其中,绕过PG是一种可选方案,但对于旨在保护操作系统的软件来说,这并非一个安全、长久的解决方案。

在本文中,我们将为读者全面介绍可以用来绕过用户模式hook的最流行、最有效的各种技术,并指出每种技术对红队的优缺点。最后,我们将总结一些防御者可以用来保护或检测这些技术的方法。

内核模式通知

在探索UM hook绕过方法之前,首先需要了解的一点是,Windows已经简化了接收对于检测恶意软件很有用的事件的通知的方法,并且,利用这些通知,就不用在内核中打补丁或使用hook了。其中,比较常见的事件包括进程或线程的创建、终止,以及映像/DLL的映射等。
1.png

Microsoft建议安全供应商使用微型过滤器驱动程序来拦截、检查和选择性地阻止I/O事件。实际上,许多文件系统和网络功能都是通过NtDeviceIoControlFile系统调用实现的。

绕过方法

由于Microsoft没有为内核组件提供合法的方式来接收有关内存操作的通知,这迫使供应商不得不在每个进程中安装UM hook。为了应对这种情况,人们设计了各种绕过它们的技术,下面将对当前正在使用的一些技术进行简单的介绍,并给出对应的C语言源代码。

1.导出地址表(EAT)

恶意软件通常组合使用GetModuleHandle和GetProcAddress来解析系统调用的地址。另一种方法是在进程环境块(PEB)中手动定位ntdll.dll,并通过解析导出地址表(EAT)找到系统调用。下面给出用于解析EAT的示例代码。

static

LPVOID

WINAPI

GetProcAddressFromEAT(

    LPVOIDDllbase,

    const char*FunctionName)

{

   PIMAGE_DOS_HEADER       DosHeader;

   PIMAGE_NT_HEADERS       NtHeaders;

    DWORD                   NumberOfNames,VirtualAddress;

   PIMAGE_DATA_DIRECTORY  DataDirectory;

   PIMAGE_EXPORT_DIRECTORY ExportDirectory;

    PDWORD                  Functions;

    PDWORD                  Names;

    PWORD                   Ordinals;

    PCHAR                   Name;

    LPVOID                  ProcAddress=NULL;

   

   DosHeader      =(PIMAGE_DOS_HEADER)Dllbase;

   NtHeaders      =RVA2VA(PIMAGE_NT_HEADERS, Dllbase, DosHeader->e_lfanew);

   DataDirectory  =(PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;

    VirtualAddress= DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

 

    if(VirtualAddress==0) return NULL;

   

   ExportDirectory = RVA2VA(PIMAGE_EXPORT_DIRECTORY, Dllbase,VirtualAddress);

   NumberOfNames   =ExportDirectory->NumberOfNames;

 

    if(NumberOfNames==0) return NULL;

   

    Functions =RVA2VA(PDWORD,Dllbase, ExportDirectory->AddressOfFunctions);

    Names     = RVA2VA(PDWORD,Dllbase,ExportDirectory->AddressOfNames);

   Ordinals  = RVA2VA(PWORD, Dllbase,ExportDirectory->AddressOfNameOrdinals);

   

    do {

      Name =RVA2VA(PCHAR, Dllbase, Names[NumberOfNames-1]);

     if(lstrcmpA(Name, FunctionName) == 0) {

       ProcAddress = RVA2VA(LPVOID, Dllbase,Functions[Ordinals[NumberOfNames-1]]);

        returnProcAddress;

      }

    } while(--NumberOfNames && ProcAddress == NULL);

   

    returnProcAddress;

}

如果使用内存中已有的NTDLL基址,则无法绕过系统调用的任何UM hook。如果您希望绕过KERNEL32或KERNELbase的hook,不妨使用GetProcAddress,这样的话事情会简单得多。

通常情况下,Offsec工具会在调用这样的函数后会对系统调用进行unhook,然而,最近越来越多的安全厂商要么阻止unhook的尝试,要么在unhooking后不久就重新恢复hook。实际上,只要钩住NtProtectVirtualMemory函数,就可以轻松拦截覆盖hook的企图。

2. Dual-load 1(Section)

KnownDlls是对象命名空间中的一个目录,其中包含进程加载的最常见DLL的区段对象(section object)。它旨在通过减少可执行文件的加载时间来提高性能,并且可以通过打开区段名称knowndllsntdll.dll来将NTDLL的新副本映射到进程中。一旦映射了区段对象,我们就可以像前面方法中描述的那样解析系统调用的地址。不过,有一个内核通知是专门针对映像加载事件的,如果EDR或AV发现ntdll.dll被第二次加载,它就检查进程中是否存在恶意代码或至少通知用户存在可疑的活动。

虽然您可以使用NtOpenSection和NtMapViewOfSection加载新副本,但另一个问题是:它们可能已经被安装了hook。有些产品不会为NtMapViewOfSectionEx函数安装hook,但该函数只在Windows 10 1803后可用,并且,它仍然无法阻止发送与映射相关的内核通知。

   NTSTATUS          Status;

   LARGE_INTEGER     SectionOffset;

    SIZE_T            ViewSize;

    PVOID             Viewbase;

    HANDLE            SectionHandle;

   object_ATTRIBUTES objectAttributes;

   UNICODE_STRING   KnownDllsNtDllName;

    FARPROC           Function;

   

   INIT_UNICODE_STRING(

     KnownDllsNtDllName,

     L"\\KnownDlls\\ntdll.dll"

      );

   

   InitializeobjectAttributes(

       &objectAttributes,

        &KnownDllsNtDllName,

       OBJ_CASE_INSENSITIVE,

        0,

        NULL

        );

 

    Status =NtOpenSection(

             &SectionHandle,

             SECTION_MAP_EXECUTE | SECTION_MAP_READ | SECTION_QUERY,

             &objectAttributes

              );

   

   if(!NT_SUCCESS(Status)) {

     SET_LAST_NT_ERROR(Status);

     printf("Unable to open section %ld\n", GetLastError());

      gotocleanup;

    }

   

    //

    // Set theoffset to start mapping from.

    //

   SectionOffset.LowPart = 0;

   SectionOffset.HighPart = 0;

   

    //

    // Set thedesired base address and number of bytes to map.

    //

    ViewSize =0;

    Viewbase =NULL;

   

    Status =NtMapViewOfSection(

             SectionHandle,

             NtCurrentProcess(),

             &Viewbase,

             0,              // ZeroBits

             0,              // CommitSize

             &SectionOffset,

             &ViewSize,

             ViewShare,

              0,

             PAGE_EXECUTE_READ

              );

             

   if(!NT_SUCCESS(Status)) {

     SET_LAST_NT_ERROR(Status);

     printf("Unable to map section %ld\n", GetLastError());

      gotocleanup;

    }

   

    Function =(FARPROC)GetProcAddressFromEAT(Viewbase, "NtOpenProcess");

   

    printf("NtOpenProcess : %p, %ld\n",Function, GetLastError());

   

cleanup:

    if(Viewbase!= NULL) {

     NtUnmapViewOfSection(

       NtCurrentProcess(),

        Viewbase

        );

    }

   

   if(SectionHandle != NULL) {

      NtClose(SectionHandle);

    }

3. Dual-load 2 (Disk)

 

与前面的方法相比,多出来的一个步骤是,我们需要打开一个C:\Windows\System32\NTDLL.dll的文件句柄,并使用它来创建一个新的具有SEC_IMAGE页保护的区段对象。然后我们对该对象进行映射,以便进行读取或执行操作。但是,NtOpenFile、NtCreateFile可能已经被安装了hook,但即使没有的话,也无法解决前面方法中强调的问题。

   NTSTATUS          Status;

   LARGE_INTEGER     SectionOffset;

    SIZE_T            ViewSize;

    PVOID             Viewbase=NULL;

    HANDLE            FileHandle=NULL,SectionHandle=NULL;

   object_ATTRIBUTES objectAttributes;

   IO_STATUS_BLOCK   StatusBlock;

   UNICODE_STRING    FileName;

    FARPROC           Function;

   

    //

    // Try openntdll.dll on disk for reading.

    //

   INIT_UNICODE_STRING(

      FileName,

     L"\\??\\C:\\Windows\\System32\\ntdll.dll"

      );

   

    InitializeobjectAttributes(

       &objectAttributes,

       &FileName,

       OBJ_CASE_INSENSITIVE,

        0,

        NULL

        );

 

    Status =NtOpenFile(

             &FileHandle,

             FILE_READ_DATA,

             &objectAttributes,

             &StatusBlock,

             FILE_SHARE_READ,

             NULL

              );

   

   if(!NT_SUCCESS(Status)) {

     SET_LAST_NT_ERROR(Status);

     printf("NtOpenFile failed %ld\n", GetLastError());

      gotocleanup;

    }

   

    //

    // Createsection

    //

    Status =NtCreateSection(

             &SectionHandle,

             SECTION_ALL_ACCESS,

             NULL,

             NULL,

             PAGE_READONLY,

             SEC_IMAGE,

             FileHandle

              );

 

   if(!NT_SUCCESS(Status)) {

     SET_LAST_NT_ERROR(Status);

     printf("NtCreateSection failed %ld\n", GetLastError());

      gotocleanup;

    }

   

    //

    // Set theoffset to start mapping from.

    //

   SectionOffset.LowPart = 0;

   SectionOffset.HighPart = 0;

   

    //

    // Set thedesired base address and number of bytes to map.

    //

    ViewSize =0;

    Viewbase =NULL;

   

    Status =NtMapViewOfSection(

             SectionHandle,

              NtCurrentProcess(),

             &Viewbase,

             0,              // ZeroBits

             0,              // CommitSize

             &SectionOffset,

             &ViewSize,

             ViewShare,

              0,

             PAGE_EXECUTE_READ

              );

             

   if(!NT_SUCCESS(Status)) {

     SET_LAST_NT_ERROR(Status);

     printf("Unable to map section %ld\n", GetLastError());

      gotocleanup;

    }

   

    Function =(FARPROC)GetProcAddressFromEAT(Viewbase, "NtOpenProcess");

   

   printf("NtOpenProcess : %p, %ld\n", Function, GetLastError());

   

cleanup:

    if(Viewbase!= NULL) {

     NtUnmapViewOfSection(

       NtCurrentProcess(),

        Viewbase

        );

    }

   

   if(SectionHandle != NULL) {

      NtClose(SectionHandle);

    }   

   

   if(FileHandle != NULL) {

     NtClose(FileHandle);

    }

小结

在本文中,我们将为读者全面介绍可以用来绕过用户模式hook的最流行、最有效的各种技术,并指出每种技术对红队的优缺点。最后,我们将总结一些防御者可以用来保护或检测这些技术的方法。由于篇幅较长,我们准备分篇发表,更多精彩内容,敬请期待!

 

(未完待续)

本文由secM整理并翻译,不代表白帽汇任何观点和立场
来源:https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/

最新评论

昵称
邮箱
提交评论