(okwary) 小叹的学习园地

与天斗?不够高~ 与地斗?不够阔 与人斗? 脸皮不够厚

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

 

Hooks注入

 

 

 (okwary) 小叹的学习园地

Windows钩子的主要作用就是监视某个线程的消息流动。一般可分为:

局部钩子,只监视你自己进程中某个线程的消息流动。

远程钩子,又可以分为:

a、特定线程的,监视别的进程中某个线程的消息;

b、系统级的,监视整个系统中正在运行的所有线程的消息。

如果被挂钩(监视)的线程属于别的进程(情况a和b),你的钩子过程(hook procedure)必须放在一个动态连接库(DLL)中。系统把这包含了钩子过程的DLL映射到被挂钩的线程的地址空间。Windows会映射整个DLL而不仅仅是你的钩子过程。这就是为什么windows钩子可以用来向其他线程的地址空间注入代码的原因了。

1. 当SetWindowHookEx调用成功后,系统会自动映射这个DLL到被挂钩的线程,但并不是立即映射。因为所有的Windows钩子都是基于消息的,直到一个适当的事件发生后这个DLL才被映射。比如:

如果你安装了一个监视所有未排队的(nonqueued)的消息的钩子(WH_CALLWNDPROC),只有一个消息发送到被挂钩线程(的某个窗口)后这个DLL才被映射。也就是说,如果在消息发送到被挂钩线程之前调用了UnhookWindowsHookEx那么这个DLL就永远不会被映射到该线程(虽然SetWindowsHookEx调用成功了)。为了强制映射,可以在调用SetWindowsHookEx后立即发送一个适当的消息到那个线程。

同理,调用UnhookWindowsHookEx之后,只有特定的事件发生后DLL才真正地从被挂钩线程卸载。

2. 当你安装了钩子后,系统的性能会受到影响(特别是系统级的钩子)。然而如果你只是使用的特定线程的钩子来映射DLL而且不截获如何消息的话,这个缺陷也可以轻易地避免。看一下下面的代码片段:

BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved )

{

  if( ul_reason_for_call == DLL_PROCESS_ATTACH )

  {
    //用 LoadLibrary增加引用次数
    char lib_name[MAX_PATH];
    ::GetModuleFileName( hModule, lib_name, MAX_PATH );
    ::LoadLibrary( lib_name );
    // 安全卸载钩子
    ::UnhookWindowsHookEx( g_hHook );
  }
  return TRUE;
}

我们来看一下。首先,我们用钩子映射这个DLL到远程线程,然后,在DLL被真正映射进去后,我们立即卸载挂钩(unhook)。一般来说当第一个消息到达被挂钩线程后,这DLL会被卸载,然而我们通过LoadLibrary来增加这个DLL的引用次数,避免了DLL被卸载。

3.  使用完毕后如何卸载这个DLL?UnhookWindowsHookEx不行了,因为我们已经对那个线程取消挂钩(unhook)了。你可以这么做:
    ○在你想要卸载这个DLL之前再安装一个钩子;
    ○发送一个“特殊”的消息到远程线程;
    ○在你的新钩子的钩子过程(hook procedure)中截获该消息,调用FreeLibrary 和 UnhookwindowsHookEx(译者注:对新钩子调用)。
现在,钩子只在映射DLL到远程进程和从远程进程卸载DLL时使用,对被挂钩线程的性能没有影响。也就是说,我们找到了一种WinNT和Win9x下都可以使用的,不影响目的进程性能的DLL映射机制。

该技巧实用于在DLL需要在远程进程中驻留较长时间(比如你要子类[subclass]另一个进程中的控件)并且你不想过于干涉目的进程时比较适合使用。

 

应用HOOK技术进行DLL注入示例:

1、用BCB建立一个DLL工程(如果你用的是VC或其它,请自己对照),输入以下代码:

//===========================================================================

// 文件: UnitLib.cpp

// 说明: 演示利用钩子技术进行DLL注入.

// 将本DLL中的代码注入到指定的进程空间.

// 作者: 陶冶(无邪)

//===========================================================================





// 函数声明

extern "C" __declspec(dllexport) __stdcall

bool SetHook(DWORD dwThreadId);

extern "C" __declspec(dllexport) __stdcall

LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam);



static HHOOK hHook = NULL; // 钩子句柄

static HINSTANCE hInst; // 当前DLL句柄

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)

{

hInst = hinst;

return 1;

}

//---------------------------------------------------------------------------

// 安装钩子函数

bool __declspec(dllexport) __stdcall SetHook(DWORD dwThreadId)

{

if (dwThreadId != 0)

{

MessageBox(NULL, ("DLL已经注入!\nThreadId = " +

IntToStr(dwThreadId)).c_str(),"DLL",

MB_ICONINFORMATION + MB_OK);

    // 安装指定线程的钩子

hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MyProc,

hInst,dwThreadId);

if (hHook != NULL)

return true;

}else

{

MessageBox(NULL, "DLL即将从记事本进程空间中撤出!","DLL",

MB_ICONINFORMATION + MB_OK);

return (UnhookWindowsHookEx(hHook));

}

return true;

}



// 钩子函数

LRESULT CALLBACK __declspec(dllexport) __stdcall

MyProc(int nCode, WPARAM wParam, LPARAM lParam)

{

  // 因为只是演示DLL注入,所以这里什么也不做,交给系统处理

return (CallNextHookEx(hHook, nCode, wParam, lParam));

}

//---------------------------------------------------------------------------



该DLL中有两个函数,一个为安装钩子函数(SetHook),另一个为钩子函数(MyProc)。其中安装钩子函数提供了一个参数,由该参数指定安装到哪个线程,如果该参数为0,则卸载钩子。

  编译该工程,即生成我们要用来注入到指定进程中的DLL文件了。

    

2、建立测试工程。用BCB建立一个应用程序工程,在窗体中添加两个按钮,一个用来安装线程钩子,一个用来卸载。代码如下:

//---------------------------------------------------------------------------

// SetHook函数原型声明

typedef BOOL (WINAPI *LPSETHOOK)(unsigned long dwThreadId);



//---------------------------------------------------------------------------

__fastcall TfrmMain::TfrmMain(TComponent* Owner)

: TForm(Owner)

{

}

//---------------------------------------------------------------------------

// 安装钩子

void __fastcall TfrmMain::Button1Click(TObject *Sender)

{

String szPath;

LPSETHOOK lproc;

HANDLE hDll;

BOOL bRet;

PROCESS_INFORMATION info;

STARTUPINFO start;



memset(&start, 0, sizeof(start));

// 取得要载入的DLL文件名

szPath = Application->ExeName;

szPath = szPath.SubString(0, szPath.Length()

- String(StrRScan(szPath.c_str(),'\\')).Length());

szPath = szPath + "\\DllLib.dll";

  // 载入DLL

hDll = LoadLibrary(szPath.c_str());

if (hDll != NULL)

{

lproc = (LPSETHOOK)GetProcAddress(hDll,"SetHook");

if (lproc != NULL)

{

// 因为没有适当的工具可以取得线程ID,也为了简单起见,所以这里新创建了一个记事本进程,以便取得它的线程ID,对其安装钩子,把我们的DLL注入到记事本进程中。

bRet = CreateProcess(NULL,

"c:\\winnt\\system32\\notepad.exe",

NULL,

NULL,

TRUE,

0,

NULL,

NULL,

&start,

&info);

if (bRet != 0)

{

if((*lproc)(info.dwThreadId) == false)

ShowMessage("Sethook failed with error " +

IntToStr(GetLastError()));

}

else

{

ShowMessage("CreateProcess failed with error " +

IntToStr(GetLastError()));

}

}

}

}

//---------------------------------------------------------------------------

// 卸载钩子

void __fastcall TfrmMain::Button2Click(TObject *Sender)

{

String szPath;

LPSETHOOK lproc;

HANDLE hDll;



szPath = Application->ExeName;

szPath = szPath.SubString(0, szPath.Length()

- String(StrRScan(szPath.c_str(),'\\')).Length());

szPath = szPath + "\\DllLib.dll";

hDll = LoadLibrary(szPath.c_str());

if (hDll != NULL)

{

lproc = (LPSETHOOK)GetProcAddress(hDll,"SetHook");

if (lproc != NULL)

(*lproc)(0);

}

}

//---------------------------------------------------------------------------

  接下来生成可执行文件,点击第一个安装钩子按钮,然后你就可以用我们最开始写的查看模块的工具来查看了,你将会在模块中看到你刚才DLL的路径及文件名,这表明我们已经成功地将自己的DLL注入到了记事本进程空间。点击卸载按钮后,再查看记事本进程中的模块,将不会看到我们DLL文件的完整文件名,这表明已经成功撤消了对记事本进程的注入。

posted on 2008-12-20 02:07  okwary  阅读(566)  评论(0编辑  收藏  举报
ggg