Library dicom
DICOM protocol library for Nmap NSE scripts.
Implements the DICOM Upper Layer protocol (PS3.8) and core DIMSE services (PS3.7). Provides connection management, A-ASSOCIATE negotiation, P-DATA-TF PDU construction/parsing, and helpers for C-ECHO, C-STORE, C-FIND, C-GET, and C-MOVE.
Supports both Implicit VR Little Endian and Explicit VR Little Endian transfer syntaxes for dataset encoding and parsing.
Designed to be shared across multiple DICOM NSE scripts (dicom-ping, dicom-store-fuzzer, dicom-cfind-ls, etc.) so that protocol logic lives in one place.
INSTALLATION: Copy this file to your Nmap nselib/ directory to replace the built-in minimal dicom.lua: cp dicom.lua /usr/share/nmap/nselib/dicom.lua (or /usr/local/share/nmap/nselib/dicom.lua on macOS) This library is fully backward-compatible with the original: the legacy API (start_connection, send, receive, pdu_header_encode, associate, send_pdata) keeps its exact signatures and behaviour, so the official dicom-ping and dicom-brute scripts run unchanged.
CHANGES vs. the upstream nmap nselib/dicom.lua: * Legacy API preserved verbatim (drop-in compatible). * Added DIMSE service support: C-ECHO, C-STORE, C-FIND, C-GET, C-MOVE. * Added A-ASSOCIATE-RQ builder with multiple presentation contexts, configurable max PDU, and SCP/SCU Role Selection (PS3.7 D.3.3.4) so C-GET can receive C-STORE sub-operations. * Added A-ASSOCIATE-AC parser (accepted contexts, transfer syntaxes, max PDU, implementation identification, accepted roles) for fingerprinting and negotiation. * Added P-DATA-TF fragmentation/reassembly that surfaces the PDV presentation-context ID (needed to answer C-STORE sub-ops correctly). * Added Implicit/Explicit VR LE element encoders/decoders and Part-10 (.dcm) dataset extraction.
OPTIONS:
*called_aet - Called Application Entity Title (default:
ANY-SCP)
*calling_aet - Calling Application Entity Title (default:
ECHOSCU)
Author:
Copyright © Same as Nmap--See https://nmap.org/book/man-legal.html
Source: https://svn.nmap.org/nmap/nselib/dicom.lua
Script Arguments
- dicom.called_aet
Called Application Entity Title. Default: ANY-SCP
- dicom.calling_aet
Calling Application Entity Title. Default: ECHOSCU
Functions
- associate (host, port, calling_aet, called_aet)
Legacy: associate(host, port, calling_aet, called_aet)
- build_cecho_rq (msg_id)
Build a C-ECHO-RQ command set.
- build_cfind_dataset (level, query_tags, transfer_syntax)
Build a C-FIND query dataset for a given Q/R level.
- build_cfind_rq (msg_id, sop_class_uid, priority)
Build a C-FIND-RQ command set.
- build_cget_dataset (level, query_tags, transfer_syntax)
Build a C-GET query dataset.
- build_cget_rq (msg_id, sop_class_uid, priority)
Build a C-GET-RQ command set.
- build_cmove_rq (msg_id, sop_class_uid, move_dest, priority)
Build a C-MOVE-RQ command set.
- build_cstore_rq (msg_id, sop_class_uid, sop_instance_uid, priority)
Build a C-STORE-RQ command set.
- build_cstore_rsp (msg_id_rsp, sop_class_uid, sop_inst_uid, status_code)
Build a C-STORE-RSP command set.
- build_pdata_pdus (pctx_id, data, is_command, max_pdu)
Chunk data into one or more P-DATA-TF PDUs (single-stream). Use build_pdata_pdus when you need separate command/dataset PDU lists. Use send_dimse() for the preferred combined approach.
- do_release (sock, timeout_s)
Send A-RELEASE-RQ and wait for A-RELEASE-RP (best-effort).
- encode_dataset (tags, transfer_syntax)
Encode a dataset (list of tag definitions) using the specified TS. Each tag is {group=N, elem=N, vr="XX", value=...}. Tags are sorted by (group, elem) automatically.
- encode_element (group, elem, vr, val, transfer_syntax)
Encode a single element using the specified transfer syntax.
- explicit_elem (group, elem, vr, val)
Encode a single DICOM element in Explicit VR Little Endian (PS3.5 §7.1.1). Uses long format for OB/OD/OF/OL/OV/OW/SQ/UC/UN/UR/UT/SV/UV, short format for all other VRs.
- generate_fake_uid ()
Generate a plausible fake UID.
- get_accepted_ts (pctxs, pctx_id)
Get the negotiated transfer syntax for an accepted presentation context.
- get_cert (sock)
Fetch the peer's TLS certificate from a connected TLS socket, if any.
- implicit_elem (group, elem, vr, val)
Encode a single DICOM element in Implicit VR Little Endian (PS3.5 §7.1.2).
- mk_item (item_type, data)
Build a TLV item: type(1) + reserved(1) + length(2, big-endian) + data.
- mk_pres_ctx (pctx_id, sop_class, transfer_uids)
Build a Presentation Context sub-item for A-ASSOCIATE-RQ.
- mk_role_item (uid, scu, scp)
Build an SCP/SCU Role Selection sub-item (PS3.7 §D.3.3.4, item type 0x54). Used to request role reversal so the SCU can receive C-STORE sub-operations (required for C-GET).
- mk_user_info (max_pdu, roles)
Build User Information sub-item for A-ASSOCIATE-RQ.
- new_sock (timeout_s)
Create a new Nmap socket with timeout.
- pack_be (val, n)
Pack a big-endian unsigned integer into n bytes.
- pack_le (val, n)
Pack a little-endian unsigned integer into n bytes.
- pad_str (s, n)
Pad/truncate a string to exactly n bytes (space-padded on right).
- parse_assoc_ac (data)
Parse A-ASSOCIATE-AC to extract accepted presentation contexts.
- parse_cfind_rsp (data, transfer_syntax)
Parse a C-FIND-RSP from P-DATA-TF PDU bytes.
- parse_command_set (cmd_bytes)
Parse Implicit VR LE command set bytes into a keyed table. For group 0x0000, auto-decodes US/UL values; for others, returns raw.
- parse_cstore_rsp (data)
Parse a C-STORE-RSP from P-DATA-TF PDU bytes.
- parse_dataset (ds_bytes, transfer_syntax)
Parse a dataset using the appropriate decoder for the transfer syntax. If the TS indicates Implicit VR LE, uses the implicit parser; otherwise uses the Explicit VR parser. Includes a heuristic fallback: if explicit parsing is expected but the first element's VR bytes aren't valid ASCII uppercase, falls back to implicit.
- parse_dataset_explicit (ds_bytes)
Parse Explicit VR LE dataset bytes into tag -> string table. Handles both short-format and long-format VRs per PS3.5 §7.1.1.
- parse_dataset_implicit (ds_bytes)
Parse Implicit VR LE dataset bytes into tag -> string table.
- parse_pdata (data)
Extract command bytes and dataset bytes from a single P-DATA-TF PDU.
- pdu_header_encode (pdu_type, length)
Encode a DICOM PDU header (6 bytes).
- pick_accepted_pctx (pctxs)
Find the first accepted presentation context ID from a pctx map.
- read_dcm_dataset (filepath)
Extract the DICOM dataset from a raw .dcm file. Strips 128-byte preamble + "DICM" prefix and File Meta Information.
- read_file (path)
Read a file from disk. Returns bytes string or nil, err.
- receive (dcm)
Legacy: receive(dcm)
- recv_dimse (sock, timeout_s, carry)
Receive a single DIMSE response (command + optional dataset).
- recv_pdu (sock, timeout_s, carry)
Receive bytes until one complete PDU is assembled. Accepts an optional carry buffer containing leftover bytes from a previous call (when multiple PDUs arrive in a single TCP segment). Returns the extracted PDU **and** any remaining bytes so the caller can feed them back into the next call.
- send (dcm, data)
Legacy: send(dcm, data)
- send_dimse (sock, pctx_id, cmd_bytes, ds_bytes, max_pdu)
Send a DIMSE message (command set + optional dataset) as P-DATA-TF PDU(s).
- send_pdata (dicom, data)
Legacy: send_pdata(dicom, data)
- start_connection (host, port)
Legacy: start_connection(host, port) — opens TCP socket.
- tcp_connect (sock, host, port, tls)
Connect a socket. Returns ok, err.
- tcp_send (sock, data)
Send all bytes on a socket. Returns ok, err.
- tls_enabled ()
Whether DICOM-over-TLS is requested via the global "dicom.tls" script-arg.
- write_file (path, data)
Write binary data to a file. Returns true or nil, err.
Tables
- COMMAND_FIELD
DIMSE Command Field values (PS3.7 Table E.1-1).
- LONG_VRS
VRs that use the long (12-byte) explicit encoding in Explicit VR LE (PS3.5 Table 7.1-1). Membership test: LONG_VRS[vr] is true for these.
- PDU_CODES
DICOM Upper Layer PDU type codes (PS3.8 Table 9-1).
- PDU_NAMES
Reverse map of PDU_CODES: numeric type code to its name.
- QR_LEVEL
Query/Retrieve levels (0008,0052 QueryRetrieveLevel values).
- SOP_CLASS
Well-known SOP Class UIDs (storage and Query/Retrieve information models).
- STATUS
DIMSE Status codes (PS3.7 Table C.4-1 ff.).
- TRANSFER_SYNTAX
Transfer Syntax UIDs (PS3.5).
Functions
- associate (host, port, calling_aet, called_aet)
-
Legacy: associate(host, port, calling_aet, called_aet)
Parameters
- host
- port
- calling_aet
- called_aet
- build_cecho_rq (msg_id)
-
Build a C-ECHO-RQ command set.
Parameters
- msg_id
- Message ID
Return value:
Command set bytes (Implicit VR LE) - build_cfind_dataset (level, query_tags, transfer_syntax)
-
Build a C-FIND query dataset for a given Q/R level.
Parameters
- level
- QR_LEVEL value ("STUDY", "SERIES", "IMAGE")
- query_tags
- List of {group, elem, vr, value} tag tables
- transfer_syntax
- Transfer syntax for encoding (default: Explicit VR LE)
Return value:
Dataset bytes - build_cfind_rq (msg_id, sop_class_uid, priority)
-
Build a C-FIND-RQ command set.
Parameters
- msg_id
- Message ID
- sop_class_uid
- Abstract Syntax UID for the Q/R information model
- priority
- Priority (default: LOW = 0x0002)
Return value:
Command set bytes (Implicit VR LE) - build_cget_dataset (level, query_tags, transfer_syntax)
-
Build a C-GET query dataset.
Parameters
- level
- QR_LEVEL value
- query_tags
- List of {group, elem, vr, value} tag tables
- transfer_syntax
- Transfer syntax for encoding
Return value:
Dataset bytes - build_cget_rq (msg_id, sop_class_uid, priority)
-
Build a C-GET-RQ command set.
Parameters
- msg_id
- Message ID
- sop_class_uid
- Abstract Syntax UID for the Q/R information model
- priority
- Priority (default: LOW = 0x0002)
Return value:
Command set bytes (Implicit VR LE) - build_cmove_rq (msg_id, sop_class_uid, move_dest, priority)
-
Build a C-MOVE-RQ command set.
Parameters
- msg_id
- Message ID
- sop_class_uid
- Abstract Syntax UID for the Q/R information model
- move_dest
- Move Destination AE Title (max 16 chars, space-padded)
- priority
- Priority (default: LOW = 0x0002)
Return value:
Command set bytes (Implicit VR LE) - build_cstore_rq (msg_id, sop_class_uid, sop_instance_uid, priority)
-
Build a C-STORE-RQ command set.
Parameters
- msg_id
- Message ID
- sop_class_uid
- Affected SOP Class UID
- sop_instance_uid
- Affected SOP Instance UID
- priority
- Priority (default: LOW = 0x0002)
Return value:
Command set bytes (Implicit VR LE) - build_cstore_rsp (msg_id_rsp, sop_class_uid, sop_inst_uid, status_code)
-
Build a C-STORE-RSP command set.
Parameters
- msg_id_rsp
- Message ID Being Responded To
- sop_class_uid
- Affected SOP Class UID
- sop_inst_uid
- Affected SOP Instance UID
- status_code
- Status code (default: SUCCESS = 0x0000)
Return value:
Command set bytes (Implicit VR LE) - build_pdata_pdus (pctx_id, data, is_command, max_pdu)
-
Chunk data into one or more P-DATA-TF PDUs (single-stream). Use build_pdata_pdus when you need separate command/dataset PDU lists. Use send_dimse() for the preferred combined approach.
Parameters
- pctx_id
- Presentation context ID
- data
- Raw bytes to send
- is_command
- true for command set, false for dataset
- max_pdu
- Max PDU length
Return value:
List of PDU byte strings - do_release (sock, timeout_s)
-
Send A-RELEASE-RQ and wait for A-RELEASE-RP (best-effort).
Parameters
- sock
- timeout_s
- encode_dataset (tags, transfer_syntax)
-
Encode a dataset (list of tag definitions) using the specified TS. Each tag is {group=N, elem=N, vr="XX", value=...}. Tags are sorted by (group, elem) automatically.
Parameters
- tags
- List of tag tables
- transfer_syntax
- Transfer syntax UID (default: Explicit VR LE)
Return value:
Encoded dataset bytes - encode_element (group, elem, vr, val, transfer_syntax)
-
Encode a single element using the specified transfer syntax.
Parameters
- group
- Group number
- elem
- Element number
- vr
- VR string
- val
- Value
- transfer_syntax
- Transfer syntax UID (default: Explicit VR LE)
Return value:
Encoded element bytes - explicit_elem (group, elem, vr, val)
-
Encode a single DICOM element in Explicit VR Little Endian (PS3.5 §7.1.1). Uses long format for OB/OD/OF/OL/OV/OW/SQ/UC/UN/UR/UT/SV/UV, short format for all other VRs.
Parameters
- group
- Group number
- elem
- Element number
- vr
- VR string (2 characters)
- val
- Value (number for US/UL/SS/SL, string otherwise)
Return value:
Encoded element bytes - generate_fake_uid ()
-
Generate a plausible fake UID.
- get_accepted_ts (pctxs, pctx_id)
-
Get the negotiated transfer syntax for an accepted presentation context.
Parameters
- pctxs
- Table from parse_assoc_ac
- pctx_id
- Presentation context ID
Return value:
Transfer syntax UID string, or nil - get_cert (sock)
-
Fetch the peer's TLS certificate from a connected TLS socket, if any.
Parameters
- sock
- Connected Nmap socket (TLS)
Return value:
certificate table (see nmap sslcert), or nil - implicit_elem (group, elem, vr, val)
-
Encode a single DICOM element in Implicit VR Little Endian (PS3.5 §7.1.2).
Parameters
- group
- Group number (e.g. 0x0000, 0x0008, 0x0010)
- elem
- Element number
- vr
- VR string ("US", "UL", "UI", "CS", "LO", etc.)
- val
- Value (number for US/UL, string otherwise)
Return value:
Encoded element bytes - mk_item (item_type, data)
-
Build a TLV item: type(1) + reserved(1) + length(2, big-endian) + data.
Parameters
- item_type
- data
- mk_pres_ctx (pctx_id, sop_class, transfer_uids)
-
Build a Presentation Context sub-item for A-ASSOCIATE-RQ.
Parameters
- pctx_id
- Odd integer 1–255
- sop_class
- Abstract Syntax UID string
- transfer_uids
- List of Transfer Syntax UIDs (default: Explicit VR LE + Implicit VR LE)
- mk_role_item (uid, scu, scp)
-
Build an SCP/SCU Role Selection sub-item (PS3.7 §D.3.3.4, item type 0x54). Used to request role reversal so the SCU can receive C-STORE sub-operations (required for C-GET).
Parameters
- uid
- Abstract Syntax (SOP Class) UID the role applies to
- scu
- SCU role byte (0 or 1)
- scp
- SCP role byte (0 or 1)
Return value:
Encoded role selection sub-item bytes - mk_user_info (max_pdu, roles)
-
Build User Information sub-item for A-ASSOCIATE-RQ.
Parameters
- max_pdu
- Max PDU length to advertise
- roles
- (optional) list of {uid=<SOP Class UID>, scu=bool, scp=bool} role-selection requests to append (for C-GET, request scp=true on the storage SOP classes you are willing to receive).
- new_sock (timeout_s)
-
Create a new Nmap socket with timeout.
Parameters
- timeout_s
- pack_be (val, n)
-
Pack a big-endian unsigned integer into n bytes.
Parameters
- val
- n
- pack_le (val, n)
-
Pack a little-endian unsigned integer into n bytes.
Parameters
- val
- n
- pad_str (s, n)
-
Pad/truncate a string to exactly n bytes (space-padded on right).
Parameters
- s
- n
- parse_assoc_ac (data)
-
Parse A-ASSOCIATE-AC to extract accepted presentation contexts.
Parameters
- data
- Raw A-ASSOCIATE-AC PDU bytes
Return value:
Table { pctxs = {pctx_id -> {accepted, transfer_syntax}}, max_pdu = n } - parse_cfind_rsp (data, transfer_syntax)
-
Parse a C-FIND-RSP from P-DATA-TF PDU bytes.
Parameters
- data
- Raw PDU bytes
- transfer_syntax
- Transfer syntax for dataset decoding (default: Explicit VR LE)
Return value:
status (int), dataset_elements (table or nil), cmd_elements (table) - parse_command_set (cmd_bytes)
-
Parse Implicit VR LE command set bytes into a keyed table. For group 0x0000, auto-decodes US/UL values; for others, returns raw.
Parameters
- cmd_bytes
- Raw command set bytes
Return value:
Table { "GGGG,EEEE" -> {group, elem, raw, value} } - parse_cstore_rsp (data)
-
Parse a C-STORE-RSP from P-DATA-TF PDU bytes.
Parameters
- data
- Raw PDU bytes
Return value:
status_code (int), error_comment (string) - parse_dataset (ds_bytes, transfer_syntax)
-
Parse a dataset using the appropriate decoder for the transfer syntax. If the TS indicates Implicit VR LE, uses the implicit parser; otherwise uses the Explicit VR parser. Includes a heuristic fallback: if explicit parsing is expected but the first element's VR bytes aren't valid ASCII uppercase, falls back to implicit.
Parameters
- ds_bytes
- Raw dataset bytes
- transfer_syntax
- Transfer syntax UID
Return value:
Table { "GGGG,EEEE" -> cleaned_string } - parse_dataset_explicit (ds_bytes)
-
Parse Explicit VR LE dataset bytes into tag -> string table. Handles both short-format and long-format VRs per PS3.5 §7.1.1.
Parameters
- ds_bytes
- Raw dataset bytes
Return value:
Table { "GGGG,EEEE" -> cleaned_string } - parse_dataset_implicit (ds_bytes)
-
Parse Implicit VR LE dataset bytes into tag -> string table.
Parameters
- ds_bytes
- Raw dataset bytes
Return value:
Table { "GGGG,EEEE" -> cleaned_string } - parse_pdata (data)
-
Extract command bytes and dataset bytes from a single P-DATA-TF PDU.
Parameters
- data
- Raw P-DATA-TF PDU bytes
Return value:
cmd_bytes, dataset_bytes, cmd_complete, ds_complete, pctx_id cmd_complete: true if the last command PDV had the "last fragment" bit set ds_complete: true if the last dataset PDV had the "last fragment" bit set pctx_id: presentation context ID of the PDV(s) (command PDV preferred), needed to reply on the correct context (e.g. C-STORE-RSP during C-GET sub-operations). nil if no PDV was seen. - pdu_header_encode (pdu_type, length)
-
Encode a DICOM PDU header (6 bytes).
Parameters
- pdu_type
- PDU type byte
- length
- Length of the PDU body
Return value:
status (bool), header_or_err (string) - pick_accepted_pctx (pctxs)
-
Find the first accepted presentation context ID from a pctx map.
Parameters
- pctxs
- Table from parse_assoc_ac {pctx_id -> {accepted, transfer_syntax}}
Return value:
pctx_id (int) or nil - read_dcm_dataset (filepath)
-
Extract the DICOM dataset from a raw .dcm file. Strips 128-byte preamble + "DICM" prefix and File Meta Information.
Parameters
- filepath
- Path to .dcm file
Return value:
dataset_bytes, sop_class_uid, sop_instance_uid, transfer_syntax_uid, err - read_file (path)
-
Read a file from disk. Returns bytes string or nil, err.
Parameters
- path
- receive (dcm)
-
Legacy: receive(dcm)
Parameters
- dcm
- recv_dimse (sock, timeout_s, carry)
-
Receive a single DIMSE response (command + optional dataset).
The server may send the command and dataset in a single P-DATA-TF PDU (with two PDV items) or in separate P-DATA-TF PDUs. This function handles both cases by checking CommandDataSetType (0000,0800) in the command set: if a dataset is expected but not yet received, it reads the next PDU to obtain it.
Accepts and returns a carry buffer so the caller can chain calls without losing bytes when multiple PDUs arrive in one TCP segment.
Parameters
- sock
- Nmap socket
- timeout_s
- Timeout in seconds
- carry
- (optional) leftover bytes from previous recv_pdu
Return value:
pdu_type (int or nil), cmd_elems (table), ds_bytes (string), raw_pdu (string), remaining (string – leftover bytes for next call), pctx_id (int or nil – presentation context the message arrived on; needed to reply on the correct context during C-GET sub-operations) - recv_pdu (sock, timeout_s, carry)
-
Receive bytes until one complete PDU is assembled. Accepts an optional carry buffer containing leftover bytes from a previous call (when multiple PDUs arrive in a single TCP segment). Returns the extracted PDU **and** any remaining bytes so the caller can feed them back into the next call.
Parameters
- sock
- Nmap socket
- timeout_s
- Timeout in seconds (used for error classification)
- carry
- (optional) leftover bytes from a previous recv_pdu call
Return value:
ok (bool), data_or_err (string), remaining (string – leftover bytes, empty on error) - send (dcm, data)
-
Legacy: send(dcm, data)
Parameters
- dcm
- data
- send_dimse (sock, pctx_id, cmd_bytes, ds_bytes, max_pdu)
-
Send a DIMSE message (command set + optional dataset) as P-DATA-TF PDU(s).
When both the command and dataset fit within a single PDU, they are combined into one P-DATA-TF PDU with two PDV items (matching the behaviour of DCMTK and pynetdicom). For larger payloads, the dataset is fragmented across multiple PDUs.
Command sets are always Implicit VR LE. The dataset must already be encoded in the negotiated transfer syntax before calling this function.
Parameters
- sock
- Nmap socket
- pctx_id
- Accepted presentation context ID
- cmd_bytes
- Command set bytes (Implicit VR LE)
- ds_bytes
- Dataset bytes (already encoded in negotiated TS), or nil
- max_pdu
- Max PDU size (use server's negotiated value)
Return value:
ok (bool), err (string or nil) - send_pdata (dicom, data)
-
Legacy: send_pdata(dicom, data)
Parameters
- dicom
- data
- start_connection (host, port)
-
Legacy: start_connection(host, port) — opens TCP socket.
Parameters
- host
- port
- tcp_connect (sock, host, port, tls)
-
Connect a socket. Returns ok, err.
Parameters
- sock
- Nmap socket
- host
- Host object
- port
- Port object (or number)
- tls
- true = TLS, false = plain TCP, nil = honor the dicom.tls arg
- tcp_send (sock, data)
-
Send all bytes on a socket. Returns ok, err.
Parameters
- sock
- data
- tls_enabled ()
-
Whether DICOM-over-TLS is requested via the global "dicom.tls" script-arg.
Return value:
true if dicom.tls=true was supplied on the command line - write_file (path, data)
-
Write binary data to a file. Returns true or nil, err.
Parameters
- path
- data
Tables
- COMMAND_FIELD
DIMSE Command Field values (PS3.7 Table E.1-1).
- LONG_VRS
VRs that use the long (12-byte) explicit encoding in Explicit VR LE (PS3.5 Table 7.1-1). Membership test: LONG_VRS[vr] is true for these.
- PDU_CODES
DICOM Upper Layer PDU type codes (PS3.8 Table 9-1).
- PDU_NAMES
Reverse map of PDU_CODES: numeric type code to its name.
- QR_LEVEL
Query/Retrieve levels (0008,0052 QueryRetrieveLevel values).
- SOP_CLASS
Well-known SOP Class UIDs (storage and Query/Retrieve information models).
- STATUS
DIMSE Status codes (PS3.7 Table C.4-1 ff.).
- TRANSFER_SYNTAX
Transfer Syntax UIDs (PS3.5).
