zoukankan      html  css  js  c++  java
  • 探索QueueUserApc(1)

    声明:这里只看重要的调用信息和为什么这样做,其他的就不看了

    APC(Asynchronous procedure call)异步程序调用,
    在NT中,有两种类型的APCs:用户模式和内核模式。

    用户APCs运行在用户模式下目标线程当前上下文中,并且需要从目标线程得到许可来运行。特别是,用户模式的APCs需要目标线程处在alertable等待状态才能被成功的调度执行。通过调用下面任意一个函数,都可以让线程进入这种状态。这些函数是:KeWaitForSingleObject, KeWaitForMultipleObjects, KeWaitForMutexObject, KeDelayExecutionThread。
    对于用户模式下,可以调用函数SleepEx, SignalObjectAndWait, WaitForSingleObjectEx, WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx 都可以使目标线程处于alertable等待状态,从而让用户模式APCs执行,原因是这些函数最终都是调用了内核中的KeWaitForSingleObject,KeWaitForMultipleObjects,KeWaitForMutexObject, KeDelayExecutionThread等函数。

    另外通过调用一个未公开的alert-test服务KeTestAlertThread,用户线程可以使用户模式APCs执行。
    当一个用户模式APC被投递到一个线程,调用上面的等待函数,如果返回等待状态STATUS_USER_APC,在返回用户模式时,内核转去控制APC例程,当APC例程完成后,再继续线程的执行.

    以上解释是我拿别人的

    但是不太对,首先不用WaitForSingleObjectEx等上面的函数也可注入成功,这方面可以,我测试可行,为什么不可行我们就要探索一下,如下,其次内核中当KernelRoutine 执行完后,NormalRoutine为空的时候才会调用KeTestAlertThread去 检查该线程是否可以交付另一个用户模式APC

     

    探索:

    用户模式APCwindows2000调用过程如下

    QueueUserAPC(
    PAPCFUNC pfnAPC,
    HANDLE hThread,
    ULONG_PTR dwData
    )
    /*++

    Routine Description:

    This function is used to queue a user-mode APC to the specified thread. The APC
    will fire when the specified thread does an alertable wait.

    Arguments:

    pfnAPC - Supplies the address of the APC routine to execute when the
    APC fires.

    hHandle - Supplies a handle to a thread object. The caller
    must have THREAD_SET_CONTEXT access to the thread.

    dwData - Supplies a DWORD passed to the APC

    Return Value:

    TRUE - The operations was successful

    FALSE - The operation failed. GetLastError() is not defined.

    --*/

    {
    NTSTATUS Status;

    Status = NtQueueApcThread(
    hThread,
    (PPS_APC_ROUTINE)BaseDispatchAPC,
    (PVOID)pfnAPC,
    (PVOID)dwData,
    NULL
    );

    if ( !NT_SUCCESS(Status) ) {
    return 0;
    }
    return 1;
    }

    NtQueueApcThread(
    IN HANDLE ThreadHandle,
    IN PPS_APC_ROUTINE ApcRoutine,
    IN PVOID ApcArgument1,
    IN PVOID ApcArgument2,
    IN PVOID ApcArgument3
    )

    /*++

    Routine Description:

    This function is used to queue a user-mode APC to the specified thread. The APC
    will fire when the specified thread does an alertable wait

    Arguments:

    ThreadHandle - Supplies a handle to a thread object. The caller
    must have THREAD_SET_CONTEXT access to the thread.

    ApcRoutine - Supplies the address of the APC routine to execute when the
    APC fires.

    ApcArgument1 - Supplies the first PVOID passed to the APC

    ApcArgument2 - Supplies the second PVOID passed to the APC

    ApcArgument3 - Supplies the third PVOID passed to the APC

    Return Value:

    Returns an NT Status code indicating success or failure of the API

    --*/

    {
    PETHREAD Thread;
    NTSTATUS st;
    KPROCESSOR_MODE Mode;
    KIRQL Irql;
    PKAPC Apc;

    PAGED_CODE();

    Mode = KeGetPreviousMode();

    st = ObReferenceObjectByHandle(
    ThreadHandle,
    THREAD_SET_CONTEXT,
    PsThreadType,
    Mode,
    (PVOID *)&Thread,
    NULL
    );

    if ( NT_SUCCESS(st) ) {
    st = STATUS_SUCCESS;
    if ( IS_SYSTEM_THREAD(Thread) ) {
    st = STATUS_INVALID_HANDLE;
    }
    else {
    Apc = ExAllocatePoolWithQuotaTag(
    (NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE),
    sizeof(*Apc),
    'pasP'
    );

    if ( !Apc ) {
    st = STATUS_NO_MEMORY;
    }
    else {
    KeInitializeApc(
    Apc,
    &Thread->Tcb,
    OriginalApcEnvironment,
    PspQueueApcSpecialApc,
    NULL,
    (PKNORMAL_ROUTINE)ApcRoutine,
    UserMode,
    ApcArgument1
    );

    if ( !KeInsertQueueApc(Apc,ApcArgument2,ApcArgument3,0) ) {
    ExFreePool(Apc);
    st = STATUS_UNSUCCESSFUL;
    }
    }
    }
    ObDereferenceObject(Thread);
    }

    return st;

    标黑色地方看到了对系统线程的“照顾”。自己实现这套流程岂不是很吊。。。。就没有这么多限制。。。。上面一些内容不懂的拿windbg调试一下,然后看看书就知道了

    用户模式APC被插入链表后
    交付完内核APC链表中的APC对象以后,如果APC_ LEVEL软件中断发生在用户模式下(即原来的模式是UserMode),并且该线程确实有用户模式APC对象在等待交付,则进人用户模式APC对象的交付处理。其过程类似,它在访问APC链表时,也要提升IRQL
    至DISPATCH LEVEL并且锁住APC链表。类似于普通内核模式APC的交付过程,KiDeliverApc函数通过线程对象中ApcState成员的UserApcPending标志保证一一个用户模式APC不会打断另一个用户模式APC。用户模式APC的交付是这样完成的:先调用

    APC对象的KernelRoutine 例程,将NormalRoutine 作为参数传递给它,如果它返回以后,NormalRoutine 为NULL,则调用KeTestAlertThread函数, 检查该线程是否可以交付另一个用户模式APC; 否则,调用KiInitializeUserApc函数,为该用户模式APC初始化一个执行环境。

    由于用户模式APC的NormalRoutine是一个在用户模式下运行的函数(位于用户地址空间),而KiDeliverApc 是在内核模式下运行的,所以,KiDeliverApc 只是调用KilnitializeUserApc来设置好用户APC例程将来被调用的环境。由于从内核模式到用户模式是通过一个陷阱帧返回的,所以,KiInitializeUserApc 将陷阱帧中的用户模式返回地址
    (即Eip寄存器)设置为KeUserApcDispatcher函数的地址,并且将NormalRoutine等信息传递到用户栈中恰当的位置上,以便将来KeUserApcDispatcher 可以访问。这里,KeUserApcDispatcher是ntdll.dll 中的函数地址(函数名称为KiUserApcDispatcher) 。

    上面的话可能一时不好理解,但是我们可以看源码(KiDeliverApc 他是执行APC的函数)

     // Kernel APC queue is empty. If the previous mode is user, user APC
        // pending is set, and the user APC queue is not empty, then remove
        // the first entry from the user APC queue, set its inserted state to
        // FALSE, clear user APC pending, release the dispatcher database lock,
        // and call the specified kernel routine. If the normal routine address
        // is not NULL on return from the kernel routine, then initialize the
        // user mode APC context and return. Otherwise, check to determine if
        // another user mode APC can be processed.
        //

        if ((IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) &&
           (PreviousMode == UserMode) && (Thread->ApcState.UserApcPending == TRUE)) {
            Thread->ApcState.UserApcPending = FALSE;
            NextEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
            Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
            KernelRoutine = Apc->KernelRoutine;
            NormalRoutine = Apc->NormalRoutine;
            NormalContext = Apc->NormalContext;
            SystemArgument1 = Apc->SystemArgument1;
            SystemArgument2 = Apc->SystemArgument2;
            RemoveEntryList(NextEntry);
            Apc->Inserted = FALSE;
            KiUnlockApcQueue(Thread, OldIrql);
            (KernelRoutine)(Apc, &NormalRoutine, &NormalContext,
                            &SystemArgument1, &SystemArgument2);

            if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {
                KeTestAlertThread(UserMode);

            } else {
                KiInitializeUserApc(ExceptionFrame, TrapFrame, NormalRoutine,
                                    NormalContext, SystemArgument1, SystemArgument2);
            }

    所以在NormoalRoutine不为NULL下,KeUserApcDispatcher很重要,这是NTDLL他的实现(IDA f5大法)

    后面的实现还要深入才行,先吃饭下午搞

    这是APCr3简单注入(MFC)

    编程实现思路:

    我们用CreateProcess以挂起的方式打开目标进程。
    WriteProcessMemory向目标进程中申请空间,写入DLL名称。
    使用QueueUserAPC()这个API向队列中插入Loadlibrary()的函数指针,加载我们的DLL

    API介绍
    DWORD QueueUserAPC( PAPCFUNC pfnAPC, // APC function
    HANDLE hThread, // handle to thread 
    ULONG_PTR dwData // APC function parameter);
    参数1:APC回调函数地址;
    参数2:线程句柄
    参数3:回调函数的参数


    void CAPCInjectDlg::OnInject() 
    {
    // TODO: Add your control notification handler code here
    DWORD dwRet = 0;
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    ZeroMemory(&pi,sizeof(pi));
    ZeroMemory(&si,sizeof(si));
    si.cb = sizeof(STARTUPINFO);

    //以挂起的方式创建进程
    dwRet = CreateProcess(m_strExePath.GetBuffer(0),
    NULL,
    NULL,
    NULL,
    FALSE,
    NULL,
    NULL,
    NULL,
    &si,
    &pi);

    if (!dwRet)
    {
    MessageBox("CreateProcess失败!!");
    return;
    }

    PVOID lpDllName = VirtualAllocEx(pi.hProcess, 
    NULL, 
    m_strDllPath.GetLength(), 
    MEM_COMMIT, 
    PAGE_READWRITE); 


    if (lpDllName)
    {
    //将DLL路径写入目标进程空间
    if(WriteProcessMemory(pi.hProcess, lpDllName, m_strDllPath.GetBuffer(0),m_strDllPath.GetLength(), NULL))
    {
    LPVOID nLoadLibrary=(LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"),"LoadLibraryA");
    //向远程APC队列插入LoadLibraryA
    if(QueueUserAPC((PAPCFUNC)nLoadLibrary,pi.hThread,(DWORD)lpDllName))
    {

    MessageBox("QueueUserAPC成!!");

    }
    }
    else
    {
    MessageBox("WriteProcessMemory失败!!");
    return;
    }
    }

    MessageBox("APC注入成功");
    }

     //注入代码

    #include "stdafx.h"
    #define EXPORT __declspec(dllexport)

    extern "C" EXPORT void MsgBox()
    {
    MessageBox(NULL, L"成功注入了", NULL, MB_OK);
    }

    main函数执行即可

  • 相关阅读:
    DELPHI版传奇引擎学习菜鸟篇(applem2)04
    DELPHI版传奇引擎学习菜鸟篇(applem2)03
    学习win32API消息处理
    DELPHI版传奇引擎学习菜鸟篇(applem2)02
    DELPHI版传奇引擎学习菜鸟篇(applem2)01
    读写INI的通用函数
    黑马程序员选择语句 SwitchCase 和break 、return 关键字。
    黑马程序员Readonly和Const的区别
    黑马程序员计算每个字符在字符串中出现的次数
    黑马程序员ADO.NET中的五个主要对象
  • 原文地址:https://www.cnblogs.com/L-Sunny/p/9182787.html
Copyright © 2011-2022 走看看