注册表注入
在Windows NT/2000/XP/2003操作系统中,当需要加载user32.dll的程序启动时,user32.dll会加载注册表键HLM\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DIls下面列出的所有模块。
根据这个原理,外挂可以将外挂模块所在的路径写到AppInit_DIls 键下,待游戏进程启动并将外挂模块带人之后,再删除AppInit_DIls 键的值以清除痕迹,其核心函数如下:
//定义键值
#define DSTKEY “SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows”
//打开主键
RegOpenKeyEx (
HKEY LOCAL_ MACHINE,
DSTKEY,
0,
KEY_ ALL_ ACCESS,
&hKey) ;
//设置AppInit_ DLLS 键的值,其中“cDllPath" 为待注人DLL的路径
RegSetValueEx (
hKey,
“AppInit_DLLs”,
0,
REG SZ,
cD11Path,
strlen( (char*) cDl1Path) +1
) ;
优点:简单,易于实现。
缺点:第一,系统重启后才能实现注人,且对DLL的稳定性要求较高。建议只在虚拟机里试用,因为DLL写得不好会造成BSOD(蓝屏),连安全模式也进不去。第二,易于被像ProcessMonitor这样的用于监测注册表操作的软件记录,进而被安全分析人员发现。
远线程注入
远线程注入的核心思想是利用Windows提供的远线程机制,在目标进程中开启一个加载模块的远线程,使外挂模块被该远线程加载到游戏的地址空间。
远线程使用的关键API有WriteProcessMemory、CreateRemoteThread和LoadLibrary,它们的声明如下。
BOOL WriteProcessMemory (
HANDLE hProcess, //远进程句柄
LPVOID lpBaseAddress, //远进程待写地址
LPVOID lpBuffer, //本进程空间buffer地址
DWORD nSize, //lpBuffer 所指空间的大小
LPDWORD lpNumberOfBytesWritten //返回实际写入远进程的字节数
) ;
HANDLE CreateRemoteThread (
HANDLE hProcess, //远程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全描述字,指向
SECURITY_ATTRIBUTES结构的指针
SIZE_T dwStackSize, //线程栈大小,以字节为单位表示
LPTHREAD_START_ROUTINE lpStartAddress, //一个LPTHREAD_START_ROUTINE类型
的指针,指向在远程进程中执行的函数地址
LPVOID lpParameter, //传入参数
DWORD dwCreationFlags, //创建线程的其他标志
LPDWORD lpThreadId //线程ID。如果为NULL,则不返回
);
HMODULE WINAPI LoadLibrary (
_in LPCTSTR lpFileName //待加模块的文件路径
);
远线程加载流程,步骤如下:
(1)调用WriteProcessMemory函数将外挂模块的文件路径写入游戏进程空间。
(2)调用CreateRemoteThread函数开启游戏进程的LoadLibrary远线程,以加载外挂模块。
该方式比较古老,基本的系统都已防御此法,就不再过多阐述
优点:简单,易实现。
缺点:容易被监控。只要监控本进程对LoadLibrary函数的调用,就可以很好地防御远线程加载。
依赖可信进程注入
为了躲避反外挂系统的扫描和分析人员的分析,有些时候,依赖可信任第三方进程所进行的转注入,可能会取得比较好的效果。因services.exe不易调试、权限高、隐蔽性好,所以通过木马和外挂找services.exe进程是一个明智的选择。
假设外挂进程为Bot.exe, 外挂主模块为FQ.dl, 游戏进程为Game.exe,这种转注入的步骤如下。
( 1 ) Bot.exe调用WriteProcessMemory和CreateRemoteThread函数,采用远线程注人方式将FQ.dll注人services.exe进程。Bot.exe 调用Sleep(35000)函数,使自己进入睡眠状态。
( 2 )services.exe中的FQ.dll调用CreateToolhelp32Sanpshot和Process32Next来遍历进程,判断Game.exe进程是否启动。如果发现Game.exe启动,就采用远线程或其他方式将FQ.dll 注人Game.exe进程; 如果未发现Game.exe启动,则继续循环遍历。
(3)Botexe进程睡眠状态结束,开始下一步。补充:按照常理,35 000ms已经足够services.exe进程中的FQ.dll实现对Game.exe进程的注人了。
(4) Bot.exe调用CreateToolhelp32Sanpshot和Process32Next来遍历进程,寻找services.exe进程。找到该进程后,遍历其中的模块,找到FQ,dll。
(5) Bot.exe 进程通过调用CreateRemoteThread远线程来调用FreeLibrary,将services.exe中的FQ.dll从services.exe进程中卸载。
经过上面这巧妙的5步操作,FQ.dll 悄无声息地从Bot.exe进入services.exe, 然后进入Game.exe。services.exe进程在整个注入过程中只起中转作用。
优点:隐蔽性好,不易被分析和发现。
缺点:遍历进程的方法需要改进,以防止被假进程迷惑;显示调用API也不妥。
APC注入
APC ( Asynchronous Procedure Call,异步过程调用)是在一个特定线程环境下被异步执行的函数,分为用户模式APC和内核模式APC。每个线程都有一个APC队列。在用户模式下,当线程调用SleepEx、WaitForSingleObjectEx等进人“Alterable WaitStatus"状态(可警告的等待状态)的时候,系统就会遍历该线程的APC队列,然后按照先进先出的顺序来执行这些APC。
在用户模式下,微软提供了QueueUserAPC这个API来向-一个线程插入APC。下面是QueueUserAPC的声明。
DWORD WINAPI QueueUserAPC (
_in PAPCFUNC pfnAPC, //指向一个APC函数
in HANDLE hThread, //将要插入APC的线程句柄
in ULONG PTR dwData //APC函数的参数
);
下面通过APC加载的伪代码来分析加载步骤
// 1.以suspend方式创建待加载的目标进程
if (CreateProcess (sProcName, NULL, NULL, NULL, FALSE, CREATE_ SUSPENDED, NULL, NULL, &st, &pi) )
{
// 2.目标进程地址空间分配待加载的DLL路径空间
lpDllName = VirtualAllocEx (pi.hProcess,NULL, (strlen (sDllName) +1),MEM COMMIT,PAGE READWRITE) ;
// 3.把待加载的DLL路径写人目标进程空间
if (WriteProcessMemory (pi .hProcess,lpD11Name,sD11Name, strlen(sD11Name),NULL) )
{
// 4.获取LoadLibrary的地址
LPVOID nLoadLibrary= (LPVOID) GetProcAddress (GetModuleHandle
(“kerne132.d1l”),“LoadLibraryA”) ;
// 5.调用QueueUserAPC向远线程插人-个APC,这个APC就是LoadLibrary
if (! QueueUserAPC( (PAPCFUNC) nLoadLibrary, pi. hThread, (ULONG_PTR) 1pD1
1Name) )
{
OutputDebugString(" [-] APCInject QueueUserAPC call error!") ;
dRet=-6;
}
}
}
除了可以通过VirtualAllocEx和WriteProcessMemory函数在目标进程地址空间存放待加载DLL的路径,我们还可以通过CreateFileMapping、MapViewOfFile和NtMapViewOfSection函数的组合来映射路径
优点:比较隐蔽,简单。
缺点:实现的条件比较苛刻。
消息钩子注入
SetW indowsHookEx函数是微软提供给程序开发人员进行消息拦截的一个API。不过,它的功能不仅可以用作消息拦截,还可以进行DLL注入。
SetWindwosHookEx的原型声明如下。
HHOOK Se tWi ndowsHookEx (
Int idHook, //指示将要安装的挂钩处理过程的类型。例如,idHook 为“WH_
CALLWNDPROC"时代表安装一个挂钩处理过程,在系统将消
息发送至目标窗口处理过程之前对该消息进行监视。
HOOKPROC lpfn, //指向相应的挂钩处理过程。
HINSTANCE hMod, //指示了一个DLL句柄。该DLL包含参数lpfn所指向的挂钩处理过
程。
DWORD dwThreadId //指示了一个线程标识符,挂钩处理过程与线程相关。若此参数
值为0,则该挂钩处理过程与所有现存的线程相关。
);
如果要去掉消息钩子,可以调用UnhookWindowsHookEx函数。
下面是注入的核心代码。
//利用Windows API SetWindowsHookEx 实现注入DLL
BOOL SetWinHKInject (char* pszDllPath,char* pszProcess)
{
//加载待注人的DLL到本进程空间
hMod = LoadLibrary (pszDllPath) ;
if (!hMod)
{
OutputDebugString("[+] LoadLibrary error! \n");
goto Exit;
}
//获取待注人DLL中导出的消息钩子过程函数的地址
lpFunc = (DWORD) GetProcAddress (hMod, “MyMessageProc”) ;
if (!lpFunc)
{
OutputDebugString("[+] GetProcAddress error!\n") ;
goto Exit;
}
//获取待注人DLL中导出的消息钩子过程函数的地址
lpFunc = (DWORD) GetProcAddress (hMod, “MyMessageProc”) ;
if(! lpFunc)
{
OutputDebugString("[+] GetProcAddress error!\n") ;
goto Exit;
}
//获取待注入进程的线程ID
dwThreadId = GetTargetThreadIdFromProcname (pszProcess) ;
if (! dwThreadId)
goto Exit;
//调用SetwindowsHookEx实现消息钩子注人
g hhook = SetWindowsHookEx (
WH_ GETMESSAGE, / /WH KEYBOARD, / /WH_ CALLWNDPROC,
(HOOKPROC) 1pFunC,
hMod,
dwThreadId
) ;
Exit:
if (hMod)
FreeLibrary (hMod) ;
return bSuccess;
}
//待注人DLL导出的消息钩子函数MyMessageProc
_declspec (dllexport) LRESULT MyMessageProc (int code, WPARAM wParam,
LPARAM 1Param)
{
//
// 开发人员对消息的处理
//
return CallNextHookEx(g_hhook,code,wParam,lParam);
}
优点:代码和原理简单,容易发现。
缺点:容易被发现。
劫持进程创建注入
如果能在目标进程运行起来之前获取目标进程的读、写等权限,那么注入将容易得多。以下是进程创建的关键API和调用序列。
Windows下创建进程的API是CreateProcess,它的原型如下。
BOOL CreateProcess (
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID IpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
主要介绍dwCreationFlags的一个值——CREATE_SUSPENDED的含义。
当给参数dwCreationFlags赋值CREATE_SUSPENDED来调用CreateProcess()
函数的时候,该进程的主线程将以悬挂方式启动。这时,要想恢复主线程的运
行,需要调用ResumeThread()函数。劫持进程加载就是在主线程被悬挂和恢复的这段
时间内,把PE或代码注人目标进程的。
步骤为以下三步:
(1)调用以悬挂方式调用CreateProcess()函数,创建游戏进程。
(2)采用远线程方式注入外挂模块。
(3)调用ResumThread()函数恢复游戏主线程。
由于现在很多游戏进程在启动时采用双进程保护机制,所以即使通过上面的劫
持进程成功实现加载,也只不过是注入双进程保护的第一个进程。为了再次使用劫
持进程创建机制来注人真正的游戏进程,需要认识用户态下进程创建的函数调用序列,如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Srf938Ck-1587310031537)(C:\Users\80714\Pictures\Camera Roll\QQ图片20200419222041.png)]
其中的KiFastSystemCall()函数,这个函数相当于ring3转ring0的一个通道。CreateProcess()函数最后通过KiFastSystemCall()函数进入内核态。我们可以在任意层次上通过Hook对应的函数来创建劫持进程,以达到加载的目的。例如,在ring3即将进入ing0之际,即调用KiFastSystemCall()函数的时候,劫持这个函数的调用,可以实现加载。
优点:不易防御,成功率较高。
缺点:劫持时间不宜过长,否则会被发现;劫持后,如需再次劫持,则需要Hook,因此易被检测。
。为了再次使用劫
持进程创建机制来注人真正的游戏进程,需要认识用户态下进程创建的函数调用序列,如图
[外链图片转存中…(img-Srf938Ck-1587310031537)]
其中的KiFastSystemCall()函数,这个函数相当于ring3转ring0的一个通道。CreateProcess()函数最后通过KiFastSystemCall()函数进入内核态。我们可以在任意层次上通过Hook对应的函数来创建劫持进程,以达到加载的目的。例如,在ring3即将进入ing0之际,即调用KiFastSystemCall()函数的时候,劫持这个函数的调用,可以实现加载。
优点:不易防御,成功率较高。
缺点:劫持时间不宜过长,否则会被发现;劫持后,如需再次劫持,则需要Hook,因此易被检测。