Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Building a Service Fingerprinting Engine

Hello! Welcome back to our course on architecting security products.

In our last lesson, we designed a high-performance, modular port scanning engine. Its output is a list of open ports on target hosts—the digital doorways into a system. But simply knowing a door is open isn't enough; to assess for vulnerabilities, we need to know what lies behind it.

This lesson tackles that exact problem, directly addressing the learning outcome: Design a service detection and version identification engine using banner grabbing and probe-response matching. We will architect the crucial second stage of our scanner, which interrogates open ports to determine precisely what software is running and what version it is. This information is the key that unlocks vulnerability correlation, which we'll cover in the next lesson.

1. From Open Port to Service Fingerprint

Our goal is to take an input like {host: 192.0.2.1, port: 22} and produce an output like {service: "openssh", version: "8.2p1", os: "ubuntu"}.

We will explore two primary techniques to achieve this:

  1. Banner Grabbing (Passive Identification): Listening for a service to introduce itself.
  2. Probe-Response Matching (Active Identification): Actively querying a service to provoke a revealing response.

The flowchart below gives a high-level overview of these two paths.

Banner Grabbing Process Flowchart
This flowchart shows the two main methods for identifying a service. The `nmap` path represents a sophisticated probe-response system, while the `nc` path represents a simple banner grabbing approach. Both aim to identify the service and its version.

2. The Simple Path: Banner Grabbing with a NULL Probe

Many network protocols are "talkative." As soon as you connect to them, they send a "banner" to identify themselves. Think of it as a server answering the phone with "Hello, this is the SMTP service." The process of connecting and capturing this initial message is called banner grabbing.

In the context of a systematic scanning engine like Nmap, this is implemented as a NULL probe. The engine connects to an open TCP port and simply listens for a set amount of time without sending any data of its own.

Let's look at the official Nmap documentation to understand how this works.

Technique Described | Nmap Network Scanning

This section from the official Nmap book, 'Nmap Network Scanning', describes the initial steps of version scanning. It details the NULL probe technique, which is the formal implementation of banner grabbing.

Read section 3, which starts with 'Once the TCP connection is made...'. Focus on how Nmap listens for a banner, compares it against a signature database (nmap-service-probes), and can achieve a 'full match' or a 'soft match'. This is the core logic of passive service identification.

Architecting the Banner Grabbing Component

Based on this, the architecture for our passive identification module is straightforward:

  1. Input: A list of open TCP ports from the port scanning engine.
  2. Worker Logic: For each port:
    • Establish a TCP connection.
    • Start a timer (e.g., 5 seconds, as Nmap does).
    • Listen for any incoming data and buffer it.
  3. Signature Matching:
    • The received data (the banner) is compared against a database of signatures. These are typically regular expressions designed to parse the service name and version number.
    • For example, a signature like ^220 .* ESMTP Postfix$ identifies the Postfix SMTP server. A more complex one like ^SSH-2\.0-OpenSSH_([.\d]+p\d+) can capture the exact version of OpenSSH.

This image shows what a part of this signature database might look like for the NULL probe.

Service Detection Probe Configuration Example
This shows timing configurations and `match` rules for Nmap's NULL probe. The regular expressions under `match` are used to identify services like '1C: Enterprise' and '3CX Tunnel Protocol' from their initial banners.

This approach is fast and efficient. When it works, we can identify a service with a single connection and no additional traffic. However, its major limitation is obvious: what if the service is silent and doesn't send a banner? For this, we need a more active approach.

3. The Active Path: Probe-Response Matching

When a service doesn't volunteer information, we must coax it out. Probe-response matching is a systematic way of doing this. The engine sends a specifically crafted message (a probe) and analyzes the response.

The intelligence of the system lies in its database of probes and expected responses. This isn't just a list of strings; it's a structured knowledge base designed for efficiency and accuracy.

Let's go back to the Nmap documentation, which details this process perfectly.

Technique Described | Nmap Network Scanning

These sections of the Nmap book are the blueprint for a sophisticated service identification engine. They describe the logic, the data structures, and the optimizations that make Nmap's version scanning so powerful.

Read the following sections carefully: Section 4: This explains the core probe-response loop, including the concept of 'probable ports' for optimization. Section 5 & 'Cheats and Fallbacks': This details how Nmap speeds up scans using generic probes, softmatch directives, and the clever 'fallback' mechanism (including the 'NULL probe cheat'). 'Probe Selection and Rarity': This describes the algorithm Nmap uses to decide which probes to send and in what order, based on the rarity metric and the user-defined intensity level. This is key to managing the trade-off between speed and comprehensiveness.

Designing the Active Identification Engine

Distilling the information from the Nmap documentation, we can design the architecture for our engine.

1. The Probe Database (nmap-service-probes)

This is the brain of our engine. It's a file containing entries, each describing a probe. A typical entry includes:

  • Probe Name: A unique identifier (e.g., GetRequest).
  • Probe String: The data to send, e.g., Probe TCP GetRequest q|GET / HTTP/1.0\r\n\r\n|.
  • Probable Ports: A list of ports where this probe is most likely to succeed (e.g., ports 80,81,443,...). This is a critical optimization.
  • Match Directives: A set of match rules (regular expressions) to run against the response. They extract service, version, info, etc. E.g., match http m|^HTTP/1\.[01] \d\d\d (.*)\r\n|.
  • Rarity: A number from 1 to 9 indicating how likely the probe is to succeed.
  • Fallback: Which other probes' match directives to try if this probe's own directives fail.

2. The Engine's Algorithm

For each open port, the engine executes the following state machine:

  1. NULL Probe: Always try the banner grab first. If a match is found, report it and terminate for this port.
  2. Probable Port Probes: If the NULL probe fails, look up the port number in the database. Sequentially send all probes that list this port in their ports directive. For each response, check for a match. If found, report and terminate.
  3. Intensity-Based Probes: If still no match, begin iterating through the rest of the probes in the database, from lowest rarity to highest.
    • The engine only tries probes with a rarity less than or equal to the scan's configured intensity level (e.g., Nmap's --version-intensity flag).
    • For each response, first check against the probe's own match rules.
    • If that fails, check against the match rules of any probes listed in its fallback directive. This is how the "NULL probe cheat" works—a response to a later probe is checked against the NULL probe's banner rules.
  4. Special Handlers:
    • SSL/TLS: If a probe identifies an SSL/TLS handshake, the engine re-establishes the connection through an SSL/TLS layer and restarts the entire probing process from step 1, sending the probes inside the encrypted tunnel.
    • RPC/SMB: If an RPC or SMB service is identified, the engine can hand off the connection to a specialized post-processor to enumerate more detailed information.
  5. No Match: If all applicable probes are tried and none produce a match, the service remains unknown. The raw response might be saved as a "fingerprint" for manual analysis.

This multi-step, optimized, and fallback-tolerant algorithm is what makes a service detection engine both fast and accurate.

4. Advanced Architecture: Dynamic Protocol Analysis

The Nmap model is highly effective, but we can abstract it to a more general and extensible software architecture. For this, we turn to concepts from the world of Network Intrusion Detection Systems (NIDS), which face the exact same problem.

A key paper in this area proposes a dynamic framework that is a perfect fit for a robust, modern system.

Dynamic Application-Layer Protocol Analysis for Network Intrusion ...

This paper, 'Dynamic Application-Layer Protocol Analysis for Network Intrusion Detection', presents a flexible architecture for protocol identification. While its context is NIDS, the design principles are directly applicable to our scanner and represent a more formalized version of the probe-response model.

Please read these sections: 4.1 Design: Focus on the five design requirements and the concept of a per-connection 'analyzer tree'. Understand the role of the 'Protocol Identification Analyzer' (PIA). 4.2 Implementation: See how this design is implemented in the Bro NIDS, using signatures as a primary heuristic to activate specific analyzers. 4.3 Trade-Offs: This is a crucial section that discusses the real-world engineering challenges: choosing good signatures (probes), handling false positives, and preventing evasion. This directly relates to your goal of understanding design trade-offs.

The core idea is to move away from a rigid, sequential probing algorithm to a dynamic, event-driven one.

The Analyzer Tree Architecture:

  1. Initial State: For a new connection, the system creates a simple "analyzer tree," perhaps just IP -> TCP.
  2. Protocol Identification Analyzer (PIA): At the end of the chain, a generic PIA is attached. This component's job is to buffer the initial bytes of a connection and run multiple detection heuristics against it (our probe-response signatures are one such heuristic).
  3. Dynamic Activation: When the PIA gets a potential match (e.g., the response to a GetRequest probe looks like HTTP), it doesn't just report the result. It instantiates and attaches an actual HTTP analyzer to the tree. The data stream is then fed to this new analyzer.
  4. Verification and Deactivation: The specialized HTTP analyzer's job is not just to parse HTTP, but also to verify it. If it receives data that is definitively not valid HTTP, it recognizes that the initial PIA match was a false positive. It can then signal to be removed from the tree, allowing the PIA to continue looking for other protocol matches.

This architecture, which you can conceptualize as a plug-in system, is more robust and extensible than a hardcoded algorithm. It cleanly separates the logic of detection (the PIA's job) from analysis and verification (the specific analyzer's job).

Conclusion

In this lesson, we designed the engine that turns a list of open ports into a rich inventory of running services. We moved from simple banner grabbing to a sophisticated, multi-stage, active probing system, using the architecture of Nmap as our blueprint.

Key Takeaways:

  • Service identification is a two-pronged approach: Passive banner grabbing (NULL probe) for fast identification of "talkative" services, and active probe-response matching for silent ones.
  • A professional-grade engine relies on a rich probe database that includes not just probe strings and match patterns, but also metadata for optimization, such as probable ports, rarity, and fallback logic.
  • The scanning process is an intelligent algorithm: It prioritizes probes to balance speed and comprehensiveness, and includes special handling for encrypted protocols like SSL/TLS.
  • A more advanced, robust architecture uses a dynamic "analyzer tree," where a generic identifier (PIA) activates specific protocol analyzers, which then verify the match, creating a feedback loop that is resilient to false positives.

Preview of the Next Lesson:

We now have an engine that can produce a detailed list of services and versions running on a network: {host, port, service, version}. The final step in building a vulnerability scanner is to use this information. In our next lesson, "Architect a vulnerability matching engine that correlates discovered services and versions against the knowledge base," we will design the system that takes this output and compares it against vulnerability databases like the NVD to find known security flaws.

Can't find a good explanation? Sign up and we'll make it for you

Sign up