目录
一、DLL概念
你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
二、静态链接库
静态链接库就是你使用的.lib文件,库中得代码最后需要连接到你的可执行文件中去,所以静态连接的可执行文件一般比较大一些。
#pragma comment(lib,"XXX.lib") 加载静态库。
三、动态库
动态链接库(Dynamic-Link Library),简称DLL。它是基于Windows程序设计的一个非常重要的组成部分。自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。 在建立应用程序的可执行文件时,不必将DLL链接到程序中,而是在运行时动态装载DLL,装载时DLL被映射到进程的地址空间中。 动态链接库通常都不能直接运行。 DLL是一种基于Windows的程序模块,它不仅可以包含可执行代码,还能有数据,各种资源,扩大了库文件的使用范围。比如:Windows API中的所有函数都包含在DLL中。我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。
四、静态库和动态库
静态库:
在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。在多个同样的程序执行时,系统保留了许多重复的代码副本,造成内存资源浪费。
动态库:
在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。使用DLL的动态链接并不是将库代码拷贝,只是在程序中记录了函数的入口点和接口,在程序执行时才将库代码装入内存;不管多少程序使用DLL,内存中都只有一个DLL的副本;当没有程序使用它时,系统就将它移出内存,减少了对内存和磁盘的要求。
静态库中不能再包含其他的动态或静态库,而动态库则可以。
dll显示加载:
LoadLibrary(...):该 API 用于加载指定的DLL;
GetProcAddress(...):该 API 用于获取DLL中导出函数的指针, 即导出函数的入口点;
FreeLibrary(...):该 API 用于卸载指定的DLL。
dll入口点函数
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } ①hModule参数:指向DLL本身的实例句柄;
②ul_reason_for_call参数:指明了DLL被调用的原因,可以有以下4个取值:
■DLL_PROCESS_ATTACH: 当DLL被进程 <<第一次>> 调用时,导致DllMain函数被调用,同时 ul_reason_for_call的值为DLL_PROCESS_ATTACH,如果同一个进程后来再 次调用此DLL时,操作系统只会增加DLL的使用次数,不会再用 DLL_PROCESS_ATTACH调用DLL的DllMain函数。
■DLL_PROCESS_DETACH: 当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的 ul_reason_for_call值是DLL_PROCESS_DETACH。 ★如果进程的终结是因为调用了TerminateProcess,系统就不会用 DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程 结束前没有机会执行任何清理工作。
■DLL_THREAD_ATTACH: 当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。 新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。
■DLL_THREAD_DETACH: 如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。 ★注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数
③lpReserved参数:保留,目前没什么意义。
五、远程DLL注入
使用这种技术和程序功能通常位于一个DLL中,在系统启动的时候,一个EXE程序会将这个DLL加载至某些系统进程(如Explorer.exe)中运行。 这样一来,普通的进程管理器就很难发现这个程序的功能模块,而且即使发现了也很难清除,因为只要寄生的进程不终止运行,那么这个DLL就不会在内存中卸载,用 户也就无法在资源管理器中删除这个DLL文件。
注入体—一个简单的DLL:
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
switch ( fdwReason )
{
case DLL_PROCESS_ATTACH:
{
MessageBox( NULL, _T("DLL已进入目标进程。"), _T("信息"), MB_ICONINFORMATION );
}
break;
case DLL_PROCESS_DETACH:
{
MessageBox( NULL, _T("DLL已从目进程卸载。"), _T("信息"), MB_ICONINFORMATION );
}
break;
}
return TRUE;
}
在DLL被加载和卸载的时候调用了MessageBox,这是用来显示我的远程注入/卸载工作是否成功完成。
查找目标进程:
DWORD FindTarget( LPCTSTR lpszProcess )
{
DWORD dwRet = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof( PROCESSENTRY32 );
Process32First( hSnapshot, &pe32 );
do{
if ( lstrcmpi( pe32.szExeFile, lpszProcess ) == 0 )
{
dwRet = pe32.th32ProcessID;
break;
}
} while ( Process32Next( hSnapshot, &pe32 ) );
CloseHandle( hSnapshot );
return dwRet;
}
打开目标进程:
HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessID );
写入数据进目标进程内存:
Win32下每个应用程序都会把kernel32.dll加载到进程地址空间中一个固定的地址
// 向目标进程地址空间写入DLL名称
DWORD dwSize, dwWritten;
dwSize = lstrlenA( lpszDll ) + 1;
LPVOID pAddr = VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE );
if ( NULL == lpBuf )
{
CloseHandle( hProcess ); // 失败处理
}
if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, dwSize, &dwWritten ) )
{ // 要写入字节数与实际写入字节数不相等,仍属失败
if ( dwWritten != dwSize )
{
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hProcess );// 失败处理
}
}
else
{
CloseHandle( hProcess );// 失败处理
}
// 使目标进程调用LoadLibrary,加载DLLDWORD dwID;LPVOID pFunc = LoadLibraryA;
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwID );
收尾:
// 等待LoadLibrary加载完毕
WaitForSingleObject( hThread, INFINITE );
// 释放目标进程中申请的空间
VirtualFreeEx( hProcess, pAddr, dwSize, MEM_DECOMMIT );
CloseHandle( hThread ); CloseHandle( hProcess );
通过CreateRemoteThread在目标进程中另外开辟了一个LoadLibrary的线程,所以我们必须等待这个线程运行完毕才能够释放那段先前申请的内存。
六、钩子
钩子链表和钩子子程:
每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。 一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始, 而最早安装的钩子放在最后,也就是后加入的先获得控制权。
Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。
钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。
钩子函数:
HHOOK SetWindowsHookEx(
int idHook, // 钩子的类型,即它处理的消息类型
HOOKPROC lpfn, // 钩子子程的地址指针。钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
HINSTANCE hMod, // 应用程序实例的句柄。标识包含lpfn所指的子程的DLL。 如果dwThreadId 标识当前进程创建的一个线程, 而且子程代码位于当前进程,hMod必须为NULL。可以很简单的设定其为本应用程序的实例句柄。
DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识, 如果为0,钩子子程与所有的线程关联,即为全局钩子。
);
函数成功则返回钩子子程的句柄,失败返回NULL。
钩子回调函数:
LRESULT CALLBACK HookProc ( int nCode, WPARAM wParam, LPARAM lParam );
HookProc是回调函数名。 nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。 wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。
解除钩子:
在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个 SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值, 返回值的类型依赖于钩子的类型。这个函数的原型如下:
LRESULT CallNextHookEx ( HHOOK hhk; int nCode; WPARAM wParam; LPARAM lParam; );
hhk为当前钩子的句柄,由SetWindowsHookEx()函数返回。 NCode为传给钩子过程的事件代码。 wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。 钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。
钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下:
UnHookWindowsHookEx ( HHOOK hhk; ); 函数成功返回TRUE,否则返回FALSE。