《逆向工程》dll注入基础

21. windows消息钩取

偷看或操作信息的行为就是钩取(hooking),其中最基本的是消息钩取(message hooking)。

windows提供GUI,它以事件驱动(event driven)方式工作。

21.2 消息钩子

举例:

  1. 敲击键盘,WM_KEYDOWN被添加到os message queue;
  2. os从中取出消息,添加到对应应用程序的message queue;
  3. 应用,比如记事本,监视自己的队列,发现新添加的WM_KEYDOWN,于是调用相应的事件处理程序。
1
2. GetMessage api
3. DispatchMessage api
4
os
message queue
application
WinProc

可以看出,os与app之间有一条钩链(hook chain),如果在中间设置消息钩子,就能进行“中间人攻击”了。

如果同时设置多个钩子,按照顺序调用,这条链条叫做钩链。

Visual Studio提供了SPY++,这个消息钩取程序可以查看os中来往的所有消息。

21.3 SetWindowsHookEx

winuser.h的这个api可以实现消息钩子。

HHOOK SetWindowsHookEx(
  int idHook,        // hook type
  HOOKPROC lpfn,     // hook procedure
  HINSTANCE hMod,    // handle to application instance
  DWORD dwThreadId   // thread identifier);
);

HOOKPROC钩子过程是由系统调用的回调函数。安装钩子时,钩子过程需要存在于某个dll内部,且该dll的示例句柄(instance handle)即hMod.

dwThreadId为0,则钩子为Global Hook,影响所有进程。

设置好后,某个进程生成指定消息时,os会将相关dll注入相应进程,调用钩子过程。

GetProcAddress(), LoadLibrary()位于libloaderapi.h

用以下代码生成dll,项目名KeyHook


//KeyHook.cpp

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

//定义目标进程名为notepad.exe
#define DEF_PROCESS_NAME "notepad.exe"

//定义全局变量
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

//DllMain()函数在DLL被加载到进程后会自动执行
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) {
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hInstance = hinstDLL;
		break;

	case DLL_PROCESS_DETACH:
		break;
	}

	return TRUE;
}

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

	if (nCode >= 0) {

		//释放键盘按键时,bit 31 : 0 => press, 1 => release
		if (!(lParam & 0x80000000)) {
			GetModuleFileNameA(NULL, szPath, MAX_PATH);
			p = strrchr(szPath, '\\');

			//比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序或下一个钩子函数
			//_stricmp()函数用于比较字符串,i表示不区分大小写,若两个值相等则返回0
			if (!_stricmp(p + 1, DEF_PROCESS_NAME)) {
				return 1;
			}
		}
	}

	//比较当前进程名称,若非notepad.exe,则消息传递给应用程序或下一个钩子函数
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

//在C++中调用C的库文件,用extern "C"告知编译器,因为C++支持函数重载而C不支持,两者的编译规则不同
#ifdef __cplusplus
extern "C" {
#endif
	//__declspec,针对编译器的关键字,用于指出导出函数
	//当调用导出函数HookStart()时,SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链
	__declspec(dllexport) void HookStart() 
	{
		g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
	}

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

同一解决方案下新建项目KeyMain

//keyhook.h

#pragma once
#ifndef _KEYHOOK_H
#define	_KEYHOOK_H

#ifdef __cplusplus 
extern "C" {
#endif

	void HookStart();
	void HookStop();

#ifdef __cplusplus 
}
#endif 

#endif



//HookMain

#include "stdio.h"
#include "windows.h"
//Console Input/Output,定义了通过控制台进行数据输入和数据输出的函数
//主要是一些用户通过按键盘产生的对应操作,比如getch()函数等等
#include "conio.h"

#include "keyhook.h"

//定义一些常量
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

//定义两个参数为空、返回值为void的函数指针
typedef void(*PFN_HOOKSTART)();
typedef void(*PFN_HOOKSTOP)();

void main() {
	//定义及初始化句柄变量和函数指针
	HMODULE hDll = NULL;
	PFN_HOOKSTART HookStart = NULL;
	PFN_HOOKSTOP HookStop = NULL;

	//加载KeyHook.dll
	hDll = LoadLibraryA(DEF_DLL_NAME);

	//若加载不成功,则输出错误信息
	if (hDll == NULL) {
		printf("[-]无法加载%s [%d]\n", DEF_DLL_NAME, GetLastError());
		return;
	}

	//获取导出函数地址
	HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
	HookStop  = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

	//开始钩取
	HookStart();

	//直至用户输入“q”退出钩取
	printf("[*]等待输入 'q' 来停止钩取...\n");
	while (_getch() != 'q');

	//终止钩取
	HookStop();

	//卸载KeyHook.dll
	FreeLibrary(hDll);
}

win10 和 记事本都是64位的情况下,编译前要把解决平台方案改为x64!!!!!!!!!

监测工具使用Process Explorer.

运行后,任何进程发生键盘输入,OS就会将KeyHook.dll注入进程,并调用KeyboardProc(),将当前进程名称与notepad.exe比较,若相同则返回1,终止KeyboardProc()函数,这意味着截获并删除消息;若不是notepad.exe,则消息被传递给下一个钩子函数。

21.5.1 调试

查找核心代码的几种方法:

  • 逐行跟踪(略)
  • 检索api
  • 检索字符串

这里使用检索字符串方法调试KeyMain.exe。

search for - all referrenced text strings,找到主函数,调试即可。注意根据SetWindowsHookEx()参数锁定钩子过程函数的值。

按照以下步骤调试notepad.exe进程中的KeyHook.dll:

  1. debugging options - events - break on new module(dll),有新的dll装入时会暂停调试,弹出executable modules窗口;
  2. 进入keyhook.dll的ep;
  3. 在钩子函数地址处下断点;

23. dll注入

dll注入的原理就是从外部促使目标进程调用LoadLibrary(),使用LoadLibrary()api加载某个dll时,dll的DllMain()会被调用执行。被注入的dll拥有目标进程的内存访问权限。

用途:消息钩取,api钩取,恶意代码,管理员也可以用来监视用户的程序。

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) {
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		//code
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		break;
	}

	return TRUE;
}

上面的消息钩取与常规的dll注入唯一的区别是,os会直接将已经注册的钩取dll注入目标进程。

dll注入主要有3种方法:

  • CreateRemoteThread() API
  • 使用注册表,涉及AppInit_DLLs
  • 消息钩取,SetWindowsHookEx() API

CreateRemoteThread()

DebugView工具用来捕获并显示进程输出的所有调试字符串,与ProcessExplorer同一开发者,微软官网Sysinternal部分可下载。

//myhack.cpp

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

#pragma comment(lib,"urlmon.lib")

#define DEF_URL         (L"http://www.naver.com/index.html")
#define DEF_FILE_NAME   (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    TCHAR szPath[_MAX_PATH] = {0,};
    
    if( !GetModuleFileName(g_hMod,szPath,MAX_PATH))
    {
        return FALSE;
    }

    TCHAR *p = _tcsrchr(szPath,'\\');
    if(!p)
    {
        return FALSE;
    }

    _tcscpy_s(p+1,_MAX_PATH,DEF_FILE_NAME);
    URLDownloadToFile(NULL,DEF_URL,szPath,0,NULL);

    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReversed)
{
    HANDLE hThread = NULL;

    g_hMod = (HMODULE)hinstDLL;

    switch(fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        OutputDebugString(L"myhack.dll injection!!!");
        hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
        CloseHandle(hThread);
        break;
    }

    return TRUE;
}

dll被加载时,会输出调试字符串"myhack.dll injection!!!",然后创建线程调用ThreadProc(),该函数通过URLDownloadToFile() api来下载网页。

所以,myhack.dll注入notepad.exe时会下载网页。

//InjectDll.cpp

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

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
    HANDLE hProcess = NULL,hThread = NULL;
    HMODULE hMod = NULL;
    LPVOID pRemoteBuf = NULL;
    DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
	LPTHREAD_START_ROUTINE pThreadProc;

    //1. get the handle of notepad.exe
    if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID)))
    {
        _tprintf(L"OpenProcess(%d) failed!!![%d]\n",dwPID,GetLastError());
        return FALSE;
    }

    //2. alloc dwBufSize bytes memory in notepad.exe, 
	pRemoteBuf = VirtualAllocEx(hProcess,NULL,dwBufSize,MEM_COMMIT,PAGE_READWRITE);
	if (pRemoteBuf == NULL)
	{
		_tprintf(L"VirtualAllocEx() failed!!![%d]\n",  GetLastError());
		return FALSE;
	}

    //3. write the path of myhack.dll into the memory allocated
	if (!WriteProcessMemory(hProcess, pRemoteBuf, (LPCVOID)szDllPath, dwBufSize, NULL))
	{
		_tprintf(L"WriteProcessMemory() failed!!![%d]\n", GetLastError());
		return FALSE;
	}

    //4. get the address of LoadLibraryW() api from module
	hMod = GetModuleHandle(L"kernel32.dll");
	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod,"LoadLibraryW");
	if (hMod == NULL)
	{
		_tprintf(L"GetModuleHandle() failed!!![%d]\n", GetLastError());
		return FALSE;
	}
	if (pThreadProc == NULL)
	{
		_tprintf(L"GetProcAddress() failed!!![%d]\n", GetLastError());
		return FALSE;
	}

    //5. run the thread in notepad.exe
	hThread = CreateRemoteThread(hProcess,NULL,0,pThreadProc,pRemoteBuf,0,NULL);
	WaitForSingleObject(hThread,INFINITE);
	CloseHandle(hThread);
	CloseHandle(hProcess);

	return TRUE;
}

int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        //_tprintf(L"USAGE: %ls pid dll_path\n", (LPCTSTR)argv[0]);
		printf("USAGE: %s pid dll_path\n", argv[0]);
        return 1;
    }
	
    //if(InjectDll((DWORD)_tstol((const wchar_t*)argv[1]), (LPCTSTR)"myhack.dll") )
	if (InjectDll((DWORD)atoi(argv[1]), (LPCTSTR)argv[2]))
    {
        //_tprintf(L"InjectDll(\"%ls\") successfully!!!\n",argv[2]);
		printf("InjectDll(\"%s\") successfully!!!\n", argv[2]);
    }
    else
    {
        //_tprintf(L"InjectDll(\"%ls\") failed!!!\n",argv[2]);
		printf("InjectDll(\"%s\") failed!!!\n", argv[2]);
    }
    return 0;
}

上面InjectDll()的步骤:

  1. 获取目标进程的句柄;
  2. 在目标进程里开辟缓冲区;
  3. 往缓冲区写入dll路径;
  4. 获得进程中kernel32.dllLoadLibraryW() api的地址;
  5. 运行远程线程。

目标其实就是让目标进程调用LoadLibrary()加载dll。

windows中,kernel32.dll在每个进程中的加载地址都是相同的。如果不同,上面的代码运行时会有内存引用错误。

windows提供了调试api,用于访问其它进程的内存空间,常用的有:VirtualAllocEx(), VirtualFreeEx(), WriteProcessMemory(), ReadProcessMemory().

第5步中,CreateRemoteThread()原型如下:

HANDLE
WINAPI
CreateRemoteThread(
    _In_ HANDLE hProcess,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );

除了第一个参数,其余与CreateThread()相同。

AppInit_DLLs

windows注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows路径下提供了AppInit_DLLsLoadInit_DLLs两个注册表项。

把dll路径字符串写入AppInit_DLLs,然后把LoadInit_DLLs设为1,重启后,某进程加载user32.dll时,会读取AppInit_DLLs,若有值,就调用LoadLibrary()加载dll。

windows xp会忽略LoadInit_DLLs

// myhack2.cpp

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

#define DEF_CMD  L"c:\\Program Files\\Internet Explorer\\iexplore.exe" 
#define DEF_ADDR L"http://www.naver.com"
#define DEF_DST_PROC L"notepad.exe"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    TCHAR szCmd[MAX_PATH]  = {0,};
    TCHAR szPath[MAX_PATH] = {0,};
    TCHAR *p = NULL;
    STARTUPINFO si = {0,};
    PROCESS_INFORMATION pi = {0,};

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    switch( fdwReason )
    {
    case DLL_PROCESS_ATTACH : 
        if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
            break;
   
        if( !(p = _tcsrchr(szPath, '\\')) )
            break;

        if( _tcsicmp(p+1, DEF_DST_PROC) )
            break;

        wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
        if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, 
                            NULL, NULL, FALSE, 
                            NORMAL_PRIORITY_CLASS, 
                            NULL, NULL, &si, &pi) )
            break;

        if( pi.hProcess != NULL )
            CloseHandle(pi.hProcess);

        break;
    }
   
    return TRUE;
}

若加载该dll的进程为记事本,则以隐藏模式运行ie。

SetWindowsHookEx

见21章。

24. dll卸载

原理

CreateRemoteThread()是让目标进程调用LoadLibrary(),dll卸载则是让目标进程调用FreeLibrary()

CreateRemoteThread()lpStartAddressFreeLibrary()地址,lpParameter为dll句柄即可。

每个windows内核对象(kernel object)都拥有一个引用计数(reference count),LoadLibrary()则加一,FreeLibrary()则减一。

实现

// EjectDll.exe

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

#define DEF_PROC_NAME	(L"notepad.exe")
#define DEF_DLL_NAME	(L"myhack.dll")

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe;

    // Get the snapshot of the system
    pe.dwSize = sizeof( PROCESSENTRY32 );
    hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    }
    while(Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 
{
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
			              &hToken) )
    {
        _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
    }

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    {
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
    { 
        _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); 
        return FALSE; 
    } 

    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
    {
        _tprintf(L"The token does not have the specified privilege. \n");
        return FALSE;
    } 

    return TRUE;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad PID
    // use TH32CS_SNAPMODULE to get the name of DLL loaded in notepad
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
    {
        if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) || 
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
        {
            bFound = TRUE;
            break;
        }
    }

    if( !bFound )
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
        return FALSE;
    }
	
    //Get the address of FreeLibrary() api
    hModule = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0, 
                                 pThreadProc, me.modBaseAddr, 
                                 0, NULL);
    WaitForSingleObject(hThread, INFINITE);	

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

int main(int argc, char* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;
 
    // find process
    dwPID = FindProcessID(DEF_PROC_NAME);
    if( dwPID == 0xFFFFFFFF )
    {
        _tprintf(L"There is no <%s> process!\n", DEF_PROC_NAME);
        return 1;
    }

    _tprintf(L"PID of \"%s\" is %d\n", DEF_PROC_NAME, dwPID);

    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // eject dll
    if( EjectDll(dwPID, DEF_DLL_NAME) )
        _tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
    else
        _tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);

    return 0;
}

之前的keyhook注入成功后,运行该程序,keyhook.dll就会卸载。

typedef struct tagMODULEENTRY32W
{
    DWORD   dwSize;
    DWORD   th32ModuleID;       // This module
    DWORD   th32ProcessID;      // owning process
    DWORD   GlblcntUsage;       // Global usage count on the module
    DWORD   ProccntUsage;       // Module usage count in th32ProcessID's context
    BYTE  * modBaseAddr;        // Base address of module in th32ProcessID's context
    DWORD   modBaseSize;        // Size in bytes of module starting at modBaseAddr
    HMODULE hModule;            // The hModule of this module in th32ProcessID's context
    WCHAR   szModule[MAX_MODULE_NAME32 + 1];	//dll name
    WCHAR   szExePath[MAX_PATH];
} MODULEENTRY32W;
typedef MODULEENTRY32W *  PMODULEENTRY32W;
typedef MODULEENTRY32W *  LPMODULEENTRY32W;

根据InjectDll.cpp部分所说,FreeLibrary()地址在所有进程中都是一样的。

FreeLibrary()ThreadProc()都只有一个参数,所以能把CreateRemoteThread()的线程函数参数换成FreeLibrary()

WINBASEAPI
BOOL
WINAPI
FreeLibrary(
    _In_ HMODULE hLibModule
    );
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值