#!/usr/bin/env python3 import argparse import contextlib import io import json import re import struct import sys from pathlib import Path import pefile ROOT = Path(__file__).resolve().parent sys.path.insert(0, str(ROOT)) from pe_signature_finder import analyze_symbols, demangle_symbol, format_bytes # type: ignore # From the current CDP-Enabler project: the Chrome/Edge allocator used for the # factory object. Wildcards are emitted as "." in the regex. OPERATOR_NEW_REGEX = ( rb"\x40\x53\x48\x83\xEC\x20\x48\x8B\xD9\xEB." rb"\x48\x8B\xCB\xE8....\x85\xC0\x74.\x48\x8B\xCB" ) OPERATOR_NEW_SIGNATURE = "40 53 48 83 EC 20 48 8B D9 EB ?? 48 8B CB E8 ?? ?? ?? ?? 85 C0 74 ?? 48 8B CB" KNOWN_ENTRY1_SIGNATURES = { "msedge.dll": bytes.fromhex("48 89 D0 0F B7 51 08 48"), "chrome.dll": bytes.fromhex( "41 57 41 56 56 57 55 53 48 81 EC 88 00 00 00 48 89 D6 48 8B 05 07" ), } def get_section(pe, section_name: str): for section in pe.sections: name = section.Name.rstrip(b"\x00").decode("ascii", errors="ignore") if name == section_name: return section, section.get_data() raise RuntimeError(f"{pe.filename} has no {section_name} section") def find_unique_regex(pattern: bytes, data: bytes) -> int: matches = [m.start() for m in re.finditer(pattern, data, re.S)] if len(matches) != 1: raise RuntimeError(f"expected exactly one match, got {len(matches)}") return matches[0] def format_c_initializer(data: bytes) -> str: return ", ".join(f"0x{b:02X}" for b in data) def choose_tcp_factory_entry1(pe_path: Path, pdb_path: Path): with contextlib.redirect_stderr(io.StringIO()): results = list( analyze_symbols( str(pe_path), str(pdb_path), "*CreateForHttpServer*", min_sig=8, max_sig=64, ) ) for result in results: demangled = demangle_symbol(result.symbol.name) if "TCPServerSocketFactory::CreateForHttpServer()" in demangled: return result, demangled raise RuntimeError("TCPServerSocketFactory::CreateForHttpServer was not found in the PDB") def main(): parser = argparse.ArgumentParser( description="Find the inputs needed to eventually call StartRemoteDebuggingServer." ) parser.add_argument("pe", type=Path, help="Path to chrome.dll/msedge.dll") parser.add_argument("pdb", type=Path, nargs="?", help="Optional path to matching PDB") args = parser.parse_args() pe = pefile.PE(str(args.pe)) image_base = pe.OPTIONAL_HEADER.ImageBase text_section, text_data = get_section(pe, ".text") rdata_section, rdata_data = get_section(pe, ".rdata") operator_new_off = find_unique_regex(OPERATOR_NEW_REGEX, text_data) operator_new_rva = text_section.VirtualAddress + operator_new_off entry1_sig = KNOWN_ENTRY1_SIGNATURES.get(args.pe.name.lower()) if entry1_sig is None: raise RuntimeError(f"no built-in CreateForHttpServer signature for {args.pe.name}") entry1_off = find_unique_regex(re.escape(entry1_sig), text_data) entry1_sig_rva = text_section.VirtualAddress + entry1_off entry1_rva = entry1_sig_rva entry1_va = image_base + entry1_rva vtable_candidates = [] for offset in range(0, len(rdata_data) - 16 + 1, 8): q0, q1 = struct.unpack_from(" wrapper", "rdx": "empty base::FilePath for active_port_output_directory", "r8": "empty base::FilePath for debug_frontend_dir", "r9d": "RemoteDebuggingServerMode", }, } if args.pdb: entry1_result, entry1_demangled = choose_tcp_factory_entry1(args.pe, args.pdb) output["pdb"] = str(args.pdb) output["tcp_server_socket_factory"]["create_for_http_server"]["symbol"] = entry1_demangled output["tcp_server_socket_factory"]["create_for_http_server"]["pdb_rva"] = f"0x{entry1_result.symbol.rva:08X}" output["tcp_server_socket_factory"]["create_for_http_server"]["pdb_vtable_refs"] = [ f"0x{ref:08X}" for ref in entry1_result.vtable_refs ] output["tcp_server_socket_factory"]["create_for_http_server"]["signature_matches_pdb_rva"] = ( entry1_sig_rva == entry1_result.symbol.rva ) print(json.dumps(output, indent=2)) if __name__ == "__main__": main()