pysap.SAPNWRFC module

SAP NetWeaver RFC (NWRFC) protocol constants and TLV parser.

The NetWeaver RFC SDK (libsapnwrfc) is the modern SAP client library used by pyrfc, SAP JCo, SAP .NET Connector, and the SAP ABAP kernel’s own outbound RFC stack. It differs from the classic SAPRFC/CPIC framing (see pysap.SAPRFC) in two key areas:

  1. Different magic bytes: NWRFC frames begin with \x06\xcb\x02\x00 rather than the classic \x06\x03\x02\x00.

  2. TLV encoding: All fields (username, password, function module name, client, language, hostname, …) are carried in a Type-Length-Value (TLV) stream using 2-byte big-endian tags and 2-byte big-endian lengths. Values are encoded in UTF-16LE.

Both frame types are transported inside SAP NI framing (4-byte big-endian payload length prefix) on the same RFC ports (default: TCP/3300).

TLV tag assignments

The tag-to-semantic mapping below was determined by analysis of live packet captures from multiple NWRFC SDK clients (pyrfc 3.x, npl_rfc, SAP ABAP kernel). Tags not listed here are observed in captures but their semantics are not yet confirmed.

Tag     Semantic                 Encoding    Size (bytes, typ./max.)
------  -----------------------  ----------  -----------------------
0x0006  RFC destination name     UTF-16LE    2 – 40
0x0007  Client IP address        UTF-16LE    4 – 32
0x0008  Server hostname          UTF-16LE    8 – 120
0x0009  Username (tertiary)      UTF-16LE    4 – 48
0x0100  Program / task name      UTF-16LE    10 – 120
0x0102  Function module name     UTF-16LE    8 – 80
0x0111  Username (primary)       UTF-16LE    4 – 48
0x0114  SAP client number        UTF-16LE    2 – 8   ("001", "100", …)
0x0117  ab_scramble password     binary      6 – 84  [4B LE seed][UTF-16LE]
0x0119  Username (secondary)     UTF-16LE    4 – 48
0x0152  Language key             UTF-16LE    2 – 4   ("E", "D", …)
0x0201  RFC param name           UTF-16LE    2 – 120
0x0203  RFC param value          UTF-16LE    variable

Password field (tag 0x0117)

The password TLV value is a raw binary field, not a UTF-16LE string:

[ 4 bytes little-endian seed ][ N×2 bytes ab_scrambled UTF-16LE payload ]

Total field length = 4 + (password_length × 2). Minimum length is 6 bytes (seed + one UTF-16LE character). Use pysap.utils.crypto.rfc.ab_descramble() with encoding="utf-16-le" to recover the plaintext.

Hostname / SID extraction

The server hostname value (tag 0x0008) frequently encodes the SAP System ID (SID) in the pattern <hostname>_<SID>_<instance_number>, for example sapnw752_NPL_00. The regex NWRFC_SID_RE matches this pattern.

References

  • Protocol reverse-engineered from libsapnwrfc (SAP NetWeaver RFC SDK).

  • TLV tag assignments confirmed from live PCAP captures.

  • See also: pysap.SAPRFC for classic RFC/CPIC framing.

pysap.SAPNWRFC.NWRFC_MAGIC = b'\x06\xcb\x02\x00'

Magic bytes that identify an NWRFC frame (bytes 0-3 of the NI payload). Classic SAPRFC frames use b'\\x06\\x03\\x02\\x00' instead.

pysap.SAPNWRFC.NWRFC_PASSWORD_SEED_SIZE = 4

Minimum byte count of the ab_scramble seed prefix inside a password field.

pysap.SAPNWRFC.NWRFC_SID_RE = re.compile('_([A-Z][A-Z0-9]{2})_\\d{2}(?:\\s|$)')

Regex that extracts the SAP System ID (SID) from a server hostname value. Matches the _SID_NN suffix, e.g. sapnw752_NPL_00NPL.

pysap.SAPNWRFC.NWRFC_TAGS = {6: 'dest', 7: 'ip', 8: 'hostname', 9: 'username', 256: 'program', 258: 'function_module', 273: 'username', 276: 'client', 279: 'password', 281: 'username', 338: 'language', 513: 'param_name', 515: 'param_value'}

Confirmed NWRFC TLV tag assignments. Keys are integer tag values; values are human-readable semantic names. All string values are encoded in UTF-16LE unless noted otherwise.

pysap.SAPNWRFC.NWRFC_TAG_CONSTRAINTS = {6: (2, 40), 7: (4, 32), 8: (4, 120), 9: (4, 48), 256: (10, 120), 258: (8, 80), 273: (4, 48), 276: (2, 8), 279: (6, 84), 281: (4, 48), 338: (2, 4), 513: (2, 120)}

Per-tag minimum and maximum value length in bytes (before decoding). Used to reject spurious TLV matches during scanning.

pysap.SAPNWRFC.NWRFC_TLV_HEADER_SIZE = 4

Size of a TLV header in bytes (2-byte tag + 2-byte length, both big-endian).

pysap.SAPNWRFC.NWRFC_USERNAME_TAGS = (273, 281, 9)

Username tags, in priority order (highest priority first). When multiple username tags are present the highest-priority non-empty value should be used.

pysap.SAPNWRFC.SAPRFC_MAGIC = b'\x06\x03\x02\x00'

Magic bytes for classic SAPRFC/CPIC frames, shown here for comparison.

pysap.SAPNWRFC.decode_string(raw)[source]

Decode a UTF-16LE TLV value, stripping null and space padding.

Args:

raw (bytes): Raw TLV value bytes.

Returns:

str: Decoded string, or an empty string if decoding fails.

pysap.SAPNWRFC.decode_value(raw)[source]

Decode a TLV value of unknown encoding, trying UTF-16LE then ASCII.

Most NWRFC TLV values are UTF-16LE (see decode_string()), but some fields observed in F_SAP_SEND bodies (e.g. CPIC marker fields on older SDK versions) are plain ASCII. This heuristically picks UTF-16LE when raw “looks like” UTF-16LE (every other byte is 0x00, as is the case for ASCII-range characters encoded that way), falling back to ASCII (with replacement of invalid bytes) otherwise.

Args:

raw (bytes | None): Raw TLV value bytes.

Returns:

str | None: Decoded string with null/space padding stripped, or None if raw is None.

pysap.SAPNWRFC.extract_rfc_params(raw)[source]

Extract RFC function call parameters from an F_SAP_SEND body.

Scans for 0x0201 (param_name) TLVs carrying a valid identifier, then reads the immediately following 0x0203 (param_value) TLV via find_tlv_field_by_marker().

Args:

raw (bytes): Raw F_SAP_SEND body to scan.

Returns:

dict[str, str]: Mapping of parameter name to decoded value, in the order they appear in raw.

pysap.SAPNWRFC.extract_xml_data(raw)[source]

Extract XML-encoded parameters and table rows from an RFC call body.

The NetWeaver RFC SDK serialises table/structure parameters as ASCII XML fragments embedded in the F_SAP_SEND body, e.g.:

<IT_MODULE><item><FIELD>value</FIELD></item></IT_MODULE>
<IV_GUID>base64==</IV_GUID>
Args:
raw (bytes): Raw F_SAP_SEND body. Everything before the first <

is treated as binary and ignored; the remainder is decoded as ASCII (XML is always ASCII in NWRFC).

Returns:

dict[str, str | list]: Mapping of parameter name to value, where value is either a plain string (scalar) or a list of rows (table parameter). Table rows are dicts of sub-field name to value, except rows of a single unnamed field which are returned as plain strings. A sub-field whose value itself contains <item> entries (nested table, e.g. ABAP source lines) is returned as a list of strings — but note rows containing a nested <item>-bearing sub-field are not split correctly, as the non-greedy outer <item> match ends at the first inner </item>.

pysap.SAPNWRFC.find_tlv_field_by_marker(data, marker, search_start=0)[source]

Find a TLV field by its 2-byte tag, scanning byte-by-byte.

This is an alternative to parse_tlv() for buffers where the field order varies across NWRFC SDK versions and parse_tlv()’s sequential scan may miss a field that is preceded by an unrecognised or misaligned entry. It scans for any 4-byte delimiter of the form [2-byte end-of-prev][2-byte tag] followed by a 2-byte big-endian length, i.e.:

[end-of-prev (2)][tag (2)][length (2)][value (length)]
Args:

data (bytes): Raw frame payload to scan. marker (int | bytes): Tag to search for, either as an integer (e.g.

0x0117) or as 2 raw bytes (e.g. b"\x01\x17").

search_start (int): Offset to start scanning from.

Returns:

tuple[bytes | None, int]: (value, end_offset) if found, where end_offset is the offset just past the value (suitable as search_start for a subsequent call); otherwise (None, search_start).

pysap.SAPNWRFC.find_tlv_field_by_padd(data, padd)[source]

Find a TLV field by its full 4-byte padding marker.

The 4-byte padd value encodes [end-of-prev-field (2)][tag (2)] and only matches when the immediately preceding field is the one the caller expects, making this more precise than find_tlv_field_by_marker() when the field order is known and fixed.

Layout:

[padd (4)][length (2, big-endian)][value (length)]
Args:

data (bytes): Raw frame payload to scan. padd (bytes): 4-byte padding marker to search for.

Returns:

bytes | None: The value bytes, or None if not found or invalid.

pysap.SAPNWRFC.parse_tlv(data, tags=None)[source]

Parse NWRFC TLV fields from a raw byte buffer.

Scans data for TLV entries whose 2-byte big-endian tag appears in tags (or in NWRFC_TAGS when tags is None). Length constraints from NWRFC_TAG_CONSTRAINTS are enforced to suppress false positives; unrecognised or out-of-range entries are skipped.

The buffer does not need to start at a TLV boundary — the function searches forward byte-by-byte, which handles frames where the TLV stream is preceded by other header fields.

Args:
data (bytes): Raw frame payload (NI payload, without the 4-byte NI

length header).

tags (collection | None): Iterable of integer tag values to extract.

Defaults to all keys in NWRFC_TAGS.

Yields:

tuple[int, bytes]: (tag, raw_value) pairs in the order they appear in data. The caller is responsible for decoding raw_value (typically raw_value.decode("utf-16-le").rstrip("\x00") for string fields, or passing to pysap.utils.crypto.rfc.ab_descramble() for the password tag).

Example:

>>> from pysap.SAPNWRFC import parse_tlv, NWRFC_TAGS
>>> for tag, value in parse_tlv(frame_data):
...     print(hex(tag), NWRFC_TAGS.get(tag), value)