有痕注入的分析和实现

背景

之前分析过某老哥的超级注入,最近突然来了兴致,简单的把它实现一下。

开整

首先说一下思路

1、找到目标进程,并且暂停它的第一个线程;

2、在目标进程中申请一个页大小的内存,放入shellcode,x86和x64分别做对应的处理(这里使用的是LdrLoadDll(加载之后痕迹比较明显),不满意可以自己映射,修复IAT);

3、替换返回到r3的eip/rip为shellcode的起始地址,恢复线程等待线程执行;

4、创建工作线程释放内存资源;

看着也挺简单的,这里有几个坑需要注意一下。

PsSuspendThread  PsResumeThread这两个函数不导出,所以这里写了一个搜索特征码的函数

	UCHAR suspendOpCodeWin7[] = { 0x4C,0x8B,0xEA,0x48,0x8B,0xF1,0x33,0xFF,0x89,0x7C,'*','*',0x65,
		'*','*','*','*','*','*','*','*',0x4C,0x89,'*','*','*','*','*','*',0x66,0x41,'*','*','*',
		'*','*','*','*',0x48,'*','*','*','*','*','*',0x0F,'*','*',0x48,0x8B,0x01 };
	UCHAR suspendOpCodeWin10[] = { 0x48,0x83,0xEC,'*',0x4C,0x8B,'*',0x48,0x8B,0xF9,0x83,0x64,0x24,
		0x20,'*',0x65,0x48,0x8B,0x34,0x25,0x88,0x01,0x00,'*',0x48,0x89,0x74,0x24,0x70 };
	g_PsSuspendThread = (PPsSuspendThread)SearchOPcode(pObj, L"ntoskrnl.exe", "PAGE", suspendOpCodeWin7, sizeof(suspendOpCodeWin7), -21);
	if (!g_PsSuspendThread)
	{
		g_PsSuspendThread = (PPsSuspendThread)SearchOPcode(pObj, L"ntoskrnl.exe", "PAGE", suspendOpCodeWin10, sizeof(suspendOpCodeWin10), -17);
		if(!g_PsSuspendThread)
			return STATUS_UNSUCCESSFUL;
	}

	UCHAR resumeOpCode1[] = { 0x40,0x53,0x48,'*','*','*',0x48,0x8B,0xDA,0xE8,'*','*','*','*',0x48,
		0x85,0xDB,0x74,'*',0x89,0x03,0x33,0xC0,0x48,'*','*','*',0x5B,0xC3 };
	UCHAR resumeOpCode2[] = { 0xFF,0xF3,0x48,'*','*','*',0x48,0x8B,0xDA,0xE8,'*','*','*','*',0x48,
		0x85,0xDB,0x74,'*',0x89,0x03,0x33,0xC0,0x48,'*','*','*',0x5B,0xC3 };
	UCHAR resumeOpCode3[] = { 0x48,0x83,0xEC,'*',0x48,0x8B,0xDA,0x48,0x8B,0xF9,0xE8,'*','*','*',
		'*',0x65,0x48,0x8B,0x14,0x25,'*','*','*','*',0x8B,0xF0,0x83,0xF8,0x01 };
	UCHAR resumeOpCode4[] = { 0x48,0x89,0x54,'*','*',0x48,0x89,'*','*','*',0x53,0x56,0x57,0x41,
		0x56,0x41,0x57 };

	g_PsResumeThread = (PPsResumeThread)SearchOPcode(pObj, L"ntoskrnl.exe", "PAGE", resumeOpCode1, sizeof(resumeOpCode1), 0);
	if (!g_PsResumeThread)
	{
		g_PsResumeThread = (PPsResumeThread)SearchOPcode(pObj, L"ntoskrnl.exe", "PAGE", resumeOpCode2, sizeof(resumeOpCode2), 0);
		if (!g_PsResumeThread)
		{
			g_PsResumeThread = (PPsResumeThread)SearchOPcode(pObj, L"ntoskrnl.exe", "PAGE", resumeOpCode3, sizeof(resumeOpCode3), -11);
			if (!g_PsResumeThread)
			{
				g_PsResumeThread = (PPsResumeThread)SearchOPcode(pObj, L"ntoskrnl.exe", "PAGE", resumeOpCode4, sizeof(resumeOpCode4), 0);
				if (!g_PsResumeThread)
				{
					return STATUS_UNSUCCESSFUL;
				}
			}
		}
	}

ZwGetNextThread只在win10上导出(只有win102h的虚拟机和win7sp1的做测试),win7不导出,还是特征码的方式定位。

	UNICODE_STRING ZwGetNextThreadString = RTL_CONSTANT_STRING(L"ZwGetNextThread");
	g_ZwGetNextThread = (PZwGetNextThread)MmGetSystemRoutineAddress(&ZwGetNextThreadString);
	if (!g_ZwGetNextThread)
	{
		UNICODE_STRING ZwGetNotificationResourceManagerString = RTL_CONSTANT_STRING(L"ZwGetNotificationResourceManager");
		PUCHAR ZwGetNotificationResourceManager = (PUCHAR)MmGetSystemRoutineAddress(&ZwGetNotificationResourceManagerString);
		if (ZwGetNotificationResourceManager)
		{
			PUCHAR starAddress = ZwGetNotificationResourceManager - 78;
			for(; starAddress < ZwGetNotificationResourceManager - 8; starAddress++)
			{
				if (starAddress[0] == 0x48 && starAddress[1] == 0x8B && starAddress[2] == 0xC4)
				{
					g_ZwGetNextThread = (PZwGetNextThread)starAddress;
					break;
				}	
			}
		}
		if(!g_ZwGetNextThread)
			return STATUS_UNSUCCESSFUL;
	}

x64的shellcode有一个坑需要注意

	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
	};

下面截图显示的是异常的时候rip执行的位置

 movaps会检测当前堆栈是否按照0x10对齐,所以那里的sub 0x26而不是0x28

x32的地方也有一个坑,+0x1488之后的context的第一个结构需要在一个dowrd大小的padding

typedef struct _WOW64_CONTEXT {

	//
	// The flags values within this flag control the contents of
	// a CONTEXT record.
	//
	// If the context record is used as an input parameter, then
	// for each portion of the context record controlled by a flag
	// whose value is set, it is assumed that that portion of the
	// context record contains valid context. If the context record
	// is being used to modify a threads context, then only that
	// portion of the threads context will be modified.
	//
	// If the context record is used as an IN OUT parameter to capture
	// the context of a thread, then only those portions of the thread's
	// context corresponding to set flags will be returned.
	//
	// The context record is never used as an OUT only parameter.
	//
	DWORD padding;
	DWORD ContextFlags;

	//
	// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
	// set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
	// included in CONTEXT_FULL.
	//

	DWORD   Dr0;
	DWORD   Dr1;
	DWORD   Dr2;
	DWORD   Dr3;
	DWORD   Dr6;
	DWORD   Dr7;

	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
	//

	WOW64_FLOATING_SAVE_AREA FloatSave;

	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_SEGMENTS.
	//

	DWORD   SegGs;
	DWORD   SegFs;
	DWORD   SegEs;
	DWORD   SegDs;

	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_INTEGER.
	//

	DWORD   Edi;
	DWORD   Esi;
	DWORD   Ebx;
	DWORD   Edx;
	DWORD   Ecx;
	DWORD   Eax;

	//
	// This section is specified/returned if the
	// ContextFlags word contians the flag CONTEXT_CONTROL.
	//

	DWORD   Ebp;
	DWORD   Eip;
	DWORD   SegCs;              // MUST BE SANITIZED
	DWORD   EFlags;             // MUST BE SANITIZED
	DWORD   Esp;
	DWORD   SegSs;

	//
	// This section is specified/returned if the ContextFlags word
	// contains the flag CONTEXT_EXTENDED_REGISTERS.
	// The format and contexts are processor specific
	//

	BYTE    ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION];

} WOW64_CONTEXT, *PWOW64_CONTEXT;

最后释放内存的时候两种情况,线程执行了shellcode,线程没有执行,如果执行了,直接释放,没有执行则在超时之后还原原始的eip/rip

VOID freeMemory(PVOID Parameter)
{
	ULONG counts = 0;
	SIZE_T Size = PAGE_SIZE;
	PEPROCESS pEprocess = NULL;
	PFREEADDRESS freeAdd = (PFREEADDRESS)Parameter;
	
	if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)(freeAdd->pid), &pEprocess)))
	{
		KAPC_STATE kApc = { 0 };
		while (TRUE)
		{
			KeStackAttachProcess(pEprocess, &kApc);
			__try 
			{
				ProbeForRead((PVOID)freeAdd->allcateAddress,sizeof(freeAdd->allcateAddress),sizeof(CHAR));
				if (freeAdd->allcateAddress->Complete || counts > MAXCOUNTS)
				{
					if (counts > MAXCOUNTS)
					{
						if (g_PsGetProcessWow64Process(pEprocess) != 0)
						{
							ProbeForRead((PVOID)freeAdd->allcateAddress->orgRipAddress, sizeof(freeAdd->allcateAddress->orgRipAddress), sizeof(CHAR));
							*(ULONG*)freeAdd->allcateAddress->orgRipAddress = (ULONG)freeAdd->allcateAddress->orgRip;
						}
						else
						{
							if(MmIsAddressValid((PVOID)freeAdd->allcateAddress->orgRipAddress))
								*(ULONG64*)freeAdd->allcateAddress->orgRipAddress = (ULONG64)freeAdd->allcateAddress->orgRip;
						}
					}
					ZwFreeVirtualMemory((HANDLE)-1, (PVOID)& freeAdd->allcateAddress, &Size, MEM_DECOMMIT);
					break;
				}
			}
			__except (EXCEPTION_EXECUTE_HANDLER) 
			{
				break;
			}
			KeUnstackDetachProcess(&kApc);
			Sleep(MSEC);
			counts++;
		}
		KeUnstackDetachProcess(&kApc);
		ObDereferenceObject(pEprocess);
	}

	ExFreePool(freeAdd);
	freeAdd = NULL;
	g_gameOver = TRUE;
}

代码

放在GitHub

还有一个简单的驱动加载代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值