乐于分享
好东西不私藏

2026软件安全赛华东赛区线下赛-pth_attcak题解wp

2026软件安全赛华东赛区线下赛-pth_attcak题解wp

分析1-pth.pcapng流量包,前面是smb的账号密码破解,然后是winrm流量,最后是加密的smb流量
首先解密winrm流量,提取hash,按照username::domain:NTLM Server Challenge:NTproofstr:ntlmv2response格式
administrator::pc:cd0a6722277096c9:3fa965e4d9af9a92bde5cefcdd309acb:010100000000000022a2d32cbc72dc01ff545caf96411c670000000002000a0044004500310041005900010004005000430004001200640065003100610079002e0063006f006d0003001800500043002e00640065003100610079002e0063006f006d0005001200640065003100610079002e0063006f006d000700080022a2d32cbc72dc010600040002000000080030003000000000000000000000000030000026544cc05c735b21ae876ab6adeaf35030fb649315896d1d685326c99ddb5f6b0a001000000000000000000000000000000000000900220048005400540050002f00310030002e00310030002e00310030002e00320030003100000000000000000000000000
使用hashcat爆破得到密码pass@word1
解密winrm流量,还原xml
使用的脚本:https://github.com/h4sh5/decrypt-winrm/blob/main/winrm_decrypt.py
python winrm_decrypt.py -p pass@word1 [流量包] > [导出xml文件]
分析xml文件,在第35个<rsp:Stream 流中,通过base64解码得到Administrator的NTLM hash
脚本计算出sessionkey和sessionid
import hashlibimport hmac#   ---------------------------------填入信息---------------------------------user = "Administrator"domain = ""ntlm_hash = "3d83254b53697355ef7498b535e7ab29"ntproofstr = "4103e8d84572fa74f220ecc20be704c1"encrypted_session_key = "7433d4ac87cdff2d38b2e8a5840b919d"sessionId = "0000480000000055"try:    from Cryptodome.Cipher import ARC4except Exception:    print("Warning: You need pycryptodomex")def generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey):    cipher = ARC4.new(keyExchangeKey)    sessionKey = cipher.encrypt(exportedSessionKey)    return sessionKeydef reverse_hex_bytes(hex_string):    return ''.join([hex_string[i:i+2for i in range(0len(hex_string), 2)][::-1])verbose = Trueuser_bytes = user.upper().encode('utf-16le')domain_bytes = domain.upper().encode('utf-16le')password = bytes.fromhex(ntlm_hash)h = hmac.new(password, digestmod=hashlib.md5)h.update(user_bytes + domain_bytes)respNTKey = h.digest()NTproofStr_bytes = bytes.fromhex(ntproofstr)h = hmac.new(respNTKey, digestmod=hashlib.md5)h.update(NTproofStr_bytes)KeyExchKey = h.digest()RsessKey = generateEncryptedSessionKey(    KeyExchKey,    bytes.fromhex(encrypted_session_key))sessionId = reverse_hex_bytes(sessionId)print("SessionId:", sessionId)print("SessionKey:", RsessKey.hex())
然后在wireshark中smb2填入sessionid和sessionkey
得到解密一部分的smb3流量,分析流量发现第二条执行了添加用户的命令
得到用户密码,wireshark中导入NTLMSSP密码
导出smb流量中的pfx证书
将pfx证书转换为pem格式,密码是mimikatz
openssl pkcs12 -in 1.pfx -nocerts -nodes -out 1.pem
打开第二个流量包,在TLS中添加pem证书
得到解密的RDP流量,筛选出RDP
导出特定分组结果为json
然后使用脚本还原键盘流量
python rdp_keyword.py [json文件]
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""RDP 键盘流量解密脚本用于解析从 Wireshark 导出的 JSON 格式 RDP 流量中的键盘事件"""import jsonimport sysfrom typing import ListDictOptional# 标准 PS/2 扫描码到 ASCII 字符的映射表# 基于 PS/2 扫描码 Set 1SCANCODE_MAP = {    # 基本按键    0x01'ESC',    0x02'1'0x03'2'0x04'3'0x05'4'0x06'5',    0x07'6'0x08'7'0x09'8'0x0a'9'0x0b'0',    0x0c'-'0x0d'='0x0e'BACKSPACE',    0x0f'TAB',    0x10'q'0x11'w'0x12'e'0x13'r'0x14't',    0x15'y'0x16'u'0x17'i'0x18'o'0x19'p',    0x1a'['0x1b']'0x1c'ENTER',    0x1d'LCTRL',    0x1e'a'0x1f's'0x20'd'0x21'f'0x22'g',    0x23'h'0x24'j'0x25'k'0x26'l'0x27';',    0x28"'"0x29'`',    0x2a'LSHIFT',    0x2b'\\'0x2c'z'0x2d'x'0x2e'c'0x2f'v',    0x30'b'0x31'n'0x32'm'0x33','0x34'.',    0x35'/'0x36'RSHIFT',    0x37'*'0x38'LALT',    0x39'SPACE',    0x3a'CAPSLOCK',    0x3b'F1'0x3c'F2'0x3d'F3'0x3e'F4'0x3f'F5',    0x40'F6'0x41'F7'0x42'F8'0x43'F9'0x44'F10',    0x45'NUMLOCK'0x46'SCROLLLOCK',    0x47'HOME'0x48'UP'0x49'PGUP',    0x4a'-'0x4b'LEFT'0x4c'CENTER'0x4d'RIGHT',    0x4e'+'0x4f'END'0x50'DOWN'0x51'PGDN',    0x52'INS'0x53'DEL',    0x57'F11'0x58'F12',    # 扩展扫描码 (extended = 1)    0x1c'ENTER',      # 小键盘 Enter    0x1d'RCTRL',      # 右 Ctrl    0x35'/',          # 小键盘 /    0x37'PRINTSCREEN'# Print Screen    0x38'RALT',       # 右 Alt (AltGr)    0x47'HOME',       # 小键盘 7    0x48'UP',         # 小键盘 8    0x49'PGUP',       # 小键盘 9    0x4b'LEFT',       # 小键盘 4    0x4d'RIGHT',      # 小键盘 6    0x4f'END',        # 小键盘 1    0x50'DOWN',       # 小键盘 2    0x51'PGDN',       # 小键盘 3    0x52'INS',        # 小键盘 0    0x53'DEL',        # 小键盘 .}class RDPKeyboardParser:    def __init__(self, json_file: str):        """        初始化 RDP 键盘解析器        Args:            json_file: Wireshark 导出的 JSON 文件路径        """        self.json_file = json_file        self.packets = []        self.key_events = []        self.shift_pressed = False        self.caps_lock_on = False    def load_packets(self) -> None:        """加载 JSON 文件中的数据包"""        try:            with open(self.json_file, 'r', encoding='utf-8'as f:                self.packets = json.load(f)            print(f"成功加载 {len(self.packets)} 个数据包")        except Exception as e:            print(f"加载 JSON 文件失败: {e}")            sys.exit(1)    def extract_scancode_events(self) -> None:        """从数据包中提取 RDP Scancode 事件"""        for packet in self.packets:            try:                # 获取 layers 部分                layers = packet.get('_source', {}).get('layers', {})                # 检查是否有 RDP 层                if 'rdp' not in layers:                    continue                rdp_layer = layers['rdp']                # 查找 Scancode 事件                if 'Scancode' in rdp_layer and isinstance(rdp_layer['Scancode'], dict):                    self._process_scancode(rdp_layer['Scancode'], layers)            except Exception as e:                # 跳过解析错误的数据包                print(f"Error processing packet: {e}")                continue    def _process_scancode(self, scancode_data: dict, layers: dict) -> None:        """        处理单个 Scancode 事件        Args:            scancode_data: Scancode 数据字典            layers: 完整的 layers 字典,用于获取时间戳        """        try:            # eventheader 是字符串,eventheader_tree 是字典            eventheader_tree = scancode_data.get('rdp.fastpath.eventheader_tree', {})            # 获取事件标志            is_release = eventheader_tree.get('rdp.fastpath.scancode.release'0) == 1            is_extended = eventheader_tree.get('rdp.fastpath.scancode.extended'0) == 1            # 获取按键码            keycode = scancode_data.get('rdp.fastpath.scancode.keycode''')            if isinstance(keycode, strand keycode.startswith('0x'):                keycode_int = int(keycode, 16)            else:                keycode_int = int(keycode) if keycode else 0            # 获取时间戳            frame = layers.get('frame', {})            timestamp = frame.get('frame.time''')            frame_number = frame.get('frame.number''')            # 只记录按键按下事件(不记录释放事件)            if not is_release:                key_event = {                    'timestamp': timestamp,                    'frame_number': frame_number,                    'keycode': keycode_int,                    'extended': is_extended,                    'raw_keycode': keycode                }                self.key_events.append(key_event)        except Exception as e:            # 跳过解析错误的事件            print(f"Error processing scancode event: {e}")            pass    def scancode_to_key(self, keycode: int, extended: bool = False) -> str:        """        将扫描码转换为按键名称        Args:            keycode: 扫描码 (十进制整数)            extended: 是否为扩展扫描码        Returns:            按键名称        """        # 基础按键映射        key = SCANCODE_MAP.get(keycode, f'UNKNOWN(0x{keycode:02x})')        # 处理 Shift 修饰键        if key == 'LSHIFT' or key == 'RSHIFT':            self.shift_pressed = True            return key        elif key == 'CAPSLOCK':            self.caps_lock_on = not self.caps_lock_on            return key        # 处理字母大小写        if len(key) == 1 and key.isalpha():            if self.shift_pressed != self.caps_lock_on:                key = key.upper()            else:                key = key.lower()        # 处理 Shift + 数字/符号        elif len(key) == 1 and self.shift_pressed:            shift_map = {                '1''!''2''@''3''#''4''$''5''%',                '6''^''7''&''8''*''9''(''0'')',                '-''_''=''+''[''{'']''}''\\''|',                ';'':'"'"'"''`''~'',''<''.''>',                '/''?'            }            key = shift_map.get(key, key)        return key    def reconstruct_keystrokes(self) -> str:        """        重建击键序列        Returns:            重构的击键字符串        """        result = []        for event in self.key_events:            keycode = event['keycode']            extended = event['extended']            # 重置修饰键状态(在下一个按键之前)            if keycode not in [0x2a0x360x1d0x380x3a]:  # Shift, Ctrl, Alt, CapsLock                self.shift_pressed = False            key = self.scancode_to_key(keycode, extended)            result.append(key)        return ' '.join(result)    def parse(self) -> None:        """执行完整的解析流程"""        print("=" * 60)        print("RDP 键盘流量解密工具")        print("=" * 60)        # 1. 加载数据包        print("\n[1/3] 加载数据包...")        self.load_packets()        # 2. 提取 Scancode 事件        print("\n[2/3] 提取键盘事件...")        self.extract_scancode_events()        print(f"找到 {len(self.key_events)} 个按键事件")        # 3. 重建击键序列        print("\n[3/3] 重建击键序列...")        keystrokes = self.reconstruct_keystrokes()        # 输出结果        print("\n" + "=" * 60)        print("解析结果:")        print("=" * 60)        print(f"\n按键总数: {len(self.key_events)}")        print(f"\n击键序列:\n{keystrokes}")        # 详细信息(前 50 个按键)        print(f"\n前 50 个按键详情:")        print("-" * 60)        for i, event in enumerate(self.key_events[:50]):            keycode = event['keycode']            extended = event['extended']            timestamp = event['timestamp']            key = self.scancode_to_key(keycode, extended)            print(f"[{i+1:3d}{timestamp} | Code: 0x{keycode:02x}({keycode:3d}) | Key: {key}")            # 重置修饰键状态            if keycode not in [0x2a0x360x1d0x380x3a]:                self.shift_pressed = False        if len(self.key_events) > 50:            print(f"... 还有 {len(self.key_events) - 50} 个按键事件")        return keystrokesdef main():    if len(sys.argv) != 2:        print("用法: python rdp_keyboard_decrypt.py <json_file>")        print("示例: python rdp_keyboard_decrypt.py 1.json")        sys.exit(1)    json_file = sys.argv[1]    # 检查文件是否存在    try:        with open(json_file, 'r'as f:            pass    except FileNotFoundError:        print(f"错误: 文件 '{json_file}' 不存在")        sys.exit(1)    except Exception as e:        print(f"错误: 无法读取文件 '{json_file}': {e}")        sys.exit(1)    # 创建解析器并执行解析    parser = RDPKeyboardParser(json_file)    keystrokes = parser.parse()    # 保存结果到文件    output_file = json_file.replace('.json''_keystrokes.txt')    try:        with open(output_file, 'w', encoding='utf-8'as f:            f.write(f"RDP 键盘流量解密结果\n")            f.write(f"=" * 60 + "\n\n")            f.write(f"按键总数: {len(parser.key_events)}\n\n")            f.write(f"击键序列:\n")            f.write(keystrokes + "\n\n")            f.write(f"详细按键事件:\n")            f.write("-" * 60 + "\n")            # 重新处理以获取详细列表            parser.shift_pressed = False            parser.caps_lock_on = False            for i, event in enumerate(parser.key_events):                keycode = event['keycode']                extended = event['extended']                timestamp = event['timestamp']                frame_num = event['frame_number']                key = parser.scancode_to_key(keycode, extended)                f.write(f"[{i+1:4d}] Frame {frame_num} | {timestamp} | Code: 0x{keycode:02x}({keycode:3d}) | Key: {key}\n")                if keycode not in [0x2a0x360x1d0x380x3a]:                    parser.shift_pressed = False        print(f"\n结果已保存到: {output_file}")    except Exception as e:        print(f"保存结果文件失败: {e}")if __name__ == '__main__':    main()
得到flag
dart{5b3a641f-9454-4518-a85d-6f7d4d6eaefb}