From c45f1a957bfe7b59333aeb503deeabcd33195764 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 23 Jun 2026 11:21:55 -0400 Subject: [PATCH] =?UTF-8?q?docs(grpc-events):=20token=20scheme=20fully=20R?= =?UTF-8?q?E'd=20via=20dnlib=20=E2=80=94=20aahCryptV2=20(MD5-keyed=20RC4?= =?UTF-8?q?=20+=20prefix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loaded dnlib in PowerShell (ILSpy crashes on the mixed-mode assembly) and scanned the IL to recover the entire v8 token construction: - ::CHistoryConnectionGrpc.GetClientKey drives the ECDH: ECDiffieHellmanCng {KeyDerivationFunction=Hash, HashAlgorithm=SHA256, KeySize=256} -> ExchangeKey -> CngKey.Import(serverPub, EccPublicBlob) -> DeriveKeyMaterial = SHA256(shared secret), the 32-byte client key. - aahClientCommon.CClientBase.ConfigureOpenConnection (the lone GetClientKey caller) builds the 26-byte token via HistorianCrypto.NRC4_V2.aahCryptV2 = a custom MD5-keyed RC4 stream cipher with a version prefix: * body/HashData = MD5 (verified by the round constants 0xd76aa478... + shifts 7/12/17/22) * prepare_key = RC4 KSA from a 16-byte MD5 key * enc_buffer = MD5 -> key, then rc4encrypt; enc prepends PrefixV2/InnerPrefixV2 (the constant 0x8e token marker) So token = prefix + RC4(plaintext, key=MD5(keyMaterial)), keyMaterial tied to the SHA256(ECDH secret) client key. 100% reproducible in pure managed code (RC4+MD5). Remaining (next cycle): read ConfigureOpenConnection's exact key/plaintext/prefix bytes, implement aahCryptV2 managed-side, set the v8 token, live-test. Frida CNG + dnlib are the RE path; nothing AVEVA is shipped. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC --- .../grpc-event-query-capture.md | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/reverse-engineering/grpc-event-query-capture.md b/docs/reverse-engineering/grpc-event-query-capture.md index 837f07f..95f021e 100644 --- a/docs/reverse-engineering/grpc-event-query-capture.md +++ b/docs/reverse-engineering/grpc-event-query-capture.md @@ -269,10 +269,36 @@ Hooked Windows CNG (`bcrypt.dll`/`ncrypt.dll`) while the native harness ran a re output). It is built in managed code between the `DeriveKeyMaterial` call and the openParameters assembly. -**Next step:** ILSpy cannot decompile the mixed-mode assembly (full-assembly and `` both crash, -exit 70). Use **dnlib** (IL-level, won't choke on the native parts) to dump the `` method that -references `ECDiffieHellmanCng.DeriveKeyMaterial` and read the post-derive token construction, then -implement it managed-side and re-test (non-destructive). +**dnlib IL extraction 2026-06-23 — the token scheme is fully reverse-engineered.** ILSpy can't +decompile the mixed-mode assembly (crashes), but loading `dnlib` in PowerShell and scanning the IL +recovered the whole construction: + +- **`::CHistoryConnectionGrpc.GetClientKey`** is the ECDH driver: `new ECDiffieHellmanCng()` + → `KeyDerivationFunction = Hash`, `HashAlgorithm = SHA256`, `KeySize = 256` → + `GrpcHistoryClient.ExchangeKey(strHandle, ourPubKey.ToByteArray(), out serverPub, out err)` → + `CngKey.Import(serverPub, CngKeyBlobFormat.EccPublicBlob)` → **`DeriveKeyMaterial`** = the 32-byte + client key = **`SHA256(ECDH shared secret)`**. (So our managed side should derive the key the same + way — `ECDiffieHellman` raw agreement then SHA256, or equivalently `DeriveKeyFromHash(..., SHA256)`.) +- **The 26-byte token is built by `aahClientCommon.CClientBase.ConfigureOpenConnection`** (the lone + caller of `GetClientKey`) using the **`HistorianCrypto.NRC4_V2.aahCryptV2`** scheme — a custom + **MD5-keyed RC4 stream cipher with a version prefix**: + - `aahCryptV2.body`/`HashData` = **MD5** (verified: the IL loads MD5 round constants `0xd76aa478`… + and rotates 7/12/17/22). + - `aahCryptV2.prepare_key` = standard **RC4 KSA** seeding the 256-byte S-box from a **16-byte (MD5)** + key (`std.array`). + - `aahCryptV2.enc_buffer` = `MD5(...)` → key, then **`rc4encrypt`** the body; `enc` prepends a + scheme **prefix** (`NRC4_V2.PrefixV2` / `InnerPrefixV2`) — the constant `0x8e` token marker. + - `from_GUID` keys the cipher from a GUID string. + +So the token = `prefix + RC4(plaintext, key = MD5(keyMaterial))`, where the key material ties back to +the `SHA256(ECDH secret)` client key. **This is 100% reproducible in pure managed code** (RC4 + MD5 +are ~40 lines; nothing AVEVA ships). + +**Remaining to finish (next cycle):** read `ConfigureOpenConnection`'s exact wiring (which value is +MD5'd for the RC4 key, what plaintext is encrypted, the exact prefix bytes — a little more dnlib IL), +implement `aahCryptV2` (RC4+MD5+prefix) managed-side, set the v8 token = that, and live-test +(non-destructive). The offline correlation data (one run's derived key + token + openParameters) is +captured under `artifacts/.../` to validate the managed reproduction before going live. **2 of 3 layers cleared** (key exchange + client key); the 3rd (token construction) is localized to a specific managed method, pending dnlib extraction. ExchangeKey + the v8 serializer are committed; the