@wzwjustdoit/dsh-plugin-git-bash 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/client.js +736 -0
  4. package/cordis.patch.yml +27 -0
  5. package/lib/agents.d.ts +5 -0
  6. package/lib/agents.d.ts.map +1 -0
  7. package/lib/agents.js +56 -0
  8. package/lib/agents.js.map +1 -0
  9. package/lib/discovery.d.ts +21 -0
  10. package/lib/discovery.d.ts.map +1 -0
  11. package/lib/discovery.js +122 -0
  12. package/lib/discovery.js.map +1 -0
  13. package/lib/index.d.ts +55 -0
  14. package/lib/index.d.ts.map +1 -0
  15. package/lib/index.js +369 -0
  16. package/lib/index.js.map +1 -0
  17. package/native/CMakeLists.txt +58 -0
  18. package/native/bin/win32-x64/msys-token-guard-hook.dll +0 -0
  19. package/native/bin/win32-x64/msys-token-guard.exe +0 -0
  20. package/native/msys-token-guard-hook.cpp +470 -0
  21. package/native/msys-token-guard.cpp +671 -0
  22. package/native/vendor/detours/LICENSE.md +23 -0
  23. package/native/vendor/detours/README.dsh.md +13 -0
  24. package/native/vendor/detours/creatwth.cpp +1577 -0
  25. package/native/vendor/detours/detours.cpp +2364 -0
  26. package/native/vendor/detours/detours.h +1059 -0
  27. package/native/vendor/detours/detver.h +27 -0
  28. package/native/vendor/detours/disasm.cpp +4236 -0
  29. package/native/vendor/detours/image.cpp +2251 -0
  30. package/native/vendor/detours/modules.cpp +929 -0
  31. package/native/vendor/detours/uimports.cpp +269 -0
  32. package/package.json +120 -4
  33. package/scripts/build-native.mjs +35 -0
  34. package/scripts/dsh.desktop.startup/README.md +27 -0
  35. package/scripts/dsh.desktop.startup/dsh.desktop.startup.gitbash.bat +28 -0
  36. package/scripts/dsh.desktop.startup/dsh.desktop.startup.gitbash.sh +46 -0
  37. package/scripts/dsh.desktop.startup/dsh.desktop.startup.gitbash.vbs +0 -0
  38. package/scripts/dsh.web.startup/README.md +33 -0
  39. package/scripts/dsh.web.startup/dsh.web.startup.gitbash.bat +28 -0
  40. package/scripts/dsh.web.startup/dsh.web.startup.gitbash.sh +48 -0
  41. package/scripts/dsh.web.startup/dsh.web.startup.gitbash.vbs +0 -0
@@ -0,0 +1,470 @@
1
+ #define WIN32_LEAN_AND_MEAN
2
+ #define NOMINMAX
3
+ #include <windows.h>
4
+ #include <aclapi.h>
5
+ #include <winternl.h>
6
+
7
+ #include <array>
8
+ #include <cstddef>
9
+ #include <cstdint>
10
+ #include <cstring>
11
+ #include <cwchar>
12
+ #include <intrin.h>
13
+
14
+ #include "detours.h"
15
+
16
+ namespace {
17
+
18
+ constexpr DWORD kGuardFailureExitCode = 125;
19
+ constexpr char kHookFailureMessage[] =
20
+ "msys-token-guard-hook: failed to initialize required MSYS IPC hooks\r\n";
21
+ constexpr char kChildFailureMessage[] =
22
+ "msys-token-guard-hook: failed to inject an MSYS child process\r\n";
23
+ constexpr wchar_t kReadyHandleEnvironment[] = L"DSH_MSYS_TOKEN_GUARD_READY_HANDLE";
24
+
25
+ using RtlAddAccessAllowedAceFn = LONG(NTAPI*)(PACL acl, ULONG revision, ACCESS_MASK mask, PSID sid);
26
+ using NtSetInformationTokenFn = NTSTATUS(NTAPI*)(
27
+ HANDLE token, TOKEN_INFORMATION_CLASS information_class, PVOID information, ULONG length);
28
+
29
+ alignas(DWORD) std::array<BYTE, SECURITY_MAX_SID_SIZE> g_logon_sid{};
30
+ alignas(DWORD) std::array<BYTE, SECURITY_MAX_SID_SIZE> g_admin_sid{};
31
+ char g_hook_path[MAX_PATH]{};
32
+ RtlAddAccessAllowedAceFn g_real_add_access_allowed_ace = nullptr;
33
+ NtSetInformationTokenFn g_real_set_information_token = nullptr;
34
+ PDETOUR_CREATE_PROCESS_ROUTINEW g_real_create_process_w = CreateProcessW;
35
+ void* volatile g_msys_runtime = nullptr;
36
+
37
+ class UniqueHandle {
38
+ public:
39
+ explicit UniqueHandle(HANDLE value = nullptr) noexcept : value_(value) {}
40
+ ~UniqueHandle() {
41
+ if (value_ != nullptr && value_ != INVALID_HANDLE_VALUE) CloseHandle(value_);
42
+ }
43
+ UniqueHandle(const UniqueHandle&) = delete;
44
+ UniqueHandle& operator=(const UniqueHandle&) = delete;
45
+ [[nodiscard]] HANDLE get() const noexcept { return value_; }
46
+
47
+ private:
48
+ HANDLE value_;
49
+ };
50
+
51
+ class LocalAllocation {
52
+ public:
53
+ explicit LocalAllocation(void* value = nullptr) noexcept : value_(value) {}
54
+ ~LocalAllocation() {
55
+ if (value_ != nullptr) LocalFree(value_);
56
+ }
57
+ LocalAllocation(const LocalAllocation&) = delete;
58
+ LocalAllocation& operator=(const LocalAllocation&) = delete;
59
+
60
+ private:
61
+ void* value_;
62
+ };
63
+
64
+ void write_diagnostic(const char* message) noexcept {
65
+ HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
66
+ if (stderr_handle == nullptr || stderr_handle == INVALID_HANDLE_VALUE) return;
67
+ DWORD written = 0;
68
+ WriteFile(
69
+ stderr_handle,
70
+ message,
71
+ static_cast<DWORD>(std::strlen(message)),
72
+ &written,
73
+ nullptr);
74
+ }
75
+
76
+ [[noreturn]] void fail_closed() noexcept {
77
+ write_diagnostic(kHookFailureMessage);
78
+ TerminateProcess(GetCurrentProcess(), kGuardFailureExitCode);
79
+ ExitProcess(kGuardFailureExitCode);
80
+ }
81
+
82
+ void signal_guard_ready() {
83
+ wchar_t value[32]{};
84
+ SetLastError(ERROR_SUCCESS);
85
+ const DWORD length = GetEnvironmentVariableW(
86
+ kReadyHandleEnvironment, value, static_cast<DWORD>(std::size(value)));
87
+ if (length == 0) {
88
+ if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) return;
89
+ fail_closed();
90
+ }
91
+ if (length >= std::size(value)) fail_closed();
92
+
93
+ wchar_t* end = nullptr;
94
+ const unsigned long long raw_handle = std::wcstoull(value, &end, 16);
95
+ if (raw_handle == 0 || end == value || end == nullptr || *end != L'\0') fail_closed();
96
+ HANDLE ready = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(raw_handle));
97
+ if (SetEnvironmentVariableW(kReadyHandleEnvironment, nullptr) == FALSE ||
98
+ SetEvent(ready) == FALSE) {
99
+ fail_closed();
100
+ }
101
+ CloseHandle(ready);
102
+ }
103
+
104
+ bool capture_process_sids() noexcept {
105
+ HANDLE raw_token = nullptr;
106
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw_token) == FALSE) return false;
107
+ UniqueHandle token(raw_token);
108
+
109
+ alignas(void*) std::array<BYTE, 512> token_buffer{};
110
+ DWORD returned = 0;
111
+ if (GetTokenInformation(
112
+ token.get(),
113
+ TokenLogonSid,
114
+ token_buffer.data(),
115
+ static_cast<DWORD>(token_buffer.size()),
116
+ &returned) == FALSE ||
117
+ returned < sizeof(TOKEN_GROUPS)) {
118
+ return false;
119
+ }
120
+
121
+ const auto* logon_groups = reinterpret_cast<const TOKEN_GROUPS*>(token_buffer.data());
122
+ if (logon_groups->GroupCount != 1 || logon_groups->Groups[0].Sid == nullptr ||
123
+ IsValidSid(logon_groups->Groups[0].Sid) == FALSE) {
124
+ return false;
125
+ }
126
+ if (GetLengthSid(logon_groups->Groups[0].Sid) > g_logon_sid.size() ||
127
+ CopySid(
128
+ static_cast<DWORD>(g_logon_sid.size()),
129
+ g_logon_sid.data(),
130
+ logon_groups->Groups[0].Sid) == FALSE) {
131
+ return false;
132
+ }
133
+
134
+ alignas(void*) std::array<BYTE, 4096> restricted_buffer{};
135
+ returned = 0;
136
+ if (GetTokenInformation(
137
+ token.get(),
138
+ TokenRestrictedSids,
139
+ restricted_buffer.data(),
140
+ static_cast<DWORD>(restricted_buffer.size()),
141
+ &returned) == FALSE ||
142
+ returned < sizeof(TOKEN_GROUPS)) {
143
+ return false;
144
+ }
145
+ const auto* restricted = reinterpret_cast<const TOKEN_GROUPS*>(restricted_buffer.data());
146
+ bool logon_is_restricting = false;
147
+ for (DWORD index = 0; index < restricted->GroupCount; ++index) {
148
+ if (restricted->Groups[index].Sid != nullptr &&
149
+ IsValidSid(restricted->Groups[index].Sid) != FALSE &&
150
+ EqualSid(g_logon_sid.data(), restricted->Groups[index].Sid) != FALSE) {
151
+ logon_is_restricting = true;
152
+ break;
153
+ }
154
+ }
155
+ if (!logon_is_restricting) return false;
156
+
157
+ DWORD admin_size = static_cast<DWORD>(g_admin_sid.size());
158
+ return CreateWellKnownSid(
159
+ WinBuiltinAdministratorsSid,
160
+ nullptr,
161
+ g_admin_sid.data(),
162
+ &admin_size) != FALSE;
163
+ }
164
+
165
+ bool capture_hook_path(HMODULE module) noexcept {
166
+ wchar_t wide_path[32768]{};
167
+ const DWORD length = GetModuleFileNameW(
168
+ module,
169
+ wide_path,
170
+ static_cast<DWORD>(std::size(wide_path)));
171
+ if (length == 0 || length >= std::size(wide_path) - 1) return false;
172
+
173
+ BOOL used_default = FALSE;
174
+ const int converted = WideCharToMultiByte(
175
+ CP_ACP,
176
+ WC_NO_BEST_FIT_CHARS,
177
+ wide_path,
178
+ -1,
179
+ g_hook_path,
180
+ static_cast<int>(std::size(g_hook_path)),
181
+ nullptr,
182
+ &used_default);
183
+ return converted > 0 && used_default == FALSE;
184
+ }
185
+
186
+ bool caller_is_msys_runtime(void* return_address) noexcept {
187
+ void* module = InterlockedCompareExchangePointer(&g_msys_runtime, nullptr, nullptr);
188
+ if (module == nullptr) {
189
+ HMODULE candidate = GetModuleHandleW(L"msys-2.0.dll");
190
+ if (candidate == nullptr) return false;
191
+ void* existing = InterlockedCompareExchangePointer(
192
+ &g_msys_runtime, candidate, nullptr);
193
+ module = existing == nullptr ? candidate : existing;
194
+ }
195
+
196
+ const auto* base = static_cast<const BYTE*>(module);
197
+ const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(base);
198
+ if (dos->e_magic != IMAGE_DOS_SIGNATURE || dos->e_lfanew <= 0) return false;
199
+ const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS64*>(base + dos->e_lfanew);
200
+ if (nt->Signature != IMAGE_NT_SIGNATURE ||
201
+ nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC ||
202
+ nt->OptionalHeader.SizeOfImage == 0) {
203
+ return false;
204
+ }
205
+
206
+ const uintptr_t caller = reinterpret_cast<uintptr_t>(return_address);
207
+ const uintptr_t start = reinterpret_cast<uintptr_t>(base);
208
+ return caller >= start && caller - start < nt->OptionalHeader.SizeOfImage;
209
+ }
210
+
211
+ bool is_current_process_token(HANDLE candidate) noexcept {
212
+ if (candidate == nullptr || candidate == INVALID_HANDLE_VALUE) return false;
213
+ TOKEN_STATISTICS candidate_statistics{};
214
+ DWORD returned = 0;
215
+ if (GetTokenInformation(
216
+ candidate,
217
+ TokenStatistics,
218
+ &candidate_statistics,
219
+ sizeof(candidate_statistics),
220
+ &returned) == FALSE ||
221
+ returned < sizeof(candidate_statistics)) {
222
+ return false;
223
+ }
224
+
225
+ HANDLE raw_current = nullptr;
226
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw_current) == FALSE) return false;
227
+ UniqueHandle current(raw_current);
228
+ TOKEN_STATISTICS current_statistics{};
229
+ returned = 0;
230
+ if (GetTokenInformation(
231
+ current.get(),
232
+ TokenStatistics,
233
+ &current_statistics,
234
+ sizeof(current_statistics),
235
+ &returned) == FALSE ||
236
+ returned < sizeof(current_statistics)) {
237
+ return false;
238
+ }
239
+ return candidate_statistics.TokenId.LowPart == current_statistics.TokenId.LowPart &&
240
+ candidate_statistics.TokenId.HighPart == current_statistics.TokenId.HighPart;
241
+ }
242
+
243
+ NTSTATUS NTAPI hooked_set_information_token(
244
+ HANDLE token,
245
+ TOKEN_INFORMATION_CLASS information_class,
246
+ PVOID information,
247
+ ULONG length) noexcept {
248
+ if (information_class == TokenDefaultDacl &&
249
+ caller_is_msys_runtime(_ReturnAddress()) &&
250
+ is_current_process_token(token)) {
251
+ return 0;
252
+ }
253
+ return g_real_set_information_token(token, information_class, information, length);
254
+ }
255
+
256
+ LONG NTAPI hooked_add_access_allowed_ace(
257
+ PACL acl,
258
+ ULONG revision,
259
+ ACCESS_MASK mask,
260
+ PSID sid) noexcept {
261
+ PSID replacement = sid;
262
+ if (sid != nullptr && mask == GENERIC_ALL && IsValidSid(sid) != FALSE &&
263
+ EqualSid(sid, g_admin_sid.data()) != FALSE &&
264
+ caller_is_msys_runtime(_ReturnAddress())) {
265
+ replacement = g_logon_sid.data();
266
+ }
267
+ return g_real_add_access_allowed_ace(acl, revision, mask, replacement);
268
+ }
269
+
270
+ bool token_adjust_default_is_denied(HANDLE process) noexcept {
271
+ HANDLE probe = nullptr;
272
+ if (OpenProcessToken(process, TOKEN_ADJUST_DEFAULT, &probe) == FALSE) {
273
+ return GetLastError() == ERROR_ACCESS_DENIED;
274
+ }
275
+ CloseHandle(probe);
276
+ return false;
277
+ }
278
+
279
+ bool deny_child_token_adjust_default(HANDLE process) noexcept {
280
+ HANDLE raw_token = nullptr;
281
+ if (OpenProcessToken(
282
+ process,
283
+ TOKEN_QUERY | READ_CONTROL | WRITE_DAC,
284
+ &raw_token) == FALSE) {
285
+ return false;
286
+ }
287
+ UniqueHandle token(raw_token);
288
+
289
+ alignas(void*) std::array<BYTE, sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE> user_buffer{};
290
+ DWORD returned = 0;
291
+ if (GetTokenInformation(
292
+ token.get(),
293
+ TokenUser,
294
+ user_buffer.data(),
295
+ static_cast<DWORD>(user_buffer.size()),
296
+ &returned) == FALSE ||
297
+ returned < sizeof(TOKEN_USER)) {
298
+ return false;
299
+ }
300
+ const auto* user = reinterpret_cast<const TOKEN_USER*>(user_buffer.data());
301
+ if (user->User.Sid == nullptr || IsValidSid(user->User.Sid) == FALSE) return false;
302
+
303
+ PACL current_dacl = nullptr;
304
+ PSECURITY_DESCRIPTOR descriptor = nullptr;
305
+ DWORD status = GetSecurityInfo(
306
+ token.get(),
307
+ SE_KERNEL_OBJECT,
308
+ DACL_SECURITY_INFORMATION,
309
+ nullptr,
310
+ nullptr,
311
+ &current_dacl,
312
+ nullptr,
313
+ &descriptor);
314
+ LocalAllocation descriptor_owner(descriptor);
315
+ if (status != ERROR_SUCCESS || current_dacl == nullptr ||
316
+ IsValidAcl(current_dacl) == FALSE) {
317
+ return false;
318
+ }
319
+
320
+ EXPLICIT_ACCESSW deny{};
321
+ deny.grfAccessPermissions = TOKEN_ADJUST_DEFAULT;
322
+ deny.grfAccessMode = DENY_ACCESS;
323
+ deny.grfInheritance = NO_INHERITANCE;
324
+ deny.Trustee.TrusteeForm = TRUSTEE_IS_SID;
325
+ deny.Trustee.TrusteeType = TRUSTEE_IS_USER;
326
+ deny.Trustee.ptstrName = static_cast<LPWSTR>(user->User.Sid);
327
+
328
+ PACL hardened_dacl = nullptr;
329
+ status = SetEntriesInAclW(1, &deny, current_dacl, &hardened_dacl);
330
+ LocalAllocation hardened_owner(hardened_dacl);
331
+ if (status != ERROR_SUCCESS || hardened_dacl == nullptr) return false;
332
+ status = SetSecurityInfo(
333
+ token.get(),
334
+ SE_KERNEL_OBJECT,
335
+ DACL_SECURITY_INFORMATION,
336
+ nullptr,
337
+ nullptr,
338
+ hardened_dacl,
339
+ nullptr);
340
+ return status == ERROR_SUCCESS && token_adjust_default_is_denied(process);
341
+ }
342
+
343
+ void discard_child(PROCESS_INFORMATION* process_information) noexcept {
344
+ if (process_information == nullptr) return;
345
+ if (process_information->hProcess != nullptr) {
346
+ TerminateProcess(process_information->hProcess, kGuardFailureExitCode);
347
+ WaitForSingleObject(process_information->hProcess, 5'000);
348
+ }
349
+ if (process_information->hThread != nullptr) CloseHandle(process_information->hThread);
350
+ if (process_information->hProcess != nullptr) CloseHandle(process_information->hProcess);
351
+ *process_information = {};
352
+ }
353
+
354
+ BOOL WINAPI hooked_create_process_w(
355
+ LPCWSTR application_name,
356
+ LPWSTR command_line,
357
+ LPSECURITY_ATTRIBUTES process_attributes,
358
+ LPSECURITY_ATTRIBUTES thread_attributes,
359
+ BOOL inherit_handles,
360
+ DWORD creation_flags,
361
+ LPVOID environment,
362
+ LPCWSTR current_directory,
363
+ LPSTARTUPINFOW startup_info,
364
+ LPPROCESS_INFORMATION process_information) noexcept {
365
+ const bool caller_requested_suspension = (creation_flags & CREATE_SUSPENDED) != 0;
366
+ const BOOL created = DetourCreateProcessWithDllExW(
367
+ application_name,
368
+ command_line,
369
+ process_attributes,
370
+ thread_attributes,
371
+ inherit_handles,
372
+ creation_flags | CREATE_SUSPENDED,
373
+ environment,
374
+ current_directory,
375
+ startup_info,
376
+ process_information,
377
+ g_hook_path,
378
+ g_real_create_process_w);
379
+ if (created == FALSE) {
380
+ const DWORD code = GetLastError();
381
+ write_diagnostic(kChildFailureMessage);
382
+ SetLastError(code);
383
+ return FALSE;
384
+ }
385
+
386
+ if (!deny_child_token_adjust_default(process_information->hProcess)) {
387
+ discard_child(process_information);
388
+ write_diagnostic(kChildFailureMessage);
389
+ SetLastError(ERROR_ACCESS_DENIED);
390
+ return FALSE;
391
+ }
392
+ if (!caller_requested_suspension && ResumeThread(process_information->hThread) ==
393
+ static_cast<DWORD>(-1)) {
394
+ const DWORD code = GetLastError();
395
+ discard_child(process_information);
396
+ write_diagnostic(kChildFailureMessage);
397
+ SetLastError(code);
398
+ return FALSE;
399
+ }
400
+ return TRUE;
401
+ }
402
+
403
+ void attach_hooks(HMODULE module) {
404
+ if (!capture_hook_path(module) || !capture_process_sids()) fail_closed();
405
+
406
+ HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
407
+ if (ntdll == nullptr) fail_closed();
408
+ g_real_add_access_allowed_ace = reinterpret_cast<RtlAddAccessAllowedAceFn>(
409
+ GetProcAddress(ntdll, "RtlAddAccessAllowedAce"));
410
+ g_real_set_information_token = reinterpret_cast<NtSetInformationTokenFn>(
411
+ GetProcAddress(ntdll, "NtSetInformationToken"));
412
+ if (g_real_add_access_allowed_ace == nullptr ||
413
+ g_real_set_information_token == nullptr ||
414
+ g_real_create_process_w == nullptr) {
415
+ fail_closed();
416
+ }
417
+
418
+ if (DetourRestoreAfterWith() == FALSE ||
419
+ DetourTransactionBegin() != NO_ERROR ||
420
+ DetourUpdateThread(GetCurrentThread()) != NO_ERROR ||
421
+ DetourAttach(
422
+ reinterpret_cast<void**>(&g_real_add_access_allowed_ace),
423
+ reinterpret_cast<void*>(&hooked_add_access_allowed_ace)) != NO_ERROR ||
424
+ DetourAttach(
425
+ reinterpret_cast<void**>(&g_real_set_information_token),
426
+ reinterpret_cast<void*>(&hooked_set_information_token)) != NO_ERROR ||
427
+ DetourAttach(
428
+ reinterpret_cast<void**>(&g_real_create_process_w),
429
+ reinterpret_cast<void*>(&hooked_create_process_w)) != NO_ERROR ||
430
+ DetourTransactionCommit() != NO_ERROR) {
431
+ fail_closed();
432
+ }
433
+ signal_guard_ready();
434
+ }
435
+
436
+ void detach_hooks() noexcept {
437
+ if (g_real_add_access_allowed_ace == nullptr ||
438
+ g_real_set_information_token == nullptr ||
439
+ g_real_create_process_w == nullptr) {
440
+ return;
441
+ }
442
+ if (DetourTransactionBegin() != NO_ERROR) return;
443
+ if (DetourUpdateThread(GetCurrentThread()) != NO_ERROR) {
444
+ DetourTransactionAbort();
445
+ return;
446
+ }
447
+ DetourDetach(
448
+ reinterpret_cast<void**>(&g_real_add_access_allowed_ace),
449
+ reinterpret_cast<void*>(&hooked_add_access_allowed_ace));
450
+ DetourDetach(
451
+ reinterpret_cast<void**>(&g_real_set_information_token),
452
+ reinterpret_cast<void*>(&hooked_set_information_token));
453
+ DetourDetach(
454
+ reinterpret_cast<void**>(&g_real_create_process_w),
455
+ reinterpret_cast<void*>(&hooked_create_process_w));
456
+ DetourTransactionCommit();
457
+ }
458
+
459
+ } // namespace
460
+
461
+ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID) {
462
+ if (DetourIsHelperProcess()) return TRUE;
463
+ if (reason == DLL_PROCESS_ATTACH) {
464
+ DisableThreadLibraryCalls(instance);
465
+ attach_hooks(instance);
466
+ } else if (reason == DLL_PROCESS_DETACH) {
467
+ detach_hooks();
468
+ }
469
+ return TRUE;
470
+ }