使用SAM功能获取策略
看到这里,设置 密码复杂度
策略仍然需要使用inf配置文件,那有没有不借助于inf文件的办法?
使用IDA逆向XP系统中的 netapi32.dll
模块,查看 NetUserModuleGet
和 NetUserModuleSet
函数,
可以看到引用了一系列的以Sam开头的函数,然后找到这些Sam开头的函数是在 samlib.dll
导出的。
经过搜索,在 WDK7.1
开发包中,找到了 ntsam.h
头文件和 samlib.lib
库文件。然后在密码破解
神器 mimikatz
工具的源码中,也找到了相关的 kull_m_samlib.h
和 samlib.lib
文件。
最后再对照着 ReactOS
的源码,找其中一些共通之处,发现密码策略涉及到的结构体为
1 2 3 4 5 6 7
| typedef struct _DOMAIN_PASSWORD_INFORMATION { USHORT MinPasswordLength; USHORT PasswordHistoryLength; ULONG PasswordProperties; LARGE_INTEGER MaxPasswordAge; LARGE_INTEGER MinPasswordAge; } DOMAIN_PASSWORD_INFORMATION, *PDOMAIN_PASSWORD_INFORMATION;
|
1 2 3 4 5 6 7
| #define DOMAIN_PASSWORD_COMPLEX 0x00000001L #define DOMAIN_PASSWORD_NO_ANON_CHANGE 0x00000002L #define DOMAIN_PASSWORD_NO_CLEAR_CHANGE 0x00000004L #define DOMAIN_LOCKOUT_ADMINS 0x00000008L #define DOMAIN_PASSWORD_STORE_CLEARTEXT 0x00000010L #define DOMAIN_REFUSE_PASSWORD_CHANGE 0x00000020L
|
可以看到使用Sam功能可以获取到 密码复杂度
策略,是在输出到外部时被忽略掉了。
在 WDK7.1
中的 ntsam.h
文件无法直接使用,我们拷出来进行改造,主要是增加函数 NTAPI
声明,比如
1 2
| NTSTATUS SamFreeMemory(__in PVOID Buffer); NTSTATUS NTAPI SamFreeMemory(__in PVOID Buffer);
|
整合数据声明的头文件
我们这里把相关功能整理到单独的h和cpp文件中,新建头文件然后补充一些相关的定义,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #pragma once
#ifndef NT_SUCCESS #define NT_SUCCESS(x) ((NTSTATUS)(x) >= 0) #endif
typedef STRING OEM_STRING; typedef PSTRING POEM_STRING;
typedef struct _OBJECT_ATTRIBUTES { ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor; PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
#include <ntsecapi.h> #include "ntsam.h"
#pragma comment(lib, "advapi32.lib") #pragma comment(lib, "samlib.lib") #pragma comment(lib, "ntdll.lib")
extern "C" NTSYSAPI LARGE_INTEGER NTAPI RtlExtendedMagicDivide( __in LARGE_INTEGER Dividend, __in LARGE_INTEGER MagicDivisor, __in CCHAR ShiftCount);
extern "C" NTSYSAPI LARGE_INTEGER NTAPI RtlExtendedIntegerMultiply( __in LARGE_INTEGER Multiplicand, __in LONG Multiplier );
typedef struct _DOMAINPWD_INFO { DWORD pwd_prop; WORD min_len; DWORD max_age; DWORD min_age; WORD hist_len; } DOMAINPWD_INFO, *PDOMAINPWD_INFO;
BOOL GetDomainPasswordInfo(DOMAINPWD_INFO &stInfo); BOOL SetDomainPasswordInfo(DOMAINPWD_INFO &stInfo);
|
其中 RtlExtendedMagicDivide
和 RtlExtendedIntegerMultiply
函数是在 ntdll.dll
中导出的,
同样在 WDK7.1
中可以找到 ntdll.lib
库文件。这两个函数的作用,主要是查询完密码策略后,
进行数据转换计算用,其功能是把64位数扩展到128位数进行乘除操作。
整合函数的代码文件
使用Sam功能查询到的策略信息,其中时间相关的数据均为 DeltaTime
时间,需要转换成 Seconds
秒数,
如下为 DeltaTime
与 Seconds
之间的转换函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| DWORD DeltaTimeToSeconds(LARGE_INTEGER &Dividend) { DWORD result = 0; LARGE_INTEGER Seconds = { 0 }; LARGE_INTEGER MagicDivisor = { 0 }; if (Dividend.QuadPart != 0) { MagicDivisor.QuadPart = 0xD6BF94D5E57A42BDL; Seconds = RtlExtendedMagicDivide(Dividend, MagicDivisor, 23); if (Seconds.HighPart == 0xFFFFFFFF) result = -(int)Seconds.LowPart; else result = 0xFFFFFFFF; } return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| LARGE_INTEGER SecondsToDeltaTime(DWORD Seconds) { LARGE_INTEGER result = { 0 }; LARGE_INTEGER DeltaTime = { 0 }; LARGE_INTEGER Multiplicand = { 0 }; if (Seconds != 0) { result.HighPart = 0x80000000; if (Seconds == 0xFFFFFFFF) return result; Multiplicand.QuadPart = Seconds; DeltaTime = RtlExtendedIntegerMultiply(Multiplicand, 10000000); if (DeltaTime.QuadPart < 0) return result; DeltaTime.QuadPart = -DeltaTime.QuadPart; } return DeltaTime; }
|
在读取密码策略之前,我们还需要获取账户所在域的SID信息,用IDA逆向时是使用Sam函数获取的,
但是这个信息还可以用其他方式获取到,在 ReactOS
和 mimikatz
中就是使用的 LSA
函数进行查询的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| NTSTATUS GetAccountDomainSid(PSID *DomainSid) { LSA_HANDLE hPolicy = NULL; LSA_OBJECT_ATTRIBUTES ObjAttrib = { 0 }; NTSTATUS Status = LsaOpenPolicy(NULL, &ObjAttrib, POLICY_VIEW_LOCAL_INFORMATION, &hPolicy); if (!NT_SUCCESS(Status)) return Status; PPOLICY_ACCOUNT_DOMAIN_INFO DomainInfo = NULL; Status = LsaQueryInformationPolicy(hPolicy, PolicyAccountDomainInformation, (PVOID*)&DomainInfo); if (!NT_SUCCESS(Status)) { LsaClose(hPolicy); return Status; } ULONG Length = GetLengthSid(DomainInfo->DomainSid); *DomainSid = HeapAlloc(GetProcessHeap(), 0, Length); if (*DomainSid == NULL) { LsaFreeMemory(DomainInfo); LsaClose(hPolicy); return 0xC000009A; } memcpy(*DomainSid, DomainInfo->DomainSid, Length); LsaFreeMemory(DomainInfo); LsaClose(hPolicy); return 0; }
|
所有的准备工作都已完毕,如下为 查询密码策略信息
和 设置密码策略信息
的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| BOOL GetDomainPasswordInfo(DOMAINPWD_INFO &stInfo) { BOOL bRetrun = FALSE; PSID DomainSid = NULL; ACCESS_MASK DesiredAccess = 0; SAM_HANDLE ServerHandle = NULL; SAM_HANDLE DomainHandle = NULL; PDOMAIN_PASSWORD_INFORMATION PasswdInfo = NULL; do { NTSTATUS Status = SamConnect(NULL, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, NULL); if (!NT_SUCCESS(Status)) break; Status = GetAccountDomainSid(&DomainSid); if (!NT_SUCCESS(Status)) break; SAM_HANDLE DomainHandle = NULL; DesiredAccess = DOMAIN_READ_OTHER_PARAMETERS | DOMAIN_READ_PASSWORD_PARAMETERS; Status = SamOpenDomain(ServerHandle, DesiredAccess, DomainSid, &DomainHandle); if (!NT_SUCCESS(Status)) break; Status = SamQueryInformationDomain(DomainHandle, DomainPasswordInformation, (PVOID*)&PasswdInfo); if (!NT_SUCCESS(Status)) break; stInfo.pwd_prop = PasswdInfo->PasswordProperties; stInfo.min_len = PasswdInfo->MinPasswordLength; stInfo.max_age = DeltaTimeToSeconds(PasswdInfo->MaxPasswordAge); if (stInfo.max_age == 0xFFFFFFFF) stInfo.max_age = 0; stInfo.min_age = DeltaTimeToSeconds(PasswdInfo->MinPasswordAge); stInfo.hist_len = PasswdInfo->PasswordHistoryLength; bRetrun = TRUE; } while (0); if (PasswdInfo != NULL) SamFreeMemory(PasswdInfo); if (DomainSid != NULL) HeapFree(GetProcessHeap(), 0, DomainSid); if (DomainHandle != NULL) SamCloseHandle(DomainHandle); if (ServerHandle != NULL) SamCloseHandle(ServerHandle); return bRetrun; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| BOOL SetDomainPasswordInfo(DOMAINPWD_INFO &stInfo) { BOOL bRetrun = FALSE; PSID DomainSid = NULL; ACCESS_MASK DesiredAccess = 0; SAM_HANDLE ServerHandle = NULL; SAM_HANDLE DomainHandle = NULL; DOMAIN_PASSWORD_INFORMATION PasswdInfo = { 0 }; do { NTSTATUS Status = SamConnect(NULL, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, NULL); if (!NT_SUCCESS(Status)) break; Status = GetAccountDomainSid(&DomainSid); if (!NT_SUCCESS(Status)) break; SAM_HANDLE DomainHandle = NULL; DesiredAccess = DOMAIN_WRITE_OTHER_PARAMETERS | DOMAIN_WRITE_PASSWORD_PARAMS; Status = SamOpenDomain(ServerHandle, DesiredAccess, DomainSid, &DomainHandle); if (!NT_SUCCESS(Status)) break; PasswdInfo.PasswordProperties = stInfo.pwd_prop; PasswdInfo.MinPasswordLength = stInfo.min_len; if (stInfo.max_age == 0) stInfo.max_age = 0xFFFFFFFF; PasswdInfo.MaxPasswordAge = SecondsToDeltaTime(stInfo.max_age); PasswdInfo.MinPasswordAge = SecondsToDeltaTime(stInfo.min_age); PasswdInfo.PasswordHistoryLength = stInfo.hist_len; Status = SamSetInformationDomain(DomainHandle, DomainPasswordInformation, &PasswdInfo); if (!NT_SUCCESS(Status)) break; bRetrun = TRUE; } while (0); if (DomainSid != NULL) HeapFree(GetProcessHeap(), 0, DomainSid); if (DomainHandle != NULL) SamCloseHandle(DomainHandle); if (ServerHandle != NULL) SamCloseHandle(ServerHandle); return bRetrun; }
|
由于 密码复杂度
是按标志位进行检测,虽然一般情况下其他的标志位都为0,但是保险起见,
尽量先查询当前的信息,然后和标志位进行 按位或
的操作,再设置回去。