寫了一個小工具,可以通過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 #include #include #include #include #include  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", 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());    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" << 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 #include #include  #include "warningUser.h" #include #include #include  #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 Hookvoid 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", 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", func_name, addr);            OutputDebugStringA("\r");            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", 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", func_name, addr);            OutputDebugStringA("\r");            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 #include #include #include #include  #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  #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。