理论:
什么是进程,什么是线程
进程就是4GB的空间,线程就是EIP
想在别的进程中执行自己的代码
- 进程空间中必须要有我的代码
- 进程空间中必须有一个线程执行我的代码
- 方法:(原来的线程JMP到我的代码,或者开辟一个新的线程)
原来的线程Jmp到我的代码,称之为HOOK,改变原来线程的执行轨迹
如何将代码放入目标进程空间:
- 代码整理成一个模块,直接插入到目标的进程空间中(容易被发现)
- 只把函数体放入对方的进程空间【硬编码】
如果直接使用硬编码的方式,会造成问题:
- 使用全局变量的地址会发生改变
- 使用API函数会导致IAT的地址找不到
使用ShellCode - 一种可变的硬编码
ShellCode是研究病毒必须要掌握的一种技能:将硬编码ShellCode化
ShellCode(代码注入)
- 不使用全局变量
- 需要什么API函数自己去获取
-
缺点:比较麻烦 - 如果代码量过多
-
优点:无法被检测到
特征码就是程序核心的硬编码
在程序的核心部分做记号,根据这个记号去找到对应的程序或者基址
无痕HOOK 就是Jmp
Inline HOOK 就是 Call
模块注入
优点:写起来非常方便
缺点:特征明显
实践:

LoadLibrary
将指定的模块加载到调用进程的地址空间中。 指定的模块可能会导致加载其他模块。
HMODULE LoadLibraryA(
[in] LPCSTR lpLibFileName
);
[in] lpLibFileName
模块的名称。 可以是库模块 (.dll 文件) ,也可以是可执行模块 (.exe 文件) 。 如果指定的模块是可执行模块,则不会加载静态导入;
LoadLibrary 可用于将库模块加载到进程的地址空间中,并返回一个句柄,该句柄可用于 在 GetProcAddress 中 获取 DLL 函数的地址。
如果指定的模块是尚未为调用进程加载的 DLL,则系统会使用DLL_PROCESS_ATTACH值调用 DLL 的 DllMain 函数。 如果 DllMain 返回 TRUE, 则 LoadLibrary 将返回模块的句柄。 如果 DllMain 返回 FALSE,则系统会从进程地址空间中卸载 DLL, 而 LoadLibrary 返回 NULL。 从 DllMain 调用 LoadLibrary 是不安全的。
返回值
如果函数成功,则返回值是模块的句柄。
如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。
CreateRemoteThread
创建在另一个进程的虚拟地址空间中运行的线程。
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
[in] hProcess
要在其中创建线程的进程句柄。
[in] lpThreadAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄。
[in] dwStackSize
堆栈的初始大小(以字节为单位)。 如果此参数为 0 (零) ,则新线程将使用可执行文件的默认大小。
[in] lpStartAddress
线程执行的应用程序定义函数的指针,表示远程进程中线程的起始地址。 函数必须存在于远程进程中。 有关详细信息,请参阅 ThreadProc。
[in] lpParameter
指向要传递给线程函数的变量的指针。
[in] dwCreationFlags
控制线程创建的标志。
| 值 | 含义 |
|---|---|
| 0 | 线程在创建后立即运行。 |
| CREATE_SUSPENDED 0x00000004 | 线程以挂起状态创建,在调用 ResumeThread 函数之前不会运行。 |
| STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 | dwStackSize 参数指定堆栈的初始保留大小。 如果未指定此标志, dwStackSize 将指定提交大小。 |
[out] lpThreadId
指向接收线程标识符的变量的指针。
如果此参数为 NULL,则不返回线程标识符。
返回值
如果函数成功,则返回值是新线程的句柄。
如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。
CreateToolhelp32Snapshot
获取指定进程以及这些进程使用的堆、模块和线程的快照。
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);
Process32First
检索有关系统快照中遇到的第一个进程的信息。
BOOL Process32First(
[in] HANDLE hSnapshot,
[in, out] LPPROCESSENTRY32 lppe
);
Process32Next
检索有关系统快照中记录的下一个进程的信息。
BOOL Process32Next(
[in] HANDLE hSnapshot,
[out] LPPROCESSENTRY32 lppe
);
VirtualAllocEx
在指定进程的虚拟地址空间中保留、提交或更改内存区域的状态。 函数将它分配的内存初始化为零。
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
WriteProcessMemory
将数据写入指定进程中的内存区域。要写入的整个区域必须可访问,否则操作将失败。
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
WaitForSingleObject
等待指定的对象处于信号状态或超时间隔已过。
DWORD WaitForSingleObject(
[in] HANDLE hHandle,
[in] DWORD dwMilliseconds
);
GetExitCodeThread
检索指定线程的终止状态。
BOOL GetExitCodeThread(
[in] HANDLE hThread,
[out] LPDWORD lpExitCode
);
VirtualFreeEx
释放、取消提交或释放和取消提交指定进程的虚拟地址空间中的内存区域。
BOOL VirtualFreeEx(
[in] HANDLE hProcess,
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD dwFreeType
);
CloseHandle
关闭打开的对象句柄。
BOOL CloseHandle(
[in] HANDLE hObject
);
靶机线程代码:
#include <iostream>
#include <Windows.h>
int main()
{
int i = 0;
while (true)
{
Sleep(1000);
i++;
std::cout << "这是一个不会被关闭的主线程 运行时间:" << i << "秒" << std::endl;
}
return 0;
}

创建DLL
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(0, TEXT("DLL_PROCESS_ATTACH : dll被加载"), 0, 0);
case DLL_THREAD_ATTACH:
MessageBox(0, TEXT("DLL_PROCESS_ATTACH : dll被卸载"), 0, 0);
case DLL_THREAD_DETACH:
MessageBox(0, TEXT("DLL_THREAD_ATTACH : 线程附加到 DLL"), 0, 0);
case DLL_PROCESS_DETACH:
MessageBox(0, TEXT("DLL_THREAD_ATTACH : 线程从 DLL 分离"), 0, 0);
break;
}
return TRUE;
}
将生成的DLL文件放置在文件目录下:

应用程序 与 DLL文件都准备好后,开始编写注入代码

// 6-3 InjectCode.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <Windows.h>
#include <iostream>
#include <Tlhelp32.h>
int main()
{
// 设置本地化环境为默认的宽字符本地化
_wsetlocale(LC_ALL, L"");
std::cout << "注入代码开始运行!!!\n";
// 定义目标进程
WCHAR wc_TargetProcessName[] = L"远程线程注入.exe";
DWORD dw_TargetProcessPid = 0;
HANDLE h_TargetProcessHandle = NULL;
// 枚举所有进程 找到要注入的进程
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
if (Process32First(hSnapshot, &pe32)) {
do {
if (!wcscmp(pe32.szExeFile, wc_TargetProcessName))
{
printf("成功:进程ID - %d ,进程名称 - %ws \n", pe32.th32ProcessID, pe32.szExeFile);
dw_TargetProcessPid = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
if (!dw_TargetProcessPid)
{
printf("ERROR : 未找到对应的进程\n");
return -1;
}
h_TargetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dw_TargetProcessPid);
if (!h_TargetProcessHandle)
{
printf("ERROR : 未能成功获取进程句柄\n");
return -1;
}
else {
printf("成功:进程句柄 %x \n", h_TargetProcessHandle);
}
//- - - - - 找到LoadLibrary的地址
HMODULE hModule = LoadLibrary(L"kernel32.dll");
FARPROC LoadLibraryAddress = 0;
LoadLibraryAddress = GetProcAddress(hModule, "LoadLibraryW"); // 请将"MyFunction"替换为要获取地址的函数名称
if (!LoadLibraryAddress)
{
printf("ERROR : 未能成功获取 LoadLibraryA 的函数地址\n");
}
else {
printf("成功:LoadLibraryA 的函数地址 0x%x \n", LoadLibraryAddress);
}
//- - - - - 在目标进程的空间内开辟内存,存放LoadLibrary的参数
WCHAR wc_DllName[] = L"Inject.dll";
LPVOID MemoryBase = VirtualAllocEx(h_TargetProcessHandle, NULL, sizeof(wc_DllName), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!MemoryBase)
{
printf("ERROR : 开辟内存空间失败\n");
}
else {
printf("成功:在目标进程开辟的内存空间基址 0x%x \n", MemoryBase);
}
SIZE_T WriteByte = 0;
BOOL ReturnWritebool = WriteProcessMemory(h_TargetProcessHandle, MemoryBase, wc_DllName, sizeof(wc_DllName), &WriteByte);
if (!ReturnWritebool)
{
printf("ERROR : 往进程写入数据失败\n");
}
else {
printf("成功:往进程中写入数据成功 写入字节数%d \n", WriteByte);
}
//- - - - - 创建远程线程,执行LoadLibrary加载我们的Inject.dll
HANDLE InJectThread = CreateRemoteThread(h_TargetProcessHandle, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryAddress, MemoryBase, 0, NULL);
WaitForSingleObject(InJectThread, 1000);
//- - - - - 释放远程线程
DWORD ExitCode = NULL; //获取目标进程的模块句柄
GetExitCodeThread(InJectThread, &ExitCode);
if (!ReturnWritebool)
{
printf("ERROR : 获取目标进程模块失败\n");
}
else {
printf("成功:目标进程模块地址 %x \n", ExitCode);
}
//- - - - - 释放远程进程中写入的空间
if (!VirtualFreeEx(h_TargetProcessHandle, MemoryBase, sizeof(wc_DllName), MEM_DECOMMIT)) {
printf("ERROR : 释放空间失败\n");
}
else {
printf("成功:进程空间已释放\n");
}
//CloseHandle(InJectThread);
//CloseHandle(h_TargetProcessHandle);
return 0;
}
本文介绍了ShellCode远程线程注入的概念和原理,包括HOOK、代码特征码、模块注入及其优缺点。通过实践部分讲解了LoadLibrary、CreateRemoteThread等关键函数的使用,阐述了如何在目标进程中创建并执行代码。
1万+

被折叠的 条评论
为什么被折叠?



