freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Remote thread injection
2023-01-29 18:10:42
所属地 重庆

前言

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习)

远程线程注入

有Windows编程基础的兄弟应该都比较熟悉这一技术,也算是比较成熟的技术了,实现方式也是多种多样。
简单来说什么是远程线程注入,即一个进程在另一个进程中创建线程的技术,可以分为Dll、代码注入(本文都采用shellcode),其实两者差距不大,
原理都是一样的。

基础知识

我们先来看看几个Windows的API函数。

CreateProcessA

创建一个进程及其主线程,我们使用这个函数主要就是为了自己创建一个进程,这样可以很方便的获得其标识进程或线程的值(当然也可以通过遍历的方式去获取目标标识进程或线程的值)。
MSDN官方文档如下
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-CreateProcessAa

image
其参数如下

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

函数的具体用法这里不再给出,具体参考MSDN的官方文档即可。

virtualAllocEx

既然我们是远程线程注入,因此在目标程序中肯定需要一块空间,那么我们就需要使用函数去目标进程中申请一块地址空间。
MSDN参考如下
https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
image
其参数如下

LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

WriteProcessMemory

我们有了空间,然后我们还需要把其移动进去,因此我们还需要一个函数WriteProcessMemory(将数据写入指定进程中的内存区域,即我们使用virtualAllocEx获得的内存区域)。

MSDN参考如下
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
image
参数如下

BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);

主要就是这几个函数,其实现都不难

CreateRemoteThread经典注入

我们看看MSDN中对于这个函数的解释
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread
image
参数如下

HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);

CreateRemoteThread函数共提供有七个参数,这些看官方文档就一目了然了。
我们来看看CreateRemoteThread函数的LPTHREAD_START_ROUTINE lpStartAddress这个参数,我们看看官方的解释是什么,
该参数表示的是远程进程中线程的起始地址,有基础的读者应该很快的明白了,我们需要在远程进程中申请一块虚拟地址
image
根据前面的基础知识,我们应该就很清楚了。

代码实现

  • CreateProcessA创建一个进程(目的为了获得其标识进程或线程的值)

  • 通过VirtualAllocEx函数在打开的进程中申请一块内存,返回值为基地址

  • WriteProcessMemory函数将数据写入上面申请的内存中

  • 利用CreateRemoteThread函数创建在上面打开的进程的虚拟地址空间中运行的线程

#include <windows.h>
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
	.....................................
	.....................................
  .....................................
	//在打开的进程中申请一块内存,返回值为基地址
	LPVOID Address = VirtualAllocEx(pi.hProcess, NULL, sizeof(buf), (MEM_RESERVE | MEM_COMMIT), 	.....................................
	.....................................
  .....................................

	CloseHandle(Th_Handle);
	CloseHandle(pi.hProcess);

	return 0;
}

这里我们执行一个弹窗来测试
image

NtCreateThreadEx注入

NtCreateThreadEx是更底层的API,很显然属于未公开的api,NtCreateThreadEx在32位下和64位下函数原型不一致。

我们任然可以很清楚的看到所需的参数,与CreateRemoteThread函数很类似,对照MSDN的官方手册填写即可

#ifdef _AMD64_
	typedef DWORD(WINAPI* functypeNtCreateThreadEx)(
		PHANDLE ThreadHandle,
		ACCESS_MASK DesiredAccess,
		LPVOID ObjectAttributes,
		HANDLE ProcessHandle,
		LPTHREAD_START_ROUTINE lpStartAddress,
		LPVOID lpParameter,
		ULONG CreateThreadFlags,
		SIZE_T ZeroBits,
		SIZE_T StackSize,
		SIZE_T MaximunStackSize,
		LPVOID pUnkown);

functypeNtCreateThreadEx NtCreateThreadEx = (functypeNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");

#else
typedef DWORD(WINAPI *functypeNtCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	BOOL CreateThreadFlags,
	DWORD  ZeroBits,
	DWORD  StackSize,
	DWORD  MaximumStackSize,
	LPVOID pUnkown);

functypeNtCreateThreadEx NtCreateThreadEx = (functypeNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");

#endif // DEBU

代码实现

  • CreateProcessA创建一个进程(目的为了获得其标识进程或线程的值)

  • 通过VirtualAllocEx函数在打开的进程中申请一块内存,返回值为基地址

  • WriteProcessMemory函数将数据写入上面申请的内存中

  • 利用NtCreateThreadEx函数创建在上面打开的进程的虚拟地址空间中运行的线程

#include <windows.h>
#include <iostream>
using namespace std;

#ifdef _AMD64_
typedef DWORD(WINAPI* functypeNtCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	ULONG CreateThreadFlags,
	SIZE_T ZeroBits,
	SIZE_T StackSize,
	SIZE_T MaximunStackSize,
	LPVOID pUnkown);

functypeNtCreateThreadEx NtCreateThreadEx = (functypeNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");

#else
typedef DWORD(WINAPI* functypeNtCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	BOOL CreateThreadFlags,
	DWORD  ZeroBits,
	DWORD  StackSize,
	DWORD  MaximumStackSize,
	LPVOID pUnkown);

functypeNtCreateThreadEx NtCreateThreadEx = (functypeNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");

#endif // DEBU


int main()
{
	.....................................
	.....................................
  .....................................

	//打开指定进程
	CreateProcessA(NULL, (LPSTR)"notepad.exe", NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
	
	.....................................
	.....................................
  .....................................
	
    CloseHandle(pi.hProcess);

	return 0;
}

image

RtlCreateUserThread注入

RtlCreateUserThread其实是对NtCreateThreadEx的包装,话不多说了直接看函数的参数
很清晰了,实现也很简单这里就不多说了

typedef DWORD(WINAPI* pRtlCreateUserThread)(    //函数申明
	IN HANDLE                     ProcessHandle,
	IN PSECURITY_DESCRIPTOR     SecurityDescriptor,
	IN BOOL                     CreateSuspended,
	IN ULONG                    StackZeroBits,
	IN OUT PULONG                StackReserved,
	IN OUT PULONG                StackCommit,
	IN LPVOID                    StartAddress,
	IN LPVOID                    StartParameter,
	OUT HANDLE                     ThreadHandle,
	OUT LPVOID                    ClientID
	);

pRtlCreateUserThread RtlCreateUserThread = (pRtlCreateUserThread)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");

代码实现

  • CreateProcessA创建一个进程(目的为了获得其标识进程或线程的值)

  • 通过VirtualAllocEx函数在打开的进程中申请一块内存,返回值为基地址

  • WriteProcessMemory函数将数据写入上面申请的内存中

  • 利用RtlCreateUserThread函数创建在上面打开的进程的虚拟地址空间中运行的线程

#include <windows.h>
#include <iostream>
using namespace std;

typedef DWORD(WINAPI* pRtlCreateUserThread)(    //函数申明
	IN HANDLE                     ProcessHandle,
	IN PSECURITY_DESCRIPTOR     SecurityDescriptor,
	IN BOOL                     CreateSuspended,
	IN ULONG                    StackZeroBits,
	IN OUT PULONG                StackReserved,
	IN OUT PULONG                StackCommit,
	IN LPVOID                    StartAddress,
	IN LPVOID                    StartParameter,
	OUT HANDLE                     ThreadHandle,
	OUT LPVOID                    ClientID
	);

pRtlCreateUserThread RtlCreateUserThread = (pRtlCreateUserThread)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");


int main()
{
	.....................................
	.....................................
  .....................................

	//打开指定进程
	CreateProcessA(NULL, (LPSTR)"notepad.exe", NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);

	.....................................
	.....................................
  .....................................
	CloseHandle(pi.hProcess);

	return 0;
}

image

Injection 突破Session 0

假如我们想要注入到lsass进程,我们是否也可以注入成功呢?
这里还需要了解到一个知识点,就是关于SeDebugPrivilege,SeDebugPrivilege是调试权限的程序,开启了这个特权之后可以可以读写system启动的进程的内存,
很显然我们想要dump或者OpenProcess下lsass.exe进程,那么肯定需要SeDebugPrivilege权限
这里就直接给出了,权限提升我另外一篇介绍过,这里用修改访问令牌进行权限提升获得SeDebugPrivilege权限

void GetPrivilege() {
	HANDLE TokenHandle = NULL;

	OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &TokenHandle);
	TOKEN_PRIVILEGES tp;
	tp.PrivilegeCount = 1;
	LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
	tp.Privileges[0].Attributes = true ? SE_PRIVILEGE_ENABLED : 0;

	AdjustTokenPrivileges(TokenHandle, FALSE, &tp, sizeof(tp), NULL, NULL);
	CloseHandle(TokenHandle);

}

我们用CreateRemoteThread函数进行测试,函数注入失败
image
在使用传统的进程注入技术的过程中,可以向普通用户用户进程注入shellcode或dll,那么如果我们想更进一步注入到系统进程内,通常会失败,这是由于session 0隔离的缘故
Session 0会话:在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,第一个登录到控制台的用户来启动服务和应用程序(所有的服务和应用程序都是运行在与第一个登录到控制台的用户得Session中),由于服务是以高权限运行的,所以会造成一些安全风险。
Session 0隔离:从Windows Vista开始应用程序和服务是隔离开来的,即服务在一个叫做Session 0 的特殊Session中承载,应用程序运行在Session 0之后(Session 1、Session 2等),因此是不能互相传递窗体消息,共享UI元素或者共享kernel对象。

ZwCreateThreadEx函数

ZwCreateThreadEx函数比CreateRemoteThread函数更接近内核,CreateRemoteThread最终也是调用ZwCreateThreadEx函数来创建线程的。
ZwCreateThreadEx的第7个参数 CreateSuspended(CreateThreadFlags)的值始终为1,它会导致线程创建完成后一直挂起无法恢复运行,于是我们选择直接调用ZwCreateThreadEx,将第7个参数直接置为0,这样可达到注入目的
函数原型

#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	ULONG CreateThreadFlags,
	SIZE_T ZeroBits,
	SIZE_T StackSize,
	SIZE_T MaximumStackSize,
	LPVOID pUnkown
	);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	BOOL CreateSuspended,
	DWORD dwStackSize,
	DWORD dw1,
	DWORD dw2,
	LPVOID pUnkown
	);
#endif

typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwCreateThreadEx");

代码实现

接下来实现就很简单了,这里采用Dll进行注入(因为系统程序中不能显示程序窗体)

#include <windows.h>
#include <iostream>

using namespace std;

#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	ULONG CreateThreadFlags,
	SIZE_T ZeroBits,
	SIZE_T StackSize,
	SIZE_T MaximumStackSize,
	LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	BOOL CreateSuspended,
	DWORD dwStackSize,
	DWORD dw1,
	DWORD dw2,
	LPVOID pUnkown);
#endif

typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(GetModuleHandleA("ntdll.dll") , "ZwCreateThreadEx");

void GetPrivilege() {
	HANDLE TokenHandle = NULL;

	.....................................
	.....................................
  .....................................
		return 1;
	}
	cout << "OpenProcess Success" << endl;
	LPVOID Address = VirtualAllocEx(Handle, NULL, sizeof(DllPath), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	if (Address == NULL) {
		cout << "VirtualAllocEx Error" << endl;
		return 1;
	}
.....................................
	.....................................
  .....................................

	CloseHandle(hRemoteThread);
	CloseHandle(Handle);
	FreeLibrary(GetModuleHandleA("Kernel32.dll"));
	FreeLibrary(GetModuleHandleA("ntdll.dll"));

	return 0;
}

可以看到成功注入Dll
image

线程劫持注入

顾名思义,我们去劫持指定进程的线程来进行注入代码,整体也很简单,我们先来看看几个Windows的API函数

SuspendThread

挂起指定的线程,非常简单的一个函数,只有一个参数
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread
image
参数如下

DWORD SuspendThread(
  [in] HANDLE hThread
);

可以看出不是很难,大致流程如上,对照MSDN的官方文档即可写出

GetThreadContext

检索指定线程的上下文
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext
image

BOOL GetThreadContext(
  [in]      HANDLE    hThread,
  [in, out] LPCONTEXT lpContext
);

SetThreadContext

设置指定线程的上下文,主要就是设置eip\rip指向payload的地址
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext
image

BOOL SetThreadContext(
  [in] HANDLE        hThread,
  [in] const CONTEXT *lpContext
);

ResumeThread

恢复由SuspendThread函数挂起的线程
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread
image

DWORD ResumeThread(
  [in] HANDLE hThread
);

代码实现

  • CreateProcessA创建一个进程(目的为了获得其标识进程或线程的值)

  • 通过VirtualAllocEx函数在打开的进程中申请一块内存,返回值为基地址

  • WriteProcessMemory函数将数据写入上面申请的内存中

  • 利用SuspendThread函数挂起指定的线程

  • GetThreadContext函数检索指定线程的上下文

  • 通过SetThreadContext函数设置eip\rip指向payload的地址

  • 最后通过ResumeThread函数恢复挂起的线程

#include <windows.h>
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
	.....................................
	.....................................
  .....................................

	//打开指定进程,为了自动获取PID
	CreateProcessA(NULL, (LPSTR)"notepad.exe", NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);

	//在打开的进程中申请一块内存,返回值为基地址
	LPVOID Address = VirtualAllocEx(pi.hProcess, NULL, sizeof(buf), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	.....................................
	.....................................
  .....................................
	ResumeThread(pi.hThread);
	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);

	return 0;
}

image

APC注入

APC(异步过程调用)。每个线程都维护着一个APC链,可以让一个线程在本应该执行的步骤前执行别的代码。
因此我们只需要向目标进程中的线程APC队列中添加APC过程,等线程恢复即可实现APC注入(当然我们可以向进程的所有线程都进行注入,虽然提高了准确率,但是风险性也提高了)
MSDN的参考链接
https://learn.microsoft.com/en-us/windows/win32/sync/asynchronous-procedure-calls

QueueUserAPC

每个线程都有自己的 APC 队列。应用程序通过调用 QueueUserAPC 函数将 APC排队到线程。调用线程在对QueueUserAPC的调用中指定APC函数的地址。
当用户模式APC排队时,除非它处于可报警状态,否则它排队的线程不会被引导调用APC函数。当线程调用SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitFormultipleObjectsOx或WaitForSingleObjectEx函数时,它将进入可报警状态。(即需要让线程进入可告警状态其才会触发)

QueueUserAPC

我们需要使用QueueUserApc这个函数将其添加到指定线程的 APC 队列中,我们看看MSDN上对于这个函数的详细解释
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc
image
函数的参数

DWORD QueueUserAPC(
  [in] PAPCFUNC  pfnAPC,
  [in] HANDLE    hThread,
  [in] ULONG_PTR dwData
);

SleepEx

我们这里使用SleepEx函数让线程进入可告警状态
https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex
image

DWORD SleepEx(
  [in] DWORD dwMilliseconds,
  [in] BOOL  bAlertable
);

该函数也很简单

代码实现

  • CreateProcessA创建一个进程(目的为了获得其标识进程或线程的值)

  • 通过VirtualAllocEx函数在打开的进程中申请一块内存,返回值为基地址

  • WriteProcessMemory函数将数据写入上面申请的内存中

  • QueueUserApc这个函数将其添加到指定线程的 APC 队列中

  • SleepEx函数使线程进入可告警状态

#include <windows.h>
#include <iostream>

using namespace std;
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")


int main()
{
	.....................................
	.....................................
  .....................................
	//在打开的进程中申请一块内存,返回值为基地址
	LPVOID Address = VirtualAllocEx(pi.hProcess, NULL, sizeof(dllpathy) + 1, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	.....................................
	.....................................
  .....................................
	SleepEx(2000, FALSE);
	//关闭线程、进程句柄
	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);

    return 0;
}

image
image

Early bird

其实大致和上面差不多,只不过这种手法是将打开进程先挂起,然后在进行申请空间,写入然后将APC插入线程,最后恢复挂起的线程。
Early Bird是一种简单而强大的技术,Early Bird本质上是一种APC注入与线程劫持的变体,由于线程初始化时会调用ntdll未导出函数NtTestAlert**,**NtTestAlert是一个检查当前线程的 APC 队列的函数,如果有任何排队作业,它会清空队列。当线程启动时,NtTestAlert会在执行任何操作之前被调用。因此,如果在线程的开始状态下对APC进行操作,就可以完美的执行。
image

代码实现

  • CreateProcessA创建一个进程,并将其主线程挂起(目的为了获得其标识进程或线程的值)

  • 通过VirtualAllocEx函数在打开的进程中申请一块内存,返回值为基地址

  • WriteProcessMemory函数将数据写入上面申请的内存中

  • QueueUserApc这个函数将其添加到指定线程的 APC 队列中

  • ResumeThread恢复挂起的线程

#include <windows.h>
#include <iostream>
#include <processthreadsapi.h>

using namespace std;
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")


int main()
{
	.....................................
	.....................................
  .....................................
	LPVOID Address = VirtualAllocEx(pi.hProcess, NULL, sizeof(dllpathy) + 1, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	.....................................
	.....................................
  .....................................
	ResumeThread(pi.hThread);
	CloseHandle(pi.hThread);

	return 0;
}

image

映射注入

映射注入是一种内存注入技术,可以避免使用一些经典注入技术使用的API,如VirtualAllocEx,WriteProcessMemory等被杀毒软件严密监控的API,同时创建Mapping对象本质上属于申请一块物理内存,而申请的物理内存又能比较方便的通过系统函数直接映射到进程的虚拟内存里,这也就避免使用经典写入函数,增加了隐蔽性。
image
我们先来看看几个基础的函数

CreateProcessA

创建一个进程
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-CreateProcessAa
image
参数如下

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

CreateFileMappingA

其返回值是新创建的文件映射对象的句柄
https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createfilemappinga
image

HANDLE CreateFileMappingA(
  [in]           HANDLE                hFile,
  [in, optional] LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
  [in]           DWORD                 flProtect,
  [in]           DWORD                 dwMaximumSizeHigh,
  [in]           DWORD                 dwMaximumSizeLow,
  [in, optional] LPCSTR                lpName
);

MapViewOfFile

将文件映射的视图映射到调用进程的地址空间,返回值为映射视图的起始地址。
https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile
image

LPVOID MapViewOfFile(
  [in] HANDLE hFileMappingObject,
  [in] DWORD  dwDesiredAccess,
  [in] DWORD  dwFileOffsetHigh,
  [in] DWORD  dwFileOffsetLow,
  [in] SIZE_T dwNumberOfBytesToMap
);

其中hFileMappingObject即是CreateFileMappingA函数的返回值

memcpy

将buf复制到被映射的虚拟地址

MapViewOfFile2

将文件视图或页面文件备份部分映射到指定进程的地址空间。
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile2
image

PVOID MapViewOfFile2(
  [in]           HANDLE  FileMappingHandle,
  [in]           HANDLE  ProcessHandle,
  [in]           ULONG64 Offset,
  [in, optional] PVOID   BaseAddress,
  [in]           SIZE_T  ViewSize,
  [in]           ULONG   AllocationType,
  [in]           ULONG   PageProtection
);

函数都不难,参照官方文档即可

代码实现

  • CreateFileMappingA在注入进程创建文件映射对象

  • MapViewOfFile函数将CreateFileMappingA创建的文件映射对象映射到调用进程的地址空间。

  • memcpy往被映射的虚拟地址写入shellcode

  • CreateProcessA创建一个进程,并将其主线程挂起(目的为了获得其标识进程或线程的值,)

  • MapViewOfFile2将文件视图或页面文件备份部分映射到指定进程的地址空间(CreateProcessA创建的进程)。

  • QueueUserApc往这个队列中插入一个回调

  • ResumeThread恢复主线程的运行

#include <windows.h>
#include <memoryapi.h>
#include <winbase.h>
#include <iostream>

using namespace std;

#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
#pragma comment (lib, "OneCore.lib")


int main() {
	.....................................
	.....................................
  .....................................
	memcpy(Mappf, dllpathy, sizeof(dllpathy));
	.....................................
	.....................................
  .....................................
	ResumeThread(pi.hThread);
    CloseHandle(pi.hProcess);
	CloseHandle(CreateHanlde);
	CloseHandle(Createmapp);
	UnmapViewOfFile(Mappf);

	return 0;
}

image

NtCreateSection&NtMapViewOfSection

此技术与映射注入大致流程差不多,但是这个技术更为接近底层,映射注入使用的api本质上是ntdll导出函数的封装,这个注入技术则是直接调用ntdll的导出函数。(NtCreateSection、NtMapViewOfSection)

这种技术与映射注入具有同样的优点,我们可以不使用VirtualAllocEx,WriteProcessMemory经典函数。
我们来看看这两个函数

NtCreateSection

创建Section对象,即具有关联文件的虚拟内存块
image

NtMapViewOfSection

就是将NtCreateSection函数创建的Section对象,映射到内存中
image

代码实现

  • NtCreateSection函数创建Section对象,即具有关联文件的虚拟内存块(RWX)

  • NtMapViewOfSection将Section对象映射到本地进程内存(RW)

  • 然后NtMapViewOfSection将Section对象又映射到目标进程内存(RX)

  • memcpy将shellcode复制到本地视图,这将反映在目标进程的映射视图中

  • 利用RtlCreateUserThread函数创建在上面打开的进程的虚拟地址空间中运行的线程,其指向目标进程映射内存的虚拟地址

#include <Windows.h>
#include <iostream>
#include <string.h>
#pragma comment(lib, "ntdll")

#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")

typedef struct _LSA_UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
	ULONG Length;
	HANDLE RootDirectory;
	PUNICODE_STRING ObjectName;
	ULONG Attributes;
	PVOID SecurityDescriptor;
	PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;

.....................................
	.....................................
  .....................................
lNtMapViewOfSection fNtMapViewOfSection = (lNtMapViewOfSection)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtMapViewOfSection");

typedef DWORD(WINAPI* lRtlCreateUserThread)(    //函数申明
	IN HANDLE                     ProcessHandle,
	IN PSECURITY_DESCRIPTOR     SecurityDescriptor,
	IN BOOL                     CreateSuspended,
	IN ULONG                    StackZeroBits,
	IN OUT PULONG                StackReserved,
	IN OUT PULONG                StackCommit,
	IN LPVOID                    StartAddress,
	IN LPVOID                    StartParameter,
	OUT HANDLE                   ThreadHandle,
	OUT LPVOID                   ClientID
	);

lRtlCreateUserThread fRtlCreateUserThread = (lRtlCreateUserThread)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");


.....................................
	.....................................
  .....................................

int main()
{
	char dllpathy[] = "C:\\1\\Messagebox.dll";
	FARPROC myLoadLibraryA = GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");

	SIZE_T size = 4096;
	LARGE_INTEGER sectionSize = { size };
	HANDLE sectionHandle = NULL;
	PVOID localSectionAddress = NULL, remoteSectionAddress = NULL;

	.....................................
	.....................................
  .....................................

	//将shellcode复制到本地视图,这将反映在目标进程的映射视图中 
	memcpy(localSectionAddress, dllpathy, sizeof(dllpathy));

	.....................................
	.....................................
  .....................................
	
	return 0;
}

image

最后

这只是注入技术的冰山一角,还很多技术没有列举到,在后面的文章中会列举出来。

# 进程注入 # 远程线程
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录