2026软件系统安全赛区域线下赛

pth_attack

题目描述

捕获到公司内网有横向移动的攻击流量,请分析攻击者做了什么。(提交dart{}内的内容即可)

附件

两个流量包:
1-pth.pcapng
2-rdp.pcapng

题解

第一个流量包主要是winrm流量和smb流量,第二个主要是rdp流量。

1-pth

可以看到有大量的smb请求,判断是在爆破账号的密码。
1

首先找到爆破成功的包

可以看到在3295已经开始加密传输了,在前面几个包可以看到使用的是administrator。

2

在3604看到登录了admin的账号加密传输,说明这里爆破成功了两个账号。

3

提取NTLMv2hash

NTLMv2的格式为:

Username::Domain:Serverchallenge:Ntproofstring:Modifiedntlmv2response

使用工具NTLMRawUnHide提取,但是这里domian提取不出来,需要手动去提取。
wireshark过滤ntlmssp,可以看到domain为pc,然后这里是要在http流中找到ServerChallenge,NTProofString,ModifiedNTLMv2Response。

administrator::pc:cd0a6722277096c9:3fa965e4d9af9a92bde5cefcdd309acb:010100000000000022a2d32cbc72dc01ff545caf96411c670000000002000a0044004500310041005900010004005000430004001200640065003100610079002e0063006f006d0003001800500043002e00640065003100610079002e0063006f006d0005001200640065003100610079002e0063006f006d000700080022a2d32cbc72dc010600040002000000080030003000000000000000000000000030000026544cc05c735b21ae876ab6adeaf35030fb649315896d1d685326c99ddb5f6b0a001000000000000000000000000000000000000900220048005400540050002f00310030002e00310030002e00310030002e00320030003100000000000000000000000000

4

5

爆破出密码:pass@word1

6

解密winrm流量

使用:https://github.com/h4sh5/decrypt-winrm
但是这个脚本遇到错误帧会崩溃,改一下脚本。

点击查看代码
#!/usr/bin/env python3

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import argparse
import base64
import hashlib
import hmac
import os
import pyshark
import binascii
import sys
import struct
import xml.dom.minidom
from Crypto.Hash import MD4

from cryptography.hazmat.primitives.ciphers import (
    algorithms,
    Cipher,
)

from cryptography.hazmat.backends import (
    default_backend,
)

try:
    import argcomplete
except ImportError:
    argcomplete = None


class SecurityContext:

    def __init__(self, port, nt_hash):
        self.port = port
        self.tokens = []
        self.nt_hash = nt_hash
        self.complete = False

        self.key_exch = False
        self.session_key = None
        self.sign_key_initiate = None
        self.sign_key_accept = None
        self.seal_handle_initiate = None
        self.seal_handle_accept = None

        self.__initiate_seq_no = 0
        self.__accept_seq_no = 0

    @property
    def _initiate_seq_no(self):
        val = self.__initiate_seq_no
        self.__initiate_seq_no += 1
        return val

    @property
    def _accept_seq_no(self):
        val = self.__accept_seq_no
        self.__accept_seq_no += 1
        return val

    def add_token(self, token):
        self.tokens.append(token)

        if token.startswith(b"NTLMSSP\x00\x03"):
            # Extract the info required to build the session key
            nt_challenge = self._get_auth_field(20, token)
            b_domain = self._get_auth_field(28, token) or b""
            b_username = self._get_auth_field(36, token) or b""
            encrypted_random_session_key = self._get_auth_field(52, token)
            flags = struct.unpack("<I", token[60:64])[0]

            encoding = 'utf-16-le' if flags & 0x00000001 else 'windows-1252'
            domain = b_domain.decode(encoding)
            username = b_username.decode(encoding)

            # Derive the session key
            nt_proof_str = nt_challenge[:16]
            response_key_nt = hmac_md5(self.nt_hash, (username.upper() + domain).encode('utf-16-le'))
            key_exchange_key = hmac_md5(response_key_nt, nt_proof_str)
            self.key_exch = bool(flags & 0x40000000)

            if self.key_exch and (flags & (0x00000020 | 0x00000010)):
                self.session_key = rc4k(key_exchange_key, encrypted_random_session_key)

            else:
                self.session_key = key_exchange_key

            # Derive the signing and sealing keys
            self.sign_key_initiate = signkey(self.session_key, 'initiate')
            self.sign_key_accept = signkey(self.session_key, 'accept')
            self.seal_handle_initiate = rc4init(sealkey(self.session_key, 'initiate'))
            self.seal_handle_accept = rc4init(sealkey(self.session_key, 'accept'))
            self.complete = True

    def unwrap_initiate(self, data):
        print('unwrap_initiate',file=sys.stderr)
        return self._unwrap(self.seal_handle_initiate, self.sign_key_initiate, self._initiate_seq_no, data)

    def unwrap_accept(self, data):
        print('unwrap_accept',file=sys.stderr)
        return self._unwrap(self.seal_handle_accept, self.sign_key_accept, self._accept_seq_no, data)

    def _unwrap(self, handle, sign_key, seq_no, data):
        header = data[4:20]
        enc_data = data[20:]
        dec_data = handle.update(enc_data)

        b_seq_num = struct.pack("<I", seq_no)

        checksum = hmac_md5(sign_key, b_seq_num + dec_data)[:8]
        if self.key_exch:
            checksum = handle.update(checksum)
        actual_header = b"\x01\x00\x00\x00" + checksum + b_seq_num

        if header != actual_header:
            # raise Exception("Signature verification failed")
            print("Signature verification failed")
            # meh, continue

        return dec_data

    def _get_auth_field(self, offset, token):
        field_len = struct.unpack("<H", token[offset:offset + 2])[0]
        if field_len:
            field_offset = struct.unpack("<I", token[offset + 4:offset + 8])[0]
            return token[field_offset:field_offset + field_len]


def hmac_md5(key, data):
    return hmac.new(key, data, digestmod=hashlib.md5).digest()


def md4(m):
    h = MD4.new()
    h.update(m)
    return h.digest()


def md5(m):
    return hashlib.md5(m).digest()


def ntowfv1(password):
    return md4(password.encode('utf-16-le'))


def rc4init(k):
    arc4 = algorithms.ARC4(k)
    return Cipher(arc4, mode=None, backend=default_backend()).encryptor()


def rc4k(k, d):
    return rc4init(k).update(d)


def sealkey(session_key, usage):
    direction = b"client-to-server" if usage == 'initiate' else b"server-to-client"
    return md5(session_key + b"session key to %s sealing key magic constant\x00" % direction)


def signkey(session_key, usage):
    direction = b"client-to-server" if usage == 'initiate' else b"server-to-client"
    return md5(session_key + b"session key to %s signing key magic constant\x00" % direction)


def unpack_message(data):
    # parts = data.split(b'--Encrypted Boundary\r\n')
    parts = list(filter(None, parts))

    messages = []
    for i in range(0, len(parts), 2):
        header = parts[i].strip()
        payload = parts[i + 1]

        length = int(header.split(b"Length=")[1])

        # remove the end MIME block if it exists
        if payload.endswith(b"--Encrypted Boundary--\r\n"):
            payload = payload[:len(payload) - 24]

        wrapped_data = payload.replace(b"Content-Type: application/octet-stream\r\n", b"")

        messages.append((length, wrapped_data))

    return messages


def pretty_xml(xml_str):
    dom = xml.dom.minidom.parseString(xml_str)
    return dom.toprettyxml()


def main():
    """Main program entry point."""
    args = parse_args()

    if args.password:
        nt_hash = ntowfv1(args.password)
        print("[*] NT Hash: %s" % binascii.hexlify(nt_hash).decode())

    else:
        nt_hash = base64.b16decode(args.hash.upper())

    captures = pyshark.FileCapture(os.path.expanduser(os.path.expandvars(args.path)),
                                   display_filter='http and tcp.port == %d' % args.port)

    contexts = []
    for cap in captures:
        try:
            source_port = int(cap.tcp.srcport)
            unique_port = source_port if source_port != args.port else int(cap.tcp.dstport)

            auth_token = None
            if hasattr(cap.http, 'authorization'):
                b64_token = cap.http.authorization.split(' ')[1]
                auth_token = base64.b64decode(b64_token)

            elif hasattr(cap.http, 'www_authenticate'):
                parts = cap.http.www_authenticate.split(' ')
                if len(parts) > 1:
                    b64_token = parts[1]
                    try:
                        auth_token = base64.b64decode(b64_token)
                    except Exception:
                        continue
                else:
                    continue

            context = None
            if auth_token:
                if not auth_token.startswith(b"NTLMSSP\x00"):
                    continue

                if auth_token.startswith(b"NTLMSSP\x00\x01"):
                    context = SecurityContext(unique_port, nt_hash)
                    contexts.append(context)

                else:
                    context = [c for c in contexts if c.port == unique_port][-1]
                    if not context:
                        raise ValueError("Missing exisitng NTLM security context")

                context.add_token(auth_token)

            if hasattr(cap.http, 'file_data'):
                if not context:
                    context = next((c for c in contexts if c.port == unique_port), None)

                if not context:
                    print("No security context found for port %s, skipping frame %s" % (unique_port, cap.number), file=sys.stderr)
                    continue

                if not context.complete:
                    raise ValueError("Cannot decode message without completed context")

                # FIX: TCP 分段丢失时 (e.g. "TCP Previous segment not captured Continuation"),
                # pyshark 无法重组 MIME 层, cap.mime_multipart 属性不存在.
                # 跳过这些帧并告警, 避免整个脚本崩溃.
                # 注意: 跳过后 RC4 流状态和 seq_no 会与对端失步,
                # 同方向后续帧可能解密出垃圾数据 (但这是丢包的本质限制, 无法恢复).
                if not hasattr(cap, 'mime_multipart'):
                    print("No mime_multipart layer for frame %s (likely TCP reassembly failure), skipping"
                          % cap.number, file=sys.stderr)
                    continue

                # file_data = cap.http.file_data #.binary_value
                # messages = unpack_message(file_data)
                length = int(cap.mime_multipart.data_len)
                msgdata = binascii.unhexlify(cap.mime_multipart.data)
                messages = [(length, msgdata)]

                unwrap_func = context.unwrap_accept if source_port == args.port else context.unwrap_initiate

                dec_msgs = []
                for length, enc_data in messages:
                    msg = unwrap_func(enc_data)
                    # if len(msg) != length:
                        # raise ValueError("Message decryption failed")
                    # print(f'decrypted msg ({len(msg)}):',msg)
                    try:
                        dec_msgs.append(pretty_xml(msg.decode('utf-8')))
                    except Exception as e:
                        print("Exception: ", e)
                        dec_msgs.append("[bad message]")


                dec_msgs = "\n".join(dec_msgs)
                print("No: %s | Time: %s | Source: %s | Destination: %s\n%s\n"
                      % (cap.number, cap.sniff_time.isoformat(), cap.ip.src_host, cap.ip.dst_host, dec_msgs))

        except Exception as e:
            raise Exception("Failed to process frame: %s" % cap.number) from e


def parse_args():
    """Parse and return args."""
    parser = argparse.ArgumentParser(description='Parse network captures from WireShark and decrypts the WinRM '
                                                 'messages that were exchanged.')

    parser.add_argument('path',
                        type=str,
                        help='The path to the .pcapng file to decrypt.')

    parser.add_argument('--port',
                        dest='port',
                        default=5985,
                        type=int,
                        help='The port to scan for the WinRM HTTP packets (default: 5985).')

    secret = parser.add_mutually_exclusive_group()

    secret.add_argument('-p', '--password',
                        dest='password',
                        help='The password for the account that was used in the authentication.')

    secret.add_argument('-n', '--hash',
                        dest='hash',
                        help='The NT hash for the account that was used in the authentication.')

    if argcomplete:
        argcomplete.autocomplete(parser)

    return parser.parse_args()


if __name__ == '__main__':
    main()

python winrm_decrypt_fix.py -p pass@word1 1-pth.pcapng > winrm.txt

再使用脚本提取其中的命令

点击查看代码
import base64
import re
import sys

def extract_winrm_content(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # 按照数据包编号分割块
    packets = re.split(r'(No: \d+ \|)', content)
    
    print(f"{'='*20} WinRM 攻击痕迹提取 {'='*20}")
    
    for i in range(1, len(packets), 2):
        header = packets[i]
        body = packets[i+1]
        
        # 提取数据包编号
        packet_no = re.search(r'No: (\d+)', header).group(1)
        
        # 提取直接执行的命令行 (rsp:Command 或 rsp:CommandLine)
        # 这种通常是不带交互的初始命令
        cmd_direct = re.search(r'<rsp:Command>(.*?)</rsp:Command>', body)
        if not cmd_direct:
            cmd_direct = re.search(r'<rsp:CommandLine[^>]*>.*?<rsp:Command>(.*?)</rsp:Command>', body, re.S)
        
        if cmd_direct:
            cmd_text = cmd_direct.group(1).replace('&quot;', '"')
            print(f"\n[Packet {packet_no}] 执行初始命令:")
            print(f"  >>> {cmd_text}")

        # 取交互式输入
        stdins = re.findall(r'<rsp:Stream Name="stdin"[^>]*>(.*?)</rsp:Stream>', body)
        for b64_data in stdins:
            try:
                decoded = base64.b64decode(b64_data).decode('gbk', errors='ignore').strip()
                if decoded:
                    print(f"\n[Packet {packet_no}] 攻击者输入 (stdin):")
                    print(f"  >>> {decoded}")
            except:
                pass

        # 提取执行结果 (stdout)
        stdouts = re.findall(r'<rsp:Stream Name="stdout"[^>]*>(.*?)</rsp:Stream>', body)
        if stdouts:
            print(f"\n[Packet {packet_no}] 系统回显 (stdout):")
            combined_output = ""
            for b64_data in stdouts:
                try:
                    # 尝试用 GBK 解码
                    part = base64.b64decode(b64_data).decode('gbk', errors='ignore')
                    combined_output += part
                except:
                    pass
            print(combined_output.strip())

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python dump.py <winrm_log_file>")
        sys.exit(1)
    extract_winrm_content(sys.argv[1])

python extract.py winrm.txt > 1.txt

找到administrator的NTLM值:3d83254b53697355ef7498b535e7ab29

7

现在就可以使用administrator的NTLM值来解密smb3了。

https://github.com/iamdonu/SMB3-Decryption

python randomSessionKeyNTLM.py -u administrator --domain= -n 3d83254b53697355ef7498b535e7ab29 -p 4103e8d84572fa74f220ecc20be704c1 -k 7433d4ac87cdff2d38b2e8a5840b919d

8

9

Random SK: 3252507a61756f507132585748475953

python randomSessionKeyNTLM.py -u randomSessionKey.pyadministrator --domain= -n 3d83254b53697355ef7498b535e7ab29 -p 4103e8d84572fa74f220ecc20be704c1 -k 7433d4ac87cdff2d38b2e8a5840b919d

将sk导入sm2,这里还需要session ID,并倒序
session ID:5500000000480000

11

12

可以在svcctl找到解密的数据

%COMSPEC% /Q /c echo net user admin kPxQ1GT9zA9E /add ^> \\127.0.0.1\C$\__output 2^>^&1 > %TEMP%\execute.bat & %COMSPEC% /Q /c %TEMP%\execute.bat & del %TEMP%\execute.bat

可以看到admin的明文密码:kPxQ1GT9zA9E

13

导入NTLMSSP密码,wireshark会计算出smb3秘钥,或者重复上述操作生成一个admin的Random SK。

方法一:计算admin的sk

  • username:admin
  • NT HASH:kPxQ1GT9zA9E -> 235B1A6A91A08976DD1DE99FF24CDEA5
  • NTProofstr:7368589eef94d340237823caa7835c29
  • session key:a93341fd28b90248aa3cc3e072da4cc4
  • session ID:0x0000480000000065 -> 6500000000480000

得到random sk:4e6939527a31453673734f3665337337

导入smb2
导出smb对象可以看到两个证书

14

方法二:直接导入密码

15

将两个证书导出,pfx中有私钥,使用openssl导出,猜测密码为默认的mimikatz

openssl pkcs12 -in 1.pfx -out private.key -nodes

然后将私钥导入第二个流量包中。

16

导出rdp流量

17

可以发现有键盘流量

18

导出
cat 1.json | grep rdp.fastpath.scancode.keycode | awk -F ":" '{print $2 ","}'

解密:

点击查看代码
def map_keycode(key_code):
    """根据扫描码返回相应的字符或描述"""
    # 特殊键的映射
    special_keys = {
        0x00: 'None',              # No key
        0x01: 'Esc',               # Esc
        0x02: '1',                 # 1
        0x03: '2',                 # 2
        0x04: '3',                 # 3
        0x05: '4',                 # 4
        0x06: '5',                 # 5
        0x07: '6',                 # 6
        0x08: '7',                 # 7
        0x09: '8',                 # 8
        0x0A: '9',                 # 9
        0x0B: '0',                 # 0
        0x0C: '-',                 # -
        0x0D: '=',                 # =
        0x0E: 'Backspace',         # Backspace
        0x0F: 'Tab',               # Tab
        0x10: 'Q',                 # Q
        0x11: 'W',                 # W
        0x12: 'E',                 # E
        0x13: 'R',                 # R
        0x14: 'T',                 # T
        0x15: 'Y',                 # Y
        0x16: 'U',                 # U
        0x17: 'I',                 # I
        0x18: 'O',                 # O
        0x19: 'P',                 # P
        0x1A: '[',                 # [
        0x1B: ']',                 # ]
        0x1C: 'Enter',             # Enter
        0x1D: 'Left Ctrl',         # Left Control
        0x1E: 'A',                 # A
        0x1F: 'S',                 # S
        0x20: 'D',                 # D
        0x21: 'F',                 # F
        0x22: 'G',                 # G
        0x23: 'H',                 # H
        0x24: 'J',                 # J
        0x25: 'K',                 # K
        0x26: 'L',                 # L
        0x27: ';',                 # ;
        0x28: "'",                 # '
        0x29: 'Grave',             # `
        0x2A: 'Left Shift',        # Left Shift
        0x2B: 'Backslash',         # \
        0x2C: 'Z',                 # Z
        0x2D: 'X',                 # X
        0x2E: 'C',                 # C
        0x2F: 'V',                 # V
        0x30: 'B',                 # B
        0x31: 'N',                 # N
        0x32: 'M',                 # M
        0x33: ',',                 # ,
        0x34: '.',                 # .
        0x35: '/',                 # /
        0x36: 'Right Shift',       # Right Shift
        0x37: 'Keypad *',          # Keypad *
        0x38: 'Alt',               # Alt
        0x39: 'Space',             # Space
        0x3A: 'Caps Lock',         # Caps Lock
        0x3B: 'F1',                # F1
        0x3C: 'F2',                # F2
        0x3D: 'F3',                # F3
        0x3E: 'F4',                # F4
        0x3F: 'F5',                # F5
        0x40: 'F6',                # F6
        0x41: 'F7',                # F7
        0x42: 'F8',                # F8
        0x43: 'F9',                # F9
        0x44: 'F10',               # F10
        0x45: 'F11',               # F11
        0x46: 'F12',               # F12
        0x47: 'Num Lock',          # Num Lock
        0x48: 'Keypad 7',          # Keypad 7
        0x49: 'Keypad 8',          # Keypad 8
        0x4A: 'Keypad 9',          # Keypad 9
        0x4B: 'Keypad -',          # Keypad -
        0x4C: 'Keypad 4',          # Keypad 4
        0x4D: 'Keypad 5',          # Keypad 5
        0x4E: 'Keypad 6',          # Keypad 6
        0x4F: 'Keypad +',          # Keypad +
        0x50: 'Keypad 1',          # Keypad 1
        0x51: 'Keypad 2',          # Keypad 2
        0x52: 'Keypad 3',          # Keypad 3
        0x53: 'Keypad 0',          # Keypad 0
        0x54: 'Keypad .',          # Keypad .
        0x5B: 'Left Win',          # Left Windows
        0x5C: 'Right Win',         # Right Windows
        0x5D: 'Menu',              # Menu
        0x5E: 'Right Ctrl',        # Right Control
        0x5F: 'Right Alt',         # Right Alt
    }

    return special_keys.get(key_code, f"Unknown key code: {key_code}")

def process_keyboard_data(data):
    """处理键盘输入数据,返回对应的按键描述"""
    output = []
    for entry in data:
        # 分割扫描码并转换为整数
        key_codes = entry.split(',')
        mapped_keys = [map_keycode(int(code, 16)) for code in key_codes]
        output.append(' '.join(mapped_keys))
    return output

# 示例键盘输入数据
keyboard_data = [
"0x0f"
,"0x2a"
,"0x36"
,"0x1d"
,"0x1d"
,"0x0f"
,"0x38"
,"0x0f"
,"0x38"
,"0x0f"
,"0x0f"
,"0x2a"
,"0x36"
,"0x1d"
,"0x1d"
,"0x0f"
,"0x38"
,"0x0f"
,"0x38"
,"0x0f"
,"0x0f"
,"0x2a"
,"0x36"
,"0x1d"
,"0x1d"
,"0x0f"
,"0x38"
,"0x0f"
,"0x38"
,"0x0f"
,"0x0f"
,"0x2a"
,"0x36"
,"0x1d"
,"0x1d"
,"0x0f"
,"0x38"
,"0x0f"
,"0x38"
,"0x0f"
,"0x23"
,"0x12"
,"0x23"
,"0x12"
,"0x13"
,"0x12"
,"0x13"
,"0x12"
,"0x39"
,"0x39"
,"0x17"
,"0x1f"
,"0x17"
,"0x39"
,"0x1f"
,"0x39"
,"0x21"
,"0x26"
,"0x21"
,"0x1e"
,"0x26"
,"0x1e"
,"0x22"
,"0x22"
,"0x1c"
,"0x1c"
,"0x20"
,"0x1e"
,"0x20"
,"0x1e"
,"0x13"
,"0x13"
,"0x14"
,"0x14"
,"0x2a"
,"0x1a"
,"0x1a"
,"0x2a"
,"0x06"
,"0x06"
,"0x30"
,"0x30"
,"0x04"
,"0x04"
,"0x1e"
,"0x1e"
,"0x07"
,"0x07"
,"0x05"
,"0x05"
,"0x02"
,"0x02"
,"0x21"
,"0x21"
,"0x0c"
,"0x0c"
,"0x0a"
,"0x0a"
,"0x05"
,"0x05"
,"0x06"
,"0x06"
,"0x05"
,"0x05"
,"0x0c"
,"0x0c"
,"0x05"
,"0x05"
,"0x06"
,"0x06"
,"0x02"
,"0x02"
,"0x09"
,"0x09"
,"0x0c"
,"0x0c"
,"0x1e"
,"0x1e"
,"0x09"
,"0x09"
,"0x06"
,"0x06"
,"0x20"
,"0x20"
,"0x0c"
,"0x0c"
,"0x07"
,"0x07"
,"0x21"
,"0x21"
,"0x08"
,"0x08"
,"0x20"
,"0x20"
,"0x05"
,"0x05"
,"0x20"
,"0x20"
,"0x07"
,"0x07"
,"0x12"
,"0x12"
,"0x1e"
,"0x1e"
,"0x12"
,"0x12"
,"0x21"
,"0x21"
,"0x30"
,"0x30"
,"0x2a"
,"0x2a"
,"0x2a"
,"0x1b"
,"0x1b"
,"0x2a"
,"0x1c"
,"0x1c"
,"0x20"
,"0x18"
,"0x20"
,"0x18"
,"0x31"
,"0x31"
,"0x12"
,"0x12"
,"0x0f"
,"0x5b"
,"0x5c"
,"0x2a"
,"0x36"
,"0x1d"
,"0x1d"
,"0x0f"
,"0x38"
,"0x0f"
,"0x38"
,"0x0f"
]

# 处理每行数据
keyboard_output = process_keyboard_data(keyboard_data)

# 相邻重复通常是按下/弹起事件,去重后保留一次有效按键
deduped_output = []
for key in keyboard_output:
    if not deduped_output or deduped_output[-1] != key:
        deduped_output.append(key)

# 输出去重后拼接结果
joined_output = ' '.join(deduped_output)
print(joined_output)

output:
Tab Left Shift Right Shift Left Ctrl Tab Alt Tab Alt Tab Left Shift Right Shift Left Ctrl Tab Alt Tab Alt Tab Left Shift Right Shift Left Ctrl Tab Alt Tab Alt Tab Left Shift Right Shift Left Ctrl Tab Alt Tab Alt Tab H E H E R E R E Space I S I Space S Space F L F A L A G Enter D A D A R T Left Shift [ Left Shift 5 B 3 A 6 4 1 F - 9 4 5 4 - 4 5 1 8 - A 8 5 D - 6 F 7 D 4 D 6 E A E F B Left Shift ] Left Shift Enter D O D O N E Tab Left Win Right Win Left Shift Right Shift Left Ctrl Tab Alt Tab Alt Tab

19

或者也可以导出pdu,然后使用pyrdp。

posted @ 2026-04-23 17:31  LingGin  阅读(18)  评论(0)    收藏  举报