【逆向】【Part 3】DLL注入

目录

一、通过自制调试器来理解其原理

1.调试器的工作原理

实现反汇编功能(重点)

重点分析exception_debug_event

重点:1.对调试器程序增加异常处理操作功能,核心API, CONTEXT结构

二、DLL注入

重点:2.DLL注入的三种基本方法

1.利用全局消息钩子(Windos消息钩取( SetWindowsHookEx() API ))

2.写注册表

3. 利用远程线程注入来实现DLL注入

扫描二维码关注公众号,回复: 8493564 查看本文章

一、Windows消息钩取

SetWindowsHookEx()

键盘消息钩取练习

练习示例

重点:3.通过键盘消息钩取的例子,理解如何实现DLL注入的,重要的API,回调函数

二、创建远程线程

重点:4. 创建远程线程完成DLL注入,步骤,关键API 函数,dllmain,LoadLibrary,Creatremotethread

总结:创建远程注入的步骤:

三、注册表 Applnit_DLLs

重点:5.修改注册表,限制


一、通过自制调试器来理解其原理

1.调试器的工作原理

前置基础知识:

  • stdafx.h的作用

当我们使用AppWizard(应用程序(Application)向导(wizard))来自动生成某些项目的时候,系统会自动把所需要include的头文件在stdafx.h中先include一下,这样,我们只需要直接include这个stdafx.h文件即可.

因为同一个项目中的不同源文件CPP都包含相同的include文件,这样,为每个.CPP文件都重复include这些文件就显得很傻了。

https://blog.csdn.net/boyaaboy/article/details/89838658

  • _tmain()

_tmain()是为了支持unicode所使用的main一个别名而已,既然是别名,应该有宏定义过的,在哪里定义的呢?就在那个让你困惑的<stdafx.h>里,有这么两行

#include <stdio.h>

#include <tchar.h>

可以在头文件<tchar.h>里找到_tmain的宏定义

#define _tmain main

所以,经过预编译以后, _tmain就变成main了

main()是标准C++的函数入口。标准C++的程序入口点函数,默认字符编码格式ANSI函数签名为:
 

int main();
int main(int argc, char* argv[]);
  • int argc, char* argv[]命令行参数

https://blog.csdn.net/renyhui/article/details/19112315

注意:

输出的是一串数字而非我们想要的路径,这是因为_TCHAR的声明:typedef wchar_t _TCHAR

在Unicode中_TCHAR被认为是宽字符,输出宽字符时我们要使用wcout进行输出。不然下面输出的是数字。

修改后代码:

#include "stdafx.h"
#include <iostream>
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	wcout<<"argc"<<argc<<endl;
	for(int i=0;i<argc;i++)
		wcout<<"argv["<<i<<"]"<<argv[i]<<endl;

	return 0;
}

运行结果:

则:

argv[0]表示输入程序的路径及名称

argv[1],argv[2]...表示自己输入的参数

argc用来统计参数的个数,因为路径为默认的参数,所以argc至少为1

先大概了解一下上面的这些知识,主要是写代码刚临门就被绊倒了,看一下再向下走。

注意PROCESS_INFORMATION在头文件#include "windows.h"中,(我用的是VC++2010)

下面是关于调试器的代码:

#include "stdafx.h"
#include "windows.h"

int _tmain(int argc, _TCHAR* argv[])
{
	//预先定义的数据结构
	PROCESS_INFORMATION pi;//在新建进程函数CreateProcess中使用用来返回新建进程的相关信息。
/*typedef struct{
HANDLE hProcess; //新建进程的内核句柄
HANDLE hThread; //新建进程中主线程的内核句柄
DWORD dwProcessId; //新建进程的ID
DWORD dwThreadId; //新建进程主线程ID
}PROCESS_INFOMATION,*LPPROCESS_INFOMATION;
*/
	STARTUPINFO si;//指定新进程的主窗口特性的一个结构

	if(argc<2){
		fprintf(stderr,"C:\\>%s <sample.exe>\n",argv[0]);//fprintf格式化输出到一个流文件中
		//stderr:标准错误输出设备。标准错误输出的屏幕显示,当参数少于两个提示应该正确输入的格式
		return 1;
	}
	memset(&pi,0,sizeof(pi));
	memset(&si,0,sizeof(si));//结构体全部填充为0(每个字节都是0)
	si.cb = sizeof(STARTUPINFO);
	/*DWORD cb;
	包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,
	它可用作版本控制手段,应用程序必须将cb初始化为sizeof(STARTUPINFO)。 */

BOOL r = CreateProcess(
//CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。
NULL,//可执行模块名称
argv[1],//命令行字符串
NULL,//安全属性
NULL,//也是安全属性
FALSE,//句柄继承选项
//接下来这个是创建标志(有三个用|连接)
NORMAL_PRIORITY_CLASS  //指示这个进程没有特殊的任务调度要求。
| CREATE_SUSPENDED //新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行。
| DEBUG_PROCESS,
/*如果这个标志被设置,调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程。
系统把被调试程序发生的所有调试事件通知给调试器。
如果你使用这个标志创建进程,只有调用进程(调用CreateProcess函数的进程)
可以调用WaitForDebugEvent函数。*/
NULL,//新进程的环境变量块
NULL,//当前路径
&si,//启动信息,指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。
&pi//进程信息,指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。
);
	if(!r)//如果函数执行成功,返回非零值。如果函数执行失败,返回零.
		return -1;
	ResumeThread(pi.hThread);//线程恢复函数,使线程的挂起时间计数减一

	while(1){
		DEBUG_EVENT de;//DEBUG_EVENT结构描述了调试事件
		if(!WaitForDebugEvent(&de,INFINITE))//de等待无限长时间
/*Kernel32.dll提供WaitForDebugEvent来监听调试事件,当目标进程发生调试事件时会通知我们的调试器
进行处理,我们用一个循环不断调用此函数来在处理完一个调试事件后立即监听下一个调试事件。
函数原型
WaiteForDebugEvent(LPDEBUG_EVENT _DEBUG_EVENT,DWORD dwMilliseconds)
第一个参数指向event结构,这个结构描述了一个调试事件,第二个参数为等待事件的毫秒数。
返回一个BOOL值*/
			break;
		DWORD dwContinueStatus = DBG_CONTINUE;//表示已处理异常,继续执行在异常代码
		switch (de.dwDebugEventCode)
		{
		case CREATE_PROCESS_DEBUG_EVENT:
			printf("CREATE_PROCESS_DEBUG_EVENT\n");
			break;
		case CREATE_THREAD_DEBUG_EVENT:
			printf("CREATE_THREAD_DEBUG_EVENT\n");
			break;
		case EXIT_THREAD_DEBUG_EVENT:
			printf("EXIT_THREAD_DEBUG_EVENT\n");
			break;
		case EXIT_PROCESS_DEBUG_EVENT:
			printf("EXIT_PROCESS_DEBUG_EVENT\n");
			break;
		case EXCEPTION_DEBUG_EVENT:
			{
			DWORD r = de.u.Exception.ExceptionRecord.ExceptionCode;//哪一种异常
			if(r!=EXCEPTION_BREAKPOINT)//如果不是断点异常
			dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;//没有被处理掉
			printf(" EXCEPTION_DEBUG_EVENT\n");
			}
			break;
		case OUTPUT_DEBUG_STRING_EVENT:
			printf("OUTPUT_DEBUG_STRING_EVENT\n");
			break;
		case RIP_EVENT:
			printf("RIP_EVENT\n");
			break;
		case LOAD_DLL_DEBUG_EVENT://调试中会出现各种事件都进行捕获
			printf("LOAD_DLL_DEBUG_EVENT\n");
			break;
		case UNLOAD_DLL_DEBUG_EVENT:
			printf("UNLOAD_DLL_DEBUG_EVENT\n");
			break;
		}
		if(de.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT)//进程退出调试异常
			break;
		ContinueDebugEvent(
//ContinueDebugEvent函数:此函数允许调试器回复先前由于调试事件而挂起的线程。
			de.dwProcessId,de.dwThreadId,dwContinueStatus);
	}
	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);
	return 0;

}

百科:https://baike.baidu.com/item/PROCESS_INFOMATION%E7%BB%93%E6%9E%84/6900641?fr=aladdin

该结构体用于在创建一个新的进程时,这个新进程包含的属性,以及与父进程之间的关系。

  • PROCESS_INFOMATION结构
typedef struct{
HANDLE hProcess; //新建进程的内核句柄
HANDLE hThread; //新建进程中主线程的内核句柄
DWORD dwProcessId; //新建进程的ID
DWORD dwThreadId; //新建进程主线程ID
}PROCESS_INFOMATION,*LPPROCESS_INFOMATION;

PROCESS_INFOMATION结构主要在新建进程函数CreateProcess中使用用来返回新建进程的相关信息。

  • STARTUPINFO

用于指定新进程的主窗口特性的一个结构。 

很多参数参考(https://baike.baidu.com/item/STARTUPINFO/2373656

(代码中用到的话再具体分析)

  • fprintf

fprintf是C/C++中的一个格式化库函数,位于头文件<cstdio>中,其作用是格式化输出到一个流文件中;

函数原型为int fprintf( FILE *stream, const char *format, [ argument ]...),

fprintf()函数根据指定的格式(format),向输出流(stream)写入数据(argument)。

  • stderr(标准错误输出设备)

The standard error stream is the default destination for error messages and other diagnostic warnings. Like stdout, it is usually also directed by default to the text console (generally, on the screen).

stderr can be used as an argument for any function that takes an argument of type FILE* expecting an output stream, like fputs or fprintf.

  • CreateProcess

WIN32API函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。

如果函数执行成功,返回非零值。

如果函数执行失败,返回零,可以使用GetLastError函数获得错误的附加信息。

  • ResumeThread线程恢复函数

使线程的挂起时间计数减一。创建一个挂起的线程或者手动挂起一个线程后调用。调用该函数后线程不一定会立刻执行,而是由操作系统继续调度,直到计数为0,系统为其分配资源时才开始执行。

DWORD ResumeThread{
HANDLE hThread  //线程句柄
);
  • DEBUG_EVENT结构描述了调试事件。

https://docs.microsoft.com/en-us/previous-versions/bb202796(v=msdn.10)?redirectedfrom=MSDN

http://www.yfvb.com/help/win32sdk/index.htm?page=html/5j5av0.htm

typedef struct _DEBUG_EVENT { 
  DWORD dwDebugEventCode; //调试事件编号。
  DWORD dwProcessId; //进程ID
  DWORD dwThreadId; //线程ID
  union { 
    EXCEPTION_DEBUG_INFO Exception; 
    CREATE_THREAD_DEBUG_INFO CreateThread; 
    CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; 
    EXIT_THREAD_DEBUG_INFO ExitThread; 
    EXIT_PROCESS_DEBUG_INFO ExitProcess; 
    LOAD_DLL_DEBUG_INFO LoadDll; 
    UNLOAD_DLL_DEBUG_INFO UnloadDll; 
    OUTPUT_DEBUG_STRING_INFO DebugString; 
    RIP_INFO RipInfo; 
  } u; 
} DEBUG_EVENT;

这个结构体简单了解下,第一个成员dwDebugEventCode代表调试事件编号。

dwProcessID为进程ID,dwThreadID为线程ID

union结构中的数据会随着dwDebugEventCode的不同而发生变化。

dwDebugEventCode可以取下列值:

EXCEPTION_DEBUG_EVENT 发生异常

CREATE_THREAD_DEBUG_EVENT 创建线程

CREATE_PROCESS_DEBUG_EVENT 创建进程

EXIT_THREAD_DEBUG_EVENT 线程结束

EXIT_PROCESS_DEBUG_EVENT 进程结束

LOAD_DLL_DEBUG_EVENT 加载DLL

UNLOAD_DLL_DEBUG_EVENT 卸载DLL

OUTPUT_DEBUG_STRING_EVENT 调用OutputDebugString函数

RIP_EVENT 发生系统调试错误

dwProcessId

指定调试事件发生的进程的标识符。调试器使用此值来定位调试器的每个进程结构。这些值不一定是可以用作表索引的小整数。

dwThreadId

指定调试事件发生的线程的标识符。调试器使用此值来定位调试器的每个线程结构。这些值不一定是可以用作表索引的小整数。

u

指定与调试事件相关的其他信息。该联合会接受与调试事件类型相适应的类型和值,如dwDebugEventCode成员所述。

运行结果:(一开始运行错误,上面给si赋初值的时候把si写成了pi结果就错误了,改了下就好了。随便用的一个hellword.exe程序调试的结果如下)

实现反汇编功能(重点)

为wdbg01a.exe增加一些新功能。

希望在发生异常时,能够显示出发生异常的地址以及当前寄存器的值。同时,我们还希望显示发生异常时所执行的命令,因此下面我们来实现反汇编功能。

首先引入:#include "udis86.h"  和#pragma comment(lib,"libudis86.lib")

PS:如果你常使用它们,扔进你的VC库Microsoft Visual Studio 10.0\VC\include和lib。在项目中用尖括号包含头文件。
如果你仅在某一项目中使用它们,把它们放在项目相关目录。

代码如下:

#include "stdafx.h"
#include "windows.h"
#include "udis86.h"

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

int disas(unsigned char *buff,char *out,int size)//机器语言反汇编到汇编代码(这部分了解即可)
{
	ud_t ud_obj;
	ud_init(&ud_obj);
	ud_set_input_buffer(&ud_obj,buff,32);

	ud_set_mode(&ud_obj, 32);
	ud_set_syntax(&ud_obj,UD_SYN_INTEL);

	if(ud_disassemble(&ud_obj)){
		sprintf_s(out,size,"%14s   %s",
			ud_insn_hex(&ud_obj),ud_insn_asm(&ud_obj));
	}else{
		return -1;
	}
	return (int)ud_insn_len(&ud_obj);
}
//重点在下面,分析exception_debug_event(DEBUG_EVENT *pde),如何处理异常
int exception_debug_event(DEBUG_EVENT *pde)//DEBUG_EVENT结构描述了调试事件
{
	DWORD dwReadBytes;
	HANDLE ph=OpenProcess(//OpenProcess用来打开一个已存在的进程对象,并返回进程的句柄。
	PROCESS_VM_WRITE | PROCESS_VM_READ |PROCESS_VM_OPERATION,//渴望得到的访问权限(标志位)
/*在进程的地址空间执行操作|使用 ReadProcessMemory 函数在进程中读取内存
|使用 WriteProcessMemory 函数在进程中写入内存。*/
		FALSE,//是否继承句柄(TRUE or FALSE)
        pde->dwProcessId//进程标识符(进程ID)
        );//成功,返回值为指定进程的句柄;

	if(!ph)//如果返回值为空
		return -1;

	HANDLE th = OpenThread(//OpenThread用于打开一个已存在的线程对象,并返回线程的句柄
                           THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
                //渴望得到的访问权限(标志位)。线程对象的访问。
               //此访问权限检查线程的安全描述符。这个参数可以是一个或多个线程访问权限。

                           FALSE,//是否继承句柄(TRUE or FALSE)
                           pde->dwThreadId  //线程标识符(ID)
                           );
	    if(!th)//如果返回值为空
		return -1;

	CONTEXT ctx;
	ctx.ContextFlags = CONTEXT_ALL;
//GetThreadContext函数根据上下文结构的ContextFlags成员的值检索选择性上下文
	GetThreadContext(th,&ctx);
/*此函数用于获取指定线程的线程上下文
对寄存器的读写操作:用OpenThread打开线程之后可通过GetThreadContext 和SetThreadContext 来读写寄存器。
第一个参数:拥有上下文的线程句柄。要获取其上下文的线程的句柄,句柄必须具有THREAD_GET_CONTEXT 访问权限。
第二个参数:接收上下文的结构体地址。指向用于接收指定线程的上下文的CONTEXT结构指针,
该结构的ContextFlags成员的值指定获取线程上下文的哪些部分。
*/
	    char asm_string[256];
	unsigned char asm_code[32];
//为了获取发生异常时所执行的指令,我们需要使用ReadProcessMemory函数
//ReadProcessMemory根据进程句柄读入该进程的某个内存空间
	ReadProcessMemory(
    ph,   //进程句柄
   (VOID *)ctx.Eip,   //读取起始地址
   asm_code,   //用于存放数据的缓冲区
   32,   //要读取的字节数
   &dwReadBytes    //实际读取的字节数
   );   // 当函数读取成功时返回1,

	if(disas(asm_code,asm_string,sizeof(asm_string))==-1)
		asm_string[0]='\0';
    printf("Exception:%08x (PID:%d,TID:%d)\n",
		pde->u.Exception.ExceptionRecord.ExceptionAddress,
		pde->dwProcessId,pde->dwThreadId);
	printf("  %08x: %s\n",ctx.Eip,asm_string);
	printf("  Reg:EAX=%08x ECX=%08x  EDX=%08x  EBX=%08x\n",
		ctx.Eax,ctx.Ecx,ctx.Edx,ctx.Edx);
	printf("   ESI=%08x  EDI=%08x ESP=%08x  EBP=%08x\n",
		ctx.Esi,ctx.Edi,ctx.Esp,ctx.Ebp);

SetThreadContext(
th,//拥有上下文的线程句柄
&ctx//存放上下文的结构体地址
);
//可以通过调用SetThreadContext函数来改变结构中的成员,并把新的寄存器值放回线程的内核对象中
	CloseHandle(th);
	CloseHandle(ph);
	return 0;
}



int _tmain(int argc, _TCHAR* argv[])
{
	//预先定义的数据结构
	PROCESS_INFORMATION pi;
	STARTUPINFO si;

	if(argc<2){
		fprintf(stderr,"C:\\>%s <sample.exe>\n",argv[0]);
		//标准错误输出的屏幕显示,当参数少于两个提示应该正确输入的格式
		return 1;
	}

	memset(&pi,0,sizeof(pi));
	memset(&si,0,sizeof(si));//结构体全部填充为0(每个字节都是0)
	si.cb = sizeof(STARTUPINFO);
	/*DWORD cb;
	包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,
	它可用作版本控制手段,应用程序必须将cb初始化为sizeof(STARTUPINFO)。 */

	BOOL r = CreateProcess(
		NULL,argv[1],NULL,NULL,FALSE,
		NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED | DEBUG_PROCESS,
		NULL,NULL,&si,&pi);

	if(!r)//如果函数执行成功,返回非零值。如果函数执行失败,返回零.
		return -1;
	ResumeThread(pi.hThread);//使线程的挂起时间计数减一

	int process_counter=0;
	do{
		DEBUG_EVENT de;
		if(!WaitForDebugEvent(&de,INFINITE))//de等待无限长时间
			break;
DWORD dwContinueStatus = DBG_CONTINUE;//表示已处理异常,继续执行在异常代码
	switch (de.dwDebugEventCode)
		{
		case CREATE_PROCESS_DEBUG_EVENT:
			 process_counter++;
			break;
		case EXIT_PROCESS_DEBUG_EVENT:
				process_counter--;
			break;
		case EXCEPTION_DEBUG_EVENT:
			{
			if(de.u.Exception.ExceptionRecord.ExceptionCode!=EXCEPTION_BREAKPOINT)
//如果不是断点异常
			dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;//没有被处理掉
			exception_debug_event(&de);
			}
			break;
		}
		ContinueDebugEvent(
			de.dwProcessId,de.dwThreadId,dwContinueStatus);
	}while(process_counter>0);

	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);
	return 0;

}

然后准备一个会发生异常的程序如下:

#include <cstdio>

int main(int argc,char * argv[])
{
	char *s=NULL;
	*s=0xFF;
	return 0;
}

运行结果如下:

 看到这个异常对应源代码的*s=0xFF,执行成功。

重点分析exception_debug_event

然后重点分析exception_debug_event,看它是如何处理异常事件的:(为了方便复习代码合并在上面了)

其中函数说明:

  • OpenProcess

OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。

HANDLE OpenProcess(//用来打开一个已存在的进程对象,并返回进程的句柄
DWORD dwDesireAcess,//渴望得到的访问权限(标志位)
BOOL bInheritHandle,//是否继承句柄(TRUE or FALSE)
DWORD dwProcessId//进程标识符(进程ID)
);

函数返回值:

  • 如果成功,返回值为指定进程的句柄;
  • 如果失败,返回值为空,可调用GetLastError获得错误代码

PS:用户没有权限时,函数返回NULL

  • 关于OpenProcess 函数的dwDesireAcess参数:获取的权限,有以下几种:(标红记熟其他看懂)
访问 描述
PROCESS_ALL_ACCESS 获取所有权限。指定进程对象的所有可能的访问标志。
PROCESS_CREATE_PROCESS 创建进程
PROCESS_CREATE_THREAD 创建线程。启用在CreateRemoteThread函数中使用进程句柄来创建一个线程。
PROCESS_DUP_HANDLE 使用DuplicateHandle功能中的进程句柄作为源或目标进程来复制句柄。
PROCESS_QUERY_INFORMATION 获取进程的令牌、退出码、优先级等信息。启用GetExitCodeProcessGetPriorityClass功能中的进程句柄来读取进程对象中的信息。
 
PROCESS_SET_QUOTA 使用SetProcessWorkingSetSize函数设置内存限制
PROCESS_SUSPEND_RESUME 暂停或者回复一个进程
PROCESS_SET_INFORMATION 设置进程的某种信息。
PROCESS_TERMINATE 使用Terminate函数终止进程。
PROCESS_VM_OPERATION 在进程的地址空间执行操作。启用VirtualProtectExWriteProcessMemory功能中的进程句柄来修改进程的虚拟内存。
PROCESS_VM_READ 使用 ReadProcessMemory 函数在进程中读取内存。
PROCESS_VM_WRITE 使用 WriteProcessMemory 函数在进程中写入内存。
SYNCHRONIZE

使用wait函数等待进程终止。

仅Windows NT:启用使用任何等待功能中的进程句柄等待进程终止。

  • OpenThread(用于打开一个已存在的线程对象,并返回线程的句柄)

OpenThread是Windows API(应用程序接口) 中的一个常用函数,用于打开一个现有线程对象。

  • 原型:
HANDLE WINAPI OpenThread(//用于打开一个已存在的线程对象,并返回线程的句柄

     DWORD dwDesiredAccess,//渴望得到的访问权限(标志位)。线程对象的访问。
                        //此访问权限检查线程的安全描述符。这个参数可以是一个或多个线程访问权限。

     BOOL  bInheritHandle,//是否继承句柄(TRUE or FALSE)

     DWORD dwThreadId  //线程标识符(ID)

);


GetThreadContext(th,&ctx);//用于获取指定线程的线程上下文
//用OpenThread打开线程之后可通过GetThreadContext 和SetThreadContext 来读写寄存器。
/*
HANDLE th = OpenThread(//OpenThread用于打开一个已存在的线程对象,并返回线程的句柄
                           THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
                //渴望得到的访问权限(标志位)。线程对象的访问。
               //此访问权限检查线程的安全描述符。这个参数可以是一个或多个线程访问权限。

                           FALSE,//是否继承句柄(TRUE or FALSE)
                           pde->dwThreadId  //线程标识符(ID)
                           );
*/

  • GetThreadContext

对寄存器的读写操作:用OpenThread打开线程之后可通过GetThreadContext 和SetThreadContext 来读写寄存器。

由于我们不需要在exception_debug_event中改写寄存器的值,因此不需要调用SetThreadContext函数。不过为了方便今后增加改写寄存器的功能,这里保留了对这个函数的调用。

  • 原型
BOOL GetThreadContext(  

HANDLE    hThread,  /*拥有上下文的线程句柄。要获取其上下文的线程的句柄,
句柄必须具有THREAD_GET_CONTEXT 访问权限。*/

LPCONTEXT lpContext/*接收上下文的结构体地址。
指向用于接收指定线程的上下文的CONTEXT结构指针,
该结构的ContextFlags成员的值指定获取线程上下文的哪些部分。*/

);
  • 返回值

如果函数成功,则返回值为非零值。

如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError

  • 备注

https://baike.baidu.com/item/GetThreadContext/6128220?fr=aladdin

此函数用于获取指定线程的线程上下文。函数根据上下文结构的ContextFlags成员的值检索选择性上下文。hThread参数标识的线程通常正在调试中,但该函数也可以在未调试线程时运行。

无法获取正在运行的线程的有效上下文,在调用GetThreadContext之前,使用SuspendThread函数挂起线程。

如果为当前线程调用GetThreadContext,则函数返回成功,但是,返回的上下文无效。

  • SetThreadContext

可以通过调用SetThreadContext函数来改变结构中的成员,并把新的寄存器值放回线程的内核对象

BOOL SetThreadContext (
HANDLE  hThread,//拥有上下文的线程句柄,句柄必须具有THREAD_SET_CONTEXT 访问权限。
CONST CONTEXT  *pContext//存放上下文的结构体地址
);
  • CONTEXT
CONTEXT ctx;
	ctx.ContextFlags = CONTEXT_ALL;
//GetThreadContext函数根据上下文结构的ContextFlags成员的值检索选择性上下文

结构体struct CONTEXT

常见寄存器都在这个列表中, 包括调试寄存器和段寄存器。

 

  • 当用于 SEH 时,CONTEXT 结构体保存着发生异常时各寄存器的值。

在查询之前,先呼叫SuspendThread函数暂停一个线程的执行,然后呼叫GetThreadContext函数取得CONTEXT结构中相关内容。

SuspendThread(hTherad);  // 必须首先暂停线程运行
CONTEXT Context;
Context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &Context);

调用GetThreadContext函数之后,CONTEXT结构相应的字段就会被赋值,此时就可以输出各个寄存器的值了。

  • 调用该函数之前需要设置CONTEXT结构的ContextFlags字段,指明你想要获取哪部分寄存器的值。该字段的取值如下:
     
CONTEXT_CONTROL
包含CPU的控制寄存器,比如指令指针,堆栈指针,标志和函数返回地址.
获取EBP,EIP,CS,EFLAGS,ESP和SS寄存器的值。
 
CONTEXT_INTEGER
用于标识CPU的整数寄存器.
获取EAX,EBX,ECX,EDX,ESI和EDI寄存器的值。
 
CONTEXT_SEGMENTS
用于标识CPU的段寄存器.
获取DS,ES,FS和GS寄存器的值。
 
CONTEXT_FLOATING_POINT
用于标识CPU的浮点寄存器.
获取有关浮点数寄存器的值。
 
CONTEXT_DEBUG_REGISTERS
用于标识CPU的调试寄存器.
获取DR0,DR1,DR2,DR3,DR6,DR7寄存器的值。
 
CONTEXT_FULL
 
等于CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS  即这三个标志的组合(一般给CONTEXT_ALL或者CONTEXT_FULL值即可得到主要的CPU寄存器的值

 CONTEXT_EXTENDED_REGISTERS:用于标识CPU的扩展寄存器  ExtendedRegisters 

在exception_debug_event函数中,为了获取发生异常时所执行的指令,我们需要使用ReadProcessMemory函数。

  • ReadProcessMemory(获取发生异常时所执行的指令

ReadProcessMemory是一个内存操作函数, 其作用为根据进程句柄读入该进程的某个内存空间;

  • 函数原型
BOOL ReadProcessMemory(

HANDLE hProcess, //进程句柄

LPCVOID lpBaseAddress, //读取起始地址

LPVOID lpBuffer,//用于存放数据的缓冲区

DWORD nSize, //要读取的字节数

LPDWORD lpNumberOfBytesRead  //实际读取的字节数

);
  • 返回值

由布尔声明可以看出, 当函数读取成功时返回1,

失败则返回0。

  • WriteProcessMemory函数

(此函数能写入某一进程的内存区域(直接写入会出Access Violation错误,故需此函数)

声明如下:

BOOL WriteProcessMemory(//此函数能写入某一进程的内存区域
HANDLE hProcess, // 进程的句柄,是用OpenProcess打开的
LPVOID lpBaseAddress, // 要写入的起始地址
LPVOID lpBuffer, // 写入的缓存区
DWORD nSize, // 要写入缓存区的大小
LPDWORD lpNumberOfBytesWritten // 这个是返回实际写入的字节。
);

 

重点:1.对调试器程序增加异常处理操作功能,核心API, CONTEXT结构

在上面的代码中,disas函数负责对机器语言进行反汇编,上面使用了udis86的功能。

exception_debug_event函数会发生异常时运行,其中调用了下列函数

  • OpenProcess
  • ReadProcessMemory
  • OpenThread
  • GetThreadContext
  • SetThreadContext

上面这些函数,再加上

  • WriteProcessMemory

就是访问其他进程的必备工具包。

在Windows中,即便我们的程序不是作为调试器挂载在目标进程上,只要能够获取目标进程的句柄,就可以随意读写该进程的内存空间。

当然,当前用户如果没有相应的权限,调用OpenProcess会失败,但只要能够通过其他方法获取进程句柄,也可以自由读写该进程的内存空间。

总结:

  • OpenProcess                 打开一个已存在的进程对象,并返回进程的句柄
  • ReadProcessMemory    读操作指令
  • WriteProcessMemory    写操作指令
  • OpenThread
  • GetThreadContext         获取线程内容信息
  • SetThreadContext         设置线程内容信息

利用这些API函数可以干预其他进程。

关于CONTEXT结构再看一下如下:

typedef struct _CONTEXT
{
    DWORD           ContextFlags    // -|               +00h
    DWORD           Dr0             //  |               +04h
    DWORD           Dr1             //  |               +08h
    DWORD           Dr2             //  >调试寄存器     +0Ch
    DWORD           Dr3             //  |               +10h
    DWORD           Dr6             //  |               +14h
    DWORD           Dr7             // -|               +18h

    FLOATING_SAVE_AREA FloatSave;   //浮点寄存器区      +1Ch~~~88h

    DWORD           SegGs           //-|                +8Ch
    DWORD           SegFs           // |\段寄存器       +90h
    DWORD           SegEs           // |/               +94h
    DWORD           SegDs           //-|                +98h

    DWORD           Edi             //________          +9Ch
    DWORD           Esi             // |  通用          +A0h
    DWORD           Ebx             // |   寄           +A4h
    DWORD           Edx             // |   存           +A8h
    DWORD           Ecx             // |   器           +ACh
    DWORD           Eax             //_|___组_          +B0h

    DWORD           Ebp             //++++++            +B4h
    DWORD           Eip             // |控制            +B8h
    DWORD           SegCs           // |寄存            +BCh
    DWORD           EFlag           // |器组            +C0h
    DWORD           Esp             // |                +C4h
    DWORD           SegSs           //++++++            +C8h

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
    typedef     CONTEXT     *PCONTEXT;
    #define     MAXIMUM_SUPPORTED_EXTENSION     512

二、DLL注入

DLL注入是渗透其他进程的最简单有效的办法。借助DLL注入技术,可以钩取API、改进程序、修复Bug等。

DLL注入指的是向运行中的其他进程强制插入特定的DLL文件

从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary()API,加载(Loading)用户指定的文件

DLL注入与一般DLL加载的区别在于,加载的目标进程是其自身或其他程序

每一种注入技术都将用到Windows API,它提供了大量的函数来附加、操纵其他进程。微软WindowsAPI中的所有函数都包含于DLL文件之中。

最重要的是Kernel32.dll(包含管理内存,进程和线程相关的函数),User32.dll和GDI32.dll。

从图中看到:

myhack.dll已被强制插入notepad进程(本来notepad并不会加载myhack.dll)。加载到Notepad.exe进程中的myhack.dll与已经加载到Notepad.exe进程中的DLL(kernel32.dll、user32.dll)一样,拥有访问notepad.exe进程内存的(正当的)权限,这样用户就可以做任何想做的事了(比如:向notepad添加通信功能以实现Messager、文本网络浏览器等。)

DLL被加载到进程后会自动运行DllMain()函数,用户可以把想执行的代码放到DllMain()函数,每当加载DLL时,添加的代码就会自然而然得到执行。(产生一个疑问,DllMain函数何以自动运行?里面的三个参数谁传进去的?)

利用该特性可以修复程序Bug,或向程序添加新功能。

重点:2.DLL注入的三种基本方法

1.利用全局消息钩子(Windos消息钩取( SetWindowsHookEx() API ))

  • Win32下程序一般都要收发信息;用钩子函数下全局钩子程序收到消息加载DLL的入口点
  • DLL进入后判断自己是不是被插入目标进程;是则执行代码,不是则退出;
  • 绝大部分WINDOWS消息都可以用(有的系统进程消息钩不住,注入不了);
  • 早期的DLL注入大部分是通过这个原理实现。

2.写注册表

  • 写HKEY_LOCAL_MAHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
  • 只能是NT下,且必须是调用user32.dll的程序;
  • 开机后所有程序会自动加载注入的DLL,这个DLL不能被卸载,不推荐使用
  • HKEY_LOCAL_MAHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

    修改为:要注入的dll文件的完整路径。

    LoadAppInit_DLLs注册表项的值为1,指定DLL就会注入所有进程

3. 利用远程线程注入来实现DLL注入

CreateRemoteThread()函数实现远程注入6步曲

  • 第一步:打开要写入的进程,赋予权限。用API函数OpenProcess实现。
  • 第二步:在目标进程的内存中分配一块内存空间。用API函数VirtualAllocEx实现。
  • 第三步:将dll写入内存区域。用API函数WriteProcessMemory实现。
  • 第四步:创建远程线程。用API函数CreateRemoteThread实现。
  • 第五步:等待加载完成。用API函数WaitForSingleObject实现。(自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止)
  • 第六步:释放目标进程中申请的空间。用API函数VirtualFreeEx实现。

一、Windows消息钩取

Windows操作系统向用户提供GUI(Graphic User Interface,图形用户界面),它以事件驱动(Event Driven)方式工作。

在操作系统中借助键盘、鼠标、选择菜单、按钮、以及移动鼠标、改变窗口大小与位置等都是事件(Event)。发生这样的事件时,OS会把实现定义好的消息发送给相应的应用程序,应用程序分析收到的信息后执行相应的动作。

也就是说,敲击键盘时,消息会从OS移动到应用程序。

所谓的“消息钩子”就在此间偷看这些信息。

常规的Winodws消息流:

  • 发生键盘输入事件时,WM_KEYDOWN消息Windows Message 键 按下 即键盘消息)添加到[OS message queue]。
  • OS判断哪个应用程序中发生了事件,然后从[OS message queue]取出消息,添加到相应的应用程序的[application message queue]中。
  • 应用程序如记事本,监视自身的消息队列[application mesage queue],发现新添加的WM_KEYDOWN后,调用相应的事件处理程序处理。
  • SetWindowsHookEx()

使用SetWindowsHookEx() API可轻松的实现消息钩子,SetWindowsHookEx() API的定义如下所示。

HOOK SetWindowsHookEx(

int idHook,  //hook type(WH_KEYBOARD)

HOOKPROC lpfn,//hook procedure==>procedure:程序,过程;
//lpfn:long ponter function 指向钩子函数(钩子过程KeyboardProc())的长指针。

HINSTANCE hMod,//hook procedure所属的DLL句柄(Handle) ,即包含lpfn定义的钩子过程的DLL句柄
     //==>hMod:handle Moudle 句柄模块(HINSTANCE是应用程序的实例句柄,HANDLE是任意对象的句柄。)

DWORD dwThreadId //想要挂钩的线程ID  ==>dw是DWORD四字节无符号整型的缩写,
//如果这个参数为0,则钩子过程keyboardProc将绑定与调用线程HookMain.exe同在一个桌面的所有线程。

);

HOOK 类型有很多种,参见书WIN32环境下程序设计408页,

这里用到的主要是 WH_KEYBOARD 类型:

每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是WM_KEYUP或WM_KEYDOWN消息,则用钩子函数。

  • 钩子过程(hook procedure)是由操作系统调用的回调函数。
  • 安装消息“钩子”时,“钩子”过程要存在于某个DLL内部,且该DLL的示例句柄(instance handle)即是hMod.
  • 若dwThreadID参数设置为0,则安装的钩子为“全局钩子”(Global Hook),它会影响到运行中的(以及以后要运行的)所有进程。

像这样,使用SetWindowsHookEx()设置好钩子之后,在某个进程生成指定消息时,操作系统会将相关的DLL文件强制注入(injection)相关进程,然后调用注册的“钩子”过程。注入进程时,用户几乎不需要做什么,非常方便。 

键盘消息钩取练习

结合上图:

KeyHook.dll文件是一个含有钩子过程KeyboardProc)的DLL文件。

HookMain.exe是最先加载KeyHook.dll安装键盘钩子的程序。HookMain.exe加载KeyHook.dll文件后使用SetWindowsHookEx()安装键盘钩子(KeyboardProc)

其他进程(explorer.exe、iexplore.exe、notepad.exe等)中发生键盘输入事件OS就会强制KeyHook.dll加载到相应进程内存,然后调用KeyboardProc函数

(△看到这里我的想法:

大概是钩子先需要一个载体就是HookMain.exe程序,这个里面写了SetWindowsHookEx()函数,然后这样应该就代表它是个钩子程序了。然后里面他也有LoadLibrary函数加载写好的dll,关于这个dll,dll里面写的是关于你钩到了之后的处理的函数即钩子过程KeyboardProc,

写成一个dll估计是因为SetWindowsHookEx()的功能,一旦有程序满足了钩取的条件(hook type如这里是产生键盘消息),然后操作系统OS就会强制将包含钩子过程KeyboardProc 的dll插入到这个进程,关于怎么个强制执行,估计和SetWindowsHookEx()怎么运行的即它的方法体有关。

一会在下面进行更细致的分析,希望可以有更进一步的理解。

 总的来说现在请记住这两点:(黑色是复习加进去的,便于背诵,记忆重点)

  • 1.HookMain.exe加载Keyhook.dll文件后,使用SetWindowsHookEx()安装键盘钩子KeyboardProc()。
  • (调用Keyhook.dll的HookStart函数,里面写了SetWindowsHookEx()安装全局钩子)
  • 2.当某进程发生键盘输入事件,OS强制将Keyhook.dll文件加载到对应进程的内存空间,而后调用钩子函数KeyboardProc().
  • (根据SetWindowsHookEx()中第一个参数设置的钩子类型WH_KEYBOARD监听键盘消息,发生WM_KEYUP和WM_KEYDOWN事件就自动调用回调函数KeyboardProc(),并根据ncode事件代码参数确定任务,传递给下一个钩子CallNextHookEx()或者直接return一个大于0的数结束。 
  • 注意:
  • SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,hMod(自己写的钩子dll句柄),0),四个参数记住。特别注意第三个,是dll的句柄!不要搞混了!
  • 后面到了keyboardProc函数,把钩子给下一个即CallNextHookEx和卸载UnhookWindowsHookEx的时候用的才是钩子句柄.
  • 注意KeyboardProc(ncode,wParam,lParam)

要注意的是OS会将KeyHook.dll强制加载到发生键盘输入事件的所有进程。换言之,消息钩取技术常常被用作一种DLL注入技术。

练习示例

  • HookMain.exe的代码:
#include "stdio.h"
#include "conio.h"
#include "windows.h"
/*Console Input/Output即"conio.h"定义了通过控制台进行数据输入和数据输出的函数,
主要是一些用户通过按键盘产生的对应操作,比如getch()函数等等*/
//定义一些常量
#define DEF_DLL_NAME "KeyHook.dll"  //DEF即define
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
//定义两个参数为空、返回值为void即没有的函数指针
typedef void(*PFN_HOOKSTART)();//PFN_HOOKSTART就代表了“返回值为空,无参数的函数指针”型
//这是C++的语法,如:typedef void (*p)();定义了一个函数指针类型p,之后每处的p都是一个类型
//代表void(*)()这个函数指针,具体参见我之前的一个博客。
typedef void(*PFN_HOOKSTOP)();
//即PFN_HOOKSTART和PFN_HOOKSTOP都是函数指针。
void main()
{
	//1.定义及初始化句柄变量和函数指针
    HMODULE hDll = NULL;//typedef HINSTANCE HMODULE;DLL的句柄
    PFN_HOOKSTART HookStart = NULL;//函数指针HookStart初始化
    PFN_HOOKSTOP HookStop = NULL;
    char ch=0;

    //2.加载KeyHook.dll
    hDll = LoadLibraryA(DEF_DLL_NAME);//获得KeyHook.dll的模块句柄
	if(hDll == NULL)//如果返回值为空,使用 GetLastError获得扩展信息
    {
    printf("LoadLibraryA(%s) failed! [%d]", DEF_DLL_NAME, GetLastError());
    return;
    }

    //3.获取导出函数的地址
    HookStart =(PFN_HOOKSTART)GetProcAddress(hDll,DEF_HOOKSTART);//函数指针
//使用GetProcAddress函数获取输出函数HookStart()的地址保存在函数指针HookStart中。
    HookStop =(PFN_HOOKSTOP)GetProcAddress(hDll,DEF_HOOKSTOP);
//使用GetProcAddress函数获取输出函数 HookStop()的地址保存在函数指针 HookStop中。

    //4.开始钩取
    HookStart();//直接使用该函数

    //5.等待直到用户输入“q”
    printf("press 'q' to quit!\n");
    while(_getch()!='q');//一个循环,输入q的时候跳出循环向下执行

    //6.终止钩取
    HookStop();//已经“填充”过的函数指针,直接食用

    //7.卸载KeyHook.dll
    FreeLibrary(hDll);//释放加载的动态链接库KeyHook.dll

}

关于里面的函数说明:https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya

  • 1.LoadLibraryA(LoadLibrary)
HMODULE LoadLibraryA(
  LPCSTR lpLibFileName//模块的名称。这可以是库模块(.dll文件)或可执行模块(.exe文件)
);
  • Return value

If the function succeeds, the return value is a handle to the module(模块的句柄).

If the function fails, the return value is NULL. To get extended error information(扩展错误信息), call GetLastError.

  • 2.GetProcAddress()
FARPROC GetProcAddress(
    HMODULE   hModule,    // DLL模块句柄
    LPCSTR       lpProcName   // 函数名
);
  •  参数:

hModule 

A handle to the DLL module that contains the function or variable. (包含函数或变量的DLL模块的句柄。)The LoadLibraryLoadLibraryExLoadPackagedLibrary, or GetModuleHandle function returns this handle(句柄).
       lpProcName 

The function or variable name(函数或变量名字), or the function's ordinal value(函数序数值). If this parameter is an ordinal value(序数值), it must be in the low-order word(在一个字的低字节); the high-order word must be zero(高字节为0).

  • 返回值

If the function succeeds, the return value is the address of the exported function or variable(DLL中的输出函数地址).

If the function fails, the return value is NULL. To get extended error information(进一步的错误信息), call GetLastError.

返回值:

如果函数调用成功,返回值是DLL中的输出函数地址。

如果函数调用失败,返回值是NULL。得到进一步的错误信息,调用函数GetLastError。

  • 注释:

  GetProcAddress()函数被用来检索在DLL中的输出函数地址。 

  lpProcName指针指向的函数名,拼写和大小写必须和DLL源代码中的模块定义文件(.DEF)中输出段(EXPORTS)中指定的相同。

  • 3.FreeLibrary()
BOOL FreeLibrary( 
             HMODULE hLibModule 
           );

https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary

Frees the loaded dynamic-link library (DLL) module(释放加载的动态链接库(DLL)模块) and, if necessary, decrements its reference count(减少其引用计数). When the reference count reaches zero(引用计数达到零), the module is unloaded(卸载模块) from the address space of the calling process(调用进程的地址空间) and the handle is no longer valid(句柄不再有效).

  • Parameters

hLibModule

A handle to the loaded library module(加载的库模块). The LoadLibraryLoadLibraryEx,
GetModuleHandle, or GetModuleHandleEx function returns this handle.

  • Return value

If the function succeeds, the return value is nonzero(非零).

If the function fails, the return value is zero. To get extended error information, call the GetLastErrorfunction.

 

我的感想:

通过对代码的分析,发现在HookMain.exe中仅仅实现的主要是对动态链接库KeyHook.dll的调用,

使用了三个主要的函数即LoadLibrary(DLL的名字),加载动态链接库,获得动态链接库的句柄,

接着使用GetProAddress(DLL模块句柄,函数名)获得dll中的函数的地址,以便于在HookMain.exe程序中直接使用这些函数(HookStart()函数和HookStop()函数),

最后就是使用FreeLibrary(加载的库模块句柄)释放加载的动态链接库模块。

由此可以看出关于钩子函数执行的一系列主要过程是放在dll里的,HookMain.exe仅仅是一个开启它的作用。

 :源代码非常简单。

加载KeyHook.dll文件(LoardLibrary(DLL的名字)),然后调用HookStart()函数开始钩取(函数指针HookStart使用GetProAddress(DLL的句柄,函数的名字)获得函数地址),用户输入“q”时,调用HookStop()函数终止钩取。重要代码处添加了注释,认真查看就能轻松理解,不会遇到什么困难。

  • 接下来 看重要的KeyHook.dll(键钩子库)的代码:
#include "stdio.h"
#include "windows.h"
///定义目标进程名为notepad.exe
#define DEF_PROCESS_NAME "notepad.exe"

//定义全局变量
HINSTANCE g_hInstance = NULL;//HINSTANCE是“句柄型”数据类型。keyhook.dll的句柄
//查了半天,global hook,g_前缀:全局的意思
HHOOK g_hHook = NULL;
//返回值,钩子句柄,需要保留,等不使用钩子时通过UnhookWindowsHookEx函数卸载钩子。

//DllMain()函数在DLL被加载到进程后会自动执行
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD dwReason,LPVOID lpvReserved)
{//三个参数依次是:自身的dll句柄,调用函数的原因,保留的任意类型函数指针
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH://调用原因是进程映射
		g_hInstance = hinstDLL;//将自身的dll句柄给参数全局钩子句柄
		break;
	case DLL_PROCESS_DETACH://调用原因是进程卸载
		break;
	}
	return TRUE;
}

//调用导出函数HookStrat()时,SetWindowsHookEx()函数会将KeybroadProc()添加到键盘钩链

LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
//三个参数分别是:nCode参数是钩子代码,钩子子程使用这个参数来确定任务
    //wParam是按下键盘按键的虚拟键值,使用ToAscii()API函数可以获得实际按下键盘的ASCII值
	char szPath[MAX_PATH]={0,};
	char *p = NULL;
	if(nCode >=0)//NCode为传给钩子过程的事件代码。Hook子程使用这个参数来确定任务。
	{//一般有0和3取值
		if(!(lParam & 0x80000000))//释放键盘按键时
		{//lParam的最高位为1代表键盘Up,0代表键盘down

			GetModuleFileNameA(NULL,szPath,MAX_PATH);
//获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。
//1.hModule参数为NULL,该函数返回该当前应用程序全路径。
// 2.lpFileName 是你存放返回的名字的内存块的指针   szPath
//3.装载到缓冲区lpFileName的最大值  MAX_PATH
			p=strrchr(szPath,'\\');
//查找字符在指定字符串中从右面开始的第一次出现的位置,
//如果成功,返回该字符以及其后面的字符,如果失败,则返回 NULL。
//比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序(或下一个“钩子”)
			if(!_stricmp(p+1,DEF_PROCESS_NAME))
//即如果发生键盘输入事件的进程为notepad.exe,则终止KeyboardProc函数,截获且删除键盘消息。
				return 1;
		}
	}
	//若非notepade.exe,则调用CallNextHookEx()函数,将消息传递给应用程序(或下一个“钩子”)。
	return CallNextHookEx(g_hHook,nCode,wParam,lParam);//注意g_hHook是钩子函数的句柄哈
}
//在C++中调用C的库文件,用extern "C"告知编译器,
//因为C++支持函数重载而C不支持,两者的编译规则不同
#ifdef __cplusplus
extern "C"{
#endif
	__declspec(dllexport)void HookStart()
//__declspec,针对编译器的关键字,用于指出导出函数
	{
	g_hHook = SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,g_hInstance,0);
		/*函数的功能是将应用程序定义的钩子函数安装到挂钩链中,监控事件
          1.WH_KEYBOARD  是钩子的拦截消息类型,对击键消息进行监视;
          2.KeyboardProc 是消息的回调函数地址,一般是填函数名。
		  3.g_hInstance  是钩子函数所在的实例dll的句柄。
对于线程钩子,该参数为NULL;
对于系统钩子,该参数为钩子函数所在的DLL句柄。
		  4.钩子所监视的线程的线程号,可通过GetCurrentThreadId()获得线程号。
对于全局钩子,该参数为NULL(或0)。
          :该函数返回钩子处理过程的句柄即回调函数
        */
	}
	__declspec(dllexport)void HookStop()
	{
		if(g_hHook)//如果非空
		{
			UnhookWindowsHookEx(g_hHook);
//使用完钩子后一定要卸载钩子,否则可能会导致BUG,甚至导致死机
			g_hHook = NULL;
		}
	}
#ifdef __cplusplus
}
#endif

关于上面的代码需要了解的一些函数:

 首先插播一句,关于自己老好追根究底,记忆力还差,这毛病很久了,有时候导致效率贼低,还有4天就考试了,我这顶多复习1/3,明天还要教资面试,佛了,上帝保佑我可以复习完。(二次复习的时候请自己不要看这句废话)

回归正题:

关于参数的定义我一直很好奇干嘛g_打头,注意哈,这个是global 全局的意思,因为我们这里的钩子dll下的是全局钩子,所以加了个g_代表这是全局钩子的一些参数


  • 1.DllMain函数

https://blog.csdn.net/tiandao2009/article/details/79839182

  • DLLMain()函数的功能

Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、Win32程序需要WinMain函数一样。根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。

对于动态链接库,DLLMain()是一个可选的入口函数。很多初学者可能都认为一个动态链接库肯定要有DllMain函数。其实不然,像很多仅仅包含资源信息的DLL是没有DllMain函数的。

  • 参数含义

https://baike.baidu.com/item/DllMain/763193?fr=aladdin

BOOL WINAPI DllMain(

  _In_ HINSTANCE hinstDLL, // 指向自身的句柄(hinstDLL==>handle instance DLL的实例句柄)

  _In_ DWORD fdwReason, // 调用原因(fdwReason==>function dword reason函数调用的原因)

  _In_ LPVOID lpvReserved // 隐式加载和显式加载(LPVOID==>是一个没有类型的指针,也就是说你可以将
//LPVOID类型的变量赋值给任意类型的指针,Reserved:保留,表示一个保留参数,目前已经很少使用)

);

系统是在什么时候调用DllMain函数的呢?静态链接时,或动态链接时调用LoadLibraryFreeLibrary都会调用DllMain函数。DllMain的第二个参数fdwReason指明了系统调用Dll的原因,它可能是:(具体参见百科或MSDN,这里只列举我考试会考到的。)

  • DLL_PROCESS_ATTACH、进程映射

大家都知道,一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:静态链接动态链接的LoadLibrary或者LoadLibraryEx。

当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

  • DLL_PROCESS_DETACH、进程卸载

当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。

那么什么时候DLL被从进程的地址空间解除映射呢?两种情况:

FreeLibrary解除DLL映射有几个LoadLibrary,就要有几个FreeLibrary

进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)


2.SetWindowsHookEx()

(上面已经写了一遍,这里再进行补充和强化,记忆时以这里的为准。(老师版本)。)PS:Ex代表扩展的函数。这种函数一般有两个版本一个版本是没有带Ex的,带了Ex的函数一般参数会比没有带的多,而且更灵活。

HHOOK SetWindowsHookEx( 
int idHook, //指示欲被安装的挂钩处理过程的类型(被安装的钩子所钩取的事件类型)
HOOKPROC lpfn,//指向相应的钩子函数,即回调函数地址.
HINSTANCE hMod,//指示了包含钩子函数的模块句柄,该DLL包含了参数lpfn所指向的钩子处理函数
DWORD dwThreadId //指定监视的线程。如果指定确定的线程,即为线程专用钩子;如果为0即为全局钩子。
//全局钩子必须包含在DLL(动态链接库)中,而线程专用钩子可以包含在可执行文件中。
);

函数功能:将一个应用程序定义的钩子函数安装到挂钩链中。通过安装钩子函数,对系统某类事件进行监控,这些事件与特定的线程或系统中的所有线程相关。


第一个参数idhook:被安装的钩子所钩取的事件类型:

  • WH_GETMESSAGE(3):安装一个挂钩处理过程,对寄送至消息队列的消息进行监视,详情参见GetMsgProc挂钩处理过程。
  • WH_KEYBOARD(2):安装一个挂钩处理过程对击键消息进行监视. 详情参见KeyboardProc挂钩处理过程.
  • WH_MOUSE(7):安装一个挂钩处理过程,对鼠标消息进行监视. 详情参见 MouseProc挂钩处理过程.
  • WH_MSGFILTER(-1):安装一个挂钩处理过程,以监视由对话框、消息框、菜单条、或滚动条中的输入事件引发的消息.详情参见MessageProc挂钩处理过程.

  • 函数返回值:

若此函数执行成功,则返回值就是该钩子处理过程的句柄,即回调函数。

返回值:若此函数执行成功,则返回值就是该挂钩处理过程的句柄;若此函数执行失败,则返回值为NULL(0).若想获得更多错误信息,请调用GetLasError函数.

  • 3.回调函数KeyboardProc()
LRESULT CALLBACK KeyboardProc( 
int code,//根据这个数值决定怎样处理消息
WPARAM wParam,//按键的虚拟键值消息,例如:VK_F1
LPARAM lParam //32位内存,内容描述包括:指定扩展键值,扫描码,上下文,重复次数。
);
  • code

如果 code 小于0,则 必须让KeyboardProc()函数返回CallNextHookEx()
       如果code 大于等于0

             并且钩子处理函数没有处理消息,返回CallNextHookEx()的返回值,否则当安装WH_KEYBOARD钩子时,钩子将不会得到通知,并返回错误结果。
                     如果钩子处理消息,可以返回一个非0值,防止系统把消息传递给钩子链中的下一个钩子,或者把消息发送到目标窗口。 

  • code可以是下列值:

HC_ACTION(0):wParam(按键的虚拟键值消息,例如:VK_F1)和lParam(32位内存,内容描述包括:指定扩展键值,扫描码,上下文,重复次数。)包含按键消息

HC_NOREMOVE(3):wParam和lParam包含按键消息,并且按键消息不能从消息队列中移除

nCode参数是钩子代码,钩子子程使用这个参数来确定任务,这个参数的值依赖于Hook类型。wParam和lParam参数包含了消息信息,我们可以从中提取需要的信息。

  • MAX_PATH

MAX_PATH是C语言运行时库中通过#define指令定义的一个宏常量,它定义了编译器所支持的最长全路径名的长度。

  • GetModuleFileName

获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。

如果想要获取另一个已加载模块的文件路径,可以使用GetModuleFileNameEx函数。

DWORD GetModuleFileName(
    HMODULE hModule,//装载一个程序实例的句柄。如果该参数为NULL,该函数返回该当前应用程序全路径。
    LPTSTR lpFilename,//是你存放返回的名字的内存块的指针,是一个输出参数
    DWORD nSize//装载到缓冲区lpFileName的最大值
);

函数功能:获得hModule所指的文件的名字

函数返回值

如果返回为成功,将在lpFileName的缓冲区当中返回相应模块的路径,如果所设的nSize过小,那么返回仅按所设置缓冲区大小返回相应字符串内容。

如果函数失败,返回值将为0,利用GetLastError可获得异常代码。

  • CallNextHookEx

https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-callnexthookex?redirectedfrom=MSDN

将钩子信息传递给当前钩子链中的下一个钩子过程。钩子过程可以在处理钩子信息之前或之后调用此函数。

LRESULT CallNextHookEx(
  HHOOK  hhk,//钩子句柄
  int    nCode,//传递给当前钩子过程的钩子代码。下一个钩子过程使用此代码来确定如何处理钩子信息。
  WPARAM wParam,// 附加消息wParam
  LPARAM lParam//附加消息lParam
);

在钩子函数的结尾调用CallNextHookEx函数处理下一条消息,将消息传递给下一个钩子

  • UnhookWindowsHookEx()

使用完钩子后一定要卸载钩子,否则可能会导致BUG,甚至导致死机。

BOOL UnhookWindowsHookEx(

HHOOK  hhk    // 要卸载的钩子句柄。
 );

小结一下:

从这个代码中可以看到一开始写了DllMain函数,他的三个参数分别是dll的句柄,调用函数的原因,保留的任意类型的函数指针。关于更多DllMain函数,希望在复习的时候,读到这你已经很清楚了。比如DllMain会自动执行云云。

然后在这个DllMain里面,他根据dwReason就是调用函数的原因(进程映射,进程卸载,弄了两个选项,注意进程映射的时候把dll句柄拿出来用了下。)

这里涉及到我的作业写错的一题:什么时候会调用DllMain函数?加载DLL和卸载DLL都会调用!

然后呢,下面是KeyboardProc函数的内容,他的三个参数注意下,第一个ncode决定函数如何处理该消息,记得一般有两个值0和3,区别是3按键消息不能从消息队列中移除,虽然我现在也不清楚具体有啥区别,你先记住得了,而且貌似和DLLMAIN一样他们的参数都不是代码里面传进去的,关于这个我还有很多问题,他们的参数是谁给他塞进去的?

这个问题暂且搁置,然后继续向下看KeyboardProc函数的内容,这个里面首先根据ncode的值如果大于等于0的时候,当高位为1的时候然后用GetModuleFileNameA获取当前进程路径,配合strrchr截取一下,然后与我们想要钩取的程序的名字比较一下,如果符合,直接返回1,不符合再用CallNextHookEx将消息传递给下一个钩子。

接着再看,然后程序写了两个函数,安装钩子的HookStart()函数,里面调用了SetWindowsHookEx(WH_KEYBOARD,keyboardProc,g_hInstance,0)。

然后又写了卸载钩子的函数HookStop,里面调用的是UnhookWindowsHookEx(g_hook);

感觉关于SetWindowsHookEx和KeyboardProc相关的参数是一定考的,所以看到这里希望你烂记于心!ヾ(◍°∇°◍)ノ゙


重点:3.通过键盘消息钩取的例子,理解如何实现DLL注入的,重要的API,回调函数

到了这,键盘消息钩取的例子在上面。

重要的API和回调函数KeyboardProc的介绍也都在上面。

二次复习时到这里希望可以背下来。留点空,有时间我再来完善。8号考试,二次最迟6号二轮到这!。

安装好键盘钩子后,无论哪个进程,只要发生键盘输入事件,OS就会强制将KeyHook.dll注入相应进程。加载了KeyHook.dll的进程中,发生键盘事件时会首先调用执行KeyHook.KeyboardProc()。

KeyboardProc()函数中发生键盘输入事件时,就会比较当前进程的名称与“notepad.exe”字符串,若相同,则返回1,终止KeyboardProc()函数,这意味着截获且删除消息。

这样,键盘消息就不会传递到notepad.exe程序的消息队列。

因notepad.exe未能接收到任何键盘消息,故无法输出。

除此之外(即当前进程名称非“notepad.exe”时),执行return CallNextHookEx(g_hHook,nCode,wParam,IParam);语句,消息会被传递到另一个应用程序或钩链的另一个“钩子函数”。

  • Windows消息钩取过程总结

  •  思考题:为什么Windows消息钩取本质上也属于DLL注入?

首先DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。与普通DLL注入 命令其他进程自行调用LoadLibrary()API,加载用户指定的DLL文件不同的是,消息钩取是OS直接将已注册的钩取DLL注入目标进程。所以说本质上也属于DLL注入。

(自己找的答案,不知道贴合不贴合,考到了,再来对比。)

PS:关于上面的代码是可执行的,做过实验的,这里就不在调试了,时间有限。

二、创建远程线程

重点:4. 创建远程线程完成DLL注入,步骤,关键API 函数,dllmain,LoadLibrary,Creatremotethread

这个虚拟机好像没有copy一下到VC2010安装路径VC的LIB下就好了。

代码是从别人的博客那直接粘贴过来的,自己运行失败了。下面用这个代码进行分析。看看分析完可不可以修改好。

myhack.dll

//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;
/*线程的任务:根据全局变量myhack.dll模块句柄g_hMod获得其所在的路径,
URLDownloadToFile下载到该路径。*/
DWORD WINAPI ThreadProc(LPVOID lParam)//函数的名称的占位符,作为一个线程的起始地址
{//这个函数完成线程所要执行的任务。可以把ThreadProc看成是线程的main函数。
	TCHAR szPath[_MAX_PATH] = { 0, };
if (!GetModuleFileName(//获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。
g_hMod, /*一个模块的句柄。可以是一个DLL模块,或者是一个应用程序的实例句柄。如果该参数为NULL,
该函数返回该应用程序全路径。*/
szPath, /*指定一个字串缓冲区,要在其中容纳文件的用NULL字符中止的路径名,hModule模块就是从这个文件装载进来的*/
MAX_PATH  //装载到缓冲区lpFileName的最大字符数量
))//如执行成功,返回复制到lpFileName的实际字符数量;零表示失败。
//→获取当前文件加载路径,存放到szPath中
		return FALSE;
	TCHAR *p = _tcsrchr(szPath, '\\');
//返回值:指向最后一次在字符串中出现的该字符的指针,第一个参数:字符串,第二个参数:查找的字符
	if (!p)//如果要查找的字符再串中没有出现,则返回NULL
		return FALSE;

	_tcscpy_s(p + 1, _MAX_PATH, DEF_FILE_NAME);//字符拷贝函数,+1是因为从\\字符开始
	URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);
//指从指定URL地址读取内容并将读取到的内容保存到特定的文件里的实现方法。

	return 0;
}
//DllMain主要是当进程映射进来的时,创建线程执行线程函数ThreadProc
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	HANDLE hThread = NULL;
	g_hMod = (HMODULE)hinstDLL;//保存dll句柄
	switch (fdwReason)//调用函数的理由
	{
	case DLL_PROCESS_ATTACH://进程映射
		OutputDebugString(L"myhack.dll Injection!!!!");//输出调试信息,搭配DebugView使用
		
   hThread = CreateThread(//返回值为线程句柄。
   NULL, //指向SECURITY_ATTRIBUTES型态的结构的指针。NULL使用默认安全性
   0, //设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小
   ThreadProc, //指向线程函数的指针
   NULL, //向线程函数传递的参数,是一个指向结构体的指针,不传递参数时为NULL
   0, //线程标志
   NULL//新线程的id
   );

		CloseHandle(hThread);
		break;
	}
	return TRUE;
}

需要了解的函数:

  • 1.  ThreadProc()函数(名字可以自己定义)
  • DWORD WINAPI 函数名 (LPVOID lpParam),格式不正确则CreateThread()无法调用成功。

ThreadProc是一个应用程序定义的函数的名称的占位符。作为一个线程的起始地址。在调用CreateThread函数时,指定该地址。这个函数完成线程所要执行的任务。可以把ThreadProc看成是线程的main函数。

DWORD WINAPI ThreadProc(
           LPVOID lpParameter//接收线程传递给函数使用的CreateThread函数lpParameter参数数据。
//这个参数是个空指针类型, 简单的说,你想传任何东西都可以,强制转换就OK了.
          );

返回值

函数应该返回一个值,表示线程函数返回退出码,一般使用0作为返回值。线程创建成功,返回非零值,否则为0。

备注https://baike.baidu.com/item/ThreadProc/6454209?fr=aladdin

一个进程可以通过调用GetExitCodeThread函数获取由CreateThread创建的线程的ThreadProc函数的返回值。

关于他的lpParameter参数,估计和DllMain和KeyboardProc的参数都差不多,是个固定形式

https://zhidao.baidu.com/question/236965657.html,详细看这个回答。

  • 2.     _tcsrchr  
TCHAR *p = _tcsrchr(szPath, '\\');//找出文件名

查找字符串中某个字符最后一次出现的位置

两个参数:第一个参数:字符串,第二个参数:查找的字符

返回值:指向最后一次在字符串中出现的该字符的指针,如果要查找的字符再串中没有出现,则返回NULL。

3.    必考填空☆CreateThread()

hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。函数成功,返回值为线程句柄。

hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向SECURITY_ATTRIBUTES型态的结构的指针。
//NULL使用默认安全性

SIZE_T dwStackSize,//initial stack size
//设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小

LPTHREAD_START_ROUTINE lpStartAddress,//thread function
//指向线程函数的指针,函数名称没有限制,但必须以下列形式声明:
//DWORD WINAPI 函数名 (LPVOID lpParam),格式不正确则无法调用成功。

LPVOID lpParameter,//向线程函数传递的参数,是一个指向结构体的指针,不传递参数时为NULL
DWORD dwCreationFlags,//线程标志
LPDWORD lpThreadId//新线程的id
)

InjectDll.exe

//InjectDll.cpp
#include"windows.h"
#include"tchar.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);
//DLL文件路径字符串的长度
	LPTHREAD_START_ROUTINE pThreadProc;

//1.使用dwpid获取目标进程句柄
if (!hProcess=OpenProcess(//用来打开一个已存在的进程对象,并返回进程的句柄
PROCESS_ALL_ACCESS,//渴望得到的访问权限(标志位):获取所有权限
FALSE,//是否继承句柄(TRUE or FALSE):不继承句柄
dwPID//进程标识符(进程ID):进程标识符
)))
	{
		_tprintf(L"OpenProcess(%d) failed!!![%d]\n", dwPID, GetLastError());
		return FALSE;
	}
//2.在目标进程内存中分配szDllname大小的内存
pRemoteBuf = VirtualAllocEx(
    hProcess, //申请内存所在的进程句柄
    NULL, //保留页面的内存地址;一般用NULL自动分配
    dwBufSize, //欲分配的内存大小,字节单位;实际分配的内存大小是页内存大小的整数倍
    MEM_COMMIT,//为特定页面区域分配的物理存储
    PAGE_READWRITE //区域可被应用程序读写
);//执行成功就返回分配内存的首地址,不成功是NULL
//(分配物理存储,可读可写)

//3.将myhack.dll路径写入目标进程(分配的)内存。
WriteProcessMemory(//此函数能写入某一进程的内存区域
    hProcess,  // 进程的句柄,是用OpenProcess打开的
    pRemoteBuf, // 要写入的起始地址
    (LPVOID)szDllPath, // 写入的缓存区
    dwBufSize, // 要写入缓存区的大小
    NULL   // 这个是返回实际写入的字节。
    );
//4.获取LoadLibraryW API的地址
hMod = GetModuleHandle(L"Kernel32.dll");//获取已经加载模块的句柄
  // GetModuleHandle函数的作用是返回指定模块名的句柄,如果为NULL,则返回本模块的句柄。
  //返回的句柄值存放在寄存器eax中。
  //lpModuleName参数是一个指向含有模块名称字符串的指针;
  //GetProcAddress返回值是DLL中的输出函数地址
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(//获取函数地址
        hMod, //含此函数的DLL模块的句柄。LoadLibrary或者GetModuleHandle函数可以返回此句柄。
       "LoadLibraryW"//含函数名的以NULL结尾的字符串,或者指定函数的序数值
    );


//5.在目标进程中运行线程
hThread = CreateRemoteThread(
/*功能:在目标进程中创建一个线程,并执行这个创建出来的线程,如LoadLibrary(),进而使目标进程加载某个DLL文件*/
    hProcess, //插入的目标进程的句柄
    NULL, //线程的安全描述结构体指针,一般为NULL表示默认的安全级别
    0, //线程堆栈大小,一般设置为0,表示默认大小:1M
    pThreadProc, //线程函数的地址:LoadLibraryW
    pRemoteBuf, //传递给线程函数的参数地址
    0, //线程的创建方式
    NULL//指针,记录创建的远程线程的ID
    );

	_tprintf(L"%d", GetLastError());
	WaitForSingleObject(hThread, INFINITE);
//(自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止)
	CloseHandle(hProcess);
	return TRUE;
}
int _tmain(int argc, TCHAR *argv[])
{
	if (argc != 3)
	{
		_tprintf(L"USAGE: %s pid dll_path\n", argv[2]);
		return 1;
	}
//inject dll
	if (InjectDll((DWORD)_tstol(argv[1]), argv[2]))
		_tprintf(L"InjectDll(\"%s\")success!!\n", argv[2]);
	else
		_tprintf(L"InjectDll(\"%s\") failed!!\n", argv[2]);

	return 0;
}

注意:

  • CreateThread与CreateRemoteThread有什么区别呢?
  • CreateRemoteThread

功能:在目标进程中创建一个线程,并执行这个创建出来的线程,如LoadLibrary(),进而使目标进程加载某个DLL文件

(多了一个插入的目标进程的句柄hProcess)

hThread = CreateRemoteThread(
/*功能:在目标进程中创建一个线程,并执行这个创建出来的线程,如LoadLibrary(),进而使目标进程加载某个DLL文件*/
    hProcess, //插入的目标进程的句柄
/*(获取目标进程句柄:
hProcess=OpenProcess(//用来打开一个已存在的进程对象,并返回进程的句柄
PROCESS_ALL_ACCESS,//渴望得到的访问权限(标志位)
FALSE,//是否继承句柄(TRUE or FALSE)
dwPID//进程标识符(进程ID)
))*/
    NULL, //线程的安全描述结构体指针,一般为NULL表示默认的安全级别
    0, //线程堆栈大小,一般设置为0,表示默认大小:1M
    pThreadProc, //线程函数的地址:LoadLibraryW
/*(获取函数地址:GetProcAddress返回值是DLL中的输出函数地址
	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(
        hMod, //含此函数的DLL模块的句柄。LoadLibrary或者GetModuleHandle函数可以返回此句柄。
       "LoadLibraryW"//含函数名的以NULL结尾的字符串,或者指定函数的序数值
    );*/
    pRemoteBuf, //传递给线程函数的参数地址
/*(pRemoteBuf = VirtualAllocEx(
    //函数的作用是在指定进程的虚拟空间保留或提交内存区域
    hProcess, //申请内存所在的进程句柄
    NULL, //保留页面的内存地址;一般用NULL自动分配
    dwBufSize, 
//欲分配的内存大小,字节单位;实际分配的内存大小是页内存大小的整数倍:DLL文件路径字符串的长度
    MEM_COMMIT,//为特定页面区域分配的物理存储
    PAGE_READWRITE //区域可被应用程序读写
);//执行成功就返回分配内存的首地址,不成功是NULL*/
    0, //线程的创建方式
    NULL//指针,记录创建的远程线程的ID
  • CreateThread 

功能:CreateThread 是Windows API提供的建立新线程的函数该函数在主线程的基础上创建一个新线程,通过CloseHandle函数关闭该线程对象。函数成功,返回值为线程句柄。

		
   hThread = CreateThread(//返回值为线程句柄。
   NULL, //指向SECURITY_ATTRIBUTES型态的结构的指针。NULL使用默认安全性
   0, //设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小
   ThreadProc, //指向线程函数的指针(ThreadProc可以看成是线程的main函数。)
   NULL, //向线程函数传递的参数,是一个指向结构体的指针,不传递参数时为NULL
   0, //线程标志
   NULL//新线程的id
   );
  • 总结:创建远程注入的步骤:

  1. 获取目标进程句柄
  2. 在目标进程中分配内存空间
  3. 将DLL文件复制到目标进程的内存空间
  4. 强制目标进程加载DLL文件

(所有步骤都是通过调用特定的API完成的)

三、注册表 Applnit_DLLs

重点:5.修改注册表,限制

这个地方的考点估计在于修改注册表注入的方法:

HKEY_LOCAL_MAHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

修改为:要注入的dll文件的完整路径。

LoadAppInit_DLLs注册表项的值为1,指定DLL就会注入所有进程。

不过那个限制是个啥子意思?是指修改的值是1,然后指定DLL就会注入所有进程吗?

===========

后记:

2020.1.08:考完了。估计以后不一定再会看这方面的东西了,除非工作需要了吧?

博客只整理了三个PART,后面进度赶不上了,就直接在书上画画写写整理整理自己的理解背诵背诵了。

关于考试:自己一开始太大意了,觉得时间很充足,以前考试考完还有很长时间,前面就做的很细致很慢,导致最后30分钟两道还没有写。

2020.1.9:今天出成绩了,意料之中,毕竟很多题没有很准确也差些没写完。班里很多同学考的都很好,请教了一个考了90+的女同学,她说考前的那个晚上她只睡了1个小时,相比较我只熬到了晚上1.30来说,自己真的还差了很多。

突然增长的一些自信又压了下去,感觉我的学习方法还需要继续改进。

我之前有一种想法,通过这些考试,我可以锻炼自己的学习方法,争取考研可以事半功倍,不要做无用之功。

现在稍微总结回顾一下我的学习方法:我总是试图找到很多可供参考的东西,然后阅读了解很多相关的东西,最后才看那些“重点”,缺少深加工 和 反复理解思考 以及 及时复习温故

那在以后的学习中,希望我可以做到至少下面这几点:

学习过程中时刻思考-多角度思考可能会出现的题目考点-思考是不是自己在“表面上努力自我感动”然后及时调整学习方法-最后,我真的佩服那个女生,自愧不如,相形见绌,(语言能力有限)总的来说就是革命尚未成功,同志仍需努力!加油!

发布了68 篇原创文章 · 获赞 20 · 访问量 6866

猜你喜欢

转载自blog.csdn.net/qq_43633973/article/details/103799723
今日推荐