远程线程注入

信号

1
2
CreateSemaphore
ReleaseSemaphore

CreateSemaphore

创建或打开命名或未命名的信号量对象。

要指定对象的访问掩码,请使用CreateSemaphoreEx函数。

image-20240712153556947

参数
lpSemaphoreAttributes
指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则子进程无法继承句柄。

结构的lpSecurityDescriptor成员为新信号量指定安全描述符。如果此参数为NULL,则信号量将获取默认安全描述符。信号量的默认安全描述符中的ACL来自创建者的主要或模拟令牌。

*lInitialCount
信号量对象的初始计数。该值必须大于或等于零且小于或等于lMaximumCount。信号量的状态在其计数大于零时发出信号,在信号为零时发出信号。只要wait函数释放等待信号量的线程,计数就会减1。通过调用ReleaseSemaphore函数将计数增加指定的量。

*lMaximumCount
信号量对象的最大计数。该值必须大于零。

*lpName
信号量对象的名称。名称仅限于MAX_PATH个字符。名称比较区分大小写。

如果lpName与现有命名信号量对象的名称匹配,则此函数请求SEMAPHORE_ALL_ACCESS访问权限。在这种情况下,lInitialCount和lMaximumCount参数将被忽略,因为它们已由创建过程设置。如果lpSemaphoreAttributes参数不为NULL,则确定是否可以继承句柄,但忽略其安全描述符成员。

如果lpName为NULL,则创建没有名称的信号量对象。

如果lpName与现有事件,mutex,waitable计时器,作业或文件映射对象的名称匹配,则函数将失败,GetLastError函数将返回ERROR_INVALID_HANDLE。这是因为这些对象共享相同的命名空间。

该名称可以具有“Global”或“Local”前缀,以在全局或会话命名空间中显式创建对象。名称的其余部分可以包含除反斜杠字符(\)之外的任何字符。有关更多信息,请参阅内核对象命名空间。使用终端服务会话实现快速用户切换。内核对象名称必须遵循为终端服务概述的准则,以便应用程序可以支持多个用户。

可以在私有名称空间中创建对象。有关更多信息,请参阅对象命名空间。

返回值
如果函数成功,则返回值是信号量对象的句柄。如果在函数调用之前存在命名的信号量对象,则该函数返回现有对象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。

如果函数失败,则返回值为NULL。要获取扩展错误信息,请调用GetLastError。

备注
CreateSemaphore返回的句柄具有SEMAPHORE_ALL_ACCESS访问权限;它可以在任何需要信号量对象句柄的函数中使用,前提是调用者已被授予访问权限。如果从服务或模拟其他用户的线程创建信号量,则可以在创建信号量时将安全描述符应用于信号量,或者通过更改其默认DACL来更改创建过程的默认安全描述符。有关更多信息,请参阅同步对象安全性和访问权限。

信号量对象的状态在其计数大于零时发出信号,并在其计数等于零时发出信号。 lInitialCount参数指定初始计数。计数永远不会小于零或大于lMaximumCount参数中指定的值。

调用进程的任何线程都可以在调用其中一个等待函数时指定信号量对象句柄。当指示对象的状态发出信号时,单对象等待函数返回。可以指示多对象等待函数在任何一个或所有指定对象发出信号时返回。当wait函数返回时,释放等待线程以继续执行。每次线程完成等待信号量对象的等待时,信号量对象的计数减1。线程完成后,它会调用ReleaseSemaphore函数,该函数会增加信号量对象的计数。

多个进程可以具有相同信号量对象的句柄,从而可以使用该对象进行进程间同步。可以使用以下对象共享机制:

由CreateProcess函数创建的子进程可以继承

ReleaseSemaphore

按指定数量增加指定信号量对象的计数。

image-20240712154712898

参数
hSemaphore
信号量对象的句柄。 CreateSemaphore 或 OpenSemaphore 函数返回此句柄。

此句柄必须具有SEMAPHORE_MODIFY_STATE访问权限。有关详细信息, 请参阅同步对象安全和访问权限.

lReleaseCount
要增加信号对象的当前计数的量。该值必须大于零。如果指定的金额将导致信号量的计数超过在创建信号时指定的最大计数, 则不会更改计数, 并且函数返回FALSE.

lpPreviousCount
指向要接收信号量的上一个计数的变量的指针。如果不需要上一个计数, 则此参数可以为NULL 。

返回值
如果函数成功, 则返回值为非零。

如果函数失败, 返回值为零。要获取扩展错误信息, 请调用时出错.

备注
当信号对象的计数大于零, 并且在其计数等于零时 nonsignaled 的状态将发出信号。调用 CreateSemaphore 函数的进程指定信号量的初始计数。每次由于信号量的终止状态而释放等待线程时, 信号量的计数将减少一个。

通常, 应用程序使用信号量来限制使用资源的线程数。在线程使用该资源之前, 它指定调用等待函数之一的信号量句柄。当等待函数返回时, 它会将信号量的计数减少一。当线程使用完资源后, 它会调用ReleaseSemaphore以增加信号量的计数。

ReleaseSemaphore的另一个用途是在应用程序的初始化过程中。应用程序可以创建一个初始计数为零的信号量。这将信号量的状态设置为 nonsignaled, 并阻止所有线程访问受保护的资源。当应用程序完成其初始化时, 它使用ReleaseSemaphore将计数增加到其最大值, 以允许对受保护资源的正常访问。

无法使用ReleaseSemaphore减少信号量对象计数, 因为lReleaseCount不能为负数。若要临时限制或减少访问, 请创建一个循环, 在其中调用 WaitForSingleObject 函数, 其超时间隔为零, 直到信号量计数已足够减少为止。(请注意, 其他线程可以在执行此循环时减少计数。要恢复访问, 请调用ReleaseSemaphore , 其释放计数等于在循环中调用WaitForSingleObject的次数。

新建控制台应用

image-20240712155157716

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
// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>

HANDLE g_hSem = NULL;

DWORD WINAPI WorkThreadProc(
LPVOID lpThreadParameter
)
{
while (true)
{
WaitForSingleObject(g_hSem, INFINITE);
printf("%d\t:洗剪吹...\r\n", GetCurrentThreadId());
Sleep(2000);
ReleaseSemaphore(g_hSem, 1, NULL);
}
}

int main()
{
g_hSem = CreateSemaphore(NULL, 2, MAXLONG, NULL);
const int nThreadCnt = 4;
HANDLE aryhTreads[nThreadCnt] = {};
for (int i = 0; i < nThreadCnt; ++i)
{
aryhTreads[i] = CreateThread(NULL, 0, WorkThreadProc, NULL, 0, NULL);
}
system("pause");
ReleaseSemaphore(g_hSem, 1, NULL);
WaitForMultipleObjects(nThreadCnt, aryhTreads, TRUE, INFINITE);
CloseHandle(g_hSem);
std::cout << "Hello World!\n";
}

每过2秒轮流出现3个员工在洗剪吹

image-20240712163252501

远程线程注入

新建控制台应用

image-20240712165057834

新建动态链接库

image-20240712165226257

获取窗口的标题

image-20240712215447536

找到Dll1的地址

image-20240712220606176

WriteProcessMemory

image-20240712221051148

参数:
[in] hProcess

要修改的进程内存的句柄。 句柄必须具有对进程的PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问权限。

[in] lpBaseAddress

指向将数据写入到的指定进程中基址的指针。 在进行数据传输之前,系统会验证指定大小的基址和内存中的所有数据是否可供写入访问,如果无法访问,则函数将失败。

[in] lpBuffer

指向缓冲区的指针,该缓冲区包含要写入指定进程的地址空间中的数据。

[in] nSize

要写入指定进程的字节数。

[out] lpNumberOfBytesWritten

指向变量的指针,该变量接收传输到指定进程的字节数。 此参数是可选的。 如果 lpNumberOfBytesWritten 为 NULL,则忽略参数。

返回值:
如果该函数成功,则返回值为非零值。

如果函数失败,则返回值为 0(零)。 要获得更多的错误信息,请调用 GetLastError。 如果请求的写入操作交叉到无法访问的进程区域,函数将失败。

注解:
WriteProcessMemory 将数据从当前进程中的指定缓冲区复制到指定进程的地址范围。 任何具有 PROCESS_VM_WRITE 句柄且PROCESS_VM_OPERATION访问要写入的进程的进程都可以调用 函数。 通常(但并非总是)正在调试包含正在写入的地址空间的进程。

要写入到的整个区域必须可访问,如果无法访问,则函数将失败。

CreateRemoteThread

image-20240712222555068

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
// RemoteInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
HWND hWndCff = FindWindow(NULL, "CFF Explorer VII");
if (hWndCff == NULL)
{
return 0;
}
DWORD dwProcId;
GetWindowThreadProcessId(hWndCff, &dwProcId);
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
if (hProc == NULL)
{
return 0;
}
//写入参数
LPVOID pDllPath = VirtualAllocEx(hProc, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
if (pDllPath == NULL)
{
return 0;
}
char szDllPath[] = { "C:\\Users\\lenovo\\source\\repos\\Windows 11\\RemoteInject\\Debug" };
BOOL bRet = WriteProcessMemory(hProc, pDllPath, szDllPath, sizeof(szDllPath), NULL);
if (!bRet)
{
return 0;
}
HMODULE hKernel32 = GetModuleHandle("kernel32");
if (hKernel32 == NULL)
{
return 0;
}
auto pfnLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
if (pfnLoadLibrary == NULL)
{
return 0;
}
HANDLE hMod = CreateRemoteThread(
hProc,
NULL,
0,
(LPTHREAD_START_ROUTINE)pfnLoadLibrary,
pDllPath,
0,
NULL
);
if (hMod == NULL)
{
cout << "注入失败" << endl;
}
cout << "注入成功\n";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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:
OutputDebugString("DLL_PROCESS_ATTACH:你被注入了!!!");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
OutputDebugString("DLL_PROCESS_DETACH:我走了!!!");
break;
}
return TRUE;
}

运行CFF Explorer VII后开始运行程序,注入成功

image-20240713124909878

image-20240713180125907

image-20240714151301307

结束后

image-20240714151353871

在Dll1中添加资源

image-20240713170650388

新建对话框

image-20240713170757630

image-20240713172846495

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <conio.h>
#include "resource.h"
INT_PTR CALLBACK InjectDlgProc(HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
switch (nMsg)
{
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case BTN_TEST:
MessageBox(hDlg, "测试", NULL, MB_OK);
break;
default:
break;
}
break;
}
case WM_CLOSE:
EndDialog(hDlg, 0);
return TRUE;
default:
break;
}
return FALSE;
}
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
return DialogBox(
GetModuleHandle("Dll1"),
MAKEINTRESOURCE(DLG_INJECT),
NULL,
InjectDlgProc);
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
HANDLE hThread;
char szMsg[80];
hThread = CreateThread(
NULL,
0,
ThreadFunc,
NULL,
0,
NULL);
}
OutputDebugString("DLL_PROCESS_ATTACH:你被注入了!!!");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
OutputDebugString("DLL_PROCESS_DETACH:我走了!!!");
break;
}
return TRUE;
}

运行CFF Explorer VII后开始运行程序,注入成功并弹出弹窗

image-20240713180321017

自卸载

卸载Dll

image-20240713180703774

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <conio.h>
#include "resource.h"
INT_PTR CALLBACK InjectDlgProc(HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
switch (nMsg)
{
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case BTN_TEST:
MessageBox(hDlg, "测试", NULL, MB_OK);
break;
case BTN_FREELIB:
EndDialog(hDlg, 0);
return FALSE;
default:
break;
}
break;
}
case WM_CLOSE:
EndDialog(hDlg, 0);
return TRUE;
default:
break;
}
return FALSE;
}
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
DialogBox(
GetModuleHandle("Dll1"),
MAKEINTRESOURCE(DLG_INJECT),
NULL,
InjectDlgProc);
CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)FreeLibrary,
GetModuleHandle("Dll1"),
0,
NULL);
return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
HANDLE hThread;
char szMsg[80];
hThread = CreateThread(
NULL,
0,
ThreadFunc,
NULL,
0,
NULL);
}
OutputDebugString("DLL_PROCESS_ATTACH:你被注入了!!!");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
OutputDebugString("DLL_PROCESS_DETACH:我走了!!!");
break;
}
return TRUE;
}

运行成功后点击Button2可以卸载Dll,弹窗消失

image-20240713182259908

FreeLibraryAndExitThread

image-20240713183524209

原理:在dll的入口点再加载一次自己,使自己的引用计数增加1,然后在线程函数的最后调用FreeLibraryAndExitThread来卸载自身并退出线程,这样的话,如果调用dll的线程先调用了FreeLibary来释放这个dll,因为引用计数依然大于0,所以这个DLL不会卸载,dll里面的线程函数可以安全的跑完知道自己卸载自己;而如果dll中的线程函数先调用FreeLibraryAndExitThread,这个时候因为dll的引用技术也大于0,dll还是不会卸载,只是线程退出而已,直到外面调用FreeLibary来将其卸载。

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <conio.h>
#include "resource.h"
DWORD WINAPI FreeThreadFunc(LPVOID lpParam)//修改
{
Sleep(1000);
FreeLibraryAndExitThread(GetModuleHandle("Dll1"), 0);//卸载指定dll并退出当前线程
return 0;
}

INT_PTR CALLBACK InjectDlgProc(HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
switch (nMsg)
{
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case BTN_TEST:
MessageBox(hDlg, "测试", NULL, MB_OK);
break;
case BTN_FREELIB://修改
CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)FreeLibrary,
0,
0,
NULL);
EndDialog(hDlg, 0);
return FALSE;
default:
break;
}
break;
}
case WM_CLOSE:
EndDialog(hDlg, 0);
return TRUE;
default:
break;
}
return FALSE;
}

DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
DialogBox(
GetModuleHandle("Dll1"),
MAKEINTRESOURCE(DLG_INJECT),
NULL,
InjectDlgProc);
return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
HANDLE hThread;
char szMsg[80];
hThread = CreateThread(
NULL,
0,
ThreadFunc,
NULL,
0,
NULL);
}
OutputDebugString("DLL_PROCESS_ATTACH:你被注入了!!!");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
OutputDebugString("DLL_PROCESS_DETACH:我走了!!!");
break;
}
return TRUE;
}

运行成功后点击Button2可以卸载Dll,弹窗消失

卸载前

image-20240713190233468

卸载后(Dll1消失)

image-20240713190253796

RemoteInject.cpp

1
2
3
4
5
6
7
8
9
10
if (hRemote == NULL)
{
cout << "注入失败" << endl;
}

WaitForSingleObject(hRemote, INFINITE);//添加这三行
HANDLE hRemoteDll = NULL;
GetExitCodeThread(hRemote, (LPDWORD)&hRemoteDll);//

cout << "注入成功\n";

image-20240713190827734

image-20240713190903825

hRemoteDll的值就是Dll1的句柄

RemoteInject.cpp

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
#include <windows.h>
#include <conio.h>
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
ExitThread(0x12345678);
return 0;
}

int main(VOID)
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
char szMsg[80];
hThread = CreateThread(
NULL,
0,
ThreadFunc,
&dwThrdParam,
0,
&dwThreadId
);
WaitForSingleObject(hThread, INFINITE);
DWORD dwExitCode = 0;
GetExitCodeThread(hThread, (LPDWORD)&dwExitCode);
return 0;
}

dwExitCode的值就是上面退出码的值

image-20240713192040530

修改ThreadFunc函数

1
2
3
4
5
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
//ExitThread(0x12345678);
return 0x98765432;
}

dwExitCode的值就是ThreadFunc函数的返回值

image-20240713192242999