146 lines
6.1 KiB
Markdown
146 lines
6.1 KiB
Markdown
# SSL hook for VDDK protocol capture
|
||
|
||
VDDK’s NBD path is TLS end to end: SOAP to vCenter on 443, then authd/NFC
|
||
to ESXi on 902. Packet captures on those ports are ciphertext, so they
|
||
cannot show command names, tickets, or NFC frames.
|
||
|
||
This project used a small `LD_PRELOAD` library (`sslhook.c`, built to
|
||
`sslhook.so`) to log OpenSSL plaintext while a ctypes wrapper ran
|
||
`VixDiskLib_ConnectEx` / `VixDiskLib_Open`. The authd sequence in
|
||
`docs/nfc_auth.md` was recovered from that log, not from VDDK source.
|
||
|
||
The hook is a reverse-engineering aid. It is not part of OpenVixDiskLib.
|
||
|
||
## Why not tcpdump or VDDK logs
|
||
|
||
| Approach | What it shows | Gap |
|
||
| -------------------------------- | -------------------------------------------------- | ------------------------------------------------ |
|
||
| tcpdump on 443 / 902 | TLS records | No SOAP bodies, no authd lines, no NFC frames |
|
||
| `strace` on `write` / `send*` | Plaintext NFC **after** PROXY (`useSSL=0`) | TLS still opaque; `-s` truncates large extras |
|
||
| `vixDiskLib.nfc.LogLevel=4` | Function names, `opId` / `type` / `size` | Not the bytes on the wire |
|
||
| Strings in `libvixDiskLib.so` | Command tokens (`SESSION`, `PROXY`, `BANNER`) | Not order, spacing, or replies |
|
||
| SSL hook on `SSL_write`/`read` | Exact buffers before encrypt / after decrypt | Must split connections and reassemble 1-byte I/O |
|
||
|
||
VDDK logs were still useful to *name* AIO message types after the hex
|
||
dump showed `type` and `size`. The hook supplied the actual framing.
|
||
|
||
## How `LD_PRELOAD` interposition works
|
||
|
||
The hook exports `SSL_write` and `SSL_read` with OpenSSL’s signatures.
|
||
When the process starts with `LD_PRELOAD=/path/sslhook.so`, the dynamic
|
||
linker binds VDDK’s calls to those symbols instead of `libssl`.
|
||
|
||
Each wrapper:
|
||
|
||
1. Resolves the real OpenSSL function with `dlsym(RTLD_NEXT, ...)`.
|
||
2. Logs the plaintext buffer.
|
||
3. Calls the real function so the session is unchanged.
|
||
|
||
```
|
||
VixDiskLib --> SSL_write (hook) --> log hex --> SSL_write (libssl)
|
||
VixDiskLib <-- SSL_read (hook) <-- log hex <-- SSL_read (libssl)
|
||
```
|
||
|
||
`SSL_write` logs **before** encryption. `SSL_read` calls OpenSSL first,
|
||
then logs `n` decrypted bytes when `n > 0`.
|
||
|
||
## Implementation notes
|
||
|
||
The working copy lived under `/tmp` during capture (`/tmp/sslhook.c`).
|
||
Behavior that mattered for parsing:
|
||
|
||
- Log path from `SSLHOOK_LOG`, default `/tmp/sslhook-open.log`.
|
||
- Unbuffered writes (`_IONBF`) so a crash still leaves a complete file.
|
||
- Each record tagged with the `SSL *` pointer so vCenter HTTPS and
|
||
ESXi:902 are separable. An earlier version omitted the pointer and
|
||
mixed both streams into one timeline.
|
||
- Payload stored as hex, not mixed ASCII, so binary NFC frames stay
|
||
unambiguous.
|
||
|
||
Record layout:
|
||
|
||
```
|
||
==== W 0x7f8a1234 46 ====
|
||
53455353494f4e2035326364656263352d...0d0a
|
||
```
|
||
|
||
| Field | Meaning |
|
||
| ------- | ---------------------------------------------------- |
|
||
| `W`/`R` | Write (plaintext to encrypt) or read (decrypted) |
|
||
| `%p` | `SSL *` for this socket |
|
||
| length | Byte count of this OpenSSL call |
|
||
| hex | Buffer contents |
|
||
|
||
`SSL_read` is often **one byte per call**. A 220 banner is therefore
|
||
dozens of `R 1` records. Adjacent records with the same `SSL *` and
|
||
direction must be concatenated before parsing lines or NFC headers.
|
||
|
||
OpenSSL 3 also has `SSL_write_ex` / `SSL_read_ex`. This VDDK 8.0.2
|
||
build still used `SSL_write` / `SSL_read`, so those two symbols were
|
||
enough. If a later library switches APIs, the hook would need matching
|
||
wrappers.
|
||
|
||
## How it was used for authd
|
||
|
||
A minimal ctypes program loaded `libvixDiskLib.so`, called
|
||
`VixDiskLib_InitEx`, `ConnectEx` (UID to vCenter, `nbd`), and `Open` on
|
||
the lab VMDK. The process was started as:
|
||
|
||
```bash
|
||
export LD_LIBRARY_PATH=/home/ubuntu/workspace/vmware_nbd_tests/.vddk
|
||
export LD_PRELOAD=/tmp/sslhook.so
|
||
export SSLHOOK_LOG=/tmp/sslhook-open.log
|
||
python /tmp/vddk_open_trace.py
|
||
```
|
||
|
||
`LD_LIBRARY_PATH` is required so VDDK uses its bundled `libssl.so.3`.
|
||
`LD_PRELOAD` still interposes that copy.
|
||
|
||
After the run, records were grouped by `SSL *`. The ESXi connection is
|
||
the one whose writes contain `SESSION ` and `PROXY `. Concatenating
|
||
that stream after the TLS handshake produced:
|
||
|
||
```
|
||
C -> SESSION <sessionId>\r\n
|
||
C -> BANNER \r\n
|
||
S -> 220 VMware Authentication Daemon Version 1.10: ...\r\n
|
||
C -> THUMBPRINT_SHA2 PlainText\r\n
|
||
S -> 200 <SHA-256 thumbprint>\r\n
|
||
C -> PROXY vpxa-nfc\r\n
|
||
S -> 200 Connect ha-nfc\r\n
|
||
```
|
||
|
||
The same log also showed the SOAP `NfcGetVmFiles` body on the vCenter
|
||
`SSL *` (`xmlns="urn:vim25"`, moref `nfcService`). That is how the
|
||
ticket call was identified as `NfcGetVmFiles` rather than guessing
|
||
from `libvim-types` strings alone.
|
||
|
||
Details that only the hex dump made obvious:
|
||
|
||
- `BANNER` includes a trailing space (`BANNER \r\n`).
|
||
- `THUMBPRINT_SHA2` argument is the literal `PlainText`, not the
|
||
ticket SHA-1 thumbprint.
|
||
- `SESSION` has no reply; waiting for a line after it looks like a hang.
|
||
- Ticket `sessionId` is the UUID string on the `SESSION` line.
|
||
|
||
Those facts are written up in `docs/nfc_auth.md`. OpenVixDiskLib
|
||
(`openvixdisklib/nfc_auth.py`) replays this sequence; it does not use
|
||
the hook.
|
||
|
||
After `200 Connect`, NFC is **not** on `SSL_write`. VDDK uses
|
||
`write`/`read` on `SSL_get_fd` (`useSSL=0`). A later hook that also
|
||
interposed those syscalls, filtered to peer port 902, recovered the
|
||
264-byte handshake and AIO frames in `docs/nfc_open.md`. TLS record
|
||
bytes (`16 03` / `17 03`) on that fd are the authd phase and must be
|
||
skipped.
|
||
|
||
## Limits
|
||
|
||
- The hook sees every OpenSSL client in the process (VDDK and, if the
|
||
same interpreter is used, anything else linked to OpenSSL). Filter by
|
||
`SSL *`.
|
||
- It does not decode TLS handshakes, certificates, or SOAP envelopes;
|
||
that is done offline on the hex log.
|
||
- It must not ship in OpenVixDiskLib. Keep it out of
|
||
the library path used by `openvixdisklib/nfc_auth.py`.
|