0%

Win32API

Win32API

字符编码

ASCII 码表

ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符
0 NUL 32 (space) 64 @ 96
1 SOH 33 65 A 97 a
2 STX 34 66 B 98 b
3 ETX 35 # 67 C 99 c
4 EOT 36 $ 68 D 100 d
5 ENQ 37 % 69 E 101 e
6 ACK 38 & 70 F 102 f
7 BEL 39 71 G 103 g
8 BS 40 ( 72 H 104 h
9 HT 41 ) 73 I 105 i
10 LF 42 * 74 J 106 j
11 VT 43 + 75 K 107 k
12 FF 44 , 76 L 108 l
13 CR 45 - 77 M 109 m
14 SO 46 . 78 N 110 n
15 SI 47 / 79 O 111 o
16 DLE 48 0 80 P 112 p
17 DCI 49 1 81 Q 113 q
18 DC2 50 2 82 R 114 r
19 DC3 51 3 83 X 115 s
20 DC4 52 4 84 T 116 t
21 NAK 53 5 85 U 117 u
22 SYN 54 6 86 V 118 v
23 TB 55 7 87 W 119 w
24 CAN 56 8 88 X 120 x
25 EM 57 9 89 Y 121 y
26 SUB 58 : 90 Z 122 z
27 ESC 59 ; 91 [ 123 {
28 FS 60 < 92 \ 124 |
29 GS 61 = 93 ] 125 }
30 RS 62 > 94 ^ 126 ~
31 US 63 ? 95 127 DEL

ASCII扩展码表

十进制 十六进制 字符 十进制 十六进制 字符
128 80 Ç 192 C0
129 81 ü 193 C1
130 82 é 194 C2
131 83 â 195 C3
132 84 ä 196 C4
133 85 à 197 C5
134 86 å 198 C6
135 87 ç 199 C7
136 88 ê 200 C8
137 89 ë 201 C9
138 8A è 202 CA
139 8B ï 203 CB
140 8C î 204 CC
141 8D ì 205 CD
142 8E Ä 206 CE
143 8F Å 207 CF
144 90 É 208 D0
145 91 æ 209 D1
146 92 Æ 210 D2
147 93 ô 211 D3
148 94 ö 212 D4 Ô
149 95 ò 213 D5
150 96 û 214 D6
151 97 ù 215 D7
152 98 ÿ 216 D8
153 99 Ö 217 D9
154 9A Ü 218 DA
155 9B ¢ 219 DB
156 9C £ 220 DC
157 9D ¥ 221 DD
158 9E ? 222 DE ?
159 9F ƒ 223 DF ?
160 A0 á 224 E0 α
161 A1 í 225 E1 ß
162 A2 ó 226 E2 Γ
163 A3 ú 227 E3 π
164 A4 ñ 228 E4 Σ
165 A5 Ñ 229 E5 σ
166 A6 ª 230 E6 µ
167 A7 º 231 E7 τ
168 A8 ¿ 232 E8 Φ
169 A9 ? 233 E9 Θ
170 AA ¬ 234 EA Ω
171 AB ½ 235 EB δ
172 AC ¼ 236 EC
173 AD ¡ 237 ED φ
174 AE « 238 EE ε
175 AF » 239 EF
176 B0 ? 240 F0
177 B1 ? 241 F1 ±
178 B2 242 F2
179 B3 243 F3
180 B4 244 F4 ?
181 B5 245 F5 ?
182 B6 246 F6 ÷
183 B7 247 F7
184 B8 248 F8
185 B9 249 F9 ?
186 BA 250 FA ·
187 BB 251 FB
188 BC 252 FC ?
189 BD 253 FD ²
190 BE FE
191 BF 255 FF ÿ

Unicode字符集

UNICODE编码方案,世界上所有的文字符号都能从这张表中找到。Unicode编码范围是0~0x10FFFF,能容纳1114111个字符。

但是Unicode只是一个字符集,它规定了每个字符对应的二进制,但是没有规定如何存储。

Unicode的存储方式有UTF-8和UTF-16

UTF-8

UTF-8是变长字符编码。

Unicode编码(HEX) UTF-8字节流(BIN)
000000 - 00007F 0xxxxxxx
000080 - 0007FF 110xxxxx 10xxxxxx
000800 - 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-16

UTF-16编码以16位无符号整数为单位,(是以16位为一个单位,不表示一个字符就有16位)。这要看字符的Unicode编码处于什么范围而定,有可能是2个字节,有可能是4个字节。现在机器上的Unicode编码一般指的是UTF-16编码

BOM

BOM,BYTE Order Mark,字符排列标志。

编码方式 BOM
UTF-8 EF BB BF
UTF-16LE(小端对齐 FF FE
UTF-16BE(大端对齐 FE FF

C语言中的宽字符

宽字符

1
wchar_t wch[] = "宽字符";

打印宽字符:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <locale.h> //添加头文件

int main(void)
{
setlocale(LC_ALL, ""); // 控制台使用默认编码
wchar_t wch[] = L"宽字符";
wprintf(L"%s\n", wch);
return 0;
}

相关函数

char wchar_t 多字节字符型 / 宽字节字符型
printf wprintf 打印到控制台
strlen wcslen 获取长度
strcpy wcscpy 字符串复制
strcat wcscat 字符串拼接
strcmp wcscmp 字符串比较
strstr wcsstr 字符串查找

Win32 API中的宽字符

Win32 API

API,Application Process Interface应用程序接口,也就是Windows提供的封装好的一些函数

几个重要DLL:

<1>Kernel32.dll最核心的功能模块,比如管理内存、进程和线程相关的函数等。
<2>User32.dll是Vindows用户界面相关应用程序接口,如创建窗口和发送消息等。
<3>GDl32.dll全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。

在Win32中写代码最好用TCHAR来写,利于跨平台使用

1
2
3
4
CHAR ch[] = "";				//char
WCHAR wch[] = L""; //wchar_t

TCHAR tch[] = TEXT(""); // 依赖于程序默认的编码,是ASCII就是ASCII,是Unicode就是Unicode
1
2
3
4
5
指针类型
PSTR pszStr = ""; //char*
PWSTR pwszStr = L""; //wchar_t*

PTSTR ptszStr = TEXT(""); //兼容

MessageBox API

1
2
3
4
5
6
7
8
9
10
11
12
13
CHAR chTitle[] = "标题";
CHAR chText[] = "内容";
MessageBoxA(0,chText,chTitle,MB_YESNO);


WCHAR wchTitle[] = L"标题";
WCHAR wchText[] = L"内容";
MessageBoxW(0,wchText,wchTitle,MB_YESNO);


TCHAR chTitle[] = TEXT("标题");
TCHAR chText[] = TEXT("内容");
MessageBox(0,chText,chTitle,MB_YESNO);

进程

进程的创建

任何进程都是别的进程创建的:由CreateProcess()这个函数创建

进程创建过程

1
2
3
4
5
6
7
1、映射EXE文件
2、创建内核对象EPROCESS
3、映射系统DLL(ntdll.dll)
4、创建线程内核对象ETHREAD
5、系统启动线程
映射DLL(ntdl.LdrInitializeThunk)
线程开始执行

创建进程

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 <stdio.h>
#include <windows.h>

BOOL CreateChildProcess(PTCHAR szChildProcessName, PTCHAR szCommandLine)
{
STARTUPINFO si; // 记录程序有多大
PROCESS_INFORMATION pi;

ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

// 创建子进程,返回成功与失败
if (!CreateProcess(
szChildProcessName, // 对象名称
szCommandLine, // 命令行
NULL, // 不继承进程句柄
NULL, // 不继承线程句柄
FALSE, // 不继承句柄
0, // 没有创建标志
NULL, // 使用父进程环境变量
NULL, // 使用父进程目录作为当前目录,可以自己设置目录
&si, // STARTUPINFO结构体详细信息
&pi) // PROCESS_INFORMATION结构体进程信息
)
{
printf("CreatChildProcess Error:%d \n", GetLastError);
return FALSE;
}

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

int main(void)
{
TCHAR szApplicationName[] = TEXT("C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe");
TCHAR szCmdline[] = TEXT("https://baidu.com");
CreateChildProcess(szApplicationName, NULL);

return 0;
}

句柄表

句柄表存储的就是一种映射关系,每个进程的内核对象对应一个句柄,用户不能直接访问内核对象(如果用户给了一个错误的内核地址会蓝屏)。为了防止访问错误的内核对象地址,Windows在用户层和内核层中间加了一个句柄表,用户通过访问句柄表来访问内核对象,句柄表相当于一道内核层外的防火墙。

image-20240304202133292

句柄表是一张私有的表,只针对当前的进程才有意义。

image-20240304222323428

如果是CloseHandle的话,内核对象不会死,而是内核对象的计数器减一(当有多个对象都运行了内核对象A)。如果所有的进程都把内核对象杀掉,也就是内核对象的计数变成0的时候,这个内核对象就会被销毁。

进程里有线程,线程不死,进程就不会死;如果进程里的唯一线程死了,进程就死了。

进程相关API

ID与句柄表

进程ID——PID,使用程序将进程ID打印出来为16进制,比如0x2914,转换成十进制是10516,也就是这里的资源管理器

image-20240305100538731

句柄表

句柄表是一个程序私有的,如果通过另一个程序关掉另一个程序,不能使用被关闭进程的句柄表,因为这个句柄表是私有的,程序A无法通过程序B的句柄表关闭程序B。

比如,程序A的句柄是0xf0,image-20240305145458857

此时如果用程序B来关闭程序A:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <windows.h>

int main(void)
{
HANDLE hProcess;
hProcess = (HANDLE)0xf0;

if (!TerminateProcess(hProcess, 1))
{
printf("终止程序失败 %d \n", GetLastError());
}
}

输出:

1
终止程序失败:6

image-20240305150145954 返回这个值说明句柄无效,因为0xf0是A的私有句柄

但是,进程ID(dwProcessID)是公有的,所以还是可以通过使用PID来终止程序。

以挂起形式创建进程

1
2
3
4
5
6
7
8
9
10
11
12
BOOL CreateProcess(
LPCTSTR lpApplicationName. //name of exeutable module
LPTSTR lpCommandLine, //command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, //SD
LPSECURITY_ATTRIBUTES lpThreadAttributes, //SD
BOOL bInheritHandles, //handle inheritance option
**DWORD dwCreationFlags,** //creation flags
LPVOID lpEnvironment, //new environment block
LPCTSTR lpCurrentDirectory, //current directory name
LPSTARTUPINFO lpStartupInfo, //startup information
LPPROCESS_INFOMATION lpProcessInformation //process information
)

在dwCreationFlags中,如果值为0,那么父进程和子进程是共用一个控制台的,如果要父进程和子进程分别打开一个控制台,那么需要将dwCreationFlags设置为CREATE_NEW_CONSOLE。**其中有一个值为CREATE_SUSPENDED:**以挂起的形式创建一个进程

模块目录与工作目录

两个API:

1
2
GetModuleFileName		//获取模块路径
GetCurrentDirectory //获取工作路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:6031)

int main(void)
{
char strModule[256];
GetModuleFileName(NULL, strModule, 256);

char strWork[1000];
int i = 1000;
GetCurrentDirectory(1000, strWork);

printf("模块路径:%s\n工作路径:%s\n", strModule, strWork);

getchar();

return 0;
}

输出

1
2
模块路径:C:\Users\23394\Desktop\code\C C++\win32AP\Debug\win32AP.exe
工作路径:C:\Users\23394\Desktop\code\C C++\win32AP

模块路径就是exe所在位置,工作路径由创建这个模块的父进程填写

线程

线程

线程是附属在进程上的执行实体,是代码的执行流程

一个进程可以包含多个线程,但一个进程至少包含一个线程

创建线程

CreateThread

1
2
3
4
5
6
7
8
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);

创建一个线程:

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
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("++++++++++%d\n", i);
}
return 0;
}

int main(void)
{
HANDLE hThread;

hThread = CreateThread(NULL,0, ThreadProc,NULL,0,NULL);

CloseHandle(hThread);

for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("----------%d\n", i);
}

return 0;

}

其中DWORD WINAPI ThreadProc(LPVOID lpParameter)这个函数可以没有返回值和参数,但是在使用时需要强制转换成(LPTHREAD_START_ROUTINE)

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
#include <stdio.h>
#include <windows.h>

void WINAPI ThreadProc()
{
for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("++++++++++%d\n", i);
}
return 0;
}

int main(void)
{
HANDLE hThread;

hThread = CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,NULL);

CloseHandle(hThread);

for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("----------%d\n", i);
}
return 0;
}

线程也可以传入参数,参数可以是任何类型,只要自己强制转换一个类型即可

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
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(500);
printf("++++++++++%d\n", i);
}
return 0;
}

int main(void)
{
int n;
n = 10;
int* np = &n;

HANDLE hThread;
hThread = CreateThread(NULL,0, ThreadProc,(LPTHREAD_START_ROUTINE)np, 0, NULL);

CloseHandle(hThread);

for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("----------%d\n", i);
}

return 0;

}

以上函数通过闯传入一个int类型指针,先强转成LPTHREAD_START_ROUTINE类型,传入一个指针,再在线程中将这个指针转换成自己需要的类型即可。

线程控制

让自己停下来Sleep(),停止当前线程。
让别的线程停下来SuspendThread(hThread)
线程恢复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
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
#include <stdio.h>
#include <windows.h>

struct handles {
HANDLE h1;
HANDLE h2;
};

DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(500);
printf("----------%d\n", i+1);
}
return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(500);
printf("++++++++++%d\n", i+1);
}
return 0;
}

DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
struct handles* hp = (handles*)lpParameter;
SuspendThread(hp->h1);
Sleep(5000);
ResumeThread(hp->h1);
SuspendThread(hp->h2);
Sleep(5000);
ResumeThread(hp->h2);
return 0;
}

int main(void)
{
int n;
n = 10;
int* np = &n;

HANDLE hThread1;
hThread1 = CreateThread(NULL,0, ThreadProc1,(LPTHREAD_START_ROUTINE)np, 0, NULL);

int l;
l = 30;
int* lp = &l;

HANDLE hThread2;
hThread2 = CreateThread(NULL, 0, ThreadProc2, (LPTHREAD_START_ROUTINE)lp, 0, NULL);

struct handles Handles;
Handles.h1 = hThread1;
Handles.h2 = hThread2;
struct handles* ph = &Handles;

HANDLE hThread3;
hThread3 = CreateThread(NULL, 0, ThreadProc3, (LPTHREAD_START_ROUTINE)ph, 0, NULL);
Sleep(200000000);

CloseHandle(hThread1);
CloseHandle(hThread2);

return 0;
}

以上是一个线程交替的实例。

输出:

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
++++++++++1
++++++++++2
++++++++++3
++++++++++4
++++++++++5
++++++++++6
++++++++++7
++++++++++8
++++++++++9
----------1
----------2
----------3
----------4
----------5
----------6
----------7
----------8
----------9
++++++++++10
----------10
++++++++++11
++++++++++12
++++++++++13
++++++++++14
++++++++++15
++++++++++16
++++++++++17
++++++++++18
++++++++++19
++++++++++20
++++++++++21
++++++++++22
++++++++++23
++++++++++24
++++++++++25
++++++++++26
++++++++++27
++++++++++28
++++++++++29
++++++++++30

等待线程中的API

1
2
3
<1>WaitForSingleObject();
<2>WaitForMultiplePbjects();
<3>GetExitCodeThread();

WaitForSingleObject等待单个线程

1
2
3
4
DWORD WaitForSingleObject(
HANDLE hHandle, //handle to object 对象句柄
DWORD dwMilliseconds //time-out interval 超时时间间隔
)

执行这个函数的时候,这个函数所在的当前线程会阻塞,等待WaitForSingleObject中传入的线程执行完毕后,WaitForSingleObject后面的程序才会执行。

image-20240305222427024

WaitForMultiplePbjects等待多个线程

1
2
3
4
5
6
DWORD WaitForMultipleObjects(
DWORD nCount, //等待几个线程对象
const HANDLE *lpHandles, //线程数组
BOOL bWaitAll, //等待模式 TRUE/FALSE前者为全部线程状态都发生改变,后者为有任意一个线程状态发生改变
DWORD dwMilliseconds //超时时间,一直等待为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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:6387)



DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("----------%d\n", i+1);
}
return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("++++++++++%d\n", i+1);
}
return 0;
}

int main(void)
{
int n;
n = 100;
int* np = &n;

HANDLE harrThreadArray[2];
harrThreadArray[0] = CreateThread(NULL, 0, ThreadProc1, (LPTHREAD_START_ROUTINE)np, 0, NULL);

int l;
l = 50;
int* lp = &l;

harrThreadArray[1] = CreateThread(NULL, 0, ThreadProc2, (LPTHREAD_START_ROUTINE)lp, 0, NULL);

//WaitForSingleObject(hThread1,INFINITE);
WaitForMultipleObjects(2, harrThreadArray, TRUE, INFINITE);
printf("线程执行完毕\n");
printf("其他程序...");

CloseHandle(harrThreadArray[0]);
CloseHandle(harrThreadArray[1]);

return 0;
}

GetExitCodeThread读取线程返回值

1
2
3
4
BOOL GetExitCodeThread(
HANDLE hThread,
LPDWORD lpExitCode
);
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 <stdio.h>
#include <windows.h>
#pragma warning(disable:6387)



DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("----------%d\n", i+1);
}
return 1;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("++++++++++%d\n", i+1);
}
return 2;
}

int main(void)
{
int n;
n = 100;
int* np = &n;

HANDLE harrThreadArray[2];
harrThreadArray[0] = CreateThread(NULL, 0, ThreadProc1, (LPTHREAD_START_ROUTINE)np, 0, NULL);

int l;
l = 50;
int* lp = &l;

harrThreadArray[1] = CreateThread(NULL, 0, ThreadProc2, (LPTHREAD_START_ROUTINE)lp, 0, NULL);

DWORD dwResultArray[2];

WaitForMultipleObjects(2, harrThreadArray, TRUE, INFINITE);
GetExitCodeThread(harrThreadArray[0], &dwResultArray[0]);
GetExitCodeThread(harrThreadArray[1], &dwResultArray[1]);
printf("线程执行完毕\n");
printf("线程1执行完毕返回:%d;线程2执行完毕返回:%d\n", dwResultArray[0], dwResultArray[1]);
printf("其他程序...");

CloseHandle(harrThreadArray[0]);
CloseHandle(harrThreadArray[1]);


return 0;
}
image-20240305224608996

CONTEXT线程上下文

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
typedef struct DECLSPEC_NOINITALL _CONTEXT {

//
// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.
//

DWORD ContextFlags;

//
// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
//

DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
//

FLOATING_SAVE_AREA FloatSave;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//

DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//

DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//

DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;

//
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
//

BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

这个结构体中存储了所有的寄存器,这也就是为什么当单核CPU能够循环执行多个线程,因为被挂起的线程会在挂起时将所有寄存器的数据都存在这个结构体中。

image-20240305230138707
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
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:6387)



DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("----------%d\n", i+1);
}
return 1;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("++++++++++%d\n", i+1);
}
return 2;
}

int main(void)
{
int n;
n = 100;
int* np = &n;

HANDLE harrThreadArray[2];
harrThreadArray[0] = CreateThread(NULL, 0, ThreadProc1, (LPTHREAD_START_ROUTINE)np, 0, NULL);



int l;
l = 50;
int* lp = &l;

harrThreadArray[1] = CreateThread(NULL, 0, ThreadProc2, (LPTHREAD_START_ROUTINE)lp, 0, NULL);

Sleep(100);
SuspendThread(harrThreadArray[0]);
CONTEXT context;
context.ContextFlags = CONTEXT_INTEGER;
GetThreadContext(harrThreadArray[0], &context);
printf("%x %x\n", context.Eax, context.Ecx);
ResumeThread(harrThreadArray[0]);

DWORD dwResultArray[2];

WaitForMultipleObjects(2, harrThreadArray, TRUE, INFINITE);
GetExitCodeThread(harrThreadArray[0], &dwResultArray[0]);
GetExitCodeThread(harrThreadArray[1], &dwResultArray[1]);
printf("线程执行完毕\n");
printf("线程1执行完毕返回:%d;线程2执行完毕返回:%d\n", dwResultArray[0], dwResultArray[1]);
printf("其他程序...");

CloseHandle(harrThreadArray[0]);
CloseHandle(harrThreadArray[1]);


return 0;
}

SetThreadContext

在挂起线程时,还可以使用SetThreadContext改变寄存器中的数值。

线程安全

临界资源

当两个线程同时要访问一个全局变量的时候,可能出现同时访问的情况,导致线程安全受影响。

临界区

一段使用临界资源的代码称为临界区。

线程锁

Windows实现线程锁的方法:image-20240305233145434

调用API

临界区之实现线程锁:

<1>创建全局变量

1
CRITICAL_SECTION cs;		//可以理解成一个令牌

<2>初始化全局变量

1
InitializeCriticalSection(&cs);

<3>实现临界区

1
2
3
EnterCriticalSection(&cs);		//进入临界区
//使用临界资源
LeaveCriticalSection(&cs); //离开临界区

比如这样一段买票的代码:

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
#include <stdio.h>
#include <windows.h>

int ticket = 20;

DWORD WINAPI ThreadProcess(LPVOID lpParameter)
{
while (ticket > 0)
{
printf("还有%d张票 ", ticket);
ticket--;
printf("卖出一张,还剩%d张\n", ticket);
}

return 0;
}

int main(void)
{
HANDLE hThreadArr[2];

hThreadArr[0] = CreateThread(NULL, 0, ThreadProcess, NULL, 0, NULL);
hThreadArr[1] = CreateThread(NULL, 0, ThreadProcess, NULL, 0, NULL);
WaitForMultipleObjects(2, hThreadArr, TRUE, INFINITE);
CloseHandle(hThreadArr[0]);
CloseHandle(hThreadArr[1]);

return 0;
}

有些时候会出现这么个情况

image-20240305235438634

说明两个线程A在阻塞时线程B停在了它不该停的地方

更改代码如下:

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
#include <stdio.h>
#include <windows.h>

CRITICAL_SECTION cs;
int ticket = 20;

DWORD WINAPI ThreadProcess(LPVOID lpParameter)
{
int* pt = (int*)lpParameter;

EnterCriticalSection(&cs);
while (ticket > 0)
{
printf("Thread:%d 还有%d张票 \n", *pt, ticket);
ticket--;
printf(" 卖出一张,还剩%d张\n", ticket);
printf("--------------------------\n");
}
LeaveCriticalSection(&cs);

return 0;
}

int main(void)
{
InitializeCriticalSection(&cs);
HANDLE hThreadArr[2];

int ThreadC[2];
ThreadC[0] = 1;
ThreadC[1] = 2;

int* pThread[2];
pThread[0] = &ThreadC[0];
pThread[1] = &ThreadC[1];

hThreadArr[0] = CreateThread(NULL, 0, ThreadProcess, (LPTHREAD_START_ROUTINE)pThread[0], 0, NULL);
hThreadArr[1] = CreateThread(NULL, 0, ThreadProcess, (LPTHREAD_START_ROUTINE)pThread[1], 0, NULL);
WaitForMultipleObjects(2, hThreadArr, TRUE, INFINITE);
CloseHandle(hThreadArr[0]);
CloseHandle(hThreadArr[1]);

return 0;
}
image-20240306141752501

当线程1执行时进入临界区,那么线程2就无法访问临界资源,直到线程1离开了临界区(归还令牌)后,线程2才能够访问临界资源,但是当线程2再拿着ticket=0进来的时候,已经不满足条件,就直接跳过了。

互斥体

互斥体

内核级临界资源怎么办?

假设A进程的B线程和C进程的D线程,同时使用的是内核级的临界资源(内核对象:线程、文件、进程…)该怎么让这个访问是安全的?使用线程锁的方式明显不行,因为线程锁仅能控制同进程中的多线程。

images/download/attachments/1015833/image2021-5-27_16-8-12.png

那么这时候我们就需要一个能够放在内核中的令牌来控制,而实现这个作用的,我们称之为互斥体

images/download/attachments/1015833/image2021-5-27_16-10-43.png

创建互斥体

1
2
3
4
5
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // SD 安全属性,包含安全描述符
BOOL bInitialOwner, // initial owner 是否希望互斥体创建出来就有信号,或者说就可以使用.如果希望的话就为FALSE;官方解释为如果该值为TRUE则表示当前进程拥有该互斥体所有权
LPCTSTR lpName // object name 互斥体的名字,随便起,不同程序之间靠这个名字使用互斥体
);

由线程A创建了一个互斥体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <windows.h>

int main(void)
{
HANDLE cm = CreateMutex(NULL,TRUE,"nMutex");

WaitForSingleObject(cm, INFINITE);

for (int i = 0; i < 13; i++)
{
printf("Process:A Thread:X ->%d++++++++++\n", i + 1);
Sleep(1000);
}

ReleaseMutex(cm);
return 0;
}

在B进程中使用了这个互斥体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <windows.h>

int main(void)
{
HANDLE tMutex = CreateMutex(NULL, TRUE, L"nMutex");

WaitForSingleObject(tMutex, INFINITE);

for (int i = 0; i < 12; i++)
{
printf("Process:B Thread:Y ->%d----------\n", i + 1);
Sleep(1000);
}

return 0;
}

这两个不同进程的不同线程通过nMutex这个互斥体名字进行访问。

当进程A启动后再启动进程B:

image-20240306152912660

发现进程A先执行,进程B被阻塞了。

互斥体实现禁止多开

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
#include <windows.h>

int main(int argc, char* argv[])
{
// 创建互斥体
HANDLE cm = CreateMutex(NULL, TRUE, "XYZ");
// 判断互斥体是否创建失败
if (cm != NULL) {
// 判断互斥体是否已经存在,如果存在则表示程序被多次打开
if (GetLastError() == ERROR_ALREADY_EXISTS) {
printf("该程序已经开启了,请勿再次开启!");
getchar();
} else {
// 等待互斥体状态发生变化,也就是有信号或为互斥体拥有者,获取令牌
WaitForSingleObject(cm, INFINITE);
// 操作资源
for (int i = 0; i < 5; i++) {
printf("Process: A Thread: B -- %d \n", i);
Sleep(1000);
}
// 释放令牌
ReleaseMutex(cm);
}
} else {
printf("CreateMutex 创建失败! 错误代码: %d\n", GetLastError());
}
return 0;
}

image-20240306153359061

当已经运行一个程序时,再打开第二个会显示程序已开启。

事件

通知类型

1
2
3
4
5
6
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD 安全属性,包含安全描述符
BOOL bManualReset, // reset type 如果你希望当前事件类型是通知类型则写TRUE,反之FALSE
BOOL bInitialState, // initial state 初始状态,决定创建出来时候是否有信号,有为TRUE,没有为FALSE
LPCTSTR lpName // object name 事件名字
);

生产者与消费者

要求:生产者生产一个产品,消费者消耗一个产品。如果不使用通知实现,使用互斥体的话:

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
#include <stdio.h>
#include <windows.h>

HANDLE hEven;
HANDLE hMutex;
int produce;

DWORD WINAPI ProduceThread(LPVOID lpParameter)
{

int* pProduceCount = (int*)lpParameter;

for (int i = 0; i < *pProduceCount; i++)
{
WaitForSingleObject(hMutex, INFINITE);
produce = 1; // 生产了一个产品
long ProduceThreadID = GetCurrentThreadId();
printf("生产者%ul生产了一个产品\n", ProduceThreadID);
ReleaseMutex(hMutex);
}
return 0;
}

DWORD WINAPI ConsumptionThread(LPVOID lpParameter)
{

int* pProduceCount = (int*)lpParameter;

for (int i = 0; i < *pProduceCount; i++)
{
WaitForSingleObject(hMutex, INFINITE);
produce = 0; // 生产了一个产品
long ConsumptionThreadID = GetCurrentThreadId();
printf(" 消费者%ul消费了一个产品\n", ConsumptionThreadID);
ReleaseMutex(hMutex);
}
return 0;
}


int main(void)
{
int produceCount = 10;
int* pProduce = &produceCount;

hMutex = CreateMutex(NULL, FALSE, NULL); // 创建互斥体,起始状态为阻塞

HANDLE hPCThread[2];
hPCThread[0] = CreateThread(NULL, 0, ProduceThread, (LPTHREAD_START_ROUTINE)pProduce, 0, NULL);
hPCThread[1] = CreateThread(NULL, 0, ConsumptionThread, (LPTHREAD_START_ROUTINE)pProduce, 0, NULL);

WaitForMultipleObjects(2, hPCThread, TRUE, INFINITE);
CloseHandle(hPCThread[0]);
CloseHandle(hPCThread[1]);
CloseHandle(hMutex);
}
1
2
3
4
5
6
1.先创建一个互斥体
2.创建两个线程
3.在线程中等待互斥体
4.互斥体锁线程后就能实现生产一次,消费一次

但是,这样生产和消费的顺序可能搞反

出现了先消费、后生产的情况:

image-20240306163350154

因此可以加条件判断现在有没有产品:

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
#include <stdio.h>
#include <windows.h>

HANDLE hEven;
HANDLE hMutex;
int produce;

DWORD WINAPI ProduceThread(LPVOID lpParameter)
{

int* pProduceCount = (int*)lpParameter;

for (int i = 0; i < *pProduceCount; i++)
{
WaitForSingleObject(hMutex, INFINITE);
if (produce == 0)
{
produce = 1; // 生产了一个产品
long ProduceThreadID = GetCurrentThreadId();
printf("生产者%ld生产了一个产品\n", ProduceThreadID);
}
else
{
i--;
}
ReleaseMutex(hMutex);
}
return 0;
}

DWORD WINAPI ConsumptionThread(LPVOID lpParameter)
{

int* pProduceCount = (int*)lpParameter;

for (int i = 0; i < *pProduceCount; i++)
{
WaitForSingleObject(hMutex, INFINITE);
if (produce == 1)
{
produce = 0; // 消费了一个产品
long ConsumptionThreadID = GetCurrentThreadId();
printf(" 消费者%ul消费了一个产品\n", ConsumptionThreadID);
}
else
{
i--;
}
ReleaseMutex(hMutex);
}
return 0;
}


int main(void)
{
int produceCount = 10;
int* pProduce = &produceCount;

hMutex = CreateMutex(NULL, FALSE, NULL); // 创建互斥体,起始状态为阻塞

HANDLE hPCThread[2];
hPCThread[0] = CreateThread(NULL, 0, ProduceThread, (LPTHREAD_START_ROUTINE)pProduce, 0, NULL);
hPCThread[1] = CreateThread(NULL, 0, ConsumptionThread, (LPTHREAD_START_ROUTINE)pProduce, 0, NULL);

WaitForMultipleObjects(2, hPCThread, TRUE, INFINITE);
CloseHandle(hPCThread[0]);
CloseHandle(hPCThread[1]);
CloseHandle(hMutex);

return 0;
}

这样就可以实现先生产、后消费。

但是,如果在else{i–}这里查看到底浪费了多少次循环:

image-20240306171259764image-20240306171019818

输出———的地方就是浪费的时间,占用了计算资源

线程同步

所以通过通知来优化这个程序:

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
#include <windows.h>
#include <stdio.h>

// 容器
int container = 0;

// 次数
int count = 10;

// 事件
HANDLE eventA;
HANDLE eventB;

// 生产者
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (int i = 0; i < count; i++) {
// 等待事件,修改事件A状态
WaitForSingleObject(eventA, INFINITE);
// 获取当前进程ID
int threadId = GetCurrentThreadId();
// 生产存放进容器
container = 1;
printf("Thread: %d, Build: %d \n", threadId, container);
// 给eventB设置信号
SetEvent(eventB);
}
return 0;
}

// 消费者
DWORD WINAPI ThreadProcB(LPVOID lpParameter) {
for (int i = 0; i < count; i++) {
// 等待事件,修改事件B状态
WaitForSingleObject(eventB, INFINITE);
// 获取当前进程ID
int threadId = GetCurrentThreadId();
printf("Thread: %d, Consume: %d \n", threadId, container);
// 消费
container = 0;
// 给eventA设置信号
SetEvent(eventA);
}
return 0;
}

int main(int argc, char* argv[])
{
// 创建事件
// 线程同步的前提是互斥
// 顺序按照先生产后消费,所以事件A设置信号,事件B需要通过生产者线程来设置信号
eventA = CreateEvent(NULL, FALSE, TRUE, NULL);
eventB = CreateEvent(NULL, FALSE, FALSE, NULL);

// 创建2个线程
HANDLE hThread[2];
hThread[0] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, NULL, ThreadProcB, NULL, 0, NULL);

WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
// 事件类型也是内核对象,所以也需要关闭句柄
CloseHandle(eventA);
CloseHandle(eventB);

return 0;
}

这样通过事件先让生产者执行,然后让生产者告诉消费者来消费,再由消费者告诉生产者生产的顺序执行,就合理利用了计算资源。

窗口

窗口的本质

image-20240306185445689

ntoskrnl.exe和win32k.exe是系统提供的两个模块,kernel32.dll、user32.dl、gdi32.dll可以看成是接口

如果要使用user32.dll绘制窗口,就是GUI,使用gdi32.dll绘制窗口就是GDI。

在创建窗口中使用的句柄是HWND,这个句柄是全局句柄,是公有的。

绘图

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
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:6031)

int main(void)
{
HWND hwnd;
HDC hdc;
HPEN hpen;
HBRUSH hbrush;

//1. 设备对象
hwnd = (HWND)NULL;

//2. 获取对象上下文
hdc = GetDC(hwnd);

//3. 创建画笔 设直线条属性
hpen = CreatePen(PS_SOLID, 5, RGB(0xff, 0x45, 0x10));
hbrush = (HBRUSH)GetStockObject(DC_BRUSH);

//4. 关联 如果不关联,会仍然使用系统提供的画笔
SelectObject(hdc, hpen);
SetDCBrushColor(hdc, RGB(0xc0, 0x30, 0x00));


//5. 画
LineTo(hdc, 2560, 1600);
Rectangle(hdc, 200, 200, 600, 800);
getchar();


//6. 释放资源
DeleteObject(hpen);
ReleaseDC(hwnd, hdc);

return 0;
}
image-20240306200851412

Windows程序

创建

image-20240306203415711

入口函数:

1
2
3
4
5
6
7
8
9
10
#include <windows.h>


int APIENTRY WinMain(HINSTANCE hInstance, //指向模块的句柄
HINSTANCE hPrevInstance, //永远为空
PSTR szCmdLine,
int iCmdShow) //以最大化还是最小化还是隐藏等形式运行
{
//...
}

窗口程序没有控制台打印输出,可以输出到调试里面

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
char szOutBuff[0x80];
DWORD dwAddr = (DWORD)hInstance;
sprintf(szOutBuff, "Buff: %d\n", dwAddr);
OutputDebugString(szOutBuff);
}
image-20240307213505432

第一步,定义窗口是什么样的

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
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
#pragma warning(disable:28251)
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
char szOutBuff[0x80];
// 1.第一步:定义窗口是什么样的
TCHAR className[] = TEXT("Window Application"); //创建一个字符串,存储窗口名称
WNDCLASS wndclass = { 0 }; //使用WNDCLASS类定义一个窗体,创建一个对象并初始化
wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND; //窗口背景色->要转成画刷形式的
wndclass.lpszClassName = className; //窗口的名字
wndclass.hInstance = hInstance; //当前窗口属于哪个程序,就是属于本程序,所以把hInstance传进来
wndclass.lpfnWndProc = WindowProc;
RegisterClass(&wndclass); //将刚才定义好的wndclass类告诉系统一声

// 2.第二步:创建并显示窗口、
HWND hwnd = CreateWindow(
className, //与上面定义的窗口关联
TEXT("My First Window"), //窗体名字
WS_OVERLAPPEDWINDOW, //窗体风格
200, //相对于父窗口的x坐标
100, //相对于父窗口的y坐标
600, //窗体的宽度
400, //窗体的高度
NULL, //父窗口
NULL, //是否有菜单
hInstance, //当前窗口是属于哪个模块的
NULL //附加数据,先不管,填空
);
if (hwnd == NULL) //创建失败就返回
{
sprintf(szOutBuff, "Error: %d\n", GetLastError());
OutputDebugString(szOutBuff);
return -10;
}

ShowWindow(hwnd,SW_SHOW);

// 3.第三步:接收消息并处理
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
sprintf(szOutBuff, "Error: %d\n", GetLastError());
OutputDebugString(szOutBuff);
}
else
{
// 转换消息
TranslateMessage(&msg);
// 分发消息 为了调用消息处理函数将这个消息处理掉
DispatchMessage(&msg);
}
}

return 0;
}

//消息处理函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);//调用一下默认的消息处理函数
}

image-20240307224351324

消息处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//消息处理函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
/*char szOutBuff[0x80];
sprintf(szOutBuff, "消息类型: %d\n", GetLastError());
OutputDebugString(szOutBuff);*/
switch (uMsg) //想要的消息使用switch接收,其他的交给默认消息处理函数
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
case WM_CHAR:
{
//MessageBox(0,TEXT("456"),TEXT("123"),MB_OK);
char szOutBuff[0x80];
sprintf(szOutBuff, "按键:%x - %x - %c\n", uMsg, wParam, wParam);
OutputDebugString(szOutBuff);
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);//调用默认的消息处理函数
}

子窗口控件

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include <stdio.h>
#include <windows.h>
#pragma warning(disable:4996)
#pragma warning(disable:28251)

#define IDC_EDIT_1 0x100
#define IDC_BUTTON_1 0x101
#define IDC_BUTTON_2 0x102

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

HINSTANCE g_hInstance;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
g_hInstance = hInstance;
char szOutBuff[0x80];
// 1.第一步:定义窗口是什么样的
TCHAR className[] = TEXT("Window Application"); //创建一个字符串,存储窗口名称
WNDCLASS wndclass = { 0 }; //使用WNDCLASS类定义一个窗体,创建一个对象并初始化
wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND; //窗口背景色->要转成画刷形式的
wndclass.lpszClassName = className; //窗口的名字
wndclass.hInstance = hInstance; //当前窗口属于哪个程序,就是属于本程序,所以把hInstance传进来
wndclass.lpfnWndProc = WindowProc;
RegisterClass(&wndclass); //将刚才定义好的wndclass类告诉系统一声

// 2.第二步:创建并显示窗口、
HWND hwnd = CreateWindow(
className, //与上面定义的窗口关联
TEXT("My First Window"), //窗体名字
WS_OVERLAPPEDWINDOW, //窗体风格
200, //相对于父窗口的x坐标
100, //相对于父窗口的y坐标
600, //窗体的宽度
400, //窗体的高度
NULL, //父窗口
NULL, //是否有菜单
hInstance, //当前窗口是属于哪个模块的
NULL //附加数据,先不管,填空
);
if (hwnd == NULL) //创建失败就返回
{
sprintf(szOutBuff, "Error: %d\n", GetLastError());
OutputDebugString(szOutBuff);
return -10;
}

ShowWindow(hwnd, SW_SHOW);

// 3.第三步:接收消息并处理
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
sprintf(szOutBuff, "Error: %d\n", GetLastError());
OutputDebugString(szOutBuff);
}
else
{
// 转换消息
TranslateMessage(&msg);
// 分发消息 为了调用消息处理函数将这个消息处理掉
DispatchMessage(&msg);
}
}

return 0;
}

//消息处理函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
char szOutBuff[0x80];
/*sprintf(szOutBuff, "消息类型: %d\n", GetLastError());
OutputDebugString(szOutBuff);*/
switch (uMsg)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
case WM_CREATE:
{
//当创建了父窗口开始画子窗口
CreateWindow(
TEXT("EDIT"), // Windows就知道这是一个文本框
"", // 文本框不需要标题,所以给空
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE, // 保证子窗口创建出来的时候在上层可见
10,
10,
200,
100,
hwnd,
(HMENU)IDC_EDIT_1, //将宏定义的ID转换为HMENU类型
g_hInstance,
NULL
);
CreateWindow(
TEXT("BUTTON"), // 按钮
TEXT("设置"),
WS_CHILD | WS_VISIBLE,
300,
300,
100,
30,
hwnd,
(HMENU)IDC_BUTTON_1, //将宏定义的ID转换为HMENU类型
g_hInstance,
NULL
);
CreateWindow(
TEXT("BUTTON"), // 按钮
TEXT("获取"),
WS_CHILD | WS_VISIBLE,
300,
250,
100,
30,
hwnd,
(HMENU)IDC_BUTTON_2, //将宏定义的ID转换为HMENU类型
g_hInstance,
NULL
);
break;
}
case WM_COMMAND:
{

switch (LOWORD(wParam))
{
case IDC_BUTTON_1:
{
SetDlgItemText(hwnd, IDC_EDIT_1, TEXT("123"));
break;
}
case IDC_BUTTON_2:
{
GetDlgItemText(hwnd, IDC_EDIT_1,szOutBuff,100);
MessageBox(hwnd, szOutBuff, szOutBuff, MB_OK);
break;
}
default:
break;
}
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);//调用一下默认的消息处理函数
}

一个文本框、两个按钮。

文件系统

卷相关API

1
2
3
4
5
6
7
8
9
10
11
<1>获取卷
GetLogicalDrives()

<2>获取一个所卷的盘符的字符串
GetLogicalDrives()

<3>获取卷的类型
GetLogicalDrives()

<4>获取卷的类型
GetVolumelnformation()

目录相关API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<1>创建目录
CreateDirectory();

<2>删除目录
RemoveDirectory

<3>修改目录名称
MoveFile();

<4>获取程序当前目录
GetCurrentDirectory();

<5>设置程序当前目录
SetCurrentDirectory

动态链接库

动态链接库

动态链接库(Dynamic Link Library,缩写DLL),是微软咋Windows操作系统中,实现共享函数库的一种方式,这些库函数的扩展名是.dll或.ocx

创建动态链接库

首先要告诉编译器,如果定义两个函数,这两个函数是要给别人用的

1
extern "C" _declspec(dllexport) 调用约定 返回类型 函数名(参数列表)

比如这两个函数:

1
2
3
4
5
6
7
8
int Plus(int x, int y)
{
return X + y;
}
int Sub(int x, int y)
{
return x - y;
}

如果是自己用这两个函数,那么声明函数的时候直接:

1
2
int Plus(int x, int y);
int Sub(int x, int y);

如果是要创建DLL,那么声明的时候使用:

1
2
extern "C" _declspec(dllexport) __sdtcall int Plus(int x, int y);
extern "C" _declspec(dllexport) __sdtcall int Sub(int x, int y);

声明时也可以使用.def文件:

1
2
3
EXPORTS
函数名 @编号
函数名 @编号 NONAME

使用序号导出的好处:名字是一段程序最精炼的注释,通过名字可能直接猜测到函数的功能,通过使用序号,可以达到隐藏的目的。

使用DLL

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
#include <stdio.h>
#include <windows.h>

//1.定义函数指针
typedef int(_cdecl* lpPlus)(int, int);
typedef int(_cdecl* lpSub)(int, int);

//2.声明函数指针变量
lpPlus myPlus;
lpSub mySub;

int main(void)
{
//3.动态加载dll到内存中
HINSTANCE hModule = LoadLibrary("C:\\Users\\23394\\Desktop\\code\\C C++\\Dll1\\Debug\\Dll1.dll");

//4.获取函数地址
myPlus = (lpPlus)GetProcAddress(hModule, "plus");
mySub = (lpSub)GetProcAddress(hModule, "sub");
//5.调用函数
int x = myPlus(1, 2);
int y = mySub(5, 2);
//6.释放动态链接库
FreeLibrary(hModule);

return 0;
}

示例代码:

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
// MyDLL.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdio.h>


int plus(int x, int y)
{
return x + y;
}

int sub(int x, int y)
{
return x - y;
}



BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{

switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
printf("DLL_PROCESS_ATTACH\n");
break;
}
case DLL_THREAD_ATTACH:
{
printf("DLL_THREAD_ATTACH\n");
break;
}
case DLL_THREAD_DETACH:
{
printf("DLL_THREAD_DETACH\n");
break;
}
case DLL_PROCESS_DETACH:
{
printf("DLL_PROCESS_DETACH\n");
break;
}
}
return TRUE;
}

pch.h中添加:

1
2
extern "C" _declspec(dllexport) int plus(int x, int y);
extern "C" _declspec(dllexport) int sub(int x, int y);

编译完成后生成一个DLL

创建一个新项目,使用这个DLL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <windows.h>

typedef int(_cdecl* lpPlus)(int, int);
typedef int(_cdecl* lpSub)(int, int);

lpPlus myPlus;
lpSub mySub;

int main(void)
{
HINSTANCE hModule = LoadLibrary("C:\\Users\\23394\\Desktop\\code\\C C++\\Dll1\\Debug\\Dll1.dll"); // 这里填DLL的路径(绝对路径或相对路径)

myPlus = (lpPlus)GetProcAddress(hModule, "plus");
mySub = (lpSub)GetProcAddress(hModule, "sub");

int x = myPlus(1, 2);
int y = mySub(5, 2);

FreeLibrary(hModule);

return 0;
}

隐式链接

步骤

步骤1:将.dll .lib文件放到工程目录下面

步骤2:将`#pragma comment(lib,”DLL名.lib”)添加到调用文件中

步骤3:加入函数的声明

链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <windows.h>
#pragma comment(lib,"C:\\Users\\23394\\Desktop\\code\\C C++\\win32AP\\Dll1.lib")//填写lib文件的路径

//声明函数,这个声明跟在dll中的pch头文件中的声明一样
extern "C" int __declspec(dllimport) plus(int x, int y);
extern "C" int __declspec(dllimport) sub(int x, int y);

int main(void)
{
//直接使用函数即可
printf("plus(1, 2):%d, sub(3, 1):%d", plus(1, 2), sub(3, 1));

return 0;
}

DLL入口函数

1
2
3
BOOL APIENTRY DLLMain(HANDLE hModule, 	//dll被加载到了哪个进程中,返回一个句柄
DWORD ul_reason_for_call, //dll被调用的原因
LPVOID lpReserved)

参数:DWORD ul_reason_for_call

1
2
3
4
5
6
7
1.当LoadLibrary时,DLL_PROCESS_ATTACH	(LoadLibrary)

2.当FreeLibrary时,DLL_PROCESS_DETACH (FreeLibrary)

3.当在线程中加载dll时,DLL_THREAD_ATTACH

4.当加载这个dll的线程结束的时候也会再次加载这个dll,此时传递的参数就是DLL_THREAD_DETACH

远程线程

CreateRemoteThread

创建远程线程,就是在另一个进程中创建一个新的线程。比如我的进程是Demo.exe,那么可以在一个IE.exe中创建一个新的线程让他跑起来。

远程线程函数

1
2
3
4
5
6
7
8
9
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
);

这个函数与创建线程的函数基本相同,只是多了第一个参数,也就是要在哪个进程中创建这个线程。

实现

首先创建一个程序,里面创建一个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("-----%d-----\n", i);
}
return 0;
}

int main(void)
{
HANDLE hThread;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);

getchar();
return 0;
}

这个线程在getchar处阻塞。

再写远程线程的程序:

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
#include <stdio.h>
#include <Windows.h>

BOOL MyCreateRemoteThread(DWORD dwProcessId, DWORD dwProcessAddr) {
DWORD dwThreadId;
HANDLE hProcess;
HANDLE hThread;
// 1. 获取进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
// 判断OpenProcess是否执行成功
if (hProcess == NULL) {
OutputDebugString("OpenProcess failed! \n");
return FALSE;
}
// 2. 创建远程线程
hThread = CreateRemoteThread(
hProcess, // handle to process
NULL, // SD
0, // initial stack size
(LPTHREAD_START_ROUTINE)dwProcessAddr, // thread function
NULL, // thread argument
0, // creation option
&dwThreadId // thread identifier
);
// 判断CreateRemoteThread是否执行成功
if (hThread == NULL) {
OutputDebugString("CreateRemoteThread failed! \n");
CloseHandle(hProcess);
return FALSE;
}

// 3. 关闭
CloseHandle(hThread);
CloseHandle(hProcess);

// 返回
return TRUE;
}

int main(void)
{
MyCreateRemoteThread(39772, 0x6117B0);
return 0;
}

MyCreateRemoteThread函数中的第一个参数,传入的是要创建远程线程的程序的PID,也就是上面那个程序的PID;第二个参数是第一个程序中创建的线程的地址。

远程线程注入

注入

什么是注入
所谓注入就是在第三方进程不知道或者不允许的情况下将模块或者代码写入对方进程空间,并设法执行的技术。

在安全领域,“注入”是非常重要的一种技术手段,注入与反注入也一直处于不断变化的,而且也愈来愈激烈的对抗当中。

已知的注入方式:

远程线程注入、APC注入、消息钩子注入、注册表注入、导入表注入、输入法注入等等。

事例:

远程线程注入程序.exe:

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
135
136
137
138
139
140
141
142
// Test.cpp : Defines the entry point for the console application.
//

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <stdbool.h>
#pragma warning(disable:4996)

#pragma warning(disable:6031)

// LoadDll需要两个参数一个参数是进程ID,一个是DLL文件的路径
BOOL LoadDll(DWORD dwProcessID, const char* szDllPathName) {

BOOL bRet;
HANDLE hProcess;
HANDLE hThread;
DWORD dwLength;
DWORD dwLoadAddr;
LPVOID lpAllocAddr;
DWORD dwThreadID;
HMODULE hModule;

bRet = 0;
dwLoadAddr = 0;
hProcess = 0;

// 1. 获取进程句柄 被注入的进程的句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hProcess == NULL) {
OutputDebugString("OpenProcess failed! \n");
return FALSE;
}

// 2. 获取DLL文件路径的长度,并在最后+1,因为要加上0结尾的长度
dwLength = strlen(szDllPathName) + 1;

// 3. 在目标进程分配内存 在被注入的进程中分别配一段空间,存储DLL的地址
lpAllocAddr = VirtualAllocEx(hProcess, NULL, dwLength, MEM_COMMIT, PAGE_READWRITE);
if (lpAllocAddr == NULL) {
OutputDebugString("VirtualAllocEx failed! \n");
CloseHandle(hProcess);
return FALSE;
}

// 4. 拷贝DLL路径名字到被注入进程的内存
bRet = WriteProcessMemory(hProcess, lpAllocAddr, reinterpret_cast<LPCVOID>(szDllPathName), dwLength, NULL);
if (!bRet) {
OutputDebugString("WriteProcessMemory failed! \n");
CloseHandle(hProcess);
return FALSE;
}

// 5. 获取模块句柄
// LoadLibrary这个函数是在kernel32.dll这个模块中的,所以需要先获取kernel32.dll这个模块的句柄
hModule = GetModuleHandle("kernel32.dll");
if (!hModule) {
OutputDebugString("GetModuleHandle failed! \n");
CloseHandle(hProcess);
return FALSE;
}

// 6. 获取LoadLibraryA函数地址
dwLoadAddr = reinterpret_cast<DWORD>(GetProcAddress(hModule, "LoadLibraryA"));
if (!dwLoadAddr) {
OutputDebugString("GetProcAddress failed! \n");
CloseHandle(hModule);
CloseHandle(hProcess);
return FALSE;
}

// 7. 创建远程线程,加载DLL
hThread = CreateRemoteThread(hProcess, NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(dwLoadAddr), lpAllocAddr, 0, &dwThreadID);
if (!hThread) {
OutputDebugString("CreateRemoteThread failed! \n");
DWORD dwError = GetLastError();
printf("CreateRemoteThread failed with error: %d\n", dwError);

CloseHandle(hModule);
CloseHandle(hProcess);
return FALSE;
}

// 8. 关闭进程句柄
CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

DWORD GetProcessIdByName(const char* processName) {
HANDLE hSnap;
PROCESSENTRY32 pe32;
DWORD processId = 0;

hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) {
printf("Failed to create snapshot\n");
return 0;
}

pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnap, &pe32)) {
printf("Failed to get the first process\n");
CloseHandle(hSnap);
return 0;
}

while (true) {
if (strcmp(pe32.szExeFile, processName) == 0) {
processId = pe32.th32ProcessID;
break;
}

if (!Process32Next(hSnap, &pe32)) {
break;
}
}

CloseHandle(hSnap);
return processId;
}

int main(int argc, char* argv[])
{

// 通过进程名获取进程PID
const char* processName = "被注入程序.exe";
DWORD processId = GetProcessIdByName(processName);
if (processId == 0) {
printf("Failed to find the process\n");
}
else {
printf("Process ID: %lu\n", processId);
}

// 注入DLL
LoadDll(processId, "./远程线程注入dll.dll");
getchar();
return 0;
}

被注入程序.exe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <windows.h>
#pragma warning(disaboe:6031)

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("-----%d-----\n", i + 1);
}
return 0;
}

int main(void)
{
HANDLE hThread;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);

getchar();
return 0;

}

注入的DLL:

pch.h头文件:

1
2
3
4
5
6
7
8
#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"
#include <stdio.h>

#endif //PCH_H

dllmain.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
27
28
29
30
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
while (1)
{
printf("The program is being injected\n");
Sleep(1000);
}
return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

模块隐藏

模块隐藏之断链

TEB(Thread Environment Block线程环境块) ,他记录线程相关的信息,每一个线程都有自己的TEB,FS:[0]是当前线程的TEB。

PEB(Process Environment Block进程环境块)存放进程信息,每个进程都有自己的PEB信息,TEB偏移0x30即当前进程的PEB地址

TEB和PEB都在用户空间

image-20240320200212614

在OD中使用指令dd [FS],跳转到TEB的位置:

比如,先打开kernel32.dll的位置,

image-20240320170850397

image-20240320191115578

image-20240320171515791

当API函数遍历模块的时候就是查PEB中的表

image-20240320171915170

PEB断链原码

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
#include<stdio.h>
#include<Windows.h>

typedef struct _UNICODE_STRING
{
USHORT Length; //字符串长度
USHORT MaximumLength; //字符串最大长度
PWSTR Buffer; //双字节字符串指针
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList; //代表按加载顺序构成的模块列表
LIST_ENTRY InMemoryOrderModuleList; //代表按内存顺序构成的模块列表
LIST_ENTRY InInitializationOrderModuleList; //代表按初始化顺序构成的模块链表
}PEB_LDR_DATA, * PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderModuleList; //代表按加载顺序构成的模块列表
LIST_ENTRY InMemoryOrderModuleList; //代表按内存顺序构成的模块列表
LIST_ENTRY InInitializeationOrderModuleList; //代表按初始化顺序构成的模块链表
PVOID DllBase; //该模块的基地址
PVOID EntryPoint; //该模块的入口
ULONG SizeOfImage; //该模块的影像大小
UNICODE_STRING FullDllName; //模块的完整路径
UNICODE_STRING BaseDllName; //模块名
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDataStamp;
}LDR_MODULE, * PLDR_MODULE;

PEB_LDR_DATA* g_pPebLdr = NULL;
LDR_MODULE* g_pLdrModule = NULL;
LIST_ENTRY* g_pInLoadOrderModule;
LIST_ENTRY* g_pInMemoryOrderModule;
LIST_ENTRY* g_pInInitializeationOrderModule;

void ring3BrokenChains(HMODULE hModule)
{
LIST_ENTRY* pHead = g_pInLoadOrderModule;
LIST_ENTRY* pCur = pHead;

do {
pCur = pCur->Blink;
g_pLdrModule = (PLDR_MODULE)pCur; // 这里为什么可以直接将pCur转为PLDR_MODULE,见下面代码解释

// CONTAINING_RECORD这个宏返回成员变量所在结构体的基址,ldte == g_pLdrModule
// PLDR_MODULE ldte = CONTAINING_RECORD(pCur, _LDR_DATA_TABLE_ENTRY, InLoadOrderModuleList);


if (hModule == g_pLdrModule->DllBase)
{
g_pLdrModule->InLoadOrderModuleList.Blink->Flink = g_pLdrModule->InLoadOrderModuleList.Flink;
g_pLdrModule->InLoadOrderModuleList.Flink->Blink = g_pLdrModule->InLoadOrderModuleList.Blink;

g_pLdrModule->InInitializeationOrderModuleList.Blink->Flink = g_pLdrModule->InInitializeationOrderModuleList.Flink;
g_pLdrModule->InInitializeationOrderModuleList.Flink->Blink = g_pLdrModule->InInitializeationOrderModuleList.Blink;

g_pLdrModule->InMemoryOrderModuleList.Blink->Flink = g_pLdrModule->InMemoryOrderModuleList.Flink;
g_pLdrModule->InMemoryOrderModuleList.Flink->Blink = g_pLdrModule->InMemoryOrderModuleList.Blink;
break;
}
} while (pHead != pCur);
}

int main(int argc, char* argv[])
{
__asm
{
mov eax, fs: [0x30] ; // PPEB
mov ecx, [eax + 0xC]; // ldr
mov g_pPebLdr, ecx;

mov ebx, ecx;
add ebx, 0xC;
mov g_pInLoadOrderModule, ebx; // 第1个链表

mov ebx, ecx;
add ebx, 0x14;
mov g_pInMemoryOrderModule, ebx; // 第2个链表

mov ebx, ecx;
add ebx, 0x1C;
mov g_pInInitializeationOrderModule, ebx; // 第3个链表
}

printf("点任意按键开始断链");
getchar();
ring3BrokenChains(GetModuleHandleA("kernel32.dll"));
printf("断链成功\n");
getchar();
return 0;
}

比如:使用以上函数,将以下几个库都断开

1
2
ring3BrokenChains(GetModuleHandleA("kernel32.dll"));
ring3BrokenChains(GetModuleHandleA("ntdll.dll"));

断开前:

image-20240320205056436

断开后:

image-20240320205125990

参见:[原创]超详细的3环和0环断链隐藏分析-软件逆向-看雪-安全社区|安全招聘|kanxue.com

代码注入

代码注入原则

四种代码不能注入的情况:

<1> 不能有全局变量
<2> 不能使用常量字符串
<3> 不能使用系统调用
<4> 不能嵌套其他函数

参数传递

有这么多限制该怎么办?假设我们要将代码进程的代码拷贝过去,这段代码的作用就是创建文件,那么它得流程可以如下图所示:

image-20240321135504313

首先将代码进程的ThreadProc复制过去,然后将复制过去之后目标进程的地址给到CreateRemoteThread函数,这样就解决了自定义函数的问题;

其次我们要创建文件的话就必须要使用CreateFile函数,我们不能直接这样写,因为它依赖当前进程的导入表,当前进程和目标进程导入表的地址肯定是不一样的,所以不符合复制代码的编写原则;所以我们可以通过线程函数的参数来解决,我们先将所有用到的目标参数写到一个结构体中复制到目标进程,然后将目标进程结构体的地址作为线程函数的参数。

代码实现

传递参数进行远程注入代码的实现:

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#include <tlhelp32.h>
#include <stdio.h>
#include <windows.h>

typedef struct {
DWORD dwCreateAPIAddr; // Createfile函数的地址
LPCTSTR lpFileName; // 下面都是CreateFile所需要用到的参数
DWORD dwDesiredAccess;
DWORD dwShareMode;
LPSECURITY_ATTRIBUTES lpSecurityAttributes;
DWORD dwCreationDisposition;
DWORD dwFlagsAndAttributes;
HANDLE hTemplateFile;
} CREATEFILE_PARAM;

// 定义一个函数指针
typedef HANDLE(WINAPI* PFN_CreateFile) (
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);

// 编写要复制到目标进程的函数
DWORD _stdcall CreateFileThreadProc(LPVOID lparam)
{
CREATEFILE_PARAM* Gcreate = (CREATEFILE_PARAM*)lparam;
PFN_CreateFile pfnCreateFile;
pfnCreateFile = (PFN_CreateFile)Gcreate->dwCreateAPIAddr;

// creatFile结构体全部参数
pfnCreateFile(
Gcreate->lpFileName,
Gcreate->dwDesiredAccess,
Gcreate->dwShareMode,
Gcreate->lpSecurityAttributes,
Gcreate->dwCreationDisposition,
Gcreate->dwFlagsAndAttributes,
Gcreate->hTemplateFile
);

return 0;
}

// 远程创建文件
BOOL RemotCreateFile(DWORD dwProcessID, char* szFilePathName)
{
BOOL bRet;
DWORD dwThread;
HANDLE hProcess;
HANDLE hThread;
DWORD dwThreadFunSize;
CREATEFILE_PARAM GCreateFile;
LPVOID lpFilePathName;
LPVOID lpRemotThreadAddr;
LPVOID lpFileParamAddr;
DWORD dwFunAddr;
HMODULE hModule;


bRet = 0;
hProcess = 0;
dwThreadFunSize = 0x400;
// 1. 获取进程的句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hProcess == NULL)
{
OutputDebugString("OpenProcessError! \n");
return FALSE;
}
// 2. 分配3段内存:存储参数,线程函数,文件名

// 2.1 用来存储文件名 +1是要计算到结尾处
lpFilePathName = VirtualAllocEx(hProcess, NULL, strlen(szFilePathName)+1, MEM_COMMIT, PAGE_READWRITE); // 在指定的进程中分配内存

// 2.2 用来存储线程函数
lpRemotThreadAddr = VirtualAllocEx(hProcess, NULL, dwThreadFunSize, MEM_COMMIT, PAGE_READWRITE); // 在指定的进程中分配内存

// 2.3 用来存储文件参数
lpFileParamAddr = VirtualAllocEx(hProcess, NULL, sizeof(CREATEFILE_PARAM), MEM_COMMIT, PAGE_READWRITE); // 在指定的进程中分配内存


// 3. 初始化CreateFile参数
GCreateFile.dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
GCreateFile.dwShareMode = 0;
GCreateFile.lpSecurityAttributes = NULL;
GCreateFile.dwCreationDisposition = OPEN_ALWAYS;
GCreateFile.dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
GCreateFile.hTemplateFile = NULL;

// 4. 获取CreateFile的地址
// 因为每个进程中的LoadLibrary函数都在Kernel32.dll中,而且此dll的物理页是共享的,所以我们进程中获得的LoadLibrary地址和别的进程都是一样的
hModule = GetModuleHandle("kernel32.dll");
GCreateFile.dwCreateAPIAddr = (DWORD)GetProcAddress(hModule, "CreateFileA");
FreeLibrary(hModule);

// 5. 初始化CreatFile文件名
GCreateFile.lpFileName = (LPCTSTR)lpFilePathName;

// 6. 修改线程函数起始地址
dwFunAddr = (DWORD)CreateFileThreadProc;
// 间接跳
if (*((BYTE*)dwFunAddr) == 0xE9)
{
dwFunAddr = dwFunAddr + 5 + *(DWORD*)(dwFunAddr + 1);
}

// 7. 开始复制
// 7.1 拷贝文件名
WriteProcessMemory(hProcess, lpFilePathName, szFilePathName, strlen(szFilePathName) + 1, 0);

// 7.2 拷贝线程函数
WriteProcessMemory(hProcess, lpRemotThreadAddr, (LPVOID)dwFunAddr, dwThreadFunSize, 0);

// 7.3 拷贝参数
WriteProcessMemory(hProcess, lpFileParamAddr, &GCreateFile, sizeof(CREATEFILE_PARAM), 0);

// 8. 创建远程线程
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemotThreadAddr, lpFileParamAddr, 0, &dwThread);// lpAllocAddr传给线程函数的参数.因为dll名字分配在内存中
if (hThread == NULL)
{
OutputDebugString("CreateRemoteThread Error! \n");
CloseHandle(hProcess);
CloseHandle(hModule);
return FALSE;
}

// 9. 关闭资源
CloseHandle(hProcess);
CloseHandle(hThread);
CloseHandle(hModule);
return TRUE;

}

// 根据进程名称获取进程ID
DWORD GetPID(char *szName)
{
HANDLE hProcessSnapShot = NULL;
PROCESSENTRY32 pe32 = {0};

hProcessSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnapShot == (HANDLE)-1)
{
return 0;
}

pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hProcessSnapShot, &pe32))
{
do {
if (!strcmp(szName, pe32.szExeFile)) {
return (int)pe32.th32ProcessID;
}
} while (Process32Next(hProcessSnapShot, &pe32));
}
else
{
CloseHandle(hProcessSnapShot);
}
return 0;
}

int main()
{
RemotCreateFile(GetPID("进程名"), "文件名");
return 0;
}