DLL注入的多种方式

DLL注入的多种方式

注册表注入

在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,因此易被检测。

你可能感兴趣的:(笔记)