diff --git a/docs/reverse-engineering/grpc-tag-query-odata.md b/docs/reverse-engineering/grpc-tag-query-odata.md index 26c18db..b86a97e 100644 --- a/docs/reverse-engineering/grpc-tag-query-odata.md +++ b/docs/reverse-engineering/grpc-tag-query-odata.md @@ -51,3 +51,22 @@ discipline: no guessed wire bytes). Probe helpers live in `Grpc/HistorianGrpcTagClient` (`ProbeStartTagQuery`, `ProbeTagQuerySequence`) and the gated `StartTagQuery_OverGrpc_AcceptsODataFilter` test pins the StartTagQuery+OData result. + +### Native-RE attempt (2026-06-21) — needs Ghidra/IDA + +The QueryTag packet-id is built in native code (the C++ HCAL inside the **mixed-mode** +`aahClientManaged.dll`). Lightweight tooling was exhausted and is insufficient: +- **ilspycmd** cannot decompile or even list the mixed-mode assembly (throws). +- **capstone** byte-search: the StartTagQuery marker `0x6751` is **not** a plain `mov` immediate in + `.text` (no `66 C7 … 51 67` and no clean `B8 51 67 00 00`); the three `51 67 00 00` hits in `.text` + are coincidental jump-table data (disassembly around them is garbage). The constant lives in the + `.rdata` pool (which holds the `51 67` bytes) and is loaded RIP-relative — so the serializer can't be + found by immediate-scanning without cross-reference analysis. +- Managed metadata (`ArchestrA.CloudHistorian.Contract`) gives the field semantics — + `QueryTagRequest{SessionId, QueryHandle:uint, QueryType:ushort, StartIndex:uint, TagCount:uint}`, + DataContract code `QTLQ` (StartTagQuery = `TLQR`) — but **not** the binary packet-id/framing. + +Finishing QueryTag therefore requires **Ghidra/IDA** (decompiler + xref to the `.rdata` marker constant +to find the serializer and read the QueryTag packet-id + byte layout), or the **live capture** option +(IL-rewrite `Archestra.Historian.GrpcClient.QueryTag` to log `requestBuffer` while a real 2023 R2 +client browses). StartTagQuery (the hard OData part) remains cracked and live-verified.