Heuristically. nmap observes the behaviour of the system during probes, develops a "fingerprint", which analagous to a real fingerprint test, looks mainly for the minutiae:
- out of specification behaviour and extra-specification or undefined behaviour
- retransmission times
- response to fragmentation and various ICMP probes
- patterns in TCP sequence numbers, IP ID numbers and TTLs
- behaviour with various TCP options and flags
That's just a quick summary, you can read a more thorough description here
http://nmap.org/book/osdetect-methods.html
The fingerprint is then compared with a database of known fingerprints. In the event that you scanned a system that has a unique fingerprint you will get a good match, otherwise you will get a guess based on the best fit (which is sometime wrong). It might be wrong anyway, some people like to play games (old but interesting). If there are no good matches, nmap
will encourage you to find out what the system is and submit it.
You can find the fingerprints in /usr/share/nmap/nmap-os-db
(probably), but you'll have to read the documentation to decipher the details.
In the current nmap database (6.47) there are 4485 fingerprints for 5009 CPE (platform) names, over 800 of which match various versions of MS-Windows, and over 1000 of which match various Linux systems (including appliances).
Here's an actual signature for a Linux system, an Asus Nexus 7 tablet:
# Linux 3.1.10-g52027f9 #1 SMP PREEMPT Thu Jun 28 16:19:26 PDT 2012 armv71 GNU/Linux, Asus Nexus 7 Android Tablet
Fingerprint Linux 3.1
Class Asus | Linux | 3.X | general purpose
CPE cpe:/o:asus:linux_kernel:3 auto
SEQ(SP=102-10C%GCD=1-6%ISR=108-112%TI=Z%CI=Z%II=I%TS=6|7)
OPS(O1=M566ST11NW6%O2=M566ST11NW6%O3=M566NNT11NW6%O4=M566ST11NW6%O5=M566ST11NW6%O6=M566ST11)
WIN(W1=3890%W2=3890%W3=3890%W4=3890%W5=3890%W6=3890)
ECN(R=Y%DF=Y%T=3B-45%TG=40%W=3908%O=M566NNSNW6%CC=Y%Q=)
T1(R=Y%DF=Y%T=3B-45%TG=40%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=Y%DF=Y%T=3B-45%TG=40%W=3890%S=O%A=S+%F=AS%O=M566ST11NW6%RD=0%Q=)
T4(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T5(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
T6(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T7(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
U1(DF=N%T=3B-45%TG=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)
IE(DFI=N%T=3B-45%TG=40%CD=S)
(Wow, that's not pretty, since you can find them in your nmap installation in nmap-os-db
so I'm not going to show more.)
I'll pick a simple field IE()
— ICMP echo behaviour (ping is simple, right?)
IE(DFI=N%T=3B-45%TG=40%CD=S)
If you check you'll see that's shared with over 1250 other systems (about two-thirds of which are Linux systems).
From the documentation:
The IE test involves sending two ICMP echo request packets to the target. The first one has the IP DF bit set, a type-of-service (TOS) byte value of zero, a code of nine (even though it should be zero), the sequence number 295, a random IP ID and ICMP request identifier, and 120 bytes of 0x00 for the data payload.
The second ping query is similar, except a TOS of four (IP_TOS_RELIABILITY) is used, the code is zero, 150 bytes of data is sent, and the ICMP request ID and sequence numbers are incremented by one from the previous query values.
This decodes as:
DFI=N
no ICMP reply will have DF (don't fragment) set
T=3B-45
TTL will be between 0x3B and 0x45
TG=40
IP initial TTL guess
CD=S
ICMP response codes (same as ICMP request probe)
You can find all these codes and details here.
The other fields are contain similarly detailed data for ECN, TCP sequence (SEQ, OPS, WIN, T1) and TCP options (T2-T7).