0%

用SAM函数修改密码策略(3)

使用SAM功能获取策略

看到这里,设置 密码复杂度 策略仍然需要使用inf配置文件,那有没有不借助于inf文件的办法?

使用IDA逆向XP系统中的 netapi32.dll 模块,查看 NetUserModuleGetNetUserModuleSet 函数,
可以看到引用了一系列的以Sam开头的函数,然后找到这些Sam开头的函数是在 samlib.dll 导出的。

经过搜索,在 WDK7.1 开发包中,找到了 ntsam.h 头文件和 samlib.lib 库文件。然后在密码破解
神器 mimikatz 工具的源码中,也找到了相关的 kull_m_samlib.hsamlib.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
// 密码规则 PasswordProperties 对应的标志
#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);

其中 RtlExtendedMagicDivideRtlExtendedIntegerMultiply 函数是在 ntdll.dll 中导出的,
同样在 WDK7.1 中可以找到 ntdll.lib 库文件。这两个函数的作用,主要是查询完密码策略后,
进行数据转换计算用,其功能是把64位数扩展到128位数进行乘除操作。

整合函数的代码文件

使用Sam功能查询到的策略信息,其中时间相关的数据均为 DeltaTime 时间,需要转换成 Seconds 秒数,
如下为 DeltaTimeSeconds 之间的转换函数

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函数获取的,
但是这个信息还可以用其他方式获取到,在 ReactOSmimikatz 中就是使用的 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本地策略
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;
}
// 获取域SID信息
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
{
// 连接本机SAM服务器
NTSTATUS Status = SamConnect(NULL, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, NULL);
if (!NT_SUCCESS(Status)) break;
// 获取账户的域SID
Status = GetAccountDomainSid(&DomainSid);
if (!NT_SUCCESS(Status)) break;
// 打开本机SAM服务器中对应的域
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);
// 最大期限策略为0时,实际的数据为0xFFFFFFFF
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
{
// 连接本机SAM服务器
NTSTATUS Status = SamConnect(NULL, &ServerHandle, SAM_SERVER_LOOKUP_DOMAIN, NULL);
if (!NT_SUCCESS(Status)) break;
// 获取账户的域SID
Status = GetAccountDomainSid(&DomainSid);
if (!NT_SUCCESS(Status)) break;
// 打开本机SAM服务器中对应的域
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;
// 最大期限策略为0时,实际的数据为0xFFFFFFFF
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,但是保险起见,
尽量先查询当前的信息,然后和标志位进行 按位或 的操作,再设置回去。