拦截 Windows 关闭信息
写了一个小工具,可以通过Hook Winlogon进程主模块的导入表、延迟导入表来拦截对于User32!ExitWindowsEx函数的调用
整个步骤如下:
1、启动一个进程,注入DLL到Winlogon进程
(1) 因为winlogon进程是system级别进程,所以要让注入进程已管理员方式启动,并使能SeDebugPrivilege权限,这样才可以注入system进程。
(2) 通过进程枚举获取到winlogon进程的PID,这个过程还可以判断session会话,因为同一时刻,系统中可能存在多个winlogon进程。
(3) 打开此进程,可以使用PROCESS_ALL_ACCESS标志。
(4) 在目标进程winlogon进程中申请内存,并写入需要注入的DLL对应的路径名称,写入绝对路径名称。
(5) 通过远线程注入,启动函数为LoadLibraryA(W),传入参数为目标进程中需要注入DLL的路径地址。
2、在DLL的入口函数DllMain中HOOk 导入表,延迟导入表
(1) 通过尝试,在windows 7 x86中,winlogon进程是在导入表中引用了User32模块的ExitWindowsEx函数。
(2) 而在 Windows 10 1809 x64中,winlogon进程是在延迟导入表中引用了User32模块的ExitWindowsEx函数。所以直接对这两个表都进行修复即可
3、Hook函数指向自己写的函数,在自己写的函数中编写功能代码
(1) 通过尝试,Winlogon进程会调用两次ExitWindowsEx函数,调用时使用的标志数据uFlags是不一样的,第一次调用ExitWindowsEx时会向桌面进程发送一些用户注销或者关机消息,用户进程可以在此时处理以便需要保存的内容。所有进程都处理完成后,第一次调用的ExitWindowsEx函数就返回TRUE了,否则返回FALSE。
(2) 如果第一次调用ExitWindowsEx成功,那么就说明桌面进程都同意关机或者注销了,会第二次调用ExitWindowsEx函数,需要意识到第二次调用前,桌面进程都被销毁了,此时如果要显示一个程序窗口,我们需要在桌面"WinSta0\\winlogon"上进行显示,指定STARTUPINFOA(W)的lpDesktop指向此字符串。
备注:在windows 7上存在会话隔离,服务进程默认处于会话0(Session 0)下,所以窗口正常情况下都看不见,Windows启动后的第一个Winlogon进程为会话1,Winlogon下创建了工作站WinSta0,在此工作站下创建了桌面"WinSta0\\winlogon",登录对话框就是显示在这个桌面上的,当用户成功登录后,显示的桌面为"WinSta0\\Default",也就是说一般情况下我们看到的的程序都运行在WinSta0\\Default桌面上,Winlogon进程还创建了桌面"WinSta0\\Disconnect"。可以通过Process Explorer工具来查看某个进程中的句柄,可以看到类似的桌面都打开了WindowsStation对象,对应的名称为\Sessions\1\Windows\WindowStations\WinSta0 不同的会话对应的会话ID不同,所以WindowsStation对象对应的Name为\Sessions\XXX\Windows\WindowStations\WinSta0。SysinternalsSuite套件中提供了一个工具叫做Desktops.exe,这个工具除了当前默认的桌面外,还可以模拟出3个桌面,查看此进程可以看到共对应四个Desktop对象,分别为:【\Default】、【\Sysinternals Desktop 1】、【\Sysinternals Desktop 2】、【\Sysinternals Desktop 3】,通过Alt 1、2、3、4即可在四个桌面之间快速进行切换。
通过上面的知识,我们可以选择在Winlogon进程第二次调用ExitWindowsEx前,进行拦截,可以弹窗提示告知用户即将关机,或者显示其它消息。因为第一次ExitWindowsEx返回TRUE才会进行第二次调用,表明用户进程都已知晓,如果有未保存文件,此时用户都已知晓并处理过了。
当第一次调用ExitWindowsEx成功后,此时Windows桌面已经进入到了上面所说的WinSta0\\winlogon下,所以调用CreateProcessAsUserA(W)时需要指定对应的桌面。
4、需要考虑到我们的DLL是运行在system进程中的,而提示时需要启动一个新的进程,最好不要让这个进程拥有过高权限,所以我们使用CreateProcessAsUserA(W)来创建进程。
通过尝试,在第二次调用ExitWindowsEx前,我们先打开当前进程,因为之后需要复制token,再修改token进程等级。但是在打开当前进程时程序执行失败,所以我们选择在DllMain函数中创建好一个Token进行保存,在调用CreateProcessAsUserA(W)使用这个token就可以了。
注入进程代码如下:
#include <Windows.h> #include <iostream> #include <Psapi.h> #include <Tlhelp32.h> #include <sddl.h> #include <Shlwapi.h> using namespace std; #pragma comment (lib,"advapi32.lib") #pragma comment (lib,"Shlwapi.lib") VOID InjectToWinLogon() { PROCESSENTRY32 entry; HANDLE snapshot = NULL, proc = NULL; entry.dwSize = sizeof(PROCESSENTRY32); snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); INT pid = -1; if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (wcscmp(entry.szExeFile, L"winlogon.exe") == 0) { pid = entry.th32ProcessID; break; } } } CloseHandle(snapshot); if (pid < 0) { //puts("[-] Could not find winlogon.exe"); return; } proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (proc == NULL) { DWORD error = GetLastError(); puts("[-] Failed to open process."); printf("error %d\n", error); return; } TCHAR buffDll[MAX_PATH] = { 0 }; GetModuleFileName(NULL, buffDll, _countof(buffDll)); PathRemoveFileSpec(buffDll); _tcscat_s(buffDll, _countof(buffDll), L"\\DllHookExitWindowsEx.dll"); LPVOID buffer = VirtualAllocEx(proc, NULL, sizeof(buffDll), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) { printf("[-] Failed to allocate remote memory"); } if (!WriteProcessMemory(proc, buffer, buffDll, sizeof(buffDll), 0)) { puts("[-] Failed to write to remote memory"); return; } LPTHREAD_START_ROUTINE start = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW"); HANDLE hthread = CreateRemoteThread(proc, 0, 0, (LPTHREAD_START_ROUTINE)start, buffer, 0, 0); DWORD error = GetLastError(); if (hthread == INVALID_HANDLE_VALUE) { puts("[-] Failed to create remote thread"); return; } } void EnableSeDebugPrivilegePrivilege() { LUID luid; HANDLE currentProc = OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId()); //CreateRemoteThreadEx() if (currentProc) { HANDLE TokenHandle = NULL; BOOL hProcessToken = OpenProcessToken(currentProc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle); if (hProcessToken) { BOOL checkToken = LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &luid); if (!checkToken) { //std::cout << "[+] Current process token already includes SeDebugPrivilege\n" << std::endl; } else { TOKEN_PRIVILEGES tokenPrivs; tokenPrivs.PrivilegeCount = 1; tokenPrivs.Privileges[0].Luid = luid; tokenPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; BOOL adjustToken = AdjustTokenPrivileges(TokenHandle, FALSE, &tokenPrivs, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL); if (adjustToken != 0) { //std::cout << "[+] Added SeDebugPrivilege to the current process token" << std::endl; } } CloseHandle(TokenHandle); } } CloseHandle(currentProc); } int _tmain(int argc, _TCHAR* argv[]) { //开启权限 使之可以注入到syetem进程 EnableSeDebugPrivilegePrivilege(); //注入dll InjectToWinLogon(); getchar(); return 0; }
被注入的DLL代码如下:
#include <Windows.h> #include <Psapi.h> #include <Tlhelp32.h> #include "warningUser.h" #include <Shlwapi.h> #include <stdlib.h> #include <tchar.h> #pragma comment (lib,"Shlwapi.lib") LPVOID _copyNtShutdownSystem = NULL; LPVOID _ExitWindowsExAddTwoByte = NULL; HMODULE _gloDllModule = NULL; #pragma warning(disable:4996) /*__declspec(naked)*/ void MyExitWindowsEx() { /*__asm { call testMsgBox; jmp _ExitWindowsExAddTwoByte }*/ } typedef BOOL(WINAPI* FuncExitWindowsEx)(_In_ UINT uFlags, _In_ DWORD dwReason); FuncExitWindowsEx _OldExitWindowsEx = NULL; HANDLE gloCreateProcessHandle = NULL; BOOL WINAPI IATHookExitWindowsEx(_In_ UINT uFlags, _In_ DWORD dwReason) { BOOL bRet = FALSE; static BOOL bNeedWarning = FALSE; //__asm int 3 //DebugBreak(); /*if (uFlags & 0x200000)//win7 x86可以通过这句来判断是否是第二次调用 通过调试获得的 需要测试 { }*/ if (bNeedWarning) { TCHAR wszProcessName[MAX_PATH] = { 0 }; GetModuleFileName(_gloDllModule, wszProcessName, _countof(wszProcessName)); PathRemoveFileSpec(wszProcessName); _tcscat_s(wszProcessName, _countof(wszProcessName), L"\\LogOffWillRun.exe"); useTokenCreateProcess(gloCreateProcessHandle, wszProcessName); } bRet = _OldExitWindowsEx(uFlags, dwReason); if (bRet) { bNeedWarning = TRUE; } return bRet; } //这是 win7 x86上的 Iniline Hook void hook_ExitWindowsEx() { HMODULE hUser32 = GetModuleHandle(L"user32.dll"); char* pOldExitWindowsEx = (char*)GetProcAddress(hUser32, "ExitWindowsEx"); char* pOldAddr = pOldExitWindowsEx; //00540000 8bff mov edi, edi int iLengthCopy = 7; if (NULL != pOldAddr) { _copyNtShutdownSystem = VirtualAlloc(0, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE); char* pNewAddr = (char*)_copyNtShutdownSystem; char* pnop = pOldAddr - 5; //有5个字节的NOP char aa = *pOldAddr; char bb = *(pOldAddr+1); if ((char)0x8b == *pOldAddr && (char)0xff == *(pOldAddr+1)) { DWORD oldshutdownProtect = 0; if (VirtualProtect(pOldAddr-5, iLengthCopy, PAGE_EXECUTE_READWRITE, &oldshutdownProtect)) { //*pOldNtShutdownSyetem = (char)0xe9;//jmp *pOldExitWindowsEx = (char)0xeB;//jmp 短跳转 *(UCHAR*)(pOldExitWindowsEx + 1) = (USHORT)(-0x7); //addr *pnop = (char)0xe9;//jmp *(int*)(pnop + 1) = (int)MyExitWindowsEx-(int)(pnop + 5); //addr _ExitWindowsExAddTwoByte = pOldExitWindowsEx + 2; VirtualProtect(pOldAddr-5, iLengthCopy, oldshutdownProtect, NULL); } } } return; } BYTE* getNtHdrs(BYTE* pe_buffer) { if (pe_buffer == NULL) return NULL; IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)pe_buffer; if (idh->e_magic != IMAGE_DOS_SIGNATURE) { return NULL; } const LONG kMaxOffset = 1024; LONG pe_offset = idh->e_lfanew; if (pe_offset > kMaxOffset) return NULL; IMAGE_NT_HEADERS32* inh = (IMAGE_NT_HEADERS32*)((BYTE*)pe_buffer + pe_offset); if (inh->Signature != IMAGE_NT_SIGNATURE) return NULL; return (BYTE*)inh; } IMAGE_DATA_DIRECTORY* getPeDir(PVOID pe_buffer, size_t dir_id) { if (dir_id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return NULL; BYTE* nt_headers = getNtHdrs((BYTE*)pe_buffer); if (nt_headers == NULL) return NULL; IMAGE_DATA_DIRECTORY* peDir = NULL; IMAGE_NT_HEADERS* nt_header = (IMAGE_NT_HEADERS*)nt_headers; peDir = &(nt_header->OptionalHeader.DataDirectory[dir_id]); if (peDir->VirtualAddress == NULL) { return NULL; } return peDir; } bool FixDelayIATHook(PVOID modulePtr) { IMAGE_DATA_DIRECTORY* importsDir = getPeDir(modulePtr, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); if (importsDir == NULL) return false; size_t maxSize = importsDir->Size; size_t impAddr = importsDir->VirtualAddress; IMAGE_DELAYLOAD_DESCRIPTOR* lib_desc = NULL; size_t parsedSize = 0; bool bFound = TRUE; size_t addrExitWindowsEx = (size_t)GetProcAddress(GetModuleHandle(L"User32"), "ExitWindowsEx"); for (; parsedSize < maxSize; parsedSize += sizeof(IMAGE_DELAYLOAD_DESCRIPTOR)) { lib_desc = (IMAGE_DELAYLOAD_DESCRIPTOR*)(impAddr + parsedSize + (ULONG_PTR)modulePtr); if (lib_desc->ImportAddressTableRVA == NULL && lib_desc->ImportNameTableRVA == NULL) break; LPSTR lib_name = (LPSTR)((ULONGLONG)modulePtr + lib_desc->DllNameRVA); size_t call_via = lib_desc->ImportAddressTableRVA; size_t thunk_addr = lib_desc->ImportNameTableRVA; if (thunk_addr == NULL) thunk_addr = lib_desc->ImportAddressTableRVA; size_t offsetField = 0; size_t offsetThunk = 0; for (;; offsetField += sizeof(IMAGE_THUNK_DATA), offsetThunk += sizeof(IMAGE_THUNK_DATA)) { IMAGE_THUNK_DATA* fieldThunk = (IMAGE_THUNK_DATA*)(size_t(modulePtr) + offsetField + call_via); IMAGE_THUNK_DATA* orginThunk = (IMAGE_THUNK_DATA*)(size_t(modulePtr) + offsetThunk + thunk_addr); if (0 == fieldThunk->u1.Function && 0 == orginThunk->u1.Function) { break; } PIMAGE_IMPORT_BY_NAME by_name = NULL; LPSTR func_name = NULL; size_t addrOld = NULL; if (orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32 || orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) // check if using ordinal (both x86 && x64) { addrOld = (size_t)GetProcAddress(LoadLibraryA(lib_name), (char*)(orginThunk->u1.Ordinal & 0xFFFF));//通过序号也可以获取到 获取低两个字节 也可以获取到函数地址 //printf(" [V] API %x at %x\n", orginThunk->u1.Ordinal, addr); //fieldThunk->u1.Function = addr; continue; } else { by_name = (PIMAGE_IMPORT_BY_NAME)(size_t(modulePtr) + orginThunk->u1.AddressOfData); func_name = (LPSTR)by_name->Name; addrOld = (size_t)GetProcAddress(LoadLibraryA(lib_name), func_name); } //printf(" [V] API %s at %x\n", func_name, addr); OutputDebugStringA("\r\n"); OutputDebugStringA(func_name); //HOOK if (strcmpi(func_name, "ExitWindowsEx") == 0) { //DebugBreak(); DWORD dOldProtect = 0; size_t* pFuncAddr = (size_t*)&fieldThunk->u1.Function; if (VirtualProtect(pFuncAddr, sizeof(size_t), PAGE_EXECUTE_READWRITE, &dOldProtect)) { fieldThunk->u1.Function = (size_t)IATHookExitWindowsEx; VirtualProtect(pFuncAddr, sizeof(size_t), dOldProtect, &dOldProtect); _OldExitWindowsEx = (FuncExitWindowsEx)addrExitWindowsEx; bFound = true; return bFound; } break; } } } return true; } bool FixIATHook(PVOID modulePtr) { IMAGE_DATA_DIRECTORY* importsDir = getPeDir(modulePtr, IMAGE_DIRECTORY_ENTRY_IMPORT); if (importsDir == NULL) return false; size_t maxSize = importsDir->Size; size_t impAddr = importsDir->VirtualAddress; IMAGE_IMPORT_DESCRIPTOR* lib_desc = NULL; size_t parsedSize = 0; bool bFound = TRUE; size_t addrExitWindowsEx = (size_t)GetProcAddress(GetModuleHandle(L"User32"), "ExitWindowsEx"); for (; parsedSize < maxSize; parsedSize += sizeof(IMAGE_IMPORT_DESCRIPTOR)) { lib_desc = (IMAGE_IMPORT_DESCRIPTOR*)(impAddr + parsedSize + (ULONG_PTR)modulePtr); if (lib_desc->OriginalFirstThunk == NULL && lib_desc->FirstThunk == NULL) break; LPSTR lib_name = (LPSTR)((size_t)modulePtr + lib_desc->Name); size_t call_via = lib_desc->FirstThunk; size_t thunk_addr = lib_desc->OriginalFirstThunk; if (thunk_addr == NULL) thunk_addr = lib_desc->FirstThunk; size_t offsetField = 0; size_t offsetThunk = 0; for (;; offsetField += sizeof(IMAGE_THUNK_DATA), offsetThunk += sizeof(IMAGE_THUNK_DATA)) { IMAGE_THUNK_DATA* fieldThunk = (IMAGE_THUNK_DATA*)(size_t(modulePtr) + offsetField + call_via); IMAGE_THUNK_DATA* orginThunk = (IMAGE_THUNK_DATA*)(size_t(modulePtr) + offsetThunk + thunk_addr); if (0 == fieldThunk->u1.Function && 0 == orginThunk->u1.Function) { break; } PIMAGE_IMPORT_BY_NAME by_name = NULL; LPSTR func_name = NULL; size_t addrOld = NULL; if (orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32 || orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) // check if using ordinal (both x86 && x64) { addrOld = (size_t)GetProcAddress(LoadLibraryA(lib_name), (char*)(orginThunk->u1.Ordinal & 0xFFFF));//通过序号? //printf(" [V] API %x at %x\n", orginThunk->u1.Ordinal, addr); //fieldThunk->u1.Function = addr; //DebugBreak(); continue; } else { by_name = (PIMAGE_IMPORT_BY_NAME)(size_t(modulePtr) + orginThunk->u1.AddressOfData); func_name = (LPSTR)by_name->Name; addrOld = (size_t)GetProcAddress(LoadLibraryA(lib_name), func_name); } //printf(" [V] API %s at %x\n", func_name, addr); OutputDebugStringA("\r\n"); OutputDebugStringA(func_name); //HOOK if (strcmpi(func_name, "ExitWindowsEx") == 0) { //DebugBreak(); DWORD dOldProtect = 0; size_t* pFuncAddr = (size_t*)&fieldThunk->u1.Function; if (VirtualProtect(pFuncAddr, sizeof(size_t), PAGE_EXECUTE_READWRITE, &dOldProtect)) { fieldThunk->u1.Function = (size_t)IATHookExitWindowsEx; VirtualProtect(pFuncAddr, sizeof(size_t), dOldProtect, &dOldProtect); _OldExitWindowsEx = (FuncExitWindowsEx)addrExitWindowsEx; bFound = true; return bFound; } } } } return true; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { //DebugBreak(); _gloDllModule = hModule; gloCreateProcessHandle = getMediumProcessToken(); HMODULE exeModule = GetModuleHandle(NULL); FixIATHook(exeModule); FixDelayIATHook(exeModule); break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: { if (gloCreateProcessHandle != NULL) { CloseHandle(gloCreateProcessHandle); gloCreateProcessHandle = NULL; } } break; } return TRUE; }
其中引用了warningUser.h文件内容如下:
HANDLE getMediumProcessToken(); void useTokenCreateProcess(HANDLE hToken, TCHAR* szProcessName);
源文件warningUser.cpp文件内容为:
#include "warningUser.h" #include <Windows.h> #include <sddl.h> #include <tchar.h> #include <Shlwapi.h> #include <stdlib.h> #pragma comment(lib, "Advapi32.lib") #pragma comment (lib,"Shlwapi.lib") HANDLE getMediumProcessToken() { WCHAR* wszIntegritySid = L"S-1-16-8192"; //CreateIntegritySidProcess(L"S-1-16-4096");//low权限进程 //CreateIntegritySidProcess(L"S-1-16-8192");//medium权限进程 //CreateIntegritySidProcess(L"S-1-16-12288");//high权限进程 //CreateIntegritySidProcess(L"S-1-16-16384");//system权限进程 HANDLE mediumToken = NULL; HANDLE hToken = NULL; HANDLE hNewToken = NULL; PSID pIntegritySid = NULL; TOKEN_MANDATORY_LABEL TIL = { 0 }; __try { if (FALSE == OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hToken)) { __leave; } if (FALSE == DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hNewToken)) { __leave; } if (FALSE == ConvertStringSidToSid(wszIntegritySid, &pIntegritySid)) { __leave; } TIL.Label.Attributes = SE_GROUP_INTEGRITY; TIL.Label.Sid = pIntegritySid; // Set the process integrity level if (FALSE == SetTokenInformation(hNewToken, TokenIntegrityLevel, &TIL, sizeof(TOKEN_MANDATORY_LABEL)+GetLengthSid(pIntegritySid))) { __leave; } mediumToken = hNewToken; } __finally { if (NULL != pIntegritySid) { LocalFree(pIntegritySid); pIntegritySid = NULL; } if (NULL != hToken) { CloseHandle(hToken); hToken = NULL; } } return mediumToken; } void useTokenCreateProcess(HANDLE hToken, TCHAR* szProcessName) { //LogOffWillRun //WCHAR wszProcessName[MAX_PATH] = L"C:\\Windows\\System32\\CMD.exe"; PROCESS_INFORMATION ProcInfo = { 0 }; STARTUPINFO StartupInfo = { 0 }; StartupInfo.cb = sizeof(STARTUPINFO); //si.dwXSize = 120; //StartupInfo.lpDesktop = L"WinSta0\\Default"; StartupInfo.lpDesktop = L"WinSta0\\winlogon"; StartupInfo.dwFlags = STARTF_USESHOWWINDOW; StartupInfo.wShowWindow = SW_SHOWNORMAL; BOOL bRet = CreateProcessAsUser(hToken, NULL, szProcessName, NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcInfo); if (bRet) { WaitForSingleObject(ProcInfo.hProcess, INFINITE); } if (ProcInfo.hProcess) { CloseHandle(ProcInfo.hProcess); ProcInfo.hProcess = NULL; } if (ProcInfo.hThread) { CloseHandle(ProcInfo.hThread); ProcInfo.hThread = NULL; } }
在提示进程LogOffWillRun中,代码很简单,进行提示,如下
#include <Windows.h> #pragma comment(lib, "User32.lib") int _tmain(int argc, _TCHAR* argv[]) { MessageBox(GetConsoleWindow(), _TEXT("调用了ExitWindowsEx进行关机或者注销"), _TEXT("提示"), MB_OK); return 0; }
代码基本到此结束,运行注入DLL的进程时需要以管理员权限运行。
winlogon调用两次ExitWindowsEx后,wininit进程会去调用ntdll!NtShutDownSystem进程进行关机。在win 10 1809 x64上,发现wininit会进行第三次调用ExitWindowsEx函数。本来想远线程注入wininit进程,但是失败,错误代码为8,含义为内存资源不足,无法处理此命令。
还有一个想法是Hook wininit进程,具体如下:
1、打开wininit进程
2、远程修改导入表,Hook NtShutDownSystem
(1) 可以通过在本进程中调用LoadLibraryExA(W)展开wininit的PE文件,定位到NtShutDownSystem函数对应的地址,通过此地址与内存中的wininit映像计算一个偏移。
(2)枚举wininit进程模块,获取主进程基地址,然后通过上一步的偏移量来修改导入表或者延迟导入表。
(3)修改地址为一段shellcode的起始地址,并保留原来wininit中导入表的内容
a、远程申请一段空间,可读可写可执行,这段空间包括两部分内容,第一部分是数据,第二部分是代码。
b、数据是原来导入表的内容,也可以写入其它数据。
c、代码可以使用PIC_Bindshell项目,然后自己编写C语言函数,编写一段shellcode,引用之前的数据(可以通过call pop重定位或者生成shellcode后,通过每次修改shellcode硬编码来引用数据)。
3、修改导入表后,Hook函数指针指向我们自己的shellcode(如果数据与代码在连续内存,记得加上偏移,使指针指向shellcode起始地址),当wininit调用NtShutDownSystem时会先调用我们自己的函数,使用PIC_Bindshell编写代码可以加载我们自己的动态库,调用动态库导出函数来启动一个进程提示用户关机了。
备注:winlogon进程调用两次ExitWindowsEx后程序就退出了,如果我们选择注销当前用户,当前winlogon结束,会启动新的winlogon,此时没有我们注入的动态库,即可以将注入器写成服务进程,后台枚举winlogon进程,注入DLL。或者HOOk Wininit进程。也可以在桌面进程中监听窗口消息WM_QUERYENDSESSION。或者在服务进程中通过RegisterServiceCtrlHandlerA(W)注册一个回调函数,在回调函数中处理SERVICE_CONTROL_SHUTDOWN请求。
2022-12-28添加:在Hook的ExitWindowsEx函数中也可以通过MessageBox来提示用户,如下:
if (bNeedWarning) { MessageBox(NULL, _TEXT("弹框提示"), _TEXT("提示"), MB_OK); }
效果如下:
如果是通过启动一个新进程的方式,优点是更加灵活,自己编写新进程,不需要修改DLL。
本文在看雪论坛也有发帖,原文地址为https://bbs.pediy.com/thread-275407.htm
上一篇: Windows 编程偏门概念:会话(会话)、窗口站(窗口站)、桌面
下一篇: 注册表启用的启动项
推荐阅读
-
拦截 Windows 关闭信息
-
通过钩子拦截 Windows 休眠和冬眠操作
-
Windows 11 的智能应用程序控制:如何开启或关闭
-
[阿里云镜像] 配置阿里巴巴开源镜像站镜像 - PyPI 镜像 - 下载:https://mirrors.aliyun.com/pypi/ 三、🌈 PyPI 镜像配置方法(Windows 系统)🌈 默认情况下 pip 使用的是国外镜像,在下载的时候速度非常慢,本文我们介绍使用阿里云镜像源,地址是:https://mirrors.aliyun.com/pypi/ 1️ ️ ⃣ 打开运行框,输入:%APPDATA% 2️ ️ ️ 按回车跳转到此目录,新建一个名为 pip 文件夹 3️⃣ 在 pip 文件夹内创建 pip.ini文件 4️⃣在pip.ini文件夹中填写以下配置信息
-
如何在 Windows 上获取 cpu 信息、cpuid、cpu id
-
linux 或 windows 获取唯一的硬件信息 - 命令行和 c++ 代码
-
使用 gco 获取 Windows 系统的 CPU 信息
-
使用 gco 获取 Windows 系统的 CPU 信息
-
Windows 平台、C++ 获取 CPU 型号、读取注册表以获取系统硬件和软件信息代码
-
Windows 共享文件夹无法访问,提示信息为 "不允许使用一个以上用户名的用户多次连接服务器或共享资源"。