背景
之前分析过某老哥的超级注入,最近突然来了兴致,简单的把它实现一下。
开整
首先说一下思路
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上
还有一个简单的驱动加载代码