Add openvixdisklib as an open NBD replacement for VMware VDDK.
VDDK is no longer publicly distributed, so this library reverse-engineers the vSphere NFC path and exposes ConnectEx, Open, Read, and Write without the proprietary SDK. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# 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 the Python
|
||||
NFC client.
|
||||
|
||||
## 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 |
|
||||
| `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`. The Python client in
|
||||
`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 a production VDDK replacement. Keep it out of
|
||||
the library path used by `openvixdisklib/nfc_auth.py`.
|
||||
Reference in New Issue
Block a user