首页 > 其他分享 >远程线程注入

远程线程注入

时间:2022-09-02 20:45:01浏览次数:40  
标签:句柄 DLL 获取 线程 进程 参数 远程 注入

第一节 前置知识

  • 提起远程线程注入,大家有可能会理解为我在广西,你在北京,我注入你的线程。其实并不是这个样子。
  • 系统在每次运行一个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;
	}

第六节 结语

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

标签:句柄,DLL,获取,线程,进程,参数,远程,注入
From: https://www.cnblogs.com/atzxc/p/16651171.html

相关文章

  • 借助cpolar内网穿透轻松访问远程NAS
    在现代企业中,协同办公早已不是什么新鲜事,很多公司都会设置NAS服务器,加强不同部门或不同员工之间对同一任务或项目的跟进流程,提高工作效率。虽然这样做能提升项目或工作的效......
  • 日常开发记录- git 添加多个远程仓库
    应用场景:同步不同服务器下的项目开发进度查看远程仓库:gitremote-v添加远程仓库:temp是新的远程仓库名,不能与master重名,这样就做好本地与远程的关联了gitremote......
  • c++ x64 读取指定线程TEB地址
    调用微软未公开函数ZwQueryInformationThread网上挺多帖子说得到的地址值为全c,查阅部分资料后发现64位系统与32位有一些区别,主要是_THREAD_BASIC_INFORMATION结构体的长度......
  • c#线程池使用之_回调方法中传递多个参数的处理
    如题,在这里主要是做个关于线程池使用过程中回调方法需要用到多个参数的时候的场景 下面中的代码做个例子:我需要用到一个WebClientDown2来去远程地址下载一个文件保存到......
  • 内部类-多线程-静态代理
    内部类概念:一个类中定义另外一个类,那这个另外的类就是内部类分类:在类的成员位置:成员内部类(如果内部类被static修饰,则这个内部类称之为静态内部类)在......
  • SQL注入知多少?
    SQL注入即是指WEB应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在WEB应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句。在管理员不知情的情况下......
  • rsync同步远程复制学习
    常用服务之rsync(1)Cloud研习社 Cloud研习社 2022-08-2307:38 发表于山东收录于合集#一站式教程128个#linux61个#服务器7个#云计算43个#rsync3个教程每周二......
  • 6.线程池
    1.Python3中官方才正式提供线程池。2.线程不是开的越多越好,开的多了可能会导致系统的性能更低。注意:不要进行无限制的创建线程。3.线程池的使用示例1:  ......
  • 5.线程锁
    1.在程序中如果想要手动加锁,一般有两种:Lock和RLock(1)Lock,同步锁(不支持同时锁两次)  (2)RLock,递归锁      (3)Rlock支持多次申请锁和多次......
  • 4.线程安全
    一个进程中有很多线程,且线程共享所有进程中的资源。多进程统统是去操作一个“东西”,可能会存在数据混乱的情况,例如:1.示例一:importthreatingloop=10000number=0......