package com.netifera.platform.net.daemon.sniffing.modules.passivefp; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Set; import com.netifera.platform.net.daemon.sniffing.IIPSniffer; import com.netifera.platform.net.daemon.sniffing.IPacketModuleContext; import com.netifera.platform.net.internal.daemon.sniffing.modules.Activator; import com.netifera.platform.net.model.INetworkEntityFactory; import com.netifera.platform.net.packets.tcpip.IPv4; import com.netifera.platform.net.packets.tcpip.IPv6; import com.netifera.platform.net.packets.tcpip.TCP; import com.netifera.platform.net.sniffing.IPacketFilter; import com.netifera.platform.util.NetworkConstants; import com.netifera.platform.util.addresses.inet.InternetAddress; public class PassiveFingerprint implements IIPSniffer { final static int MODE_SYN = 1; final static int MODE_ACK = 2; final static int MODE_RST = 3; final static int MODE_OPEN = 4; private static final boolean synMode = true; private static final boolean openMode = false; private static final boolean rstMode = false; private static final boolean ackMode = true; private final SignatureSet sigSet = new SignatureSet(synMode, ackMode, rstMode, openMode); private final Set<InternetAddress> foundAddresses = new HashSet<InternetAddress>(); public IPacketFilter getFilter() { return null; } public String getName() { return "Passive OS Fingerprinting"; } public void handleIPv4Packet(IPv4 ipv4, IPacketModuleContext ctx) { if(ipv4.getNextProtocol() != NetworkConstants.IPPROTO_TCP) { return; } TCP tcp = (TCP) ipv4.findHeader(TCP.class); if(tcp == null) { return; } if(foundAddresses.contains(ipv4.getSourceAddress())) { return; } Signature s = handleTCP(ipv4, tcp, ctx); if(s == null) return; log(ctx, "match! [" + ipv4.getSourceAddress() + "] --> " + ipv4.getDestinationAddress() + " is " + s.getOSGenre() + " " + s.getOSVersion()); foundAddresses.add(ipv4.getSourceAddress()); INetworkEntityFactory factory = Activator.getInstance().getNetworkEntityFactory(); factory.setOperatingSystem(ctx.getRealm(), ctx.getSpaceId(), ipv4.getSourceAddress(), s.getOSGenre() + " " + s.getOSVersion()); } public void handleIPv6Packet(IPv6 ipv6, IPacketModuleContext ctx) { // not supported since we rely on IPv4 header fields } private Signature handleTCP(IPv4 ip, TCP tcp, IPacketModuleContext ctx) { if(synMode && tcp.getSYN() && !tcp.getACK()) { return matchPacket(ip, tcp, MODE_SYN, ctx); } else if(ackMode && tcp.getSYN() && tcp.getACK()) { return matchPacket(ip, tcp, MODE_ACK, ctx); } else if(rstMode && tcp.getRST()) { return matchPacket(ip, tcp, MODE_RST, ctx); } else if(openMode && tcp.getACK() && !tcp.getSYN()) { return matchPacket(ip, tcp, MODE_OPEN, ctx); } else { return null; } } private Signature matchPacket(IPv4 ip, TCP tcp, int mode, IPacketModuleContext ctx) { int quirks = 0; if(ip.getHeaderLength32() > 5) { quirks |= Signature.QUIRK_IPOPT; } if(mode == MODE_RST && tcp.getACK()) { quirks |= Signature.QUIRK_RSTACK; } if(tcp.sequence().equals(tcp.ackSequence())) { quirks |= Signature.QUIRK_SEQEQ; } if(tcp.sequence().toInteger() == 0) { quirks |= Signature.QUIRK_SEQ0; } if(mode == MODE_OPEN) { if(tcp.getURG() || tcp.getFIN()) { quirks |= Signature.QUIRK_FLAGS; } } else { if(tcp.getPSH() || tcp.getURG() || tcp.getFIN()) { quirks |= Signature.QUIRK_FLAGS; } } if(tcp.getNextHeader() != null) { quirks |= Signature.QUIRK_DATA; } ByteBuffer options = ByteBuffer.wrap(tcp.getOptionsBuffer()); int mss = 0; int wsc = 0; int tstamp = 0; byte[] op = new byte[Signature.MAXOPT]; int opCount = 0; boolean done = false; while(!done && options.hasRemaining()) { int currentOp = options.get() & 0xFF; switch(currentOp) { case TCP.OPT_EOL: op[opCount++] = TCP.OPT_EOL; if(options.hasRemaining()) { quirks |= Signature.QUIRK_PAST; done = true; } break; case TCP.OPT_NOP: op[opCount++] = TCP.OPT_NOP; break; case TCP.OPT_SACKOK: op[opCount++] = TCP.OPT_SACKOK; options.get(); // length byte break; case TCP.OPT_MAXSEG: if(options.remaining() < 3) { quirks |= Signature.QUIRK_BROKEN; done = true; break; } op[opCount++] = TCP.OPT_MAXSEG; options.get(); // length byte mss = options.getShort() & 0xFFFF; break; case TCP.OPT_WSCALE: if(options.remaining() < 2) { quirks |= Signature.QUIRK_BROKEN; done = true; break; } op[opCount++] = TCP.OPT_WSCALE; options.get(); // length byte wsc = options.get() & 0xFF; break; case TCP.OPT_TIMESTAMP: if(options.remaining() < 9) { quirks |= Signature.QUIRK_BROKEN; done = true; break; } op[opCount++] = TCP.OPT_TIMESTAMP; options.get(); // length byte tstamp = options.getInt(); if(options.getInt() != 0) { quirks |= Signature.QUIRK_T2; } break; default: if(!options.hasRemaining()) { quirks |= Signature.QUIRK_BROKEN; done = true; break; } op[opCount++] = (byte) currentOp; int olen = options.get() & 0xFF; if(olen > 32 || olen > options.remaining()) { // p0f has a bug and won't set the QUIRK flag // in the second case. quirks |= Signature.QUIRK_BROKEN; done = true; break; } options.position(options.position() + olen); if(opCount >= Signature.MAXOPT) { quirks |= Signature.QUIRK_BROKEN; done = true; } } } if(tcp.getACK()) quirks |= Signature.QUIRK_ACK; if(tcp.getURG()) quirks |= Signature.QUIRK_URG; if(tcp.getReserved() != 0) quirks |= Signature.QUIRK_X2; if(ip.getIdentification() == 0) quirks |= Signature.QUIRK_ZEROID; Signature res = sigSet.match(ip.getTotalLength(), ip.getDF(), ip.getTimeToLive(), tcp.getWindow(), op, opCount, mss, wsc, tstamp, ip.getTypeOfService(), quirks, mode); if(res == null) { log(ctx, "Unmatched [" + ip.getSourceAddress() + "] --> " + ip.getDestinationAddress() + " has signature " + sigSet.printSignature(ip.getTotalLength(), ip.getDF(), ip.getTimeToLive(), tcp.getWindow(), op, opCount, mss, wsc, tstamp, ip.getTypeOfService(), quirks, mode)); } return res; } // logging private final static boolean LOGGING_ENABLED = false; private void log(IPacketModuleContext ctx, String message) { if (LOGGING_ENABLED) { ctx.printOutput(message); } } }