|
1 |
| -# pyp0f |
| 1 | +<h1 align="center">pyp0f</h1> |
| 2 | +<p align="center"> |
| 3 | + <em>Native implementation of <strong>p0f v3</strong> in typed Python 3.</em> |
| 4 | +</p> |
2 | 5 |
|
3 |
| -Native implementation of ``p0f`` v3 in typed Python 3. |
| 6 | +--- |
4 | 7 |
|
5 |
| -``pyp0f`` is able to accurately guess the source OS or user application of a given packet with passive fingerprinting. |
| 8 | +`pyp0f` is able to accurately guess the source OS or user application of a given packet with passive fingerprinting, as well as impersonate packets so that `p0f` will think it has been sent by a specific OS. |
6 | 9 |
|
7 |
| -#### Motivation |
8 |
| -- ``pyp0f`` is platform independent, while p0f can be cumbersome to run on some platforms (such as Windows). |
9 |
| -- ``pyp0f`` is mainly used as a library, as opposed to p0f which runs on a seperate process and you query the results using an API. |
10 |
| -- p0f depends on full packet flow details, while ``pyp0f`` attempts to use as little information as possible. For example, you can easily fingerprint one packet from a session without knowing the session history. |
| 10 | +## Motivation |
| 11 | +- `pyp0f` is platform independent (using [Scapy](https://scapy.net)), while `p0f` can be cumbersome to run on some platforms (such as Windows). |
| 12 | +- The implementation and concepts behind `p0f` are very sophisticated, but the tool is written in C which makes it harder to understand and extend. |
| 13 | +Performance is expected to be slower in Python, but `pyp0f` still performs well enough (see [Performance benchmarks](#performance-benchmarks)) |
| 14 | +- `p0f` heavily depends on full packet flow details, while `pyp0f` attempts to use as little information as possible. For example, you may be able to fingerprint a SYN+ACK packet from a session without having the matching SYN packet. |
| 15 | +- `pyp0f` aims to be highly configurable and used as a library, without limiting its effectiveness to one packet format/library, as opposed to `p0f` which runs on a seperate process and you query the results using an API. |
11 | 16 |
|
12 | 17 | ## Installation
|
13 | 18 |
|
14 |
| -```shell |
15 |
| -pip install pyp0f |
| 19 | +```console |
| 20 | +$ pip install pyp0f |
16 | 21 | ```
|
17 | 22 |
|
18 | 23 | ## Features
|
19 |
| -- MTU fingerprinting |
20 |
| -- TCP fingerprinting |
21 |
| -- HTTP fingerprinting |
| 24 | +- [X] Full p0f fingerprinting (MTU, TCP, HTTP) |
| 25 | +- [X] p0f spoof - impersonation (MTU, TCP) |
| 26 | +- [ ] Flow tracking (In progress) |
| 27 | +- [ ] TCP uptime detection (In progress) |
| 28 | +- [ ] NAT detection (In progress) |
22 | 29 |
|
23 |
| -## TODO |
24 |
| -- Flow tracking |
25 |
| -- TCP uptime detection |
26 |
| -- p0f tool loop |
27 |
| -- Impersonation tool |
28 |
| -- NAT detection |
| 30 | +## Docs |
| 31 | +* [Database configuration](#database-configuration) |
| 32 | +* [Fingerprinting](#fingerprinting) |
| 33 | +* [Impersonation](#impersonation) |
| 34 | +* [Real world examples](#real-world-examples) |
| 35 | + * [Sniff connection attempts](#sniff-connection-attempts) |
| 36 | + * [Block connections by certain OS fingerprint](#block-connections-by-certain-os-fingerprint) |
| 37 | + * [Spoof p0f with impersonation](#spoof-p0f-with-impersonation) |
| 38 | +* [Performance benchmarks](#performance-benchmarks) |
29 | 39 |
|
30 |
| -## Usage |
31 |
| -pyp0f accepts SYN, SYN+ACK and HTTP packets. If the packet is invalid for fingerprint, ``pyp0f.exceptions.PacketError`` is raised. |
| 40 | +## Database Configuration |
| 41 | +Before using `pyp0f`, make sure to load the p0f signatures database. |
32 | 42 |
|
33 |
| -### Database |
34 |
| -Before fingerprinting, make sure to load the p0f signatures database. |
35 |
| - |
36 |
| -By default, the included (v3.09b) database will be loaded. However, you can specify a custom database path to |
37 |
| -parse. |
| 43 | +By default, the included (p0f v3.09b) database will be loaded. However, you can specify a custom database path to parse. |
38 | 44 |
|
39 | 45 | ```python
|
40 | 46 | from pyp0f.database import DATABASE
|
41 | 47 |
|
42 |
| -DATABASE.load() |
43 |
| -# or DATABASE.load("path/to/database/file/p0f.fp") |
| 48 | +DATABASE.load() # or DATABASE.load("custom/database/file/p0f.fp") |
| 49 | +``` |
| 50 | + |
| 51 | +## Fingerprinting |
| 52 | +`pyp0f` accepts SYN, SYN+ACK and HTTP packets. Invalid packets raise `pyp0f.exceptions.PacketError`. |
| 53 | + |
| 54 | +`pyp0f` makes sure to copy the packet before using it, to not modify the original accidentally. |
| 55 | + |
| 56 | +Each fingerprint function returns a custom result instance which includes some informative fields that are typed appropriately. |
| 57 | + |
| 58 | +<details markdown="1"> |
| 59 | +<summary>MTU fingerprinting example</summary> |
44 | 60 |
|
45 |
| -print(len(DATABASE)) # 322 |
| 61 | +```python |
| 62 | +from scapy.layers.inet import IP, TCP |
| 63 | +from pyp0f.fingerprint import fingerprint_mtu |
| 64 | +from pyp0f.fingerprint.results import MTUResult |
| 65 | + |
| 66 | +google_packet = IP() / TCP(options=[("MSS", 1430)]) |
| 67 | +result: MTUResult = fingerprint_mtu(google_packet) |
| 68 | +print(result.packet_signature) # MTUPacketSignature(mtu=1470) |
| 69 | +print(result.match) |
| 70 | +# MTURecord( |
| 71 | +# label=MTULabel(name="Google"), |
| 72 | +# signature=MTUSignature(mtu=1470), |
| 73 | +# raw_signature="1470", |
| 74 | +# line_number=67, |
| 75 | +# ) |
46 | 76 | ```
|
47 | 77 |
|
48 |
| -### Fingerprinting |
| 78 | +</details> |
| 79 | + |
| 80 | +<details markdown="1"> |
| 81 | +<summary>TCP fingerprinting example</summary> |
49 | 82 |
|
50 |
| -pyp0f has 3 main functions: |
51 | 83 | ```python
|
52 |
| -from pyp0f.fingerprint import fingerprint_mtu, fingerprint_tcp, fingerprint_http |
| 84 | +from scapy.layers.inet import IP, TCP |
| 85 | +from pyp0f.fingerprint import fingerprint_tcp |
| 86 | +from pyp0f.fingerprint.results import TCPResult |
| 87 | + |
| 88 | +linux_packet = IP(tos=0x10, flags=0x02, ttl=58) / TCP( |
| 89 | + seq=1, |
| 90 | + window=29200, |
| 91 | + options=[("MSS", 1460), ("SAckOK", b""), ("Timestamp", (177816630, 0)), ("NOP", None), ("WScale", 7)], |
| 92 | +) |
| 93 | +result: TCPResult = fingerprint_tcp(linux_packet) |
| 94 | +print(result.distance) # 6 |
| 95 | +print(result.match) |
| 96 | +# TCPMatch( |
| 97 | +# type=<TCPMatchType.EXACT: 1>, |
| 98 | +# record=TCPRecord( |
| 99 | +# label=Label(name='Linux', is_generic=False, os_class='unix', flavor='3.11 and newer', sys=()), |
| 100 | +# signature=TCPSignature( |
| 101 | +# ip_version=-1, |
| 102 | +# ip_options_length=0, |
| 103 | +# ttl=64, |
| 104 | +# is_bad_ttl=False, |
| 105 | +# window=WindowSignature(type=<WindowType.MSS: 4>, size=20, scale=7), |
| 106 | +# options=OptionsSignature( |
| 107 | +# layout=[<TCPOption.MSS: 2>, <TCPOption.SACKOK: 4>, <TCPOption.TS: 8>, <TCPOption.NOP: 1>, <TCPOption.WS: 3>], |
| 108 | +# mss=-1, |
| 109 | +# eol_padding_length=0 |
| 110 | +# ), |
| 111 | +# payload_class=0, |
| 112 | +# quirks=<Quirk.NZ_ID|DF: 6> |
| 113 | +# ), |
| 114 | +# raw_signature='*:64:0:*:mss*20,7:mss,sok,ts,nop,ws:df,id+:0', |
| 115 | +# line_number=97 |
| 116 | +# ) |
| 117 | +# ) |
53 | 118 | ```
|
54 | 119 |
|
55 |
| -Each fingerprint function returns a custom result object which includes some informative fields that are typed appropriately, such as: |
56 |
| -- The parsed packet |
57 |
| -- The calculated packet signature |
58 |
| -- The matched record, if any |
| 120 | +</details> |
59 | 121 |
|
60 |
| -#### Examples |
| 122 | +<details markdown="1"> |
| 123 | +<summary>HTTP fingerprinting example</summary> |
61 | 124 |
|
62 | 125 | ```python
|
63 |
| -from scapy.layers.inet import IP |
| 126 | +from pyp0f.fingerprint import fingerprint_http |
| 127 | +from pyp0f.fingerprint.results import HTTPResult |
64 | 128 |
|
65 |
| -from pyp0f.fingerprint import fingerprint_mtu, fingerprint_tcp, fingerprint_http |
| 129 | +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" |
| 130 | +result: HTTPResult = fingerprint_http(apache_payload) |
| 131 | +print(result.dishonest) # False |
| 132 | +print(result.match) |
| 133 | +# HTTPRecord( |
| 134 | +# label=Label( |
| 135 | +# name="Apache", |
| 136 | +# is_generic=False, |
| 137 | +# os_class="!", |
| 138 | +# flavor="2.x", |
| 139 | +# sys=("@unix", "Windows"), |
| 140 | +# ), |
| 141 | +# signature=HTTPSignature( |
| 142 | +# version=1, |
| 143 | +# headers=[ |
| 144 | +# SignatureHeader( |
| 145 | +# name=b"Date", lower_name=b"date", is_optional=False, value=None |
| 146 | +# ), |
| 147 | +# SignatureHeader( |
| 148 | +# name=b"Server", lower_name=b"server", is_optional=False, value=None |
| 149 | +# ), |
| 150 | +# ... |
| 151 | +# ], |
| 152 | +# expected_software=b"Apache", |
| 153 | +# absent_headers={b"keep-alive"}, |
| 154 | +# ... |
| 155 | +# ), |
| 156 | +# raw_signature="1:Date,Server,?Last-Modified,?Accept-Ranges=[bytes],?Content-Length,?Connection=[close],?Transfer-Encoding=[chunked],Content-Type:Keep-Alive:Apache", |
| 157 | +# line_number=883, |
| 158 | +# ) |
| 159 | +``` |
66 | 160 |
|
67 |
| -packet = IP(b'...') |
68 |
| -mtu_result = fingerprint_mtu(packet) |
69 |
| -tcp_result = fingerprint_tcp(packet) |
70 |
| -http_result = fingerprint_http(packet) |
| 161 | +</details> |
71 | 162 |
|
72 |
| -print(mtu_result.match.label.name) # Ethernet or modem |
73 |
| -print(tcp_result.match.record.label.dump()) # s:win:Windows:7 or 8 |
74 |
| -print(http_result.match.label.dump()) # s:!:nginx:1.x |
| 163 | +## Impersonation |
| 164 | +`pyp0f` provides functionality to modify Scapy packets so that `p0f` will think it has been sent by a specific OS. |
| 165 | + |
| 166 | +Each impersonation method must be provided with a record label or signature to impersonate. |
| 167 | + |
| 168 | +<details markdown="1"> |
| 169 | +<summary>MTU impersonation example</summary> |
| 170 | + |
| 171 | +```python |
| 172 | +from scapy.layers.inet import IP, TCP |
| 173 | +from pyp0f.impersonate import impersonate_mtu |
| 174 | +from pyp0f.fingerprint import fingerprint_mtu |
| 175 | + |
| 176 | +impersonated_packet = impersonate_mtu( |
| 177 | + IP() / TCP(), |
| 178 | + raw_label="generic tunnel or VPN", # impersonate using a label |
| 179 | + raw_signature="1300", # or using a signature |
| 180 | +) |
| 181 | +result = fingerprint_mtu(impersonated_packet) # MTUResult for "generic tunnel or VPN" |
75 | 182 | ```
|
76 | 183 |
|
| 184 | +</details> |
| 185 | + |
| 186 | + |
| 187 | +<details markdown="1"> |
| 188 | +<summary>TCP impersonation example</summary> |
| 189 | + |
| 190 | +```python |
| 191 | +from scapy.layers.inet import IP, TCP |
| 192 | +from pyp0f.impersonate import impersonate_tcp |
| 193 | +from pyp0f.fingerprint import fingerprint_tcp |
| 194 | + |
| 195 | +impersonated_packet = impersonate_tcp( |
| 196 | + IP() / TCP(), |
| 197 | + raw_label="s:unix:OpenVMS:7.x", # impersonate using a label |
| 198 | + raw_signature="4:64:0:1460:61440,0:mss,nop,ws::0", # or using a signature |
| 199 | +) |
| 200 | +result = fingerprint_tcp(impersonated_packet) # TCPResult for "s:unix:OpenVMS:7.x" |
| 201 | +``` |
| 202 | + |
| 203 | +</details> |
| 204 | + |
| 205 | +## Real World Examples |
| 206 | +`pyp0f` can be used in real world scenarios, whether its to passivly fingerprint remote hosts, |
| 207 | +or to deceive remote `p0f`. |
| 208 | + |
| 209 | +### Sniff connection attempts |
| 210 | +Using `scapy` to sniff incoming packets, we passively fingerprint remote hosts attempting to connect to our HTTP server. |
| 211 | + |
| 212 | +See example [source code](./examples/fingerprint/scapy-sniff.py) |
| 213 | + |
| 214 | +### Block connections by certain OS fingerprint |
| 215 | +With `pyp0f.fingerprint` we can block certain OS users attempting to connect to our HTTP server (in this example - Windows). |
| 216 | + |
| 217 | +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. |
| 218 | + |
| 219 | +See example [source code](./examples/fingerprint/block-os.py) |
| 220 | + |
| 221 | +### Spoof p0f with impersonation |
| 222 | +With `pyp0f.impersonate` we can spoof running p0f by impersonating a certain OS. |
| 223 | + |
| 224 | +In this case, I used Windows 10 with Ethernet, and impersonated Linux 2.2.x-3.x (barebone) with Google MTU. |
| 225 | + |
| 226 | +[Browser leaks (TCP/IP Fingerprint section)](https://browserleaks.com/ip): |
| 227 | +<div align="center"> |
| 228 | + <img src="./docs/images/browserleaks-normal.PNG"> |
| 229 | + <img src="./docs/images/browserleaks-spoof.PNG"> |
| 230 | +</div> |
| 231 | + |
| 232 | +Local Ubuntu VM running p0f: |
| 233 | +<div align="center"> |
| 234 | + <img src="./docs/images/p0f-normal.PNG"> |
| 235 | + <img src="./docs/images/p0f-spoof.PNG"> |
| 236 | +</div> |
| 237 | + |
| 238 | +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. |
| 239 | + |
| 240 | +See example [source code](./examples/impersonate/spoof-p0f.py) |
| 241 | + |
| 242 | +## Performance benchmarks |
| 243 | +`pyp0f` includes a file to benchmark the main methods it provides - fingerprint, impersonate. |
| 244 | +The latest results were ran using an i5 7th gen. |
| 245 | + |
| 246 | +```console |
| 247 | +Performance benchmark: Load Database |
| 248 | +Ran 1000 iterations in 9.76s |
| 249 | +Average time for one iteration of Load Database: 9ms |
| 250 | +----------------------------------- |
| 251 | +Performance benchmark: MTU Fingerprint (25 Packets) |
| 252 | +Ran 1000 iterations in 19.808s |
| 253 | +Average time for one iteration of MTU Fingerprint (25 Packets): 19ms |
| 254 | +----------------------------------- |
| 255 | +Performance benchmark: TCP Fingerprint (153 Packets) |
| 256 | +Ran 1000 iterations in 227.91s |
| 257 | +Average time for one iteration of TCP Fingerprint (153 Packets): 227ms |
| 258 | +----------------------------------- |
| 259 | +Performance benchmark: HTTP Fingerprint (3 Packets) |
| 260 | +Ran 1000 iterations in 0.15791s |
| 261 | +Average time for one iteration of HTTP Fingerprint (3 Packets): 0ms |
| 262 | +----------------------------------- |
| 263 | +Performance benchmark: MTU Impersonation (25 Signatures) |
| 264 | +Ran 1000 iterations in 4.5696s |
| 265 | +Average time for one iteration of MTU Impersonation (25 Signatures): 4ms |
| 266 | +----------------------------------- |
| 267 | +Performance benchmark: TCP Impersonation (153 Signatures) |
| 268 | +Ran 1000 iterations in 103.97s |
| 269 | +Average time for one iteration of TCP Impersonation (153 Signatures): 103ms |
| 270 | +``` |
| 271 | + |
| 272 | +See benchmark [source code](./scripts/benchmark.py) |
| 273 | + |
| 274 | +## Sources |
| 275 | +- [p0f source code](https://github.com/p0f/p0f) |
| 276 | +- [Scapy docs & source code](https://scapy.net) |
| 277 | + |
77 | 278 | ## Authors
|
78 | 279 | - **Itay Margolin** - [Nisitay](https://github.com/Nisitay)
|
0 commit comments