NetHook SDK
A lightweight client library for talking to a NetHook-hooked process over its named pipe — in Python 3 and C#. Connect to an injected process, enable hooks, stream captured traffic, and optionally intercept & rewrite it on the wire.
Overview
When the NetHook payload is injected into a target process it starts a
named-pipe server at \\.\pipe\nethook_<pid>. Everything
the target sends or receives through the hooked APIs (sockets, TLS, WinINet /
WinHTTP, file I/O…) is emitted over that pipe.
This SDK is the other end: a small client that connects to the pipe, turns on the hooks it cares about, and receives every captured packet. It is the same wire protocol the NetHook GUI speaks, distilled to a single drop-in file per language with no third-party dependencies.
What you get
- Connect by PID (or explicit pipe name)
- Enable any subset of hooks via a bitmask
- Stream
DATApackets (capture-only) - Intercept: forward, drop, or modify
- Detach (unhook & unload the DLL)
Requirements
- Windows only (named pipes + the payload).
- A process already injected with NetHook, so the pipe exists.
- Python 3.6+ — stdlib only (
ctypes). - .NET 6+ — BCL only (
System.IO.Pipes).
nethook_inject<arch>.exe for that. The
SDK connects to a pipe that already exists.Package layout
This release contains only what you need to build on top of the SDK. The C# binding ships as a compiled DLL (no C# source); the Python binding ships as a single module.
SdkRelease/
documentation.html All documentation (this single file)
csharp/
NetHook.Sdk.dll The library — reference this DLL (net8.0)
NetHook.Sdk.xml IntelliSense / XML docs for the DLL
python/
nethook.py The SDK — single module, standard library only
pyproject.toml Optional pip packaging
example/
python/ 01_capture_traffic.py, 02_intercept_edit.py
csharp/ 01-capture-traffic/, 02-intercept-edit/
Quick start
Both SDKs mirror the same shape. Given a hooked process with PID
1234:
Python capture everything
from nethook import NetHookClient, Func
with NetHookClient(1234) as nh:
nh.send_config(Func.ALL)
for ev in nh.events():
if ev.kind == "data":
print(ev.packet.summary())
C# capture everything
using NetHook.Sdk;
using var nh = new NetHookClient(1234);
await nh.ConnectAsync();
nh.Data += p => Console.WriteLine(p.Summary());
nh.SendConfig(Func.All);
nh.Run(); // blocks until the pipe closes
The pipe protocol
Every message is a fixed 20-byte header followed by a type-specific payload, all little-endian and packed. The client and server exchange these framed messages over the byte-mode pipe.
Header (20 bytes)
| Field | Type | Meaning |
|---|---|---|
magic | u32 | 0x314B484E ("NHK1"). Resync sentinel — a mismatch means the stream is corrupt. |
type | u32 | Message type (see table). |
id | u64 | Correlation id. For an INTERCEPT it is the ref echoed back in a DECISION; otherwise 0. |
length | u32 | Payload byte count that follows the header (may be 0). |
Conversation
\\.\pipe\nethook_<pid>.2. Server → HELLO (pid, arch, image path).
3. Client → CONFIG (which hooks; capture / intercept / block).
4. Server → DATA / LOG streamed as traffic happens.
or, in intercept mode:
Server → INTERCEPT (packet held) → Client → DECISION (forward / drop / modify).
5. Client → DETACH (optional) → Server unhooks, unloads, and the pipe closes (BYE/EOF).
HELLO the moment
you connect — no capture happens until you answer with a
CONFIG that enables at least one hook.Constants
| Concept | C# (NetHookClient) | Python (nethook) | Value |
|---|---|---|---|
| Frame magic | Magic | MAGIC | 0x314B484E ("NHK1") |
| Pipe prefix | PipePrefix | PIPE_PREFIX | \\.\pipe\nethook_ |
| Header size | HeaderSize | HEADER_SIZE | 20 |
Enums
| Enum | Members (value) |
|---|---|
Direction | Send (1), Recv (2) |
NhAction (C#) / Action (Python) | Forward (0), Drop (1), Modify (2) |
Func | 31 hook bits; All = 0x7FFFFFFF (see table) |
MsgType | see the message-type table |
nh_data payload layout (68 bytes, then payload)
The body of a DATA / INTERCEPT message; Python
struct "<QIIQIHH16s16sI".
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 8 | timestamp_ms | ms since epoch |
| 8 | 4 | func | which Func bit captured it |
| 12 | 4 | direction | 1 = Send, 2 = Recv |
| 16 | 8 | socket | SOCKET or SSL* handle (informational) |
| 24 | 4 | family | 2 = AF_INET, 23 = AF_INET6 |
| 28 | 2 | local_port | host order |
| 30 | 2 | remote_port | host order |
| 32 | 16 | local_addr | raw address bytes (4 for IPv4, 16 for IPv6) |
| 48 | 16 | remote_addr | raw address bytes |
| 64 | 4 | dlen | payload byte count that follows |
| 68 | dlen | data | the captured bytes |
CONFIG is 16 bytes ("<IBBxxII": enabled_funcs u32,
intercept u8, block u8, pad, max_capture u32, file_cap u32; a short
12-byte config without file_cap is still accepted);
DECISION is 16 bytes ("<QII": ref_id u64,
action u32, data_len u32) followed by replacement bytes when the action
is Modify.
Examples
The example/ folder ships two runnable samples per language,
each using the shipped SDK. Every sample needs a target that is already
injected with the NetHook payload.
| Example | Python | C# | What it shows |
|---|---|---|---|
| Capture traffic | example/python/01_capture_traffic.py |
example/csharp/01-capture-traffic/ |
Enable every hook and print a one-line summary of each packet. |
| Intercept & edit | example/python/02_intercept_edit.py |
example/csharp/02-intercept-edit/ |
Hold outbound packets and edit the wire, replacing nethook with netbook. |
# Python (module on the import path)
python example/python/01_capture_traffic.py 1234
python example/python/02_intercept_edit.py 1234
# C# (each project references csharp/NetHook.Sdk.dll)
dotnet run --project example/csharp/01-capture-traffic -- 1234
dotnet run --project example/csharp/02-intercept-edit -- 1234
Python Install & connect
Copy python/nethook.py next to your script — there is
nothing mandatory to pip install. It uses only the standard
library (ctypes, socket, struct). A
pyproject.toml is included if you prefer pip install
./python.
from nethook import NetHookClient, Func, Action
nh = NetHookClient(pid=1234) # or NetHookClient(pipe_name=r"\\.\pipe\nethook_1234")
nh.connect(timeout_ms=4000) # waits for the pipe to appear
# ... use nh ...
nh.close()
# Or as a context manager (auto connect on enter, close on exit):
with NetHookClient(1234) as nh:
nh.send_config(Func.SSL_WRITE | Func.SSL_READ)
for ev in nh.events():
...
Intercept example
with NetHookClient(1234) as nh:
nh.send_config(Func.ALL, intercept=True)
for ev in nh.events():
if ev.kind == "intercept":
pkt = ev.packet
if b"password" in pkt.data.lower():
nh.drop(pkt) # block it
else:
nh.forward(pkt) # or nh.forward(pkt, new_bytes)
Python API reference
class NetHookClient
| Member | Description |
|---|---|
NetHookClient(pid=None, pipe_name=None) | Construct for a PID or an explicit pipe name. |
connect(timeout_ms=4000) → self | Open the pipe, waiting for the server to appear. Raises OSError on timeout. |
close() | Close the pipe handle. Also runs on context-manager exit. |
connected | True while the handle is open. |
read_event() → Event | Block for one message, decoded into an Event. |
events() | Generator yielding Events until the pipe closes (bye/EOF). |
read_message() → (type, id, payload) | Low-level: one raw framed message. |
send_config(funcs, intercept=False, block=False, max_capture=0, file_cap=0) | Enable hooks & capture behaviour. funcs is a Func bitmask; file_cap is the hard per-call ReadFile/WriteFile ceiling. |
decision(ref_id, action, data=b"") | Answer an intercept by ref id with an Action. |
forward(packet, data=None) | Let an intercepted packet through, optionally rewriting it. |
drop(packet) | Drop an intercepted packet. |
detach() | Ask the DLL to unhook and unload itself. |
class Event
ev.kind is one of "hello", "data",
"intercept", "log", "bye". Populated
fields by kind:
| kind | Fields |
|---|---|
hello | pid, arch (32/64), image (exe path) |
data / intercept | packet — a Packet |
log | text |
bye | — (target detached / pipe closed) |
class Packet
See the packet field reference. Helpers:
packet.summary(), packet.ascii_preview(limit=64),
packet.api, packet.out, packet.source,
packet.dest. Raw bytes are packet.data.
Constants
Func (hook bits, see table),
Action.FORWARD / DROP / MODIFY,
Direction.SEND / RECV,
MsgType.*.
C# Install & connect
The C# SDK ships as a compiled library, csharp/NetHook.Sdk.dll
(namespace NetHook.Sdk, targets .NET 8, uses only
System.IO.Pipes). There is no C# source to build — reference
the DLL from your project, keeping NetHook.Sdk.xml beside it for
IntelliSense:
<ItemGroup>
<Reference Include="NetHook.Sdk">
<HintPath>path\to\csharp\NetHook.Sdk.dll</HintPath>
</Reference>
</ItemGroup>
Two ready-to-run example projects that already reference the DLL are in
example/csharp/. Basic usage:
using NetHook.Sdk;
using var nh = new NetHookClient(1234); // or new NetHookClient("nethook_1234", 1234)
await nh.ConnectAsync(timeoutMs: 4000); // or nh.Connect(4000) for the sync path
nh.Hello += (pid, arch, image) => Console.WriteLine($"{image} ({arch}-bit)");
nh.Data += pkt => Console.WriteLine(pkt.Summary());
nh.SendConfig(Func.SslWrite | Func.SslRead);
nh.Run(); // blocking read loop; use RunAsync() for a Task
Intercept example
nh.Intercepted += pkt =>
{
var text = System.Text.Encoding.ASCII.GetString(pkt.Data);
if (text.Contains("password")) nh.Drop(pkt);
else nh.Forward(pkt); // or nh.Forward(pkt, newBytes)
};
nh.SendConfig(Func.All, intercept: true);
nh.Run();
C# API reference
class NetHookClient : IDisposable
| Member | Description |
|---|---|
NetHookClient(int pid) | Construct for a PID. |
NetHookClient(string pipeName, int pid) | Construct for an explicit pipe name. |
Task ConnectAsync(int timeoutMs=4000, ct) | Open the pipe (async). |
void Connect(int timeoutMs=4000) | Open the pipe (sync). |
bool Connected | Pipe currently connected. |
void Run(ct) / Task RunAsync(ct) | Read loop that raises the events below until the pipe closes. |
bool ReadMessage(out type, out id, out payload) | Low-level: one raw message; false at EOF. |
void SendConfig(Func funcs, bool intercept=false, bool block=false, uint maxCapture=0, uint fileCap=0) | Enable hooks & capture behaviour. fileCap is the hard per-call ReadFile/WriteFile ceiling. |
void SendDecision(ulong refId, NhAction action, byte[]? data=null) | Answer an intercept. |
void Forward(Packet pkt, byte[]? data=null) | Forward (optionally rewrite) an intercepted packet. |
void Drop(Packet pkt) | Drop an intercepted packet. |
void Detach() | Ask the DLL to unhook and unload. |
Events (raised on the read loop)
| Event | Signature |
|---|---|
Hello | Action<int pid, int arch, string image> |
Data | Action<Packet> |
Intercepted | Action<Packet> |
Log | Action<string> |
Bye | Action (pipe closed) |
class Packet
Fields per the reference; helpers
Summary(), AsciiPreview(limit), properties
Api, Out, Source, Dest.
Raw bytes are Data.
Constants
Enums Func (hook bits, see table),
NhAction, Direction, MsgType;
constants NetHookClient.Magic, NetHookClient.HeaderSize.
Message types
| Name | Value | Dir | Payload |
|---|---|---|---|
HELLO | 1 | server → client | pid u32, arch u32, name_len u32, UTF-8 image path |
BYE | 2 | server → client | none (target detaching) |
LOG | 3 | server → client | UTF-8 text (debug builds) |
DATA | 4 | server → client | nh_data + captured bytes |
INTERCEPT | 5 | server → client | nh_data + bytes; held for a DECISION |
CONFIG | 100 | client → server | funcs u32, intercept u8, block u8, pad[2], max_capture u32 |
DECISION | 101 | client → server | ref_id u64, action u32, data_len u32, replacement bytes |
DETACH | 102 | client → server | none |
REPLACE | 103 | client → server | search-and-replace rule set (advanced; not wrapped in the SDK) |
FILE_FILTER | 104 | client → server | file-path capture patterns (advanced; not wrapped in the SDK) |
intercept holds every packet
for a DECISION; block drops all traffic on the enabled
hooks; max_capture caps bytes captured per event (0 = unlimited),
handy for large file / socket payloads; file_cap is a hard per-call
ceiling on ReadFile/WriteFile capture (0 = built-in 256 B default, max 8 MiB).Hook functions
Pass any OR-combination to send_config / SendConfig.
Use Func.ALL (Func.All) for everything.
| Bit | Python / C# | Hooked API |
|---|---|---|
| 0 | SEND | send |
| 1 | RECV | recv |
| 2 | WSASEND | WSASend |
| 3 | WSARECV | WSARecv |
| 4 | SSL_WRITE | SSL_write (OpenSSL) |
| 5 | SSL_READ | SSL_read (OpenSSL) |
| 6 | ENCRYPT | EncryptMessage (Schannel) |
| 7 | DECRYPT | DecryptMessage (Schannel) |
| 8 | SENDTO | sendto |
| 9 | RECVFROM | recvfrom |
| 10 | READFILE | ReadFile |
| 11 | WRITEFILE | WriteFile |
| 12 | INET_READ | InternetReadFile (WinINet) |
| 13 | INET_WRITE | InternetWriteFile (WinINet) |
| 14 | HTTP_SEND | HttpSendRequest (WinINet) |
| 15 | WINHTTP_SEND | WinHttpSendRequest / WriteData |
| 16 | WINHTTP_RECV | WinHttpReadData |
| 17 | SSL_WRITE_EX | SSL_write_ex (OpenSSL 1.1.1+/3.x) |
| 18 | SSL_READ_EX | SSL_read_ex (OpenSSL 1.1.1+/3.x) |
| 19 | PR_WRITE | PR_Write (NSS/NSPR — Firefox) |
| 20 | PR_READ | PR_Read (NSS/NSPR) |
| 21 | GNUTLS_SEND | gnutls_record_send |
| 22 | GNUTLS_RECV | gnutls_record_recv |
| 23 | WOLFSSL_WRITE | wolfSSL_write |
| 24 | WOLFSSL_READ | wolfSSL_read |
| 25 | DPAPI_PROTECT / DpapiProtect | CryptProtectData (crypt32 — cleartext in) |
| 26 | DPAPI_UNPROTECT / DpapiUnprotect | CryptUnprotectData (crypt32 — cleartext out) |
| 27 | DPAPI_PROTECT_MEM / DpapiProtectMem | CryptProtectMemory (crypt32) |
| 28 | DPAPI_UNPROTECT_MEM / DpapiUnprotectMem | CryptUnprotectMemory (crypt32) |
| 29 | RTL_ENCRYPT_MEM / RtlEncryptMem | SystemFunction040 / RtlEncryptMemory (advapi32) |
| 30 | RTL_DECRYPT_MEM / RtlDecryptMem | SystemFunction041 / RtlDecryptMemory (advapi32) |
Packet fields
A Packet is the decoded nh_data record plus its
captured bytes. Not every field is meaningful for every hook — TLS, file
and WinHTTP captures carry no socket endpoint, so the address/port fields are
empty for them.
| Python | C# | Meaning |
|---|---|---|
msg_id | MsgId | Correlation id (echo in a decision); 0 for plain DATA. |
is_intercept | IsIntercept | True if held for a decision. |
timestamp_ms | Timestamp | Capture time (ms since epoch / local DateTime). |
func / api | Func / Api | Which hook captured it; api is the real API name. |
direction / out | Direction / Out | Send (out) vs recv (in). |
socket | Socket | SOCKET / SSL* handle (informational). |
family | Family | Address family (2 = IPv4, 23 = IPv6). |
local_addr, local_port | LocalAddr, LocalPort | Local endpoint (socket hooks only). |
remote_addr, remote_port | RemoteAddr, RemotePort | Remote endpoint (socket hooks only). |
data | Data | The captured payload bytes (possibly truncated to max_capture). |
Error handling
| Situation | C# | Python |
|---|---|---|
| Pipe never appears / timeout | Connect / ConnectAsync throw | connect() raises OSError |
| Bad frame magic (desync) | InvalidDataException | ValueError |
| Pipe closed mid-stream | Run catches IOException and fires Bye | events() stops on ConnectionError/OSError |
| Clean shutdown from target | BYE ends Run, fires Bye | "bye" event ends events() |
| Sending before connect | InvalidOperationException | ConnectionError |
using / with) to release the
pipe handle. Detach tells the DLL to unhook and eject entirely.