渗透基础——获得域用户的登录信息
0x00 前言
在域渗透中,获得了域控制器权限后,需要获得域用户的登录信息,包括域用户登录的IP地址和登录时间。通常使用的方法是查看域控制器的登录日志(Eventid=4624)。然而,人工从登录日志(Eventid=4624)中筛选出域用户登录的IP地址和登录时间需要耗费大量时间,不仅无效数据多,而且需要多次判断,所以我们需要编写程序来实现这个功能。
在实际使用过程中,为了能够适配多种环境,还需要支持本地和多种协议的远程登录。于是本文将要分享我的实现方法,开源两个工具,记录细节。
0x01 简介
本文将要介绍以下内容:
- 通过EventLogSession实现
- 通过WMI实现
- 开源代码
0x02 通过EventLogSession实现
通过查询资料发现,通过EventLogSession不仅支持解析本地日志内容,还支持通过RPC远程解析日志,下面介绍关于EventLogSession的开发细节
1.输出Eventid=4624
的日志内容
C Sharp实现代码:
using System;
using System.Diagnostics.Eventing.Reader;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
var session = new EventLogSession();
string LogName = "Security";
string XPathQuery = "*[System/EventID=4624]";
EventLogQuery eventLogQuery = new EventLogQuery(LogName, PathType.LogName, XPathQuery)
{
Session = session,
TolerateQueryErrors = true,
ReverseDirection = true
};
using (EventLogReader eventLogReader = new EventLogReader(eventLogQuery))
{
eventLogReader.Seek(System.IO.SeekOrigin.Begin, 0);
do
{
EventRecord eventData = eventLogReader.ReadEvent();
if (eventData == null)
break;
Console.WriteLine(eventData.FormatDescription());
eventData.Dispose();
} while (true);
}
}
}
}
以上代码能够查询本地日志并输出日志的完整内容
2.xml格式解析
为了便于提取内容,可以选择将输出内容转换为xml格式
关键代码:
Console.WriteLine(eventData.ToXml());
输出内容示例:
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'><System><Provider Name='Microsoft-Windows-Security-Auditing' Guid='{54849625-5478-4994-A5BA-3E3B0328C30D}'/><EventID>4624</EventID><Version>1</Version><Level>0</Level><Task>12544</Task><Opcode>0</Opcode><Keywords>0x8020000000000000</Keywords><TimeCreated SystemTime='2022-01-01T06:46:34.839752100Z'/><EventRecordID>393748</EventRecordID><Correlation/><Execution ProcessID='508' ThreadID='3448'/><Channel>Security</Channel><Computer>dc1.test.com</Computer><Security/></System><EventData><Data Name='SubjectUserSid'>S-1-0-0</Data><Data Name='SubjectUserName'>-</Data><Data Name='SubjectDomainName'>-</Data><Data Name='SubjectLogonId'>0x0</Data><Data Name='TargetUserSid'>S-1-5-21-254706111-4049838133-2416586677-2102</Data><Data Name='TargetUserName'>test1</Data><Data Name='TargetDomainName'>TEST</Data><Data Name='TargetLogonId'>0xa222fb5</Data><Data Name='LogonType'>3</Data><Data Name='LogonProcessName'>NtLmSsp </Data><Data Name='AuthenticationPackageName'>NTLM</Data><Data Name='WorkstationName'></Data><Data Name='LogonGuid'>{00000000-0000-0000-0000-000000000000}</Data><Data Name='TransmittedServices'>-</Data><Data Name='LmPackageName'>NTLM V2</Data><Data Name='KeyLength'>128</Data><Data Name='ProcessId'>0x0</Data><Data Name='ProcessName'>-</Data><Data Name='IpAddress'>192.168.1.3</Data><Data Name='IpPort'>45624</Data><Data Name='ImpersonationLevel'>%%1833</Data></EventData></Event>
从xml格式中,可直接提取出EventRecordID
,关键代码:
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(eventData.ToXml());
XmlNodeList recordid = xmldoc.GetElementsByTagName("EventRecordID");
Console.WriteLine(recordid[0].InnerText);
提取TargetUserName
需要先取出Data
的内容,再做一个筛选,关键代码:
XmlNodeList data = xmldoc.GetElementsByTagName("Data");
foreach (XmlNode value in data)
{
if (value.OuterXml.Contains("TargetUserName"))
{
Console.WriteLine(value.InnerText);
}
}
这里我们一共需要筛选出以下属性:
- TargetUserSid
- TargetDomainName
- TargetUserName
- IpAddress
在做字符匹配时,由于格式固定,所以我们可以从固定偏移位置得到对应的属性,避免多次判断,提高查询效率
关键代码:
XmlNodeList data = xmldoc.GetElementsByTagName("Data");
String targetUserSid = data[4].InnerText;
String targetDomainName = data[6].InnerText;
String targetUserName = data[5].InnerText;
String ipAddress = data[18].InnerText;
3.筛选判断条件
为了筛选出有效登录信息,这里对targetUserSid
和ipAddress
的长度做了判断,targetUserSid
长度需要大于9,ipAddress
长度需要大于8
关键代码:
XmlNodeList data = xmldoc.GetElementsByTagName("Data");
String targetUserSid = data[4].InnerText;
String targetDomainName = data[6].InnerText;
String targetUserName = data[5].InnerText;
String ipAddress = data[18].InnerText;
if (targetUserSid.Length > 9 && ipAddress.Length > 8)
{
Console.WriteLine(targetUserSid);
Console.WriteLine(targetDomainName);
Console.WriteLine(targetUserName);
Console.WriteLine(ipAddress);
}
4.支持筛选指定时间内的日志
可以通过修改搜索条件实现,关键代码:
string XPathQuery = "(Event/System/EventID=4624) and Event/System/TimeCreated/@SystemTime >= '2022-01-26T02:30:39' and Event/System/TimeCreated/@SystemTime <= '2022-01-26T02:31:00'";
至此,通过EventLogSession解析本地登录日志的实现代码如下:
using System;
using System.Diagnostics.Eventing.Reader;
using System.Xml;
namespace Test1
{
class Program
{
static void Main(string[] args)
{
var session = new EventLogSession();
string LogName = "Security";
string XPathQuery = "(Event/System/EventID=4624) and Event/System/TimeCreated/@SystemTime >= '2022-01-26T02:30:39' and Event/System/TimeCreated/@SystemTime <= '2022-01-26T02:31:00'";
EventLogQuery eventLogQuery = new EventLogQuery(LogName, PathType.LogName, XPathQuery)
{
Session = session,
TolerateQueryErrors = true,
ReverseDirection = true
};
int flagTotal = 0;
int flagExist = 0;
using (EventLogReader eventLogReader = new EventLogReader(eventLogQuery))
{
eventLogReader.Seek(System.IO.SeekOrigin.Begin, 0);
do
{
EventRecord eventData = eventLogReader.ReadEvent();
if (eventData == null)
break;
flagTotal++;
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(eventData.ToXml());
XmlNodeList recordid = xmldoc.GetElementsByTagName("EventRecordID");
XmlNodeList data = xmldoc.GetElementsByTagName("Data");
String targetUserSid = data[4].InnerText;
String targetDomainName = data[6].InnerText;
String targetUserName = data[5].InnerText;
String ipAddress = data[18].InnerText;
if (targetUserSid.Length > 9 && ipAddress.Length > 8)
{
Console.WriteLine("[+] EventRecordID: " + recordid[0].InnerText);
Console.WriteLine(" TimeCreated : " + eventData.TimeCreated);
Console.WriteLine(" UserSid: " + targetUserSid);
Console.WriteLine(" DomainName: " + targetDomainName);
Console.WriteLine(" UserName: " + targetUserName);
Console.WriteLine(" IpAddress: " + ipAddress);
flagExist++;
}
eventData.Dispose();
} while (true);
Console.WriteLine("Total: " + flagTotal + ", Exist: " + flagExist);
}
}
}
}
5.支持远程登录
关键代码:
String server = "192.168.1.1";
String domain = "TEST";
String user = "Administrator";
String password = "Password@123";
SecureString securePwd = new SecureString();
foreach (char c in password)
{
securePwd.AppendChar(c);
}
var session = new EventLogSession(server, domain, user, securePwd, SessionAuthentication.Negotiate);
将以上代码整合,得出最终代码,代码已上传至github,地址如下:
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/SharpGetUserLoginIPRPC.cs
代码支持以下功能:
- 可使用csc.exe进行编译,支持3.5和4.0
- 支持本地和远程日志解析,远程日志解析使用RPC方式
- 支持判断条件,可筛选指定日期
- 自动提取信息:EventRecordID、TimeCreated、UserSid、DomainName、UserName和IpAddress
0x03 通过WMI实现
1.wmi语法测试
查询语法和属性名称可以借助wbemtest进行研究,关于wbemtest的用法可参考之前的文章《渗透基础——WMIC的使用》
查询Security
日志需要管理员权限运行wbemtest
点击Query...
查询所有Security
日志:
Select * from Win32_NTLogEvent Where Logfile = 'Security'
查询Eventid=4672
的所有日志:
Select * from Win32_NTLogEvent Where Logfile = 'Security' AND EventCode = 4624
查询EventRecordID=113438
的日志:
Select * from Win32_NTLogEvent Where Logfile = 'Security' AND RecordNumber = 113438
结合以上信息,我们不难写出wmic的查询命令:
wmic /namespace:\\root\cimv2 path win32_ntlogevent where "Logfile='Security' AND RecordNumber = 113438"
筛选出我们需要的信息:
wmic /namespace:\\root\cimv2 path win32_ntlogevent where "Logfile='Security' AND EventCode = 4624" get RecordNumber,TimeGenerated,Message
RecordNumber
对应为EventRecordID,TimeGenerated
为日志创建时间,Message
中需要筛选出以下内容:
- Security ID
- Account Domain
- Account Name
- Source Network Address
筛选功能我们可以通过批处理或者Powershell实现,但是考虑到兼容性和后续利用接口的统一,这里同样选择C Sharp实现
2.输出RecordNumber=131
的日志内容
实现代码:
using System;
using System.Management;
namespace Test2
{
class Program
{
static void Main(string[] args)
{
String queryString = "SELECT * FROM Win32_NTLogEvent Where Logfile = 'Security' AND RecordNumber = 131";
ManagementScope s = new ManagementScope("root\\CIMV2");
SelectQuery q = new SelectQuery(queryString);
ManagementObjectSearcher mos = new ManagementObjectSearcher(s, q);
foreach (ManagementObject o in mos.Get())
{
PropertyDataCollection searcherProperties = o.Properties;
foreach (PropertyData sp in searcherProperties)
{
Console.WriteLine("Name = {0, -20}, Value = {1, -20}", sp.Name, sp.Value);
}
}
}
}
}
从输出结果中发现,同wmic的命令执行结果相同:
RecordNumber
对应为EventRecordID,TimeGenerated
为日志创建时间,Message
中需要筛选出以下内容:
- Security ID
- Account Domain
- Account Name
- Source Network Address
3.筛选判断条件
为了筛选出有效登录信息,这里对targetUserSid
和ipAddress
的长度做了判断,targetUserSid
长度需要大于9,ipAddress
长度需要大于8
关键代码:
String Message = o.GetPropertyValue("Message").ToString();
int pos1 = Message.LastIndexOf("Security ID");
int pos2 = Message.LastIndexOf("Account Name");
int pos3 = Message.LastIndexOf("Account Domain");
int pos4 = Message.LastIndexOf("Logon ID");
int pos5 = Message.LastIndexOf("Source Network Address");
int pos6 = Message.LastIndexOf("Source Port");
int length1 = pos2 - pos1 - 16;
int length2 = pos4 - pos3 - 20;
int length3 = pos3 - pos2 - 17;
int length4 = pos6 - pos5 - 27;
if (length1 < 0 || length2 < 0 || length3 < 0 || length4 < 0)
continue;
String targetUserSid = Message.Substring(pos1+14, length1);
String targetDomainName = Message.Substring(pos3 + 17, length2);
String targetUserName = Message.Substring(pos2 + 15, length3);
String ipAddress = Message.Substring(pos5 + 24, length4);
if (targetUserSid.Length > 9 && ipAddress.Length > 8)
{
Console.WriteLine("[+] EventRecordID: " + o.GetPropertyValue("RecordNumber"));
Console.WriteLine(" TimeCreated : " + o.GetPropertyValue("TimeGenerated"));
Console.WriteLine(" UserSid: " + targetUserSid);
Console.WriteLine(" DomainName: " + targetDomainName);
Console.WriteLine(" UserName: " + targetUserName);
Console.WriteLine(" IpAddress: " + ipAddress);
}
4.支持筛选指定时间内的日志
可以通过修改搜索条件实现,关键代码:
String queryString = "SELECT * FROM Win32_NTLogEvent Where Logfile = 'Security' AND EventCode = 4624 AND TimeGenerated>=20210526 AND TimeGenerated<=20220426";
至此,通过WMI解析本地登录日志的实现代码如下:
using System;
using System.Management;
namespace Test2
{
class Program
{
static void Main(string[] args)
{
String queryString = "SELECT * FROM Win32_NTLogEvent Where Logfile = 'Security' AND EventCode = 4624 AND TimeGenerated>=20210526 AND TimeGenerated<=20220426";
ManagementScope s = new ManagementScope("root\\CIMV2");
SelectQuery q = new SelectQuery(queryString);
ManagementObjectSearcher mos = new ManagementObjectSearcher(s, q);
int flagTotal = 0;
int flagExist = 0;
foreach (ManagementObject o in mos.Get())
{
flagTotal++;
String Message = o.GetPropertyValue("Message").ToString();
int pos1 = Message.LastIndexOf("Security ID");
int pos2 = Message.LastIndexOf("Account Name");
int pos3 = Message.LastIndexOf("Account Domain");
int pos4 = Message.LastIndexOf("Logon ID");
int pos5 = Message.LastIndexOf("Source Network Address");
int pos6 = Message.LastIndexOf("Source Port");
int length1 = pos2 - pos1 - 16;
int length2 = pos4 - pos3 - 20;
int length3 = pos3 - pos2 - 17;
int length4 = pos6 - pos5 - 27;
if (length1 < 0 || length2 < 0 || length3 < 0 || length4 < 0)
continue;
String targetUserSid = Message.Substring(pos1+14, length1);
String targetDomainName = Message.Substring(pos3 + 17, length2);
String targetUserName = Message.Substring(pos2 + 15, length3);
String ipAddress = Message.Substring(pos5 + 24, length4);
{
Console.WriteLine("[+] EventRecordID: " + o.GetPropertyValue("RecordNumber"));
Console.WriteLine(" TimeCreated : " + o.GetPropertyValue("TimeGenerated"));
Console.WriteLine(" UserSid: " + targetUserSid);
Console.WriteLine(" DomainName: " + targetDomainName);
Console.WriteLine(" UserName: " + targetUserName);
Console.WriteLine(" IpAddress: " + ipAddress);
flagExist++;
}
}
Console.WriteLine("Total: " + flagTotal + ", Exist: " + flagExist);
}
}
}
5.支持远程登录
关键代码:
var opt = new ConnectionOptions(); ;
opt.Username = "TEST\\Administrator";
opt.Password = "Password@123";
ManagementScope s = new ManagementScope("\\\\192.168.1.1\\root\\CIMV2", opt);
将以上代码整合,得出最终代码,代码已上传至github,地址如下:
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/SharpGetUserLoginIPWMI.cs
代码支持以下功能:
- 可使用csc.exe进行编译,支持3.5和4.0
- 支持本地和远程日志解析,远程日志解析使用WMI方式
- 支持判断条件,可筛选指定日期
- 自动提取信息:EventRecordID、TimeCreated、UserSid、DomainName、UserName和IpAddress
0x04 小结
本文介绍了获得域用户登录信息的实现细节,开源两个工具SharpGetUserLoginIPRPC.cs和SharpGetUserLoginIPWMI.cs,在通信效率上,RPC要快于WMI。