线程注入
今天共享一篇线程注入的知识,提到注入大家会 想到的是不好的名词对吗,其实注入是在调试工具中最常用的如 VC 自带的DLL查看器(Depends.Exe)、微点 的主动防御软件、Symbian 内存泄露检测工具HookLogger 等等。
本文适合人群为高级程序员、高级技术人员和中 级程序员等。如果对进程,线程,虚拟内存管理,DLL以及字符集编码不了解的,那就回头翻 一翻相关知识再看本文。本文用到了大量的WIN32 API,也希望大家有关于WIN32 API的储备知识来阅读本文。本文的DLL主 要是获取目标进程所加载的DLL。目标进程是我们获取的任何一个进程。
切入主题,何谓注入?注入就是把自己的DLL或者代码注入的目标进程中,来达到获取相关信息的目的。在此我想分三步介绍,第一步先介绍注入整个思 路,第二步画一个顺序图给大家一个直观的了解,第三步就是贴代码,带大家进入代码的世界。
第一步思路介绍
首先要提升本进程权限,提权代码不做重点介 绍,在第三步上会给出。其次是系统进程的查找,不做介绍,在第二步中给出流程图,在第三步中给出代码;最后还是讲我们的线程注入。
刚才介绍了线程注入,线程注入就是把自己的DLL或 者代码注入的目标进程中,来达到获取相关信息的目的。那如何让它们加载我们的代码或者DLL呢, 在WIN32 中提供了LoadLibrary这个函数,就可以提供这个功能,具体用法大家可以参考一下MSDN的介绍,在此不做介绍了。但有一点要注意,在编码使用字符集的时候要正确的引用,否则会调用失败。就 以LoadLibrary为例,看一看在代码中的定义吧:
#ifdef UNICODE
#define LoadLibrary LoadLibraryW
#else
#define LoadLibrary LoadLibraryA
#endif // !UNICODE
在使用UNICODE字符集时使用的是LoadLibraryW,在使用ASCII字 符集时用的是LoadLibrayA。有了LoadLibrary还是不 行,因为Windows函数只允许一个进程对它自己进程操作,这样可以防止一个进程对对另一个进程进行破坏。但是Windows也提供了一些函数允许一个进程对另外一个进程操作。如WIN 32 API CreateRemoteThread就是其中之一,查MSDN 可 知CreateRemoteThread 就可以实现我们在目标进程中创建一个新线程的目的。其原 型如下:
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES
lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE
lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数我简单的介绍一下吧,HANDLE hProcess 就是我们想要注入进程的句柄;LPSECURITY_ATTRIBUTES lpThreadAttributes,
设置我们线程安全属性的,可以不设置其值;SIZE_T dwStackSize,设置我们线程栈需要空间的大小;LPTHREAD_START_ROUTINE lpStartAddress
函数地址;LPVOID lpParameter 函数需要的参数;DWORD dwCreationFlags 我们创建线程的FLAG,LPDWORD lpThreadId,创建线程的ID。通过这些知识我们是不是有思路了呢?是的,lpStartAddress就是我们LoadLibrary的地址,lpParameter参数就是我们的DLL的路径,或者是代码。这样就实现了注入。
或许有人会问我们的代码注入了系统进程,系统系统又是如何执行我们DLL中的代码呢?原因是:当DLL被加载到进程执行时,系统会向我们的DLL发一个DLL_PROCESS_ATTACH和一个DLL_THREAD_ATTACH消息。DLL_PROCESS_ATTACH是DLL在加载到进程的地址空间进行相关的初始化发送的消息。而DLL_THREAD_ATTACH 则是系统执行线程时发送
的一个消息。所有当我们的DLL接受到如上消息时则自动
运行。
方法简单吧,但是有几个地方需要注意的。第一,我们要获取LoadLibrary的真实地址。第二,输入有效的参数。刚才讲过进程是不能直接操
作另一个进程的。所以我们的lpParameter参数,在本进程中是有
效的,但是在目标进程中是无效的。那该如何使其在目标进程中叶有效呢?我们看MSDN上的VirtualAllocEx 和WriteProcessMemory这两个API。他们原型如下:
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
简单介绍一下相关参数 HANDLE hProcess 就是目标进程句柄,LPVOID lpAddress 就是指定位置要开辟的
内存,可有可无;SIZE_T dwSize要开辟的大小;DWORD flAllocationType,分配的类型;DWORD flProtect 分配内存具有的属性。
返回值就是在目标进程中分配内存的首地址。翻译的或许不好大家可有参考MSDN。
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T*
lpNumberOfBytesWritten
);
同理。HANDLE hProcess 目标进程的句柄;LPVOID lpBaseAddress开辟的地址空间;LPCVOID lpBuffer 要写入的数据的地址;SIZE_T nSize写入的字节数;SIZE_T* lpNumberOfBytesWritten
实际写入的字节数。
通过如上两个API就可有在目标进程中开
辟有效地址,在其地址中写入有效数据了。这样目标进程就可以识别我们DLL的路径了。第三,注意
编码的字符集,不要把UNICODE和ASCII码编码时的函数搞混了。第四,不要忘记释放在目标进程中开辟的空间,具体应
用可参考 WIN32 的API VirtualFreeEx函数,也不要忘记Dll执行完后要释放加载的Dll哦。
所需理论知识介绍完了,接下来进行我们的第二步流程图介绍。流程图介绍分两步一步是进程的操作,一步是注入的操作。
流程图如下:
进程查找
创建进程 |
提升进程权限 |
遍历进程,保存至容器中 |
查找要注入的进程 |
注入 |
线程注入
目标进程 |
开辟存储数据的空间 |
写入有效数据 |
在目标进程中创建线程 |
我们的DLL被调用 |
第三步主要是共享主要代码。
第一 我们DLL的代码。DLL的代码的功能就是获取目标进程加载的DLL。
void
GetProcessInfoFromRemoteProcess(HMODULE hModule)
{
// 获取计算机名称
WSADATA data;
char hostName[MAX_PATH] = {0};
int error = -1;
// 网路环境初始化
if ( 0 == WSAStartup(MAKEWORD(2,2), &data))
{
// 成功返回0
error =
gethostname(hostName,MAX_PATH);
}
DWORD dProcID =
::GetProcessId(hModule);
HANDLE hModuleSnap =
INVALID_HANDLE_VALUE;
MODULEENTRY32 me32;
// 创建文件映射文件
hModuleSnap =
CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dProcID );
if( hModuleSnap == INVALID_HANDLE_VALUE )
{
return ;
}
// 初始化
me32.dwSize = sizeof( MODULEENTRY32 );
// 遍历开始
if( !Module32First( hModuleSnap, &me32 ) )
{
return ;
}
char path[MAX_PATH] = {0};// 文件路径
if (error)
{
sprintf(path,"%s","c:\\han_wjian.log");
}
else
{
sprintf(path,"c:\\%s.log",hostName);
}
FILE* file =
fopen(path,"w+"); // 打开一个文件指针
if (NULL == file)
{
return;
}
char buf[MAX_PATH] = {0}; // 文件缓冲区
sprintf(buf," 进程名: %S\r\n", me32.szModule);
fwrite(buf,1,strlen(buf)+1,file);
while( Module32Next( hModuleSnap, &me32 ) )
{
sprintf(buf,"加载动态库 = %S\r\n", me32.szExePath);
// 遍历到的数据写入文件中
fwrite(buf,1,strlen(buf)+1,file);
}
CloseHandle(
hModuleSnap );
fclose(file); // 关闭文件指针,释放内存
WSACleanup();
}
第二 提升进程权限的代码
void GetSystemToken(HANDLE hSelf)
{
// 打开当前进程的句柄
HANDLE GetHandle;
if (NULL != hSelf)
{
if
(::OpenProcessToken(hSelf,
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&GetHandle))
{
TRACE(L"OpenProcessToken ID = %d\n",GetLastError());
TOKEN_PRIVILEGES
Token; // 指令牌
Token.PrivilegeCount = 1;
DWORD hLeng =
sizeof(TOKEN_PRIVILEGES);
// 遍历Debug 调
试权限的LUID
if
(::LookupPrivilegeValue(NULL, SE_DEBUG_NAME,&Token.Privileges[0].Luid))
{
Token.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//
赋予进程的权限
AdjustTokenPrivileges(GetHandle, FALSE,
&Token,sizeof(Token),NULL,NULL);
TRACE(L"AdjustTokenPrivileges ID = %d\n",GetLastError());
}
TRACE(L"LookupPrivilegeValue ID = %d\n",GetLastError());
}
::CloseHandle(GetHandle);
}
}
第三 进程遍历代码
BOOL GetProcess()
{
HANDLE hProcessSnap;
PROCESSENTRY32
pe32; // 进程属性
// 创建映射句柄.
hProcessSnap =
CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
if( hProcessSnap == INVALID_HANDLE_VALUE )
{
return FALSE;
}
pe32.dwSize = sizeof( PROCESSENTRY32 );
// 遍历系统进程
if( !Process32First( hProcessSnap, &pe32 ) )
{
CloseHandle(
hProcessSnap );
return FALSE;
}
while( Process32Next( hProcessSnap, &pe32 ) )
{
// 把查找到的进程放入容器中
m_mapProcess.insert(MapContinar::value_type(pe32.szExeFile,
pe32.th32ProcessID));
}
CloseHandle(
hProcessSnap );
return( TRUE );
}
第四
是注入代码
void Inject(int iProID)
{
#define MAXMEMORY 4000
#define REMOTESAPCE 125
TCHAR Buf[REMOTESAPCE] = {0} ; //
注入DLL的路径
lstrcpy(Buf, L"GetProcessInfo.dll");
int leng = lstrlen(Buf) +
1;
leng = leng *
sizeof(TCHAR);
// 打开目标进程并赋予足够权限
HANDLE hProcess =
::OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ, FALSE, iProID);
TRACE(L"OpenProcess ID = %d\n",GetLastError());
if (NULL != hProcess)
{
// 在目标进程中开辟足够空间存放 数据
PWSTR pRemoteSpace
= NULL; //
在目标进程里开辟的空间
pRemoteSpace = (PWSTR)::VirtualAllocEx(hProcess, NULL,MAXMEMORY,MEM_COMMIT,PAGE_READWRITE);
TRACE(L"VirtualAllocEx ID = %d\n",GetLastError());
if (NULL != pRemoteSpace)
{
if (::WriteProcessMemory(hProcess, pRemoteSpace, (PVOID)Buf,leng,NULL))
{
TRACE(L"WriteProcessMemory ID = %d\n",GetLastError());
// 获取LoadLibary的真实地址
PTHREAD_START_ROUTINE pFun = (PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(L"Kernel32.dll"),"LoadLibraryW");
TRACE(L"LoadLibraryW ID = %d\n",GetLastError());
if (NULL != pFun)
{
DWORD ThreadID =
0;
HANDLE hThread =
::CreateRemoteThread(hProcess,NULL,0,pFun,pRemoteSpace,0,&ThreadID);
TRACE(L"Child Thread is = %d
Errord = %d\n",ThreadID,GetLastError());
if (NULL != hThread)
{
DWORD hLibModule = 0;
::WaitForSingleObject(hThread, INFINITE);
::GetExitCodeThread( hThread, &hLibModule ); //
获取进程执行码
if (!hLibModule)
{
return;
}
// 释放在目标进程中开辟的空间
::VirtualFreeEx(hProcess,pRemoteSpace,0,MEM_RELEASE);
// 卸载加载的dLL
UnInject(iProID,
Buf, hProcess);
::CloseHandle(hThread);
}
}
}
}
}
::CloseHandle(hProcess);
}
第五是卸载代码
void UnInject(int iProID ,PCWSTR iName,HANDLE hThread)
{
HANDLE hProcessSnap;
MODULEENTRY32 pe32; //
进程加载模块属性
// 创建映射句柄.
hProcessSnap =
CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, iProID);
if( hProcessSnap == INVALID_HANDLE_VALUE )
{
return ;
}
pe32.dwSize = sizeof( MODULEENTRY32);
// 遍历系统进程
if( !Module32First( hProcessSnap, &pe32 ) )
{
CloseHandle(
hProcessSnap );
return ;
}
while( Module32Next( hProcessSnap, &pe32 ) )
{
TRACE(L"Remote Process had been load dll =
%s\n",pe32.szModule);
if ( (_wcsicmp(pe32.szExePath, iName) ==
0)|| (_wcsicmp(pe32.szModule, iName) == 0))
{
TRACE(L"############### Find Success
#######################\n");
// 获取系统卸载DLL 的api
PTHREAD_START_ROUTINE pFun =
(PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(L"Kernel32.dll"),"FreeLibrary");
HANDLE hTmp =
::CreateRemoteThread(hThread,NULL,0,pFun,pe32.modBaseAddr,0,NULL);
if (NULL != hTmp)
{
::WaitForSingleObject(hTmp, INFINITE);
::CloseHandle(hTmp);
}
break;
}
}
CloseHandle(
hProcessSnap );
}