封装的线程注入类

来源:互联网 发布:巨人网络借壳标的猜想 编辑:程序博客网 时间:2024/05/20 04:47

      不在江湖已许多年,今天偶然想起江湖事。以前有很多想法,最终由于种种原因都草草收尾。今天突然有了感觉,想起线程注入。奋笔疾书,写了一个不算太完美的类,完成了以前的一个夙愿,稍感欣慰。线程注入本人认为可以分成两步:第一步是代码和数据的注入,也可以看成是资源的注入;第二步是远程代码的唤醒。这个类主要是针对第一步做了封装。注入其他进程的代码要想成功运行,会受到不少限制,首先是所有的静态数据必须是事先注入好的,另外用到的函数也要是事先注入好的,还有就是API的入口地址问题。这些工作是十分繁杂的。我的这个类将这个工作封装起来,使得这一步更容易完成。至于第二步,方法很多,我这里只写了两种,一种是CreateRemoteThread,优点是直接了当,立竿见影,缺点是大部分杀毒软件都挂钩了这个API,如果要用就得像办法解决这个问题;另一种是QueueUserAPC,优点是大部分杀毒软件免杀(至少卡巴6是的),缺点是需要找到处于内核等待状态的线程。我这里选了Shell_TrayWnd。不废话了,下面看代码:­

­

­

//Source File: InjectThread.h­

#include <afxtempl.h>

//#define INJECT_TEST                //本地注入测试
#define INJECT_APC                //采用APC注入方式

#ifdef _DEBUG
#define INJECTFUNC_ATTRIBUTE             //Debug版本不能静态定义
#define INJECTFUNC_END(funcName)            //Debug版本无需结束标记
#else
#define INJECTFUNC_ATTRIBUTE static            //Release版本,防止Jmp指令
#define INJECTFUNC_END(funcName) static LPVOID funcName = &funcName    //Release版本,标记函数结束地址
#endif

#ifdef INJECT_APC                //APC注入方式的线程入口函数定义
#define INJECTFUNC_MAINSTART(funcName, paraName)      /
 INJECTFUNC_ATTRIBUTE VOID APIENTRY funcName(ULONG_PTR paraName)    //函数申明定义
#define INJECTFUNC_MAINRETURN(value)           //返回语句定义
#else                   //默认注入方式的入口函数定义
#define INJECTFUNC_MAINSTART(funcName, paraName)      /
 INJECTFUNC_ATTRIBUTE DWORD WINAPI funcName(LPVOID paraName)     //函数申明定义
#define INJECTFUNC_MAINRETURN(value) return(value)        //返回语句定义
#endif

/***************************************************************************

* 功能  : 线程注入类
* 输入参数 : 无
* 输出参数 : 无
* 返回值  : 无
* 作者  : ***

***************************************************************************/
class CThreadInject                //线程注入类
{
public:
 typedef enum tagParaType             //参数类型
 {
  ParaType_Var,               //变量
  ParaType_Func,               //用户自定义函数
  ParaType_SysFunc              //系统函数
 } EParaType;

 typedef struct tagParaDataNode            //参数信息节点
 {
  UINT32 iVarId;               //Id
  LPVOID pDataAddr;              //本地地址
  LPVOID pRemoteAddr;              //运程地址
  UINT32 iDataSize;              //大小
  EParaType eType;              //类型
 } SParaDataNode, *PSParaDataNode;

public:
 CThreadInject(void);              //构造
 ~CThreadInject(void);              //析构

 BOOL SetProcessId(DWORD dwProcessId);          //设置进程Id
 BOOL SetProcessIdByWindow(HWND hWnd);          //根据窗口设置进程Id和线程Id

 BOOL AddPara(UINT32 iVarId, LPVOID pVarAddr, UINT32 iDataSize
  , EParaType paraType);             //增加参数
 BOOL AddSysFunc(UINT32 iFuncId, PCHAR pcFuncName);       //增加系统函数

 BOOL Inject(UINT32 iStartFuncId, BOOL bWaitBck);       //注入线程

 static UINT32 GetPara(UINT32 pDirAddr, UINT32 iVarId);      //获取参数

protected:
 VOID ReleasePara();               //释放本地资源
 VOID ReleaseRemoteData(HANDLE hProcess);         //释放远程资源
 BOOL EnableDebugPriv();              //提升权限
 BOOL InjectPara(HANDLE hProcess, EParaType paraType, LPVOID *ppRemoteAddr //注入参数
  , UINT32 *pIRemoteSize);
 BOOL InjectDir(HANDLE hProcess, LPVOID *ppRemoteAddr, UINT32 *pIRemoteSize);//注入参数表
 UINT32 GetTotalParaSize(EParaType paraType);        //获取某类参数大小总和
 LPVOID GetStartFuncAddr(UINT32 iStartFuncId);        //获取入口函数地址

 CList<SParaDataNode> m_lstParaData;           //参数链表
 DWORD m_dwRemoteProcessId;             //远程进程Id
 DWORD m_dwRemoteThreadId;             //远程线程Id

private:
 LPVOID m_pRemoteVarAddr;             //远程变量地址
 LPVOID m_pRemoteFuncAddr;             //远程函数地址
 LPVOID m_pRemoteDirAddr;             //远程参数表地址
 UINT32 m_iRemoteVarSize;             //远程变量大小
 UINT32 m_iRemoteFuncSize;             //远程函数大小
 UINT32 m_iRemoteDirSize;             //远程目录大小
};

typedef UINT32 (* Func_GetPara)(UINT32 pDirAddr, UINT32 iVarId);    //获取参数函数原形

#define THREADINJECT_CREATE(pThis) CThreadInject *pThis = new CThreadInject() //创建线程注入类实例
#define THREADINJECT_RELEASE(pThis) delete (CThreadInject *)pThis;    //释放线程注入类实例

#define THREADINJECT_ADDPARA(pThis, varId, varName, type)    /
 ((CThreadInject *)pThis)->AddPara(varId, &varName, sizeof(type), /
 CThreadInject::ParaType_Var)            //增加参数

#define THREADINJECT_ADDSYSFUNC(pThis, funcId, funcName)    /
 ((CThreadInject *)pThis)->AddSysFunc(funcId, #funcName)      //增加系统函数

#ifdef _DEBUG
#define THREADINJECT_ADDFUNC(pThis, funcId, funcName, finishTag)  /
 ((CThreadInject *)pThis)->AddPara(funcId, funcName, 0x8000,   /
 CThreadInject::ParaType_Func)            //Debug版本需要保留附加信息
#else
#define THREADINJECT_ADDFUNC(pThis, funcId, funcName, endTag)   /
 ((CThreadInject *)pThis)->AddPara(funcId, funcName,     /
 (UINT32)(INT64)endTag-(UINT32)(INT64)funcName,      /
 CThreadInject::ParaType_Func)            //Release版本无需保留附加信息
#endif

#define THREADINJECT_SETPROCESS(pThis, processId)      /
 ((CThreadInject *)pThis)->SetProcessId((DWORD)processId)     //设置远程进程句柄
#define THREADINJECT_SETWINDOW(pThis, hWnd)        /
 ((CThreadInject *)pThis)->SetProcessIdByWindow((HWND)hWnd)     //设置远程进程和线程句柄

#define THREADINJECT_INJECT(pThis, startFuncId)       /
 ((CThreadInject *)pThis)->Inject(startFuncId, FALSE)      //远程注入线程

#define THREADINJECT_GETFUNC(pDirAddr, funcId)       /
 ((*(Func_GetPara *)(&((PBYTE)pDirAddr)[8]))((UINT32)(INT64)pDirAddr,/
 funcId))                 //获取函数地址
#define THREADINJECT_GETPARA(pDirAddr, varId)       /
 THREADINJECT_GETFUNC(pDirAddr, varId)          //获取参数

­

­

//Source File: InjectThread.cpp­

#include "StdAfx.h"­

#include "InjectThread.h"­

CThreadInject::CThreadInject(void)
{
 m_dwRemoteProcessId = 0;
 m_dwRemoteThreadId = 0;
 m_pRemoteVarAddr = NULL;
 m_pRemoteFuncAddr = NULL;
 m_pRemoteDirAddr = NULL;
 m_iRemoteVarSize = 0;
 m_iRemoteFuncSize = 0;
 m_iRemoteDirSize = 0;

#ifdef _DEBUG
 AddPara(0, &CThreadInject::GetPara, 0x4000, ParaType_Func);     //Debug版本保留附加信息
#else
 AddPara(0, &CThreadInject::GetPara, 0x110, ParaType_Func);     //Release版本无需保存附加信息
#endif
}

CThreadInject::~CThreadInject(void)
{
 ReleasePara();
}

BOOL CThreadInject::SetProcessId(DWORD dwProcessId)
{
 if(dwProcessId == 0)
 {
  return FALSE;
 }

 m_dwRemoteProcessId = dwProcessId;
 return TRUE;
}

BOOL CThreadInject::SetProcessIdByWindow(HWND hWnd)
{
 DWORD dwProcessId;
 m_dwRemoteThreadId = ::GetWindowThreadProcessId(hWnd, &dwProcessId);
 return SetProcessId(dwProcessId);
}

BOOL CThreadInject::AddPara(UINT32 iVarId,
       LPVOID pVarAddr,
       UINT32 iDataSize,
       EParaType paraType)
{
 for(INT32 i=0; i<m_lstParaData.GetCount(); ++i)
 {
  if(m_lstParaData.GetAt(m_lstParaData.FindIndex(i)).iVarId == iVarId)
  {
   return FALSE;
  }
 }

 SParaDataNode dataNode;
 dataNode.iVarId = iVarId;
 dataNode.pRemoteAddr = NULL;
 dataNode.iDataSize = iDataSize;
 dataNode.eType = paraType;

 if(paraType == ParaType_Var)
 {
  dataNode.pDataAddr = new BYTE[iDataSize];
  memcpy(dataNode.pDataAddr, pVarAddr, iDataSize);
 }
 else
 {
  dataNode.pDataAddr = pVarAddr;
 }

 m_lstParaData.AddTail(dataNode);
 return TRUE;
}

BOOL CThreadInject::AddSysFunc(UINT32 iFuncId, PCHAR pcFuncName)
{
 PTCHAR pcSysModuleName[] =
 {
  TEXT("USER32.dll"),
  TEXT("Kernel32.dll"),
  TEXT("GDI32.dll"),
  TEXT("ntdll.dll"),
  TEXT("OLE32.dll"),
  TEXT("WS32_32.dll"),
  TEXT("WSOCK32.dll")
 };

 LPVOID pFuncAddr = NULL;

 for(UINT32 i=0; i< sizeof(pcSysModuleName)/sizeof(PTCHAR); ++i)
 {
  HINSTANCE hModule = LoadLibrary(pcSysModuleName[i]);

  if(hModule!=NULL && (pFuncAddr=(LPVOID)GetProcAddress(hModule
   , pcFuncName))!=NULL)
  {
   break;                //从系统模块表中找到指定函数的入口地址
  }
 }

 if(!pFuncAddr)
 {
  return FALSE;
 }

 return AddPara(iFuncId, pFuncAddr, 0, CThreadInject::ParaType_SysFunc);
}

BOOL CThreadInject::Inject(UINT32 iStartFuncId,
         BOOL bWaitBck)
{
 EnableDebugPriv();

#ifdef INJECT_TEST                //本地注入测试仅将代码注入当前线程
 m_dwRemoteProcessId = GetCurrentProcessId();
 m_dwRemoteThreadId = GetCurrentThreadId();
#endif

 LPVOID pStartFunc = NULL;
 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_dwRemoteProcessId);

 if(hProcess == NULL)
 {
  return FALSE;
 }

 if(!InjectPara(hProcess, ParaType_Var, &m_pRemoteVarAddr, &m_iRemoteVarSize)
  || !InjectPara(hProcess, ParaType_Func, &m_pRemoteFuncAddr, &m_iRemoteFuncSize))
 {
  ReleaseRemoteData(hProcess);
  CloseHandle(hProcess);
  return FALSE;
 }

 DWORD dwOldProtect;
 if(m_pRemoteFuncAddr && !VirtualProtectEx(hProcess, m_pRemoteFuncAddr, m_iRemoteFuncSize
  , PAGE_EXECUTE, &dwOldProtect))
 {
  ReleaseRemoteData(hProcess);
  CloseHandle(hProcess);
  return FALSE;
 }

 if((pStartFunc=GetStartFuncAddr(iStartFuncId)) == NULL)
 {
  ReleaseRemoteData(hProcess);
  CloseHandle(hProcess);
  return FALSE;
 }

 if(!InjectDir(hProcess, &m_pRemoteDirAddr, &m_iRemoteDirSize))
 {
  ReleaseRemoteData(hProcess);
  CloseHandle(hProcess);
  return FALSE;
 }

#ifdef INJECT_APC
 HANDLE hRemoteThread = OpenThread(THREAD_SET_CONTEXT, FALSE, m_dwRemoteThreadId);
#elif defined INJECT_TEST
 HANDLE hRemoteThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pStartFunc
  , m_pRemoteDirAddr, 0, NULL);
#else
 HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)pStartFunc
   , m_pRemoteDirAddr, 0, NULL);
#endif //INJECT_APC

 if(hRemoteThread == INVALID_HANDLE_VALUE)
 {
  CloseHandle(hProcess);
  return FALSE;
 }

#ifdef INJECT_APC
 if(!QueueUserAPC((PAPCFUNC)pStartFunc, hRemoteThread, (ULONG_PTR)m_pRemoteDirAddr))
 {
  CloseHandle(hRemoteThread);
  CloseHandle(hProcess);
  return FALSE;
 }
#else
 if(bWaitBck)
 {
  ::WaitForSingleObject(hRemoteThread, INFINITE);
  ReleaseRemoteData(hProcess);
 }
#endif //INJECT_APC

 CloseHandle(hRemoteThread);
 CloseHandle(hProcess);
 return TRUE;
}

UINT32 CThreadInject::GetPara(UINT32 pDirAddr,
         UINT32 iVarId)
{
 UINT32 i, iDirLength;
 UINT32 pResult = NULL;
 PCHAR pRecord = &((PCHAR)(INT64)pDirAddr)[4];

 ((PCHAR)&iDirLength)[0] = ((PCHAR)(INT64)pDirAddr)[0];
 ((PCHAR)&iDirLength)[1] = ((PCHAR)(INT64)pDirAddr)[1];
 ((PCHAR)&iDirLength)[2] = ((PCHAR)(INT64)pDirAddr)[2];
 ((PCHAR)&iDirLength)[3] = ((PCHAR)(INT64)pDirAddr)[3];

 for(i=0; i<iDirLength; ++i)
 {
  UINT32 iTempVarId;

  ((PCHAR)&iTempVarId)[0] = ((PCHAR)(INT64)pRecord)[0];
  ((PCHAR)&iTempVarId)[1] = ((PCHAR)(INT64)pRecord)[1];
  ((PCHAR)&iTempVarId)[2] = ((PCHAR)(INT64)pRecord)[2];
  ((PCHAR)&iTempVarId)[3] = ((PCHAR)(INT64)pRecord)[3];

  if(iTempVarId == iVarId)
  {
   ((PCHAR)&pResult)[0] = ((PCHAR)pRecord)[4];
   ((PCHAR)&pResult)[1] = ((PCHAR)pRecord)[5];
   ((PCHAR)&pResult)[2] = ((PCHAR)pRecord)[6];
   ((PCHAR)&pResult)[3] = ((PCHAR)pRecord)[7];
   break;
  }

  pRecord += 8;
 }

 return pResult;
}

VOID CThreadInject::ReleasePara()
{
 while(m_lstParaData.GetCount() > 0)
 {
  SParaDataNode &dataNode = m_lstParaData.GetAt(m_lstParaData.FindIndex(0));

  if(dataNode.eType==ParaType_Var && dataNode.pDataAddr)
  {
   delete [](LPVOID)(INT64)dataNode.pDataAddr;
   dataNode.pDataAddr = NULL;
  }

  m_lstParaData.RemoveAt(m_lstParaData.FindIndex(0));
 }
}

VOID CThreadInject::ReleaseRemoteData(HANDLE hProcess)
{
 for(INT32 i=0; i<m_lstParaData.GetCount(); ++i)
 {
  SParaDataNode &dataNode = m_lstParaData.GetAt(m_lstParaData.FindIndex(i));

  if(dataNode.pRemoteAddr)
  {
   dataNode.pRemoteAddr = NULL;
  }
 }

 if(m_pRemoteVarAddr)
 {
  VirtualFreeEx(hProcess, m_pRemoteVarAddr, m_iRemoteVarSize, MEM_RELEASE);
  m_pRemoteVarAddr = NULL;
  m_iRemoteVarSize = 0;
 }

 if(m_pRemoteFuncAddr)
 {
  VirtualFreeEx(hProcess, m_pRemoteFuncAddr, m_iRemoteFuncSize, MEM_RELEASE);
  m_pRemoteFuncAddr = NULL;
  m_iRemoteFuncSize = 0;
 }

 if(m_pRemoteDirAddr)
 {
  VirtualFreeEx(hProcess, m_pRemoteDirAddr, m_iRemoteDirSize, MEM_RELEASE);
  m_pRemoteDirAddr = NULL;
  m_iRemoteDirSize = 0;
 }
}

BOOL CThreadInject::EnableDebugPriv()
{
 HANDLE hToken;
 LUID sedebugnameValue;
 TOKEN_PRIVILEGES tkp;

 if (!OpenProcessToken(GetCurrentProcess()
  , TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
 {
  return FALSE;
 }

 if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue))
 {
  CloseHandle(hToken);
  return FALSE;
 }

 tkp.PrivilegeCount = 1;
 tkp.Privileges[0].Luid = sedebugnameValue;
 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

 if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
 {
  CloseHandle(hToken);
  return FALSE;
 }

 return TRUE;
}

BOOL CThreadInject::InjectDir(HANDLE hProcess,
         LPVOID *ppRemoteAddr,
         UINT32 *pIRemoteSize)
{
 UINT32 iParaCount = (UINT32)m_lstParaData.GetCount();

 *pIRemoteSize = (iParaCount<<3)+4;
 *ppRemoteAddr = VirtualAllocEx(hProcess, NULL, *pIRemoteSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
 LPVOID pRemoteTempAddr = *ppRemoteAddr;

 if(!pRemoteTempAddr || !WriteProcessMemory(hProcess, pRemoteTempAddr, &iParaCount, 4, NULL))
 {
  return FALSE;
 }

 pRemoteTempAddr = &((PCHAR)pRemoteTempAddr)[4];
 for(UINT32 i=0; i<iParaCount; ++i)
 {
  SParaDataNode &dataNode = m_lstParaData.GetAt(m_lstParaData.FindIndex(i));

  if(!WriteProcessMemory(hProcess, pRemoteTempAddr, &dataNode.iVarId, 4, NULL)
   || !WriteProcessMemory(hProcess, &((PBYTE)pRemoteTempAddr)[4]
#ifdef INJECT_TEST
   , &dataNode.pDataAddr, 4, NULL))
#else
   , (dataNode.eType==ParaType_SysFunc ? &dataNode.pDataAddr : &dataNode.pRemoteAddr), 4, NULL))
#endif
  {
   return FALSE;
  }

  pRemoteTempAddr = &((PCHAR)pRemoteTempAddr)[8];
 }

 return TRUE;
}

BOOL CThreadInject::InjectPara(HANDLE hProcess,
          EParaType paraType,
          LPVOID *ppRemoteAddr,
          UINT32 *pIRemoteSize)
{
 if((*pIRemoteSize=GetTotalParaSize(paraType)) == 0)
 {
  return TRUE;
 }

 if((*ppRemoteAddr=VirtualAllocEx(hProcess, NULL, *pIRemoteSize
  , MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)) == NULL)
 {
  return FALSE;
 }

 PBYTE pRemoteTempAddr = (PBYTE)*ppRemoteAddr;
 for(INT32 i=0; i<m_lstParaData.GetCount(); ++i)
 {
  SParaDataNode &dataNode = m_lstParaData.GetAt(m_lstParaData.FindIndex(i));

  if(dataNode.eType == paraType)
  {
   if(!WriteProcessMemory(hProcess, pRemoteTempAddr, dataNode.pDataAddr
    , dataNode.iDataSize, NULL))
   {
    return FALSE;
   }

   dataNode.pRemoteAddr = pRemoteTempAddr;
   pRemoteTempAddr += dataNode.iDataSize;
  }
 }

 return TRUE;
}

UINT32 CThreadInject::GetTotalParaSize(EParaType paraType)
{
 UINT32 iTotalSize = 0;

 for(INT32 i=0; i<m_lstParaData.GetCount(); ++i)
 {
  SParaDataNode &dataNode = m_lstParaData.GetAt(m_lstParaData.FindIndex(i));

  if(dataNode.eType == paraType)
  {
   iTotalSize += dataNode.iDataSize;
  }
 }

 return iTotalSize;
}

LPVOID CThreadInject::GetStartFuncAddr(UINT32 iStartFuncId)
{
 for(INT32 i=0; i<m_lstParaData.GetCount(); ++i)
 {
  SParaDataNode &dataNode = m_lstParaData.GetAt(m_lstParaData.FindIndex(i));

  if(dataNode.iVarId == iStartFuncId)
  {
#ifdef INJECT_TEST
   return dataNode.pDataAddr;
#else
   return dataNode.pRemoteAddr;
#endif
  }
 }

 return NULL;
}

­

­

//Source File: Test Code­

#define VARID_CAPTION     0x00000001
#define VARID_TITLE      0x00000002
#define FUNID_SHOWMESSAGE    0x10000001
#define FUNID_SUBWINDOWTHREAD   0x10000002
#define SYSID_MESSAGEW     0x20000001

typedef int (WINAPI *Func_MessageBoxW)(IN HWND hWnd,
            IN LPCWSTR lpText,
            IN LPCWSTR lpCaption,
            IN UINT uType);

typedef VOID (*Func_ShowMessage)(LPVOID pDirAddr);

INJECTFUNC_MAINSTART(SubWindowThread, lpThreadParameter)
{
 Func_ShowMessage pShowMessage = (Func_ShowMessage)(INT64)THREADINJECT_GETFUNC(lpThreadParameter, FUNID_SHOWMESSAGE);
    
 pShowMessage((LPVOID)lpThreadParameter);

 INJECTFUNC_MAINRETURN(0);
}

INJECTFUNC_ATTRIBUTE VOID ShowMessage(LPVOID pDirAddr)
{
 Func_MessageBoxW pMessageBox = (Func_MessageBoxW)(INT64)THREADINJECT_GETFUNC(pDirAddr, SYSID_MESSAGEW);
 LPCWSTR pTitle = (LPCWSTR)(INT64)THREADINJECT_GETPARA(pDirAddr, VARID_TITLE);
 LPCWSTR pCaption = (LPCWSTR)(INT64)THREADINJECT_GETPARA(pDirAddr, VARID_CAPTION);

 pMessageBox(NULL, pTitle, pCaption, MB_OK);
}

INJECTFUNC_END(EndTag);

­

 TCHAR Caption[20] = TEXT("Caption");
 TCHAR Title[20] = TEXT("Title"); 

 THREADINJECT_CREATE(pInject);

 THREADINJECT_ADDPARA(pInject, VARID_CAPTION, Caption, TCHAR[20]);
 THREADINJECT_ADDPARA(pInject, VARID_TITLE, Title, TCHAR[20]);

 THREADINJECT_ADDFUNC(pInject, FUNID_SUBWINDOWTHREAD, SubWindowThread, ShowMessage);
 THREADINJECT_ADDFUNC(pInject, FUNID_SHOWMESSAGE, ShowMessage, EndTag);

 THREADINJECT_ADDSYSFUNC(pInject, SYSID_MESSAGEW, MessageBoxW);

  HWND hWnd = ::FindWindow(TEXT("Shell_TrayWnd"), NULL);
 THREADINJECT_SETWINDOW(pInject, hWnd);
 THREADINJECT_INJECT(pInject, FUNID_SUBWINDOWTHREAD);

 THREADINJECT_RELEASE(pInject);

­

­

上面的代码将用APC把相关数据和代码注入任务栏,等待若干秒后,会看到线程执行。如果将InjectThread.h里的#define INJECT_APC一句注释掉,则采用CreateRemoteThread远程唤醒代码。有兴趣的仔细看下测试代码,可见这个封装确实可以简化不少操作,呵呵。­