|
| 1 | +<h1 align="center">Documentation</h1> |
| 2 | + |
| 3 | +* [Database configuration](#database-configuration) |
| 4 | +* [Fingerprinting](#fingerprinting) |
| 5 | +* [Impersonation](#impersonation) |
| 6 | +* [Real world examples](#real-world-examples) |
| 7 | + * [Sniff connection attempts](#sniff-connection-attempts) |
| 8 | + * [Block connections by certain OS fingerprint](#block-connections-by-certain-os-fingerprint) |
| 9 | + * [Spoof p0f with impersonation](#spoof-p0f-with-impersonation) |
| 10 | +* [Performance benchmarks](#performance-benchmarks) |
| 11 | + |
| 12 | +## Database Configuration |
| 13 | +Before using `pyp0f`, make sure to load the p0f signatures database. |
| 14 | + |
| 15 | +By default, the included (p0f v3.09b) database will be loaded. However, you can specify a custom database path to parse. |
| 16 | + |
| 17 | +```python |
| 18 | +from pyp0f.database import DATABASE |
| 19 | + |
| 20 | +DATABASE.load() # or DATABASE.load("custom/database/file/p0f.fp") |
| 21 | +``` |
| 22 | + |
| 23 | +## Fingerprinting |
| 24 | +`pyp0f` accepts SYN, SYN+ACK and HTTP packets. Invalid packets raise `pyp0f.exceptions.PacketError`. |
| 25 | + |
| 26 | +`pyp0f` makes sure to copy the packet before using it, to not modify the original accidentally. |
| 27 | + |
| 28 | +Each fingerprint function returns a custom result instance which includes some informative fields that are typed appropriately. |
| 29 | + |
| 30 | +<details markdown="1"> |
| 31 | +<summary>MTU fingerprinting example</summary> |
| 32 | + |
| 33 | +```python |
| 34 | +from scapy.layers.inet import IP, TCP |
| 35 | +from pyp0f.fingerprint import fingerprint_mtu |
| 36 | +from pyp0f.fingerprint.results import MTUResult |
| 37 | + |
| 38 | +google_packet = IP() / TCP(options=[("MSS", 1430)]) |
| 39 | +result: MTUResult = fingerprint_mtu(google_packet) |
| 40 | +print(result.packet_signature) # MTUPacketSignature(mtu=1470) |
| 41 | +print(result.match) |
| 42 | +# MTURecord( |
| 43 | +# label=MTULabel(name="Google"), |
| 44 | +# signature=MTUSignature(mtu=1470), |
| 45 | +# raw_signature="1470", |
| 46 | +# line_number=67, |
| 47 | +# ) |
| 48 | +``` |
| 49 | + |
| 50 | +</details> |
| 51 | + |
| 52 | +<details markdown="1"> |
| 53 | +<summary>TCP fingerprinting example</summary> |
| 54 | + |
| 55 | +```python |
| 56 | +from scapy.layers.inet import IP, TCP |
| 57 | +from pyp0f.fingerprint import fingerprint_tcp |
| 58 | +from pyp0f.fingerprint.results import TCPResult |
| 59 | + |
| 60 | +linux_packet = IP(tos=0x10, flags=0x02, ttl=58) / TCP( |
| 61 | + seq=1, |
| 62 | + window=29200, |
| 63 | + options=[("MSS", 1460), ("SAckOK", b""), ("Timestamp", (177816630, 0)), ("NOP", None), ("WScale", 7)], |
| 64 | +) |
| 65 | +result: TCPResult = fingerprint_tcp(linux_packet) |
| 66 | +print(result.distance) # 6 |
| 67 | +print(result.match) |
| 68 | +# TCPMatch( |
| 69 | +# type=<TCPMatchType.EXACT: 1>, |
| 70 | +# record=TCPRecord( |
| 71 | +# label=Label(name='Linux', is_generic=False, os_class='unix', flavor='3.11 and newer', sys=()), |
| 72 | +# signature=TCPSignature( |
| 73 | +# ip_version=-1, |
| 74 | +# ip_options_length=0, |
| 75 | +# ttl=64, |
| 76 | +# is_bad_ttl=False, |
| 77 | +# window=WindowSignature(type=<WindowType.MSS: 4>, size=20, scale=7), |
| 78 | +# options=OptionsSignature( |
| 79 | +# layout=[<TCPOption.MSS: 2>, <TCPOption.SACKOK: 4>, <TCPOption.TS: 8>, <TCPOption.NOP: 1>, <TCPOption.WS: 3>], |
| 80 | +# mss=-1, |
| 81 | +# eol_padding_length=0 |
| 82 | +# ), |
| 83 | +# payload_class=0, |
| 84 | +# quirks=<Quirk.NZ_ID|DF: 6> |
| 85 | +# ), |
| 86 | +# raw_signature='*:64:0:*:mss*20,7:mss,sok,ts,nop,ws:df,id+:0', |
| 87 | +# line_number=97 |
| 88 | +# ) |
| 89 | +# ) |
| 90 | +``` |
| 91 | + |
| 92 | +</details> |
| 93 | + |
| 94 | +<details markdown="1"> |
| 95 | +<summary>HTTP fingerprinting example</summary> |
| 96 | + |
| 97 | +```python |
| 98 | +from pyp0f.fingerprint import fingerprint_http |
| 99 | +from pyp0f.fingerprint.results import HTTPResult |
| 100 | + |
| 101 | +apache_payload = b"HTTP/1.1 200 OK\r\nDate: Fri, 10 Jun 2011 13:27:01 GMT\r\nServer: Apache\r\nLast-Modified: Thu, 09 Jun 2011 17:25:43 GMT\r\nExpires: Mon, 13 Jun 2011 17:25:43 GMT\r\nETag: 963D6BC0ED128283945AF1FB57899C9F3ABF50B3\r\nCache-Control: max-age=272921,public,no-transform,must-revalidate\r\nContent-Length: 491\r\nConnection: close\r\nContent-Type: application/ocsp-response\r\n\r\n" |
| 102 | +result: HTTPResult = fingerprint_http(apache_payload) |
| 103 | +print(result.dishonest) # False |
| 104 | +print(result.match) |
| 105 | +# HTTPRecord( |
| 106 | +# label=Label( |
| 107 | +# name="Apache", |
| 108 | +# is_generic=False, |
| 109 | +# os_class="!", |
| 110 | +# flavor="2.x", |
| 111 | +# sys=("@unix", "Windows"), |
| 112 | +# ), |
| 113 | +# signature=HTTPSignature( |
| 114 | +# version=1, |
| 115 | +# headers=[ |
| 116 | +# SignatureHeader( |
| 117 | +# name=b"Date", lower_name=b"date", is_optional=False, value=None |
| 118 | +# ), |
| 119 | +# SignatureHeader( |
| 120 | +# name=b"Server", lower_name=b"server", is_optional=False, value=None |
| 121 | +# ), |
| 122 | +# ... |
| 123 | +# ], |
| 124 | +# expected_software=b"Apache", |
| 125 | +# absent_headers={b"keep-alive"}, |
| 126 | +# ... |
| 127 | +# ), |
| 128 | +# raw_signature="1:Date,Server,?Last-Modified,?Accept-Ranges=[bytes],?Content-Length,?Connection=[close],?Transfer-Encoding=[chunked],Content-Type:Keep-Alive:Apache", |
| 129 | +# line_number=883, |
| 130 | +# ) |
| 131 | +``` |
| 132 | + |
| 133 | +</details> |
| 134 | + |
| 135 | +## Impersonation |
| 136 | +`pyp0f` provides functionality to modify Scapy packets so that `p0f` will think it has been sent by a specific OS. |
| 137 | + |
| 138 | +Each impersonation method must be provided with a record label or signature to impersonate. |
| 139 | + |
| 140 | +<details markdown="1"> |
| 141 | +<summary>MTU impersonation example</summary> |
| 142 | + |
| 143 | +```python |
| 144 | +from scapy.layers.inet import IP, TCP |
| 145 | +from pyp0f.impersonate import impersonate_mtu |
| 146 | +from pyp0f.fingerprint import fingerprint_mtu |
| 147 | + |
| 148 | +impersonated_packet = impersonate_mtu( |
| 149 | + IP() / TCP(), |
| 150 | + raw_label="generic tunnel or VPN", # impersonate using a label |
| 151 | + raw_signature="1300", # or using a signature |
| 152 | +) |
| 153 | +result = fingerprint_mtu(impersonated_packet) # MTUResult for "generic tunnel or VPN" |
| 154 | +``` |
| 155 | + |
| 156 | +</details> |
| 157 | + |
| 158 | + |
| 159 | +<details markdown="1"> |
| 160 | +<summary>TCP impersonation example</summary> |
| 161 | + |
| 162 | +```python |
| 163 | +from scapy.layers.inet import IP, TCP |
| 164 | +from pyp0f.impersonate import impersonate_tcp |
| 165 | +from pyp0f.fingerprint import fingerprint_tcp |
| 166 | + |
| 167 | +impersonated_packet = impersonate_tcp( |
| 168 | + IP() / TCP(), |
| 169 | + raw_label="s:unix:OpenVMS:7.x", # impersonate using a label |
| 170 | + raw_signature="4:64:0:1460:61440,0:mss,nop,ws::0", # or using a signature |
| 171 | +) |
| 172 | +result = fingerprint_tcp(impersonated_packet) # TCPResult for "s:unix:OpenVMS:7.x" |
| 173 | +``` |
| 174 | + |
| 175 | +</details> |
| 176 | + |
| 177 | +## Real World Examples |
| 178 | +`pyp0f` can be used in real world scenarios, whether its to passivly fingerprint remote hosts, |
| 179 | +or to deceive remote `p0f`. |
| 180 | + |
| 181 | +### Sniff connection attempts |
| 182 | +Using `scapy` to sniff incoming packets, we passively fingerprint remote hosts attempting to connect to our HTTP server. |
| 183 | + |
| 184 | +See example [source code](../examples/fingerprint/scapy-sniff.py) |
| 185 | + |
| 186 | +### Block connections by certain OS fingerprint |
| 187 | +With `pyp0f.fingerprint` we can block certain OS users attempting to connect to our HTTP server (in this example - Windows). |
| 188 | + |
| 189 | +We use `pydivert` to capture incoming packets before they enter the network stack, fingerprint them, and drop every connection attempt to our server from Windows hosts. |
| 190 | + |
| 191 | +See example [source code](../examples/fingerprint/block-os.py) |
| 192 | + |
| 193 | +### Spoof p0f with impersonation |
| 194 | +With `pyp0f.impersonate` we can spoof running p0f by impersonating a certain OS. |
| 195 | + |
| 196 | +In this case, I used Windows 10 with Ethernet, and impersonated Linux 2.2.x-3.x (barebone) with Google MTU. |
| 197 | + |
| 198 | +[Browser leaks (TCP/IP Fingerprint section)](https://browserleaks.com/ip): |
| 199 | +<div align="center"> |
| 200 | + <img src="./images/browserleaks-normal.PNG"> |
| 201 | + <img src="./images/browserleaks-spoof.PNG"> |
| 202 | +</div> |
| 203 | + |
| 204 | +Local Ubuntu VM running p0f: |
| 205 | +<div align="center"> |
| 206 | + <img src="./images/p0f-normal.PNG"> |
| 207 | + <img src="./images/p0f-spoof.PNG"> |
| 208 | +</div> |
| 209 | + |
| 210 | +We use `pydivert` to capture packets before they leave the network stack, create a new packet that impersonates an OS, and finally re-inject the impersonated packet back to the network stack to spoof p0f on the other end. |
| 211 | + |
| 212 | +See example [source code](../examples/impersonate/spoof-p0f.py) |
| 213 | + |
| 214 | +## Performance benchmarks |
| 215 | +`pyp0f` includes a file to benchmark the main methods it provides - fingerprint, impersonate. |
| 216 | +The latest results were ran using an i5 7th gen. |
| 217 | + |
| 218 | +```console |
| 219 | +Performance benchmark: Load Database |
| 220 | +Ran 1000 iterations in 9.76s |
| 221 | +Average time for one iteration of Load Database: 9ms |
| 222 | +----------------------------------- |
| 223 | +Performance benchmark: MTU Fingerprint (25 Packets) |
| 224 | +Ran 1000 iterations in 19.808s |
| 225 | +Average time for one iteration of MTU Fingerprint (25 Packets): 19ms |
| 226 | +----------------------------------- |
| 227 | +Performance benchmark: TCP Fingerprint (153 Packets) |
| 228 | +Ran 1000 iterations in 227.91s |
| 229 | +Average time for one iteration of TCP Fingerprint (153 Packets): 227ms |
| 230 | +----------------------------------- |
| 231 | +Performance benchmark: HTTP Fingerprint (3 Packets) |
| 232 | +Ran 1000 iterations in 0.15791s |
| 233 | +Average time for one iteration of HTTP Fingerprint (3 Packets): 0ms |
| 234 | +----------------------------------- |
| 235 | +Performance benchmark: MTU Impersonation (25 Signatures) |
| 236 | +Ran 1000 iterations in 4.5696s |
| 237 | +Average time for one iteration of MTU Impersonation (25 Signatures): 4ms |
| 238 | +----------------------------------- |
| 239 | +Performance benchmark: TCP Impersonation (153 Signatures) |
| 240 | +Ran 1000 iterations in 103.97s |
| 241 | +Average time for one iteration of TCP Impersonation (153 Signatures): 103ms |
| 242 | +``` |
| 243 | + |
| 244 | +See benchmark [source code](../scripts/benchmark.py) |
0 commit comments