windows核心编程之DLL注入例子分析

来源:互联网 发布:ukcn中介怎么样 知乎 编辑:程序博客网 时间:2024/05/22 04:41
windows核心编程一书中的注入DLL的例子是一个小应用:Desktop Item Position Saver(DIPS)工具。该工具的作用是:保存桌面上图标的位置,并可在之后恢复桌面图标的位置。
实现DISP的先决条件是取得桌面各个图标的位置,我们需要先取得桌面的ListView控件的窗口句柄,向该窗口发送消息来枚举其中的元素,得到桌面图标的位置。获取桌面位置可以用下面的宏实现:
#define ListView_GetItemPosition(hwndLV, i, ppt) \    (BOOL)SNDMSG((hwndLV), LVM_GETITEMPOSITION, (WPARAM)(int)(i), (LPARAM)(POINT *)(ppt))

该宏实质上是向ListView控件发送一条LVM_GETITEMPOSITION的消息,有一个WPARAM和一个LPARAM的参数。其中LPARAM参数实际上传的是一个地址,这要求发送消息方和接收消息方必须为同一个进程,这是大多数公共控件的窗口消息的特别之处:不能跨越进程边界进行消息收发。
而桌面的ListView控件是在Windows程序管理器Explorer.exe进程中的,我们必须先把获取桌面图标的代码注入到Explorer.exe中,在Explorer.exe的进程空间中才能向ListView控件发送获取桌面图标位置的消息。这里把获取桌面图标的代码注入就是DLL注入了,DISP采用窗口挂钩注入方式。

DISP.exe的实现步骤如下:
(1)获取ListView控件的窗口句柄;要想获取ListView句柄可先用Spy++工具来查看ListView所在的窗口:
    打开Spy++,在工具栏的第五个按钮按下:


弹出一个框,将中间的Finder Tool按键拖到桌面,然后按下OK键:



可以找到桌面对应的窗口:



可以看到有一个SysListView32,这个就是我们要找的桌面的ListView控件,同时,可看到SysListView32的父窗口为SHELLDLL_DefView,SHELLDLL_DefView的父窗口为Progman,所以,可获取桌面的ListView控件的窗口句柄为:

  // The Desktop ListView window is the grandchild of the ProgMan window.   HWND hWndLV = GetFirstChild(GetFirstChild(      FindWindow(TEXT("ProgMan"), NULL)));

(2)获取创建ListView的线程标识符:
DWORD dwThreadId = GetWindowThreadProcessId(hWndLV, NULL)

(3)向创建ListView控件的线程安装一个WH_GETMESSAGE挂钩:
SetDIPSHook(dwThreadId);
这个函数是在DIPSLib.dll中实现的,展开如下:
BOOL WINAPI SetDIPSHook(DWORD dwThreadId) {   BOOL bOk = FALSE;      if (dwThreadId != 0) {      // Make sure that the hook is not already installed.      chASSERT(g_hHook == NULL);      g_dwThreadIdDIPS = GetCurrentThreadId();      // Install the hook on the specified thread      g_hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInstDll,          dwThreadId);      bOk = (g_hHook != NULL);      if (bOk) {         bOk = PostThreadMessage(dwThreadId, WM_NULL, 0, 0);      }   } else {      bOk = UnhookWindowsHookEx(g_hHook);      g_hHook = NULL;   }   return(bOk);}

这个函数里面首先将DISP.exe的线程Id存在了一个共享变量里面,其声明如下:
#pragma data_seg("Shared")HHOOK g_hHook = NULL;DWORD g_dwThreadIdDIPS = 0;#pragma data_seg()
这种声明方式是让同个DLL的多个实例可以共享同一个变量,这个变量在后面两个进程进行通信时会用到。
接着调用SetWindowsHookEx()注册挂钩到目的线程,同时,如果注册成功,则马上发送一条消息到目的线程:PostThreadMessage()。目的线程一收到消息则会马上加载DLL并调用挂钩过滤函数GetMsgProc()。

(4)目的线程(即创建ListView的线程)中,GetMsgProc()函数被执行:
LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {   static BOOL bFirstTime = TRUE;   if (bFirstTime) {      // The DLL just got injected.      bFirstTime = FALSE;      // Uncomment the line below to invoke the debugger       // on the process that just got the injected DLL.      // ForceDebugBreak();      // Create the DIPS Server window to handle the client request.      CreateDialog(g_hInstDll, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc);      // Tell the DIPS application that the server is up       // and ready to handle requests.      PostThreadMessage(g_dwThreadIdDIPS, WM_NULL, 0, 0);   }
该函数会在目的线程创建一个隐藏的对话框。这个对话框是用来和DISP.exe进行通信的,DISP.exe中通过发送窗口消息给这个对话框来执行命令,这个对话框的窗口过程如下:

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {   switch (uMsg) {      chHANDLE_DLGMSG(hWnd, WM_CLOSE, Dlg_OnClose);      case WM_APP:         // Uncomment the line below to invoke the debugger          // on the process that just got the injected DLL.         // ForceDebugBreak();         if (lParam)             SaveListViewItemPositions((HWND) wParam);         else             RestoreListViewItemPositions((HWND) wParam);         break;   }   return(FALSE);}
即收到一条WM_APP消息时,如果参数为true则保存桌面图标位置信息,如果收到的命令为false时,则恢复桌面图标的位置。SaveListViewItemPositions()和RestoreListViewItemPosition()的实现就是文章开头将的向ListView控件发送LVM_GETITEMPOSITION等消息。

同时,目的线程也可以通过之前保存的DISP.exe的线程标识符向DISP.exe发送消息:
PostThreadMessage(g_dwThreadIdDIPS, WM_NULL, 0, 0);

windows给出的这个例子程序还有很多细节可以通过源码琢磨出来,但是大体的步骤就是上面讲的几个步骤。通过这么一个实例的分析,相信可以更好的理解windows的DLL注入的使用。