Skip to content

Commit 10bb637

Browse files
authored
Merge pull request #2 from Nisitay/feat/impersonation
p0f spoofing - impersonation
2 parents 5869e82 + 1eb5e3c commit 10bb637

File tree

127 files changed

+3621
-2042
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+3621
-2042
lines changed

.flake8

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
[flake8]
22
max-line-length = 88
33
ignore = E501, W503
4-
exclude = __init__.py

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Release History
2+
3+
## pyp0f 0.2.1
4+
5+
* Added impersonation utility.
6+
7+
## pyp0f 0.1.2
8+
9+
* Initial release.

README.md

Lines changed: 42 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,66 @@
1-
# pyp0f
1+
<h1 align="center">pyp0f</h1>
2+
<p align="center">Native implementation of <strong>p0f v3</strong> in typed Python 3.</p>
23

3-
Native implementation of ``p0f`` v3 in typed Python 3.
44

5-
``pyp0f`` is able to accurately guess the source OS or user application of a given packet with passive fingerprinting.
5+
---
66

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.
7+
**Documentation**: <a href="https://github.com/Nisitay/pyp0f/blob/master/docs/README.md" target="_blank">https://github.com/Nisitay/pyp0f/blob/master/docs/README.md</a>
118

12-
## Installation
9+
**Source Code**: <a href="https://github.com/Nisitay/pyp0f" target="_blank">https://github.com/Nisitay/pyp0f</a>
10+
11+
---
12+
13+
`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.
1314

14-
```shell
15-
pip install pyp0f
15+
## Motivation
16+
- `pyp0f` is platform independent (using [Scapy](https://scapy.net)), while `p0f` can be cumbersome to run on some platforms (such as Windows).
17+
- The implementation and concepts behind `p0f` are very sophisticated, but the tool is written in C which makes it harder to understand and extend. Performance is expected to be slower in Python, but `pyp0f` still performs well enough (see [Performance benchmarks](https://github.com/Nisitay/pyp0f/blob/master/docs/README.md#performance-benchmarks))
18+
- `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.
19+
- `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.
20+
21+
## Installation
22+
```console
23+
$ pip install pyp0f
1624
```
1725

1826
## Features
19-
- MTU fingerprinting
20-
- TCP fingerprinting
21-
- HTTP fingerprinting
27+
- Full p0f fingerprinting (MTU, TCP, HTTP)
28+
- p0f spoofing - impersonation (MTU, TCP)
2229

23-
## TODO
30+
## In Progress
2431
- Flow tracking
2532
- TCP uptime detection
26-
- p0f tool loop
27-
- Impersonation tool
2833
- NAT detection
2934

30-
## Usage
31-
pyp0f accepts SYN, SYN+ACK and HTTP packets. If the packet is invalid for fingerprint, ``pyp0f.exceptions.PacketError`` is raised.
32-
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.
38-
35+
## Getting Started
3936
```python
37+
from scapy.layers.inet import IP, TCP
4038
from pyp0f.database import DATABASE
41-
42-
DATABASE.load()
43-
# or DATABASE.load("path/to/database/file/p0f.fp")
44-
45-
print(len(DATABASE)) # 322
46-
```
47-
48-
### Fingerprinting
49-
50-
pyp0f has 3 main functions:
51-
```python
5239
from pyp0f.fingerprint import fingerprint_mtu, fingerprint_tcp, fingerprint_http
53-
```
40+
from pyp0f.fingerprint.results import MTUResult, TCPResult, HTTPResult
5441

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
42+
DATABASE.load() # Load the fingerprints database
5943

60-
#### Examples
44+
# MTU Fingerprinting
45+
google_packet = IP() / TCP(options=[("MSS", 1430)])
46+
mtu_result: MTUResult = fingerprint_mtu(google_packet)
6147

62-
```python
63-
from scapy.layers.inet import IP
64-
65-
from pyp0f.fingerprint import fingerprint_mtu, fingerprint_tcp, fingerprint_http
48+
# TCP Fingerprinting
49+
linux_packet = IP(tos=0x10, flags=0x02, ttl=58) / TCP(
50+
seq=1,
51+
window=29200,
52+
options=[("MSS", 1460), ("SAckOK", b""), ("Timestamp", (177816630, 0)), ("NOP", None), ("WScale", 7)],
53+
)
54+
tcp_result: TCPResult = fingerprint_tcp(linux_packet)
6655

67-
packet = IP(b'...')
68-
mtu_result = fingerprint_mtu(packet)
69-
tcp_result = fingerprint_tcp(packet)
70-
http_result = fingerprint_http(packet)
71-
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
56+
# HTTP Fingerprinting
57+
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"
58+
http_result: HTTPResult = fingerprint_http(apache_payload)
7559
```
7660

61+
## Sources
62+
- [p0f source code](https://github.com/p0f/p0f)
63+
- [Scapy docs & source code](https://scapy.net)
64+
7765
## Authors
7866
- **Itay Margolin** - [Nisitay](https://github.com/Nisitay)

docs/README.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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)

docs/images/browserleaks-normal.PNG

3.58 KB
Loading

docs/images/browserleaks-spoof.PNG

3.86 KB
Loading

docs/images/p0f-normal.PNG

41.7 KB
Loading

docs/images/p0f-spoof.PNG

37.6 KB
Loading

0 commit comments

Comments
 (0)