• 远程线程注入


    第一节 前置知识

    • 提起远程线程注入,大家有可能会理解为我在广西,你在北京,我注入你的线程。其实并不是这个样子。
    • 系统在每次运行一个exe 程序的时候系统会默认分配一个4G 的地址空间,给这个exe 程序。
    • 然而,我们的系统有16G、32G等等。那岂不是只能运行几个exe 程序了?
    • 其实我们在给exe 程序分配地址空间的时候,是一个虚拟地址空间。
    • 通过映射的方式,映射到我们的真实机上。
    • 每个exe 程序之间是不能互相访问的。比如QQ 和 微信不能互相访问。如果可以访问,就会非常不安全。

    第二节 注入初始

    DLL文件,是动态链接库,我们执行程序的时候最后调用的都是这个DLL文件。

    每一个进程中都会有多个线程。
    远程线程注入,简单来说,就是指我们通过一个工具或者其他的方法,在一个指定的进程中,开辟一个线程的内存地址空间,然后用来执行加载我们的DLL文件,或者是我们想做的事情。

    第三节 注入原理

    • 我们的程序在执行的过程中,都会创建一个进程、线程,然后通过进程线程加载DLL文件,从而执行我们想要的功能。
    • 在我们的系统程序中, 有两个DLL程序就是Kernel32.dll 和user32.dll 。这两个DLL 是在大部分程序上都会调用的DLL。
    • 其中Kernel32.dll 文件中有一个LoadLibraryW函数。这个函数是应用程序调用动态链接库时的函数。
    • 为什么使用这两个函数呢?
    • 因为同一个DLL,在不同的进程中,不一定被映射(加载)到同一个内存地址下
      有的加载的是同一个DLL文件的A函数
      有的加载的是同一个DLL文件的B函数
    • 但是Kernel32.dll 和user32.dll 是例外的,他们总是被映射到进程的内存首选地址。
    • 因此在所有使用这个DLL文件的进程中,这两个DLL的内存地址是相同的

    第四节 注入思路

    思路一:

    • 获取一个目标线程的句柄
    • 在我们的进程中得到LoadLibrary 函数的地址,因为加载时这个DLL文件的内存地址相同,所以这个地址也是目标进程中的地址
    • 传入我们想要注入进去的DLL的地址
    • 开启一个线程(开辟一块内存地址空间)
    • 让这个线程,在我们想要注入的目标进程中工作,这个线程的作用就是使用LoadLibrary 这个函数加载我们想注入的DLL

    思路二:

    • 提升进程的权限:因为我们要将程序注入到别的进程中,所有我们的权限一定要够,比如说我们的系统有system用户和administrator用户等
    • 查看我们获得到的特权信息是什么
    • 调节进程权限
    • 查找窗口,就是获得指定程序的进程,可以理解为就是获取窗口句柄
    • 根据窗口句柄获取进程的PID(Process ID)
    • 根据PID获取进程句柄。由于PID只是一个进程的序号,不够强大,所以我们需要获取一个更加强大的控制进程的东西,叫做进程句柄。这个进程句柄可以控制exe的关闭、暂停、执行等等行为。
    • 根据进程句柄在指定的进程中申请一块内存地址空间。拿到进程句柄后,就可以对exe进行操作了。由于我们想要学习的进程注入,所以演示进程注入
    • DLL的路径写入到远程进程中
    • 在远程进程中开辟一个线程

    第五节 项目实战

    好了,经过上面的学习,我们对远程线程注入有了一个基本的了解。虽然有了了解,但是还是对远程线程注入的过程、原理以及使用有一些模糊,不太理解。(个人学习过程中的感悟)
    我们需要配合一些远程线程注入的实战来进一步了解

    项目一:
    要求:编写一个程序,在程序中,指定注入的DLL的文件的路径以及被注入进程的信息。当我们点击注入后,就可以将DLL程序注入到进程中,从而执行我们注入的DLL文件

    成果展示:

    代码展示:

    1. 步骤一获取我们想要获取权限的锁
    
    	//1. 提升进程的权限
    	//   因为我们要将我们写的程序注入到别的进程之中,所以我们的权限一定要够
    	//   比如操作系统有system\administrator用户
    	/*
    		函数简介:
    			OpenProcessToken:我们想要提升权限,首先要进入到提升权限的空间中,相当于那一把钥匙,打开提升权限的盒子。
    			GetCurrentProcess:获取我的进程。得到我的自己的进程的锁。
    			TOKEN_ALL_ACCESS:打开所有的权限。打开读写等等所有权限
    	*/
    
    	HANDLE hToken;//将获取到的“钥匙”保存到这个变量。
    	// || OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken);
    	//如果返回的结果是FALSE,就是获取失败,提示获取钥匙失败
    	if (FALSE == OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)){
    		MessageBox(L"打开进程,访问令牌失败");
    		return;
    	}
    
    	//==================执行到这里就说明是拿到了锁==================
    
    
    1. 查看进程里面的特权信息
            //2. 查看进程里面的特权信息
    	/*
    		LookupPrivilegeValue:查看盒子,去看一下盒子里面的信息
    		参数一:系统特权名字
    			NULL:查看本机系统
    		参数二:主要看什么特权
    			SE_DEBUG_NAME:调试权限
    		参数三:将系统的权限赋值给变量
    			luid
    	*/
    	//将获取到的权限,赋值给一个变量
    	LUID luid;
    	// || LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid);
    	//判断是否获取成功
    	if (FALSE == LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)){
    		MessageBox(L"查看进程里面的特权信息失败。");
    		return;
    	}
    
    	//==================执行到这里就说明是获取到了权限==================
    
    1. 调节进程的权限
            //3. 调节进程权限
    	/*
    		AdjustTokenPrivileges:
    			参数一:拿着钥匙hToken,第一步获取到的
    			参数二:是否禁用所有的特权。我们使用的是不禁用
    	*/
    	//定义一个新的特权,用来接收函数调节后的权限。
    	TOKEN_PRIVILEGES tkp;
    	tkp.PrivilegeCount = 1;//特权数组的个数为一个
    	tkp.Privileges[0].Attributes + SE_PRIVILEGE_ENABLED;//因为只有一个元素,所有数组就是0
    	tkp.Privileges[0].Luid = luid; //将获得到的权限赋值给这个特权。
    	if (FALSE == AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)){
    		MessageBox(L"调节进程权限失败");
    		return;
    	}
    
    
    	//==================执行到这里就说明是已经调节了权限==================
    
    1. 查找窗口(就是获取指定应用程序的进程)
    	//4. 查找窗口(就是获取指定应用程序的进程)
    	/*
    		FindWindow:
    			参数一:Notepad,进程的类,一个应用程序的类是一样的
    			参数二:就是标题
    	*/
    	//如果找到了,这个变量就有值了
    	HWND hNotepader = ::FindWindow(L"Notepad",L"新建文本文档.txt - 记事本");
    	//判断这个窗口是否打开
    	if (hNotepader == NULL){
    		MessageBox(L"没有打开记事本");
    		return;
    	}
    
    	//==================执行到这里就说明是已经获取到了应用程序的窗口==================
    
    1. 获取进程PID(Process ID进程ID)
    	//5. 获取进程PID(Process ID进程ID)
    	/*
    		GetWindowThreadProcessId:函数就是根据窗口句柄,获取PID
    			参数一:传入一个窗口的句柄
    			参数二:传入接收获取到的PID的变量
    	*/
    	DWORD dwPID = 0;
    	GetWindowThreadProcessId(hNotepader,&dwPID);
    	if (dwPID == 0){
    		MessageBox(L"获取进程PID失败");
    		return;
    	}
    
    
    	//==================执行到这里就说明是已经获取到了进程PID==================
    
    
    1. 根据PID(进程的序号)获取进程句柄
    	//由于PID只是一个进程的序号,不够强大,所以我们需要获取一个更加强大的控制进程的东西,叫做进程句柄
    	//这个进程句柄可以控制exe的关闭、暂停、执行等等行为
    
    	//6. 根据PID(进程的序号)获取进程句柄
    	/*
    		OpenProcess()函数:打开一个进程
    			参数一:以所有(最大)的权限打开,就是我们可以执行任何权限
    			参数二:是否可以继承父进程的环境变量,一些属性,FALSE是不继承
    			参数三:根据指定的PID打开一个进程
    		RETURN:
    			打开成功会返回一个进程句柄
    	*/
    	//记事本的进程句柄
    	HANDLE hNotepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
    	//判断记事本的进程句柄是否成功
    	if (hNotepad == NULL){
    		MessageBox(L"打开进程失败");
    	}
    
    	//==================执行到这里就说明是已经获取到了进程句柄==================
    
    1. 因为每个进程中,有4G的虚拟地址空间,在远程进程中申请一小块内存空间
    	//拿到进程句柄后,就可以对exe进行操作了。
    	//由于我们想要学习的进程注入,所以演示进程注入
    
    	//7. 因为每个进程中,有4G的虚拟地址空间,在远程进程中申请一小块内存空间
    	/*
    		VirtualAllocEx()函数:专门在远程进程中进行内存申请的
    			参数一:指定在哪个进程中申请(根据进程句柄)
    			参数二:指定申请的位置,NULL是指不指定那一块,随便给一块地址就可以
    			参数三:指定申请的大小,0x1000:4096个字节
    			参数四:申请一块物理地址,物理存储器用来存储虚拟内存。
    			参数五:让这个空间可读可写可执行。就是我们可以进行操作
    		RETURN:
    			返回一个地址空间
    	*/
    	LPVOID lpAddr = VirtualAllocEx(hNotepad, NULL, 0x0100, MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    	//判断申请的空间是否成功
    	if (lpAddr == NULL){
    		MessageBox(L"在远程进程中申请内存是否成功");
    	}
    
    
    
    	//==================执行到这里就说明是已经获取到了远程进程的内存空间==================
    
    1. 将DLL路径写入远程进程中
    	//8. DLL的路径写入到远程进程中
    	/*
    		WriteProcessMemory()函数:
    			参数一:写入到指定的进程中
    			参数二:写入到指定的申请的地址空间中
    			参数三:将指定的DLL文件的路径写入进去
    			参数四:指定的DLL文件大小,多少字节
    			参数五:实际写入到多少个字节,NULL不关注,只关注当前就可以
    		RETURN:
    			失败返回FALSE
    			成功放回TRUE
    	*/
    	//指定我们需要注入的DLL的文件路径
    	TCHAR szDLLPath[] = L"C:\\Users\\lenovo\\Desktop\\123.dll";
    
    	if (FALSE == WriteProcessMemory(hNotepad, lpAddr, szDLLPath, sizeof(szDLLPath), NULL)){
    		MessageBox(L"在远程进程中写入数据失败");
    		return;
    	}
    	/*
    		GetModuleHandle()函数:能够获得我们Kernel32.dll的句柄
    			参数一:获取指定名字的句柄
    		GetProcAddress()函数:返回一个指定函数的地址
    			参数一:是一个获取到的dll
    			参数二:是一个指定的函数
    
    
    		Kernel32.dll是一个核心的动态库,所有的exe进程都加载了这个动态链接函数
    		记事本也加载了Kernel32.dll
    		但是所有的dll动态链接函数在动态链接库中都是只有一份
    		不同的exe程序调用的dll文件都是只有一份
    		也就是说所有的exe都加载了Kernel32.dll文件
    		既然是共享的,那么dll里面的函数也是共享的
    
    	*/
    	//GetModuleHandle(L"Kernel32.dll");
    	//GetProcAddress(GetModuleHandle(L"Kernel32.dll"),"LoadLibraryA");//窄字符
    	//可以理解为返回一个函数指针
    	PTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW");//宽字符
    	//LPTHREAD_START_ROUTINE *pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW");//宽字符
    	
    	if (pfnStartAddr == NULL){
    		MessageBox(L"返回指针失败");
    		return;
    	}
    
    	//==================执行到这里就说明是已经获取到了指定DLL文件的指定函数的指针==================
    
    1. 再远程线程中开辟一个线程
    	//9. 在远程进程中开辟一个线程
    	/*
    		CreateRemoteThread()函数:我们打开注册器,让它在记事本中自己开一个线程
    			参数一:在指定的进程中开线程
    			参数二:线程的安全属性为NULL
    			参数三:堆栈大小默认为0
    			参数四:远程线程执行哪个函数( 线程入口函数的起始地址)
    			参数五:传进来我们申请的地址空间
    			参数六:什么时候启动,0为马上启动
    			参数七:线程ID,NULL就可以
    		RETURN:
    			返回一个远程线程句柄
    	*/
    	HANDLE hRemote = CreateRemoteThread(hNotepad, NULL, 0, (LPTHREAD_START_ROUTINE)pfnStartAddr, lpAddr, 0, NULL);
    	WaitForSingleObject(hRemote, INFINITE);
    	//判断创建远程线程是否成功
    	if (hRemote == NULL){
    		MessageBox(L"创建远程线程失败");
    		return;
    	}
    

    第六节 结语

    本次博客只是演示了一个项目用于让没有基础的同学,理解远程线程注入的基础,更加高深的利用手法,需要自行琢磨
    本次的项目灵感来自于顿开教育里奇老师。

  • 相关阅读:
    专业隐私文件/文件夹加密隐藏软件 - Wise Folder Hider
    Go-Zero自定义goctl实战:定制化模板,加速你的微服务开发效率(四)
    工业4.0的安全挑战与解决方案
    网站被劫持勒索怎么办
    IDEA 开发插件,插件依赖|文件路径转VirtualFile 遇坑随笔
    python视频帧转图像
    工作积累——JPA事务中数据更新后查询结果为旧数据的问题
    Redis为什么变慢了
    视频推流测试——使用ffmpeg进行推流生成rtsp视频流
    【Python基础篇015】第叁章模块大全之《 os模块》
  • 原文地址:https://www.cnblogs.com/atzxc/p/16651171.html