mirror of
https://github.com/ivre/masscanned.git
synced 2025-10-02 22:58:20 +00:00
Compare commits
545 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df0dacb85a | ||
|
|
d47b3d7b62 | ||
|
|
ff8b360021 | ||
|
|
6fd6872372 | ||
|
|
6cc6232066 | ||
|
|
40f1c38f4c | ||
|
|
ebf55aff47 | ||
|
|
de8d6bfafc | ||
|
|
76704c71c4 | ||
|
|
68db6e757e | ||
|
|
48070b0fca | ||
|
|
b5b8dbd328 | ||
|
|
a52ae83fdd | ||
|
|
ce33df4ffc | ||
|
|
4c14fe4c4c | ||
|
|
2f3dccfd7f | ||
|
|
e31cab4b4f | ||
|
|
53cb47f493 | ||
|
|
70d74ce840 | ||
|
|
046145ac90 | ||
|
|
9c04957c32 | ||
|
|
8e25fae44d | ||
|
|
aa91ed8c26 | ||
|
|
ab748df291 | ||
|
|
092e6821ec | ||
|
|
1a8566c736 | ||
|
|
34db7abc0e | ||
|
|
5d8c332043 | ||
|
|
93807104c4 | ||
|
|
3d2ae6a62a | ||
|
|
4f54661828 | ||
|
|
d5feca7d21 | ||
|
|
1f113a09c3 | ||
|
|
e8d595ec52 | ||
|
|
3e3a7b5fd0 | ||
|
|
98d1631ca1 | ||
|
|
4cc2ed2bf4 | ||
|
|
e9dca669e9 | ||
|
|
ec1d564897 | ||
|
|
9ba56354f3 | ||
|
|
966bf7fad3 | ||
|
|
0e971f0306 | ||
|
|
9345a1294b | ||
|
|
a01f63839c | ||
|
|
42d8b70d28 | ||
|
|
da0a3b02ab | ||
|
|
d43ab4716f | ||
|
|
e7d573b4db | ||
|
|
ca3d7dc6f1 | ||
|
|
e6454d5f68 | ||
|
|
695b132605 | ||
|
|
34ca9b6ae0 | ||
|
|
bb8c5fa184 | ||
|
|
64698a6625 | ||
|
|
fe0312b066 | ||
|
|
c852680930 | ||
|
|
f5d40dd1a8 | ||
|
|
7fd54daafd | ||
|
|
5b99525852 | ||
|
|
bb95ceeef0 | ||
|
|
436026f83c | ||
|
|
a83f80f8c8 | ||
|
|
c01ab14225 | ||
|
|
6e133021b7 | ||
|
|
e07aacd2a3 | ||
|
|
a41cdc4dd3 | ||
|
|
fa94f2a809 | ||
|
|
515a4752a0 | ||
|
|
28da801ceb | ||
|
|
6ea7ec069a | ||
|
|
0969f3f59b | ||
|
|
7b8f79903e | ||
|
|
1428fec25b | ||
|
|
ec828f83b3 | ||
|
|
e540a1e0f5 | ||
|
|
b1af76e4fd | ||
|
|
058790f07c | ||
|
|
fd26da0652 | ||
|
|
e8922074e3 | ||
|
|
08a8c7f3a9 | ||
|
|
a78934b911 | ||
|
|
d563bb36a5 | ||
|
|
3d3058dc94 | ||
|
|
4666ae3785 | ||
|
|
04ab094f0f | ||
|
|
625672f8f8 | ||
|
|
84cc2dce86 | ||
|
|
24be84814c | ||
|
|
91ec1d2b1b | ||
|
|
45f815d60a | ||
|
|
101a62c090 | ||
|
|
36f24b8977 | ||
|
|
570829cee6 | ||
|
|
9fdfd8bf72 | ||
|
|
6c34c625f1 | ||
|
|
65e053e842 | ||
|
|
623a144609 | ||
|
|
631b6868d6 | ||
|
|
126425833e | ||
|
|
1b3755df91 | ||
|
|
fb547aff3b | ||
|
|
7fc4ac2441 | ||
|
|
bd799eaed7 | ||
|
|
16da61c797 | ||
|
|
a3e0f468c2 | ||
|
|
40191729db | ||
|
|
456b9a92b6 | ||
|
|
85c88a94e4 | ||
|
|
20f422dfba | ||
|
|
7787da5a5d | ||
|
|
f903d9db86 | ||
|
|
ec6a7856c7 | ||
|
|
42821d2ae7 | ||
|
|
c74f24bcdb | ||
|
|
f1bb7c40da | ||
|
|
8f2cea06a0 | ||
|
|
9e7e816cdd | ||
|
|
f68e6e3484 | ||
|
|
70e9edb8ea | ||
|
|
8a34ff6214 | ||
|
|
4a24a30ee6 | ||
|
|
b29a95da7b | ||
|
|
db8d1dbf52 | ||
|
|
55f8509209 | ||
|
|
9ca4609b66 | ||
|
|
b677b1c4be | ||
|
|
647ca441ad | ||
|
|
8690aed6d7 | ||
|
|
d1d035890a | ||
|
|
69af3a42c0 | ||
|
|
1cdc7b0b31 | ||
|
|
0354f68b72 | ||
|
|
ab4cf78638 | ||
|
|
936207f0a3 | ||
|
|
dc1d0740ad | ||
|
|
541b1c2887 | ||
|
|
283cf235b8 | ||
|
|
14a1387251 | ||
|
|
80abf6b685 | ||
|
|
c383c6ed88 | ||
|
|
10c353940f | ||
|
|
656bf3951e | ||
|
|
8b5ba06ae3 | ||
|
|
513cc8f43a | ||
|
|
e0aa8ae62c | ||
|
|
0541223ee1 | ||
|
|
9d9b8b2757 | ||
|
|
890f2abb55 | ||
|
|
fa7c35049d | ||
|
|
b7ae19c109 | ||
|
|
627d1ec22b | ||
|
|
4ccb4f05d7 | ||
|
|
254e6ebc7e | ||
|
|
be4d9480ab | ||
|
|
104a91839a | ||
|
|
050cbdd897 | ||
|
|
f2ae05b3af | ||
|
|
227af61527 | ||
|
|
e96cb84ed4 | ||
|
|
b8eae57022 | ||
|
|
d134404860 | ||
|
|
35b4a53b0b | ||
|
|
4ea3a8df38 | ||
|
|
9e23e342a6 | ||
|
|
3ceff8e112 | ||
|
|
006bf6713a | ||
|
|
b642c34e42 | ||
|
|
7406a1a191 | ||
|
|
e5d9633904 | ||
|
|
e1756f9345 | ||
|
|
07b9af36f3 | ||
|
|
521236b60c | ||
|
|
c02ea0d62e | ||
|
|
13c7d2d3c1 | ||
|
|
a0b8572089 | ||
|
|
5383927684 | ||
|
|
e9bc2f611b | ||
|
|
66d4622238 | ||
|
|
446fbf9bd7 | ||
|
|
ffcbc54702 | ||
|
|
d6dda62b4e | ||
|
|
0ada44b229 | ||
|
|
28095b5a4e | ||
|
|
15e981b45d | ||
|
|
f804962dd3 | ||
|
|
7cfa11015b | ||
|
|
1f676ae06d | ||
|
|
a3e7fa92e7 | ||
|
|
50c9984f54 | ||
|
|
0b20421e7c | ||
|
|
a28d450eb1 | ||
|
|
b2ce253dbc | ||
|
|
4513e93a92 | ||
|
|
c0bb698834 | ||
|
|
74474df673 | ||
|
|
12d95aa35c | ||
|
|
bc99e527ab | ||
|
|
807a185db9 | ||
|
|
caab648c48 | ||
|
|
055a0ca6d3 | ||
|
|
bec538b52f | ||
|
|
b498d70602 | ||
|
|
0b200ceaf4 | ||
|
|
e654f416d3 | ||
|
|
cd3b31e9b4 | ||
|
|
a85a7b6027 | ||
|
|
130a7601cb | ||
|
|
14a271b9f4 | ||
|
|
8223c98304 | ||
|
|
7fcb05cb2a | ||
|
|
dbde2bf663 | ||
|
|
3ebe8c604b | ||
|
|
641479cbe6 | ||
|
|
34c5dcc31b | ||
|
|
ac50f58e02 | ||
|
|
eae17b33b5 | ||
|
|
4662692ab8 | ||
|
|
039dc81139 | ||
|
|
ff0b447538 | ||
|
|
bc8fbaaf99 | ||
|
|
9f67e3ca18 | ||
|
|
3b328be1b8 | ||
|
|
039700dd46 | ||
|
|
12a4ba04d8 | ||
|
|
ae6b7829e0 | ||
|
|
4b6a53caf3 | ||
|
|
293c50dcc3 | ||
|
|
8c396bce57 | ||
|
|
7ee5eca0ba | ||
|
|
4ede8c06cc | ||
|
|
8989d0c357 | ||
|
|
3e075de237 | ||
|
|
f6f3fca3d5 | ||
|
|
496d40c03c | ||
|
|
dd25bcb6f8 | ||
|
|
c132c39ebf | ||
|
|
092a16631c | ||
|
|
4134008d11 | ||
|
|
4318d5bf74 | ||
|
|
cc17dec2d5 | ||
|
|
66f2d6138d | ||
|
|
67be84abb4 | ||
|
|
45810978a9 | ||
|
|
748608a71f | ||
|
|
d48e20971e | ||
|
|
ae76ed9df2 | ||
|
|
0dd011be04 | ||
|
|
ea61dd668a | ||
|
|
c69fd9012e | ||
|
|
974ed60687 | ||
|
|
47b681742c | ||
|
|
0a2e007e0c | ||
|
|
4297389cd3 | ||
|
|
935283b543 | ||
|
|
938661663b | ||
|
|
11591420ab | ||
|
|
f9d272c2dd | ||
|
|
c5fbd7e60d | ||
|
|
7e95fc8cb1 | ||
|
|
0fd38bb0e6 | ||
|
|
09c0a9176f | ||
|
|
f27b8572aa | ||
|
|
0e1d391d74 | ||
|
|
18e3e1700d | ||
|
|
968bd4ac7f | ||
|
|
a1ab84b81a | ||
|
|
fd3ee36dd0 | ||
|
|
a86d4b7c68 | ||
|
|
0a07dc2b3d | ||
|
|
cfe6fae649 | ||
|
|
a32335b6df | ||
|
|
f151126c72 | ||
|
|
3afb763a42 | ||
|
|
3835780505 | ||
|
|
c86752057d | ||
|
|
40a4047961 | ||
|
|
d9c9edd136 | ||
|
|
8e7b5a7fbc | ||
|
|
60ec9e9b00 | ||
|
|
1b1c826bee | ||
|
|
774fbb694a | ||
|
|
2313d3013e | ||
|
|
e8fe8bd8e9 | ||
|
|
d57cdab782 | ||
|
|
a2fa57c64c | ||
|
|
511c816645 | ||
|
|
f63b43f0b5 | ||
|
|
e3a6171b57 | ||
|
|
ed44da5267 | ||
|
|
9047faf871 | ||
|
|
70d5721472 | ||
|
|
a6004f6528 | ||
|
|
bbe78bcae9 | ||
|
|
e68491c709 | ||
|
|
4af9804110 | ||
|
|
a4b1908909 | ||
|
|
43b2cfe499 | ||
|
|
b6808d37df | ||
|
|
1cdc68c298 | ||
|
|
0019e821fd | ||
|
|
ba358181d4 | ||
|
|
8f4467a421 | ||
|
|
643d5868aa | ||
|
|
8620c5ff20 | ||
|
|
7c6d8258ef | ||
|
|
fe92f12af0 | ||
|
|
3159ecf743 | ||
|
|
498c811323 | ||
|
|
8d82f7e04a | ||
|
|
27aac1c444 | ||
|
|
97c4618f32 | ||
|
|
e3ea48444d | ||
|
|
8ae727d554 | ||
|
|
e63da23be0 | ||
|
|
beed2c4f4c | ||
|
|
42e3e908ab | ||
|
|
047e15a579 | ||
|
|
cc644e81aa | ||
|
|
4607370c7c | ||
|
|
e0c2a87e0b | ||
|
|
3eb975decc | ||
|
|
9f4a14abbf | ||
|
|
9f786e9a6e | ||
|
|
089c971ae9 | ||
|
|
61a8cebf8f | ||
|
|
58fba4bf31 | ||
|
|
deb7df490b | ||
|
|
bad2c5e02c | ||
|
|
044b1a1b14 | ||
|
|
1d208c769b | ||
|
|
9fde25ca27 | ||
|
|
ad7d9bd4f0 | ||
|
|
adbbf2259a | ||
|
|
d6768cac11 | ||
|
|
67a2113fd9 | ||
|
|
1181d6eb93 | ||
|
|
e541d1f5ee | ||
|
|
b136728f8f | ||
|
|
ca3b9a75fc | ||
|
|
774af0b781 | ||
|
|
0f8ef335a0 | ||
|
|
29f89f21e4 | ||
|
|
86e8b6a607 | ||
|
|
668f8a143c | ||
|
|
fd4f04671f | ||
|
|
e38e2e6e6f | ||
|
|
1ccd464ba6 | ||
|
|
8bb6f6a41f | ||
|
|
046ecfd443 | ||
|
|
d5264c8077 | ||
|
|
eb1228a7aa | ||
|
|
38a0384f82 | ||
|
|
890451b0b4 | ||
|
|
375f0e3d04 | ||
|
|
b564e9762f | ||
|
|
31544ff464 | ||
|
|
090770a5ee | ||
|
|
523e2117b4 | ||
|
|
ae175e7b77 | ||
|
|
d5d2e7c966 | ||
|
|
04c9621c7e | ||
|
|
6573194b6e | ||
|
|
4932805271 | ||
|
|
c1e2da5714 | ||
|
|
e86fadf32e | ||
|
|
cd1cbcfa53 | ||
|
|
87c789953b | ||
|
|
ba66e7ccaa | ||
|
|
29400cba61 | ||
|
|
7cf72610b8 | ||
|
|
0fc137ac3b | ||
|
|
b4dc2e210e | ||
|
|
409efd4bc5 | ||
|
|
0fbdbaa4a1 | ||
|
|
e5bbd08d49 | ||
|
|
bf6881e2f8 | ||
|
|
366d891d6e | ||
|
|
1f02849656 | ||
|
|
60492a052e | ||
|
|
b5525d1060 | ||
|
|
1104123e7a | ||
|
|
a9da565785 | ||
|
|
b91ac120b9 | ||
|
|
0f93e1309f | ||
|
|
699f7572ef | ||
|
|
2fb1bc699a | ||
|
|
ce4ac2858d | ||
|
|
fdf95c63bb | ||
|
|
15c0d9c8b0 | ||
|
|
8b24ab4f24 | ||
|
|
3122d4e362 | ||
|
|
cc2457db67 | ||
|
|
4df3d17626 | ||
|
|
26eeb73db7 | ||
|
|
331aca3d21 | ||
|
|
b65ed20c1a | ||
|
|
726b5d2e87 | ||
|
|
851a418add | ||
|
|
a4e9db8ead | ||
|
|
64161228ed | ||
|
|
321b8813aa | ||
|
|
754232de9d | ||
|
|
6a2b7e0666 | ||
|
|
cc26132081 | ||
|
|
2b2fcd8125 | ||
|
|
5d43397cf1 | ||
|
|
b7dc26f288 | ||
|
|
ed464181bc | ||
|
|
1030dc7d43 | ||
|
|
7cf67e01b3 | ||
|
|
1b3c0cbd2f | ||
|
|
87c95ff240 | ||
|
|
aaf2eb5e8f | ||
|
|
9638e0900c | ||
|
|
2e296d7546 | ||
|
|
c6be16382f | ||
|
|
0dd0e1d645 | ||
|
|
379f48ed80 | ||
|
|
1d4feb49ec | ||
|
|
ea65f23a6c | ||
|
|
6eecc91ce4 | ||
|
|
d0aa42ab33 | ||
|
|
c59893c2a6 | ||
|
|
f696afa45d | ||
|
|
9fdecf5be7 | ||
|
|
e34f3a6bcd | ||
|
|
d2d916239d | ||
|
|
843729b961 | ||
|
|
bf1a2c7429 | ||
|
|
2ebeefb730 | ||
|
|
c2adf50f59 | ||
|
|
b5f1846ad7 | ||
|
|
28a8f9b033 | ||
|
|
d6b6de7d3f | ||
|
|
b8fd84bf94 | ||
|
|
088639109f | ||
|
|
e38498a3cd | ||
|
|
b0bb49dd00 | ||
|
|
44d00831fa | ||
|
|
48003a383e | ||
|
|
dfe49d26de | ||
|
|
b465a7f78e | ||
|
|
b24d0df124 | ||
|
|
dbe1b608a5 | ||
|
|
9d892d90c2 | ||
|
|
c6118e1448 | ||
|
|
b74d891385 | ||
|
|
950d40fbbd | ||
|
|
0008cae1ef | ||
|
|
5922dcd370 | ||
|
|
997b6ae2b6 | ||
|
|
4152f19fe2 | ||
|
|
fe2fd6ca5b | ||
|
|
f1368df0de | ||
|
|
e28ea53b5d | ||
|
|
5a2b2927d9 | ||
|
|
beefcc9185 | ||
|
|
b356e52a93 | ||
|
|
cafef21436 | ||
|
|
cda878bd3c | ||
|
|
99bafe232d | ||
|
|
5b97b738e9 | ||
|
|
7590b02a2f | ||
|
|
6cace5d64b | ||
|
|
f3d8ff3d12 | ||
|
|
77ee5e2401 | ||
|
|
7b431950eb | ||
|
|
7e5cb39dd3 | ||
|
|
26f74ad6a5 | ||
|
|
27f1c4ba65 | ||
|
|
908ff3689d | ||
|
|
290f236157 | ||
|
|
e9212ae438 | ||
|
|
7c4e2bac55 | ||
|
|
e76ba12611 | ||
|
|
1825336826 | ||
|
|
f6870c98b5 | ||
|
|
b1c49a977a | ||
|
|
479ee9a034 | ||
|
|
07c6f1f9ec | ||
|
|
25a9d431f8 | ||
|
|
a0cefe4f46 | ||
|
|
cc399f3828 | ||
|
|
334e9743d2 | ||
|
|
d3ab52dcad | ||
|
|
7c56fd27a8 | ||
|
|
310bd1a9fa | ||
|
|
891f4e2aeb | ||
|
|
57bba9e700 | ||
|
|
294aa3ba3e | ||
|
|
cb5cba7a4d | ||
|
|
0718e35c15 | ||
|
|
b068a2e617 | ||
|
|
e85f527ca6 | ||
|
|
dab5cb737f | ||
|
|
81e6100713 | ||
|
|
0749e23eea | ||
|
|
797c30ce91 | ||
|
|
d5c3fddc19 | ||
|
|
d29ee1553c | ||
|
|
0f742f4f2c | ||
|
|
1874bc5d61 | ||
|
|
166f121d76 | ||
|
|
f28e0770f6 | ||
|
|
ddfeb1461d | ||
|
|
6f2f3226a4 | ||
|
|
cffc94feb7 | ||
|
|
a422f60a9a | ||
|
|
54fede96a2 | ||
|
|
c70d9b3f1d | ||
|
|
5a52dcace7 | ||
|
|
37895f9528 | ||
|
|
122607190a | ||
|
|
951b5a0ba0 | ||
|
|
719101110f | ||
|
|
dfd35d233d | ||
|
|
920a0a0768 | ||
|
|
c116c7bfdb | ||
|
|
dfb4707577 | ||
|
|
c127fec54c | ||
|
|
6da8a23ede | ||
|
|
af659e272b | ||
|
|
7427d28d52 | ||
|
|
e533b1ce25 | ||
|
|
5fb3481607 | ||
|
|
b5fb589361 | ||
|
|
96b82bdce2 | ||
|
|
e65ce63e70 | ||
|
|
ec8011a6e4 | ||
|
|
2163383613 | ||
|
|
afe1619b9e | ||
|
|
2c73d44923 | ||
|
|
d2054ffc05 | ||
|
|
05a739bdb1 | ||
|
|
f010a38d17 | ||
|
|
6efcbbbf59 | ||
|
|
1619261ebf | ||
|
|
db75ffb9c5 | ||
|
|
023b3f9b38 | ||
|
|
4c508ccea6 | ||
|
|
70eae9bc0c | ||
|
|
f397198d75 | ||
|
|
9fb050188d | ||
|
|
12aa60b848 | ||
|
|
ea5b58556f |
67 changed files with 10975 additions and 1215 deletions
22
.github/dependabot.yml
vendored
Normal file
22
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: cargo
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
120
.github/workflows/test.yml
vendored
Normal file
120
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - 2024 The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
name: Build masscanned
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: -- --check
|
||||||
|
|
||||||
|
- name: Install packages for build
|
||||||
|
run: sudo apt-get -q update && sudo apt-get -qy install libpcap-dev
|
||||||
|
|
||||||
|
- name: Run cargo build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
|
||||||
|
- name: Create build archive
|
||||||
|
run: tar cf masscanned.tar target/debug/masscanned
|
||||||
|
|
||||||
|
- name: Upload binary
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: masscanned.tar
|
||||||
|
path: masscanned.tar
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get binary
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: masscanned.tar
|
||||||
|
|
||||||
|
- name: Extract build archive
|
||||||
|
run: tar xf masscanned.tar
|
||||||
|
|
||||||
|
- name: Use Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo pip install -U -r test/requirements.txt
|
||||||
|
|
||||||
|
- name: Install linting tools
|
||||||
|
run: sudo pip install -U flake8 black
|
||||||
|
|
||||||
|
- name: Install packages for tests
|
||||||
|
run: sudo apt-get -q update && sudo apt-get -qy install nmap rpcbind smbclient
|
||||||
|
|
||||||
|
- name: Run black
|
||||||
|
run: black -t py36 --check test/test_masscanned.py test/src/
|
||||||
|
|
||||||
|
- name: Run flake8
|
||||||
|
run: flake8 --ignore=E266,E501,W503 test/test_masscanned.py test/src/
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: sudo python test/test_masscanned.py
|
||||||
|
|
||||||
|
- name: Display logs
|
||||||
|
run: echo STDOUT; cat test/res/masscanned.stdout && echo && echo STDERR && cat test/res/masscanned.stderr
|
||||||
|
if: failure()
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Git checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Build archive
|
||||||
|
run: git archive --format=tar --prefix=masscanned-master/ HEAD -o docker/masscanned.tar
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
push: false
|
||||||
|
context: docker/
|
||||||
|
file: docker/Dockerfile-local
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,10 +1,11 @@
|
||||||
/target/
|
/target/
|
||||||
Cargo.lock
|
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
# Vim temporary files
|
# Vim temporary files
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
# Emacs temporary files
|
||||||
|
*~
|
||||||
|
|
||||||
*__pycache__*
|
*__pycache__*
|
||||||
test/res/*
|
test/res/*
|
||||||
|
|
|
||||||
1139
Cargo.lock
generated
Normal file
1139
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
31
Cargo.toml
31
Cargo.toml
|
|
@ -21,22 +21,23 @@ authors = ["_Frky <3105926+Frky@users.noreply.github.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pcap = "0.7.0"
|
bitflags = "2.9.4"
|
||||||
pcap-file = "1.1.1"
|
byteorder = "1.5.0"
|
||||||
pnet = "0.26.0"
|
chrono = "0.4.42"
|
||||||
# pnet = { path = "libpnet" }
|
clap = "4.5.47"
|
||||||
clap = "2.33.3"
|
|
||||||
log = "0.4.11"
|
|
||||||
stderrlog = "0.5.0"
|
|
||||||
itertools = "0.9.0"
|
|
||||||
rand = "0.7.3"
|
|
||||||
dns-parser = "0.8.0"
|
dns-parser = "0.8.0"
|
||||||
netdevice = "0.1.1"
|
flate2 = "1.1"
|
||||||
bitflags = "1.2.1"
|
itertools = "0.14.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.5.0"
|
||||||
siphasher = "0.3"
|
log = "0.4.28"
|
||||||
chrono = "0.4.19"
|
pcap = "2.3.0"
|
||||||
byteorder = "1.4.3"
|
pcap-file = "2.0.0"
|
||||||
|
pnet = { version = "0.33.0", features = ["std"] }
|
||||||
|
rand = "0.9.2"
|
||||||
|
siphasher = "1.0"
|
||||||
|
stderrlog = "0.6.0"
|
||||||
|
strum = "0.27.2"
|
||||||
|
strum_macros = "0.27.2"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "masscanned"
|
name = "masscanned"
|
||||||
|
|
|
||||||
396
README.md
396
README.md
|
|
@ -1,8 +1,10 @@
|
||||||
|
[](https://github.com/ivre/masscanned/actions/workflows/test.yml?branch=master)
|
||||||
|
|
||||||
# Masscanned
|
# Masscanned
|
||||||
|
|
||||||
**Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan))
|
**Masscanned** (name inspired, of course, by [masscan](https://github.com/robertdavidgraham/masscan))
|
||||||
is a network responder. Its purpose is to provide generic answers to as many protocols as possible,
|
is a network responder. Its purpose is to provide generic answers to as many protocols as possible,
|
||||||
and with as few asumptions as possible on the client's intentions.
|
and with as few assumptions as possible on the client's intentions.
|
||||||
|
|
||||||
> *Let them talk first.*
|
> *Let them talk first.*
|
||||||
|
|
||||||
|
|
@ -17,22 +19,32 @@ For example, when it receives network packets:
|
||||||
* **masscanned** answers to `TCP SYN` (any port) with `TCP SYN/ACK` on any port,
|
* **masscanned** answers to `TCP SYN` (any port) with `TCP SYN/ACK` on any port,
|
||||||
* **masscanned** answers to `HTTP` requests (any verb) over `TCP/UDP` (any port) with a `HTTP 401` web page.
|
* **masscanned** answers to `HTTP` requests (any verb) over `TCP/UDP` (any port) with a `HTTP 401` web page.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
**Masscanned** currently supports most common protocols at layers 2-3-4, and a few application
|
**Masscanned** currently supports most common protocols at layers 2-3-4, and a few application
|
||||||
protocols:
|
protocols.
|
||||||
|
|
||||||
* `Eth::ARP::REQ`,
|
### Network protocols
|
||||||
* `Eth::IPv{4,6}::ICMP::ECHO-REQ`,
|
|
||||||
* `Eth::IPv{4,6}::TCP::SYN` (all ports),
|
* ARP (answers to ARP requests)
|
||||||
* `Eth::IPv{4,6}::TCP::PSHACK` (all ports),
|
* ICMP (answers to ping)
|
||||||
* `Eth::IPv6::ICMP::ND_NS`.
|
* ICMPv6 (answers to ND NS)
|
||||||
* `Eth::IPv{4,6}::{TCP,UDP}::HTTP` (all HTTP verbs),
|
* TCP (answers to SYN and PUSH)
|
||||||
* `Eth::IPv{4,6}::{TCP,UDP}::STUN`,
|
|
||||||
* `Eth::IPv{4,6}::{TCP,UDP}::SSH` (Server Protocol only).
|
### Application protocols
|
||||||
|
|
||||||
|
* HTTP (answers to all verbs)
|
||||||
|
* SSH (answers to the client banner)
|
||||||
|
* STUN (answers to binding requests)
|
||||||
|
* SMB
|
||||||
|
* DNS (answers to IN/A queries)
|
||||||
|
|
||||||
## Try it locally
|
## Try it locally
|
||||||
|
|
||||||
|
### On your host
|
||||||
|
|
||||||
1. Build **masscanned**
|
1. Build **masscanned**
|
||||||
```
|
```
|
||||||
$ cargo build
|
$ cargo build
|
||||||
|
|
@ -64,13 +76,77 @@ $ cargo build
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Protocols
|
### In a Docker
|
||||||
|
|
||||||
|
1. Install docker:
|
||||||
|
```
|
||||||
|
# apt install docker.io
|
||||||
|
```
|
||||||
|
1. Build docker container:
|
||||||
|
```
|
||||||
|
$ cd masscanned/docker && docker build -t masscanned:test .
|
||||||
|
```
|
||||||
|
1. Run docker container:
|
||||||
|
```
|
||||||
|
$ docker run --cap-add=NET_ADMIN masscanned:test
|
||||||
|
```
|
||||||
|
1. Send packets to **masscanned**
|
||||||
|
```
|
||||||
|
# arping 172.17.0.2
|
||||||
|
# ping 172.17.0.2
|
||||||
|
# nc -n -v 172.17.0.2 80
|
||||||
|
# nc -n -v -u 172.17.0.2 80
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use it
|
||||||
|
|
||||||
|
A good use of **masscanned** is to deploy it on a VPS with one or more public IP addresses.
|
||||||
|
|
||||||
|
To use the results, the best way is to capture all network traffic on the interface **masscanned** is listening to/responding on.
|
||||||
|
The pcaps can then be analyzed using [zeek](https://zeek.org/) and the output files can typically be pushed in an instance of **IVRE**.
|
||||||
|
|
||||||
|
A documentation on how to deploy an instance of **masscanned** on a VPS is coming (see [Issue #2](https://github.com/ivre/masscanned/issues/2)).
|
||||||
|
|
||||||
|
### Supported options
|
||||||
|
|
||||||
|
```
|
||||||
|
Network answering machine for various network protocols (L2-L3-L4 + applications)
|
||||||
|
|
||||||
|
Usage: masscanned [OPTIONS] --iface <iface>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-i, --iface <iface>
|
||||||
|
the interface to use for receiving/sending packets
|
||||||
|
-m, --mac-addr <mac>
|
||||||
|
MAC address to use in the response packets
|
||||||
|
--self-ip-file <selfipfile>
|
||||||
|
File with the list of IP addresses handled by masscanned
|
||||||
|
--self-ip-list <selfiplist>
|
||||||
|
Inline list of IP addresses handled by masscanned, comma-separated
|
||||||
|
--remote-ip-deny-file <remoteipdenyfile>
|
||||||
|
File with the list of IP addresses from which masscanned will ignore packets
|
||||||
|
--remote-ip-deny-list <remoteipdenylist>
|
||||||
|
Inline list of IP addresses from which masscanned will ignore packets
|
||||||
|
-v...
|
||||||
|
Increase message verbosity
|
||||||
|
-q, --quiet
|
||||||
|
Quiet mode: do not output anything on stdout
|
||||||
|
--format <format>
|
||||||
|
Format in which to output logs [default: console] [possible values: console, logfmt]
|
||||||
|
-h, --help
|
||||||
|
Print help information
|
||||||
|
-V, --version
|
||||||
|
Print version information
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported protocols - details
|
||||||
|
|
||||||
### Layer 2
|
### Layer 2
|
||||||
|
|
||||||
#### ARP
|
#### ARP
|
||||||
|
|
||||||
`masscanned` anwsers to `ARP` requests, for requests that target an `IPv4` address
|
`masscanned` answers to `ARP` requests, for requests that target an `IPv4` address
|
||||||
that is handled by `masscanned` (*i.e.*, an address that is in the
|
that is handled by `masscanned` (*i.e.*, an address that is in the
|
||||||
IP address file given with option `-f`).
|
IP address file given with option `-f`).
|
||||||
|
|
||||||
|
|
@ -112,7 +188,7 @@ An additionnal requirement is that the next layer protocol is supported - see be
|
||||||
|
|
||||||
#### IPv4
|
#### IPv4
|
||||||
|
|
||||||
The following L4 protocols are suppported for an `IPv4` packet:
|
The following L3+/4 protocols are supported for an `IPv4` packet:
|
||||||
|
|
||||||
* `ICMPv4`
|
* `ICMPv4`
|
||||||
* `UDP`
|
* `UDP`
|
||||||
|
|
@ -122,7 +198,7 @@ If the next layer protocol is not one of them, the packet is dropped.
|
||||||
|
|
||||||
#### IPv6
|
#### IPv6
|
||||||
|
|
||||||
The following L4 protocols are suppported for an `IPv6` packet:
|
The following L3+/4 protocols are supported for an `IPv6` packet:
|
||||||
|
|
||||||
* `ICMPv6`
|
* `ICMPv6`
|
||||||
* `UDP`
|
* `UDP`
|
||||||
|
|
@ -148,7 +224,7 @@ code `0` and the same payload as the incoming packet, as specified by [RFC 792](
|
||||||
|
|
||||||
* the `ICMP` type is `NeighborSol` (`135`) **and**:
|
* the `ICMP` type is `NeighborSol` (`135`) **and**:
|
||||||
* no IP (v4 or v6) was speficied for `masscanned`
|
* no IP (v4 or v6) was speficied for `masscanned`
|
||||||
* **or** the target address of the Neighbor Solicitation is one of `masccanned`
|
* **or** the target address of the Neighbor Solicitation is one of `masscanned`
|
||||||
|
|
||||||
*In that case, the answer is a `Neighbor Advertisement` (`136`) packet with `masscanned` `MAC` address*
|
*In that case, the answer is a `Neighbor Advertisement` (`136`) packet with `masscanned` `MAC` address*
|
||||||
|
|
||||||
|
|
@ -166,19 +242,68 @@ code `0` and the same payload as the incoming packet, as specified by [RFC 792](
|
||||||
a supported protocol (Layer 5/6/7) has been detected,
|
a supported protocol (Layer 5/6/7) has been detected,
|
||||||
* if the received packet has flag `ACK`, it is ignored,
|
* if the received packet has flag `ACK`, it is ignored,
|
||||||
* if the received packet has flag `RST` or `FIN-ACK`, it is ignored,
|
* if the received packet has flag `RST` or `FIN-ACK`, it is ignored,
|
||||||
* if the received packet has flag `SYN`, then `masscanned` answers with a `SYN-ACK` packet, setting a **SYNACK-cookie** in the sequence number.
|
* if the received packet has flag `SYN`, then `masscanned` tries to imitate the behaviour
|
||||||
|
of a standard Linux stack - which is:
|
||||||
|
* if there are additional flags that are not among `PSH`, `URG`, `CWR`, `ECE`, then the `SYN` is ignored,
|
||||||
|
* if the flags `CWR` and`ECE` are simultaneously set, then the `SYN` is ignored,
|
||||||
|
* in any other case, `masscanned` answers with a `SYN-ACK` packet, setting a **SYNACK-cookie** in the sequence number.
|
||||||
|
|
||||||
#### UDP
|
#### UDP
|
||||||
|
|
||||||
`masscanned` answers to an `UDP` packet if and only if the upper-layer protocol
|
`masscanned` answers to an `UDP` packet if and only if the upper-layer protocol
|
||||||
is handled and provides an answer.
|
is handled and provides an answer.
|
||||||
|
|
||||||
### Protocols
|
### Application protocols
|
||||||
|
|
||||||
#### HTTP
|
#### HTTP
|
||||||
|
|
||||||
|
`masscanned` answers to any `HTTP` request (any **valid** verb) with a `401 Authorization Required`.
|
||||||
|
Note that `HTTP` requests with an invalid verb will not be answered.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -X GET 10.11.10.129
|
||||||
|
<html>
|
||||||
|
<head><title>401 Authorization Required</title></head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
<center><h1>401 Authorization Required</h1></center>
|
||||||
|
<hr><center>nginx/1.14.2</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
$ curl -X OPTIONS 10.11.10.129
|
||||||
|
<html>
|
||||||
|
<head><title>401 Authorization Required</title></head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
<center><h1>401 Authorization Required</h1></center>
|
||||||
|
<hr><center>nginx/1.14.2</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
$ curl -X HEAD 10.11.10.129
|
||||||
|
Warning: Setting custom HTTP method to HEAD with -X/--request may not work the
|
||||||
|
Warning: way you want. Consider using -I/--head instead.
|
||||||
|
<html>
|
||||||
|
<head><title>401 Authorization Required</title></head>
|
||||||
|
<body bgcolor="white">
|
||||||
|
<center><h1>401 Authorization Required</h1></center>
|
||||||
|
<hr><center>nginx/1.14.2</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
$ curl -X XXX 10.11.10.129
|
||||||
|
[timeout]
|
||||||
|
```
|
||||||
|
|
||||||
#### STUN
|
#### STUN
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ stun 10.11.10.129
|
||||||
|
STUN client version 0.97
|
||||||
|
Primary: Open
|
||||||
|
Return value is 0x000001
|
||||||
|
```
|
||||||
|
|
||||||
#### SSH
|
#### SSH
|
||||||
|
|
||||||
`masscanned` answers to `SSH` `Client: Protocol` messages with the following `Server: Protocol` message:
|
`masscanned` answers to `SSH` `Client: Protocol` messages with the following `Server: Protocol` message:
|
||||||
|
|
@ -187,6 +312,57 @@ is handled and provides an answer.
|
||||||
SSH-2.0-1\r\n
|
SSH-2.0-1\r\n
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### SMB
|
||||||
|
|
||||||
|
`masscanned` answers to `Negotiate Protocol Request` packets in order for the
|
||||||
|
client to send a `NTLMSSP_NEGOTIATE`, to which `masscanned` answers with a challenge.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
##$ smbclient -U user \\\\10.11.10.129\\shared
|
||||||
|
Enter WORKGROUP\user's password:
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DNS
|
||||||
|
|
||||||
|
`masscanned` answers to `DNS` queries of class `IN` and type `A` (for now).
|
||||||
|
The answer it provides always contains the IP address the query was sent to.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ host -t A masscan.ned 10.11.10.129
|
||||||
|
Using domain server:
|
||||||
|
Name: 10.11.10.129
|
||||||
|
Address: 10.11.10.129#53
|
||||||
|
Aliases:
|
||||||
|
|
||||||
|
masscan.ned has address 10.11.10.129
|
||||||
|
$ host -t A masscan.ned 10.11.10.130
|
||||||
|
Using domain server:
|
||||||
|
Name: 10.11.10.130
|
||||||
|
Address: 10.11.10.130#53
|
||||||
|
Aliases:
|
||||||
|
|
||||||
|
masscan.ned has address 10.11.10.130
|
||||||
|
$ host -t A masscan.ned 10.11.10.131
|
||||||
|
Using domain server:
|
||||||
|
Name: 10.11.10.131
|
||||||
|
Address: 10.11.10.131#53
|
||||||
|
Aliases:
|
||||||
|
|
||||||
|
masscan.ned has address 10.11.10.131
|
||||||
|
$ host -t A masscan.ned 10.11.10.132
|
||||||
|
Using domain server:
|
||||||
|
Name: 10.11.10.132
|
||||||
|
Address: 10.11.10.132#53
|
||||||
|
Aliases:
|
||||||
|
|
||||||
|
masscan.ned has address 10.11.10.132
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Internals
|
## Internals
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
@ -196,68 +372,118 @@ SSH-2.0-1\r\n
|
||||||
```
|
```
|
||||||
$ cargo test
|
$ cargo test
|
||||||
Compiling masscanned v0.2.0 (/zdata/workdir/masscanned)
|
Compiling masscanned v0.2.0 (/zdata/workdir/masscanned)
|
||||||
Finished test [unoptimized + debuginfo] target(s) in 2.34s
|
Finished test [unoptimized + debuginfo] target(s) in 3.83s
|
||||||
Running target/debug/deps/masscanned-b86211a090e50323
|
Running unittests (target/debug/deps/masscanned-f9292f8600038978)
|
||||||
|
|
||||||
running 36 tests
|
running 92 tests
|
||||||
test client::client_info::tests::test_client_info_eq ... ok
|
test client::client_info::tests::test_client_info_eq ... ok
|
||||||
test layer_2::arp::tests::test_arp_reply ... ok
|
test layer_2::arp::tests::test_arp_reply ... ok
|
||||||
test layer_3::ipv4::tests::test_ipv4_reply ... ok
|
test layer_2::tests::test_eth_empty ... ok
|
||||||
test layer_3::ipv6::tests::test_ipv6_reply ... ok
|
|
||||||
test layer_4::icmpv6::tests::test_icmpv6_reply ... ok
|
|
||||||
test layer_2::tests::test_eth_reply ... ok
|
test layer_2::tests::test_eth_reply ... ok
|
||||||
test layer_4::icmpv6::tests::test_nd_na_reply ... ok
|
test layer_3::ipv4::tests::test_ipv4_reply ... ok
|
||||||
test layer_4::tcp::tests::test_synack_cookie_ipv4 ... ok
|
test layer_3::ipv4::tests::test_ipv4_empty ... ok
|
||||||
|
test layer_3::ipv6::tests::test_ipv6_empty ... ok
|
||||||
|
test layer_3::ipv6::tests::test_ipv6_reply ... ok
|
||||||
test layer_4::icmpv4::tests::test_icmpv4_reply ... ok
|
test layer_4::icmpv4::tests::test_icmpv4_reply ... ok
|
||||||
|
test layer_4::icmpv6::tests::test_icmpv6_reply ... ok
|
||||||
|
test layer_4::icmpv6::tests::test_nd_na_reply ... ok
|
||||||
test layer_4::tcp::tests::test_synack_cookie_ipv6 ... ok
|
test layer_4::tcp::tests::test_synack_cookie_ipv6 ... ok
|
||||||
test proto::http::test_http_request_field ... ok
|
test layer_4::tcp::tests::test_tcp_fin_ack_wrap ... ok
|
||||||
test proto::http::test_http_request_no_field ... ok
|
test proto::dns::cst::tests::class_parse ... ok
|
||||||
test proto::http::test_http_request_line ... ok
|
test layer_4::tcp::tests::test_tcp_fin_ack ... ok
|
||||||
test proto::http::test_http_verb ... ok
|
test layer_4::tcp::tests::test_synack_cookie_ipv4 ... ok
|
||||||
test proto::stun::tests::test_change_request_port ... ok
|
test proto::dns::cst::tests::type_parse ... ok
|
||||||
test proto::stun::tests::test_proto_stun_ipv6 ... ok
|
test proto::dns::header::tests::parse_byte_by_byte ... ok
|
||||||
test proto::stun::tests::test_proto_stun_ipv4 ... ok
|
test proto::dns::header::tests::repl_id ... ok
|
||||||
|
test proto::dns::header::tests::repl_opcode ... ok
|
||||||
|
test proto::dns::header::tests::repl_ancount ... ok
|
||||||
|
test proto::dns::header::tests::repl_rd ... ok
|
||||||
|
test proto::dns::query::tests::parse_in_a_all ... ok
|
||||||
|
test proto::dns::header::tests::parse_all ... ok
|
||||||
|
test proto::dns::query::tests::repl ... ok
|
||||||
|
test proto::dns::query::tests::reply_in_a ... ok
|
||||||
|
test proto::dns::rr::tests::parse_all ... ok
|
||||||
|
test proto::dns::rr::tests::parse_byte_by_byte ... ok
|
||||||
|
test proto::dns::query::tests::parse_in_a_byte_by_byte ... ok
|
||||||
|
test proto::dns::tests::parse_qd_all ... ok
|
||||||
|
test proto::dns::tests::parse_qd_byte_by_byte ... ok
|
||||||
|
test proto::dns::rr::tests::build ... ok
|
||||||
|
test proto::dns::tests::parse_qd_rr_all ... ok
|
||||||
|
test proto::dns::tests::parse_qr_rr_byte_by_byte ... ok
|
||||||
|
test proto::dns::tests::parse_rr_byte_by_byte ... ok
|
||||||
|
test proto::dns::tests::parse_rr_all ... ok
|
||||||
|
test proto::dns::tests::reply_in_a ... ok
|
||||||
|
test proto::http::tests::test_http_request_line ... ok
|
||||||
|
test proto::http::tests::test_http_request_no_field ... ok
|
||||||
|
test proto::http::tests::test_http_request_field ... ok
|
||||||
|
test proto::http::tests::test_http_verb ... ok
|
||||||
|
test proto::rpc::tests::test_probe_nmap ... ok
|
||||||
|
test proto::rpc::tests::test_probe_nmap_split1 ... ok
|
||||||
|
test proto::rpc::tests::test_probe_portmap_v4_dump ... ok
|
||||||
|
test proto::rpc::tests::test_probe_nmap_split2 ... ok
|
||||||
|
test proto::rpc::tests::test_probe_nmap_udp ... ok
|
||||||
|
test proto::smb::tests::test_smb1_session_setup_request_parse ... ok
|
||||||
|
test proto::smb::tests::test_smb1_protocol_nego_parsing ... ok
|
||||||
|
test proto::smb::tests::test_smb1_protocol_nego_reply ... ok
|
||||||
|
test proto::smb::tests::test_smb1_session_setup_request_reply ... ok
|
||||||
|
test proto::smb::tests::test_smb2_protocol_nego_parsing ... ok
|
||||||
|
test proto::smb::tests::test_smb2_protocol_nego_reply ... ok
|
||||||
|
test proto::smb::tests::test_smb2_session_setup_request_reply ... ok
|
||||||
|
test proto::smb::tests::test_smb2_session_setup_request_parse ... ok
|
||||||
|
test proto::ssh::tests::ssh_1_banner_cr ... ok
|
||||||
|
test proto::ssh::tests::ssh_1_banner_crlf ... ok
|
||||||
|
test proto::ssh::tests::ssh_1_banner_lf ... ok
|
||||||
|
test proto::ssh::tests::ssh_1_banner_space ... ok
|
||||||
|
test proto::ssh::tests::ssh_2_banner_cr ... ok
|
||||||
|
test proto::ssh::tests::ssh_1_banner_parse ... ok
|
||||||
|
test proto::ssh::tests::ssh_2_banner_parse ... ok
|
||||||
|
test proto::ssh::tests::ssh_2_banner_lf ... ok
|
||||||
|
test proto::ssh::tests::ssh_2_banner_crlf ... ok
|
||||||
test proto::stun::tests::test_change_request_port_overflow ... ok
|
test proto::stun::tests::test_change_request_port_overflow ... ok
|
||||||
test smack::smack::tests::test_anchor_end ... ok
|
test proto::stun::tests::test_proto_stun_ipv4 ... ok
|
||||||
test smack::smack::tests::test_anchor_begin ... ok
|
test proto::stun::tests::test_change_request_port ... ok
|
||||||
test smack::smack::tests::test_multiple_matches ... ok
|
test proto::ssh::tests::ssh_2_banner_space ... ok
|
||||||
test smack::smack::tests::test_http_banner ... ok
|
test proto::stun::tests::test_proto_stun_ipv6 ... ok
|
||||||
test smack::smack::tests::test_multiple_matches_wildcard ... ok
|
test proto::tcb::tests::test_proto_tcb_proto_state_http ... ok
|
||||||
test smack::smack::tests::test_proto ... ok
|
test proto::tests::dispatch_dns ... ok
|
||||||
test smack::smack::tests::test_wildcard ... ok
|
test proto::tcb::tests::test_proto_tcb_proto_state_rpc ... ok
|
||||||
|
test proto::tcb::tests::test_proto_tcb_proto_id ... ok
|
||||||
|
test proto::tests::test_proto_dispatch_http ... ok
|
||||||
test proto::tests::test_proto_dispatch_ssh ... ok
|
test proto::tests::test_proto_dispatch_ssh ... ok
|
||||||
|
test proto::tests::test_proto_dispatch_ghost ... ok
|
||||||
test proto::tests::test_proto_dispatch_stun ... ok
|
test proto::tests::test_proto_dispatch_stun ... ok
|
||||||
|
test smack::smack::tests::test_anchor_end ... ok
|
||||||
|
test smack::smack::tests::test_multiple_matches_wildcard ... ok
|
||||||
|
test smack::smack::tests::test_multiple_matches ... ok
|
||||||
|
test smack::smack::tests::test_anchor_begin ... ok
|
||||||
|
test smack::smack::tests::test_http_banner ... ok
|
||||||
test synackcookie::tests::test_clientinfo ... ok
|
test synackcookie::tests::test_clientinfo ... ok
|
||||||
|
test synackcookie::tests::test_ip4 ... ok
|
||||||
test synackcookie::tests::test_ip4_dst ... ok
|
test synackcookie::tests::test_ip4_dst ... ok
|
||||||
test synackcookie::tests::test_ip4_src ... ok
|
test synackcookie::tests::test_ip4_src ... ok
|
||||||
test synackcookie::tests::test_ip4 ... ok
|
|
||||||
test synackcookie::tests::test_ip6 ... ok
|
test synackcookie::tests::test_ip6 ... ok
|
||||||
test synackcookie::tests::test_key ... ok
|
test synackcookie::tests::test_key ... ok
|
||||||
test synackcookie::tests::test_tcp_dst ... ok
|
test synackcookie::tests::test_tcp_dst ... ok
|
||||||
test synackcookie::tests::test_tcp_src ... ok
|
test synackcookie::tests::test_tcp_src ... ok
|
||||||
|
test smack::smack::tests::test_wildcard ... ok
|
||||||
|
test smack::smack::tests::test_proto ... ok
|
||||||
test smack::smack::tests::test_pattern ... ok
|
test smack::smack::tests::test_pattern ... ok
|
||||||
|
|
||||||
test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
test result: ok. 92 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Functional tests
|
#### Functional tests
|
||||||
|
|
||||||
```
|
```
|
||||||
# ./test/test_masscanned.py
|
# ./test/test_masscanned.py
|
||||||
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
|
||||||
INFO test_arp_req......................................OK
|
INFO test_arp_req......................................OK
|
||||||
INFO test_arp_req_other_ip.............................OK
|
INFO test_arp_req_other_ip.............................OK
|
||||||
INFO test_ipv4_req.....................................OK
|
INFO test_ipv4_udp_dns_in_a............................OK
|
||||||
INFO test_eth_req_other_mac............................OK
|
INFO test_ipv4_udp_dns_in_a_multiple_queries...........OK
|
||||||
INFO test_ipv4_req_other_ip............................OK
|
INFO test_ipv4_tcp_ghost...............................OK
|
||||||
INFO test_icmpv4_echo_req..............................OK
|
|
||||||
INFO test_icmpv6_neighbor_solicitation.................OK
|
|
||||||
INFO test_icmpv6_neighbor_solicitation_other_ip........OK
|
|
||||||
INFO test_icmpv6_echo_req..............................OK
|
|
||||||
INFO test_tcp_syn......................................OK
|
|
||||||
INFO test_ipv4_tcp_psh_ack.............................OK
|
|
||||||
INFO test_ipv6_tcp_psh_ack.............................OK
|
|
||||||
INFO test_ipv4_tcp_http................................OK
|
INFO test_ipv4_tcp_http................................OK
|
||||||
|
INFO test_ipv4_tcp_http_segmented......................OK
|
||||||
|
INFO test_ipv4_tcp_http_incomplete.....................OK
|
||||||
INFO test_ipv6_tcp_http................................OK
|
INFO test_ipv6_tcp_http................................OK
|
||||||
INFO test_ipv4_udp_http................................OK
|
INFO test_ipv4_udp_http................................OK
|
||||||
INFO test_ipv6_udp_http................................OK
|
INFO test_ipv6_udp_http................................OK
|
||||||
|
|
@ -265,26 +491,64 @@ INFO test_ipv4_tcp_http_ko.............................OK
|
||||||
INFO test_ipv4_udp_http_ko.............................OK
|
INFO test_ipv4_udp_http_ko.............................OK
|
||||||
INFO test_ipv6_tcp_http_ko.............................OK
|
INFO test_ipv6_tcp_http_ko.............................OK
|
||||||
INFO test_ipv6_udp_http_ko.............................OK
|
INFO test_ipv6_udp_http_ko.............................OK
|
||||||
INFO test_ipv4_udp_stun................................OK
|
INFO test_icmpv4_echo_req..............................OK
|
||||||
INFO test_ipv6_udp_stun................................OK
|
INFO test_icmpv6_neighbor_solicitation.................OK
|
||||||
INFO test_ipv4_udp_stun_change_port....................OK
|
INFO test_icmpv6_neighbor_solicitation_other_ip........OK
|
||||||
INFO test_ipv6_udp_stun_change_port....................OK
|
INFO test_icmpv6_echo_req..............................OK
|
||||||
|
INFO test_ipv4_req.....................................OK
|
||||||
|
INFO test_eth_req_other_mac............................OK
|
||||||
|
INFO test_ipv4_req_other_ip............................OK
|
||||||
|
INFO test_rpc_nmap.....................................OK
|
||||||
|
INFO test_rpcinfo......................................OK
|
||||||
|
INFO test_smb1_network_req.............................OK
|
||||||
|
INFO test_smb2_network_req.............................OK
|
||||||
INFO test_ipv4_tcp_ssh.................................OK
|
INFO test_ipv4_tcp_ssh.................................OK
|
||||||
INFO test_ipv4_udp_ssh.................................OK
|
INFO test_ipv4_udp_ssh.................................OK
|
||||||
INFO test_ipv6_tcp_ssh.................................OK
|
INFO test_ipv6_tcp_ssh.................................OK
|
||||||
INFO test_ipv6_udp_ssh.................................OK
|
INFO test_ipv6_udp_ssh.................................OK
|
||||||
tcpdump: pcap_loop: The interface disappeared
|
INFO test_ipv4_udp_stun................................OK
|
||||||
604 packets captured
|
INFO test_ipv6_udp_stun................................OK
|
||||||
604 packets received by filter
|
INFO test_ipv4_udp_stun_change_port....................OK
|
||||||
0 packets dropped by kernel
|
INFO test_ipv6_udp_stun_change_port....................OK
|
||||||
|
INFO test_ipv4_tcp_empty...............................OK
|
||||||
|
INFO test_ipv6_tcp_empty...............................OK
|
||||||
|
INFO test_tcp_syn......................................OK
|
||||||
|
INFO test_ipv4_tcp_psh_ack.............................OK
|
||||||
|
INFO test_ipv6_tcp_psh_ack.............................OK
|
||||||
|
INFO test_ipv4_udp_empty...............................OK
|
||||||
|
INFO test_ipv6_udp_empty...............................OK
|
||||||
|
INFO Ran 41 tests with 0 errors
|
||||||
```
|
```
|
||||||
|
|
||||||
### Logging Policy
|
You can also chose what tests to run using the `TESTS` environment variable
|
||||||
|
```
|
||||||
|
TESTS=smb ./test/test_masscanned.py
|
||||||
|
INFO test_smb1_network_req.............................OK
|
||||||
|
INFO test_smb2_network_req.............................OK
|
||||||
|
INFO Ran 2 tests with 0 errors
|
||||||
|
```
|
||||||
|
|
||||||
* `ERR`: any error - will always be displayed.
|
## Logging
|
||||||
* `WARN`, `-v`: responses sent by `masscanned`.
|
|
||||||
* `INFO`, `-vv`: packets not handled, packets ignored.
|
### Console Logger
|
||||||
* `DEBUG`, `-vvv`: all packets received and sent by `masscanned`.
|
|
||||||
|
**Verbs**:
|
||||||
|
* `init`
|
||||||
|
* `recv`
|
||||||
|
* `send`
|
||||||
|
* `drop`
|
||||||
|
|
||||||
|
#### ARP
|
||||||
|
|
||||||
|
```
|
||||||
|
$ts arp $verb $operation $client_mac $client_ip $masscanned_mac $masscanned_ip
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Ethernet
|
||||||
|
|
||||||
|
```
|
||||||
|
$ts eth $verb $ethertype $client_mac $masscanned_mac
|
||||||
|
```
|
||||||
|
|
||||||
## To Do
|
## To Do
|
||||||
|
|
||||||
|
|
|
||||||
19
doc/Makefile
Normal file
19
doc/Makefile
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
53
doc/conf.py
Normal file
53
doc/conf.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file only contains a selection of the most common options. For a full
|
||||||
|
# list see the documentation:
|
||||||
|
# http://www.sphinx-doc.org/en/master/config
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
|
||||||
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = "IVRE"
|
||||||
|
copyright = "2021, The IVRE project"
|
||||||
|
html_logo = "img/logo.png"
|
||||||
|
master_doc = "index"
|
||||||
|
|
||||||
|
def parse_cargo():
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(os.path.join("..", "Cargo.toml"))
|
||||||
|
if "package" not in config:
|
||||||
|
return None, None, None
|
||||||
|
package = config["package"]
|
||||||
|
try:
|
||||||
|
author = literal_eval(package.get("authors"))[0].split("<", 1)[0].strip()
|
||||||
|
except KeyError:
|
||||||
|
authors = None
|
||||||
|
return literal_eval(package.get("name")), author, literal_eval(package.get("version"))
|
||||||
|
|
||||||
|
project, author, version = parse_cargo()
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
extensions = []
|
||||||
|
|
||||||
|
autosectionlabel_prefix_document = True
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
|
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
Before Width: | Height: | Size: 4.2 MiB After Width: | Height: | Size: 4.2 MiB |
BIN
doc/img/logo.png
Normal file
BIN
doc/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3 KiB |
35
doc/index.rst
Normal file
35
doc/index.rst
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
Welcome to Masscanned documentation!
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
Masscanned is a low-interaction honeypot, primarily designed to help
|
||||||
|
gather intelligence about network scanners and bots.
|
||||||
|
|
||||||
|
It has been built as a companion tool for `IVRE
|
||||||
|
<https://ivre.rocks/>`_ but can be used independently.
|
||||||
|
|
||||||
|
The code is on `GitHub <https://github.com/ivre/masscanned>`_.
|
||||||
|
|
||||||
|
Here is a quick demo:
|
||||||
|
|
||||||
|
|demo|
|
||||||
|
|
||||||
|
Status of this documentation
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
This documentation is a work in progress!
|
||||||
|
|
||||||
|
Content
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 3
|
||||||
|
:caption: Usage:
|
||||||
|
:glob:
|
||||||
|
|
||||||
|
usage
|
||||||
|
|
||||||
|
|
||||||
|
.. |demo| image:: img/demo.gif
|
||||||
92
doc/usage.rst
Normal file
92
doc/usage.rst
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
Using Masscanned
|
||||||
|
================
|
||||||
|
|
||||||
|
Dedicated addresses
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Masscanned is designed to handle its own IP addresses, which means
|
||||||
|
that the host should not have those addresses configured, and
|
||||||
|
Masscanned will answer ``ARP`` requests (or ``ICMPv6`` ``ND`` neighbor
|
||||||
|
sollicitations).
|
||||||
|
|
||||||
|
The host may have one or more (``IPv4`` and/or ``IPv6``) addresses configured
|
||||||
|
on an interface also used by masscanned, but those addresses must be
|
||||||
|
different from those configured to be used by masscanned.
|
||||||
|
|
||||||
|
In that situation (dedicated addresses), just run:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# masscanned -i <iface> -f <ip_addr_file>
|
||||||
|
|
||||||
|
where ``<ip_addr_file>`` is the path of a text file with one address (``IPv4``
|
||||||
|
or ``IPv6``) per line.
|
||||||
|
|
||||||
|
Addresses shared with the host
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Sometimes it is desirable to have an IP address used by the host
|
||||||
|
(*e.g.*, for administration tasks) and by masscanned (to handle all
|
||||||
|
other incoming packets).
|
||||||
|
|
||||||
|
Since this is not implemented in masscanned, a tiny hack is needed: we
|
||||||
|
are going to run it on a ``veth`` interface.
|
||||||
|
|
||||||
|
For this example, we suppose:
|
||||||
|
|
||||||
|
- The interface is ``eth0``, the address is ``192.168.0.10``.
|
||||||
|
- We want masscanned to handle all the traffic except for incoming SSH
|
||||||
|
connections on TCP/22 port.
|
||||||
|
|
||||||
|
We create a ``veth`` pair of interfaces, on which we are going to use
|
||||||
|
the 0.255.0.0/31 network (which should not be a problem since
|
||||||
|
0.0.0.0/8 is reserved as "Current Network"):
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# ip link add to_masscanned type veth peer masscanned
|
||||||
|
# ip link set masscanned up
|
||||||
|
# ip link set to_masscanned up
|
||||||
|
# ip addr add 0.255.0.0/31 dev to_masscanned
|
||||||
|
# masscanned -i masscanned
|
||||||
|
|
||||||
|
Masscanned can now be used, but only from the host where it runs:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# ping -c 1 0.255.0.1
|
||||||
|
PING 0.255.0.1 (0.255.0.1) 56(84) octets de données.
|
||||||
|
64 octets de 0.255.0.1 : icmp_seq=1 ttl=64 temps=0.442 ms
|
||||||
|
|
||||||
|
--- statistiques ping 0.255.0.1 ---
|
||||||
|
1 paquets transmis, 1 reçus, 0% packet loss, time 0ms
|
||||||
|
rtt min/avg/max/mdev = 0.442/0.442/0.442/0.000 ms
|
||||||
|
|
||||||
|
Now, we are going to use Netfilter / ``iptables`` to redirect incoming
|
||||||
|
traffic to masscanned:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# sysctl -w net.ipv4.ip_forward=1
|
||||||
|
# iptables -t nat -A PREROUTING -i eth0 -d 192.168.0.10 -p tcp --dport 22 -j ACCEPT
|
||||||
|
# iptables -t nat -A PREROUTING -i eth0 -d 192.168.0.10/32 -j DNAT --to-destination 0.255.0.1
|
||||||
|
|
||||||
|
And, from another host on the 192.168.0.0/24 network:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# ping -c 1 192.168.0.10
|
||||||
|
PING 192.168.0.10 (192.168.0.10) 56(84) octets de données.
|
||||||
|
64 octets de 192.168.0.10 : icmp_seq=1 ttl=63 temps=0.366 ms
|
||||||
|
|
||||||
|
--- statistiques ping 192.168.0.10 ---
|
||||||
|
1 paquets transmis, 1 reçus, 0% packet loss, time 0ms
|
||||||
|
rtt min/avg/max/mdev = 0.366/0.366/0.366/0.000 ms
|
||||||
|
|
||||||
|
|
||||||
|
The masscanned output:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
WARN - ARP-Reply to ea:c0:d6:20:0c:6a for IP 0.255.0.1
|
||||||
|
WARN - ICMP-Echo-Reply to ICMP-Echo-Request
|
||||||
45
docker/Dockerfile
Normal file
45
docker/Dockerfile
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - 2024 The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
FROM debian:12 AS fetcher
|
||||||
|
|
||||||
|
RUN apt-get -q update && \
|
||||||
|
apt-get -qy --no-install-recommends install ca-certificates curl && \
|
||||||
|
curl -L https://github.com/ivre/masscanned/archive/refs/heads/master.tar.gz | tar zxf -
|
||||||
|
|
||||||
|
|
||||||
|
FROM rust AS builder
|
||||||
|
|
||||||
|
COPY --from=fetcher /masscanned-master /masscanned-master
|
||||||
|
|
||||||
|
RUN cd masscanned-master && \
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
|
||||||
|
FROM debian:12
|
||||||
|
LABEL maintainer="Pierre LALET <pierre@droids-corp.org>"
|
||||||
|
|
||||||
|
COPY --from=builder /masscanned-master/target/release/masscanned /usr/local/bin/masscanned
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
RUN apt-get -q update && \
|
||||||
|
apt-get -qy --no-install-recommends install iproute2 iptables && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY runmasscanned /usr/local/bin/runmasscanned
|
||||||
|
|
||||||
|
CMD /usr/local/bin/runmasscanned
|
||||||
38
docker/Dockerfile-local
Normal file
38
docker/Dockerfile-local
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - 2024 The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
FROM rust AS builder
|
||||||
|
|
||||||
|
ADD masscanned.tar ./
|
||||||
|
|
||||||
|
RUN cd masscanned-master && \
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
|
||||||
|
FROM debian:12
|
||||||
|
LABEL maintainer="Pierre LALET <pierre@droids-corp.org>"
|
||||||
|
|
||||||
|
COPY --from=builder /masscanned-master/target/release/masscanned /usr/local/bin/masscanned
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
RUN apt-get -q update && \
|
||||||
|
apt-get -qy --no-install-recommends install iproute2 iptables && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY runmasscanned /usr/local/bin/runmasscanned
|
||||||
|
|
||||||
|
CMD /usr/local/bin/runmasscanned
|
||||||
35
docker/runmasscanned
Executable file
35
docker/runmasscanned
Executable file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#! /bin/bash
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - 2023 The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
iface="$(ip route get 0.0.0.1 | awk '/^0\.0\.0\.1 via / {print $5}')"
|
||||||
|
addrs="$(ip a show eth0 | awk '/ inet6? / {print $2}' | sed 's#/.*##' | tr '\n' ',' | sed 's#,$##')"
|
||||||
|
|
||||||
|
if ! capsh --print | awk '/^Current: / {print $2}' | tr ',' '\n' | grep -q '^cap_net_admin$'; then
|
||||||
|
echo "WARNING: cannot run iptables (need capability cap_net_admin)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for v in '' 6; do
|
||||||
|
for c in INPUT OUTPUT FORWARD; do
|
||||||
|
ip${v}tables -P $c DROP
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo Interface: "$iface"
|
||||||
|
echo Addresses: "$addrs"
|
||||||
|
|
||||||
|
/usr/local/bin/masscanned -i "$iface" --self-ip-list "$addrs"
|
||||||
|
|
@ -21,7 +21,7 @@ use std::net::IpAddr;
|
||||||
use pnet::packet::ip::IpNextHeaderProtocol;
|
use pnet::packet::ip::IpNextHeaderProtocol;
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
#[derive(PartialEq, Hash, Copy, Clone)]
|
#[derive(PartialEq, Hash, Copy, Clone, Debug)]
|
||||||
pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> {
|
pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> {
|
||||||
pub src: Option<A>,
|
pub src: Option<A>,
|
||||||
pub dst: Option<A>,
|
pub dst: Option<A>,
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct ClientInfoSrcDst<A: Hash + PartialEq + Clone> {
|
||||||
* - source and dest. transport port
|
* - source and dest. transport port
|
||||||
* - syn cookie
|
* - syn cookie
|
||||||
**/
|
**/
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub struct ClientInfo {
|
pub struct ClientInfo {
|
||||||
pub mac: ClientInfoSrcDst<MacAddr>,
|
pub mac: ClientInfoSrcDst<MacAddr>,
|
||||||
pub ip: ClientInfoSrcDst<IpAddr>,
|
pub ip: ClientInfoSrcDst<IpAddr>,
|
||||||
|
|
@ -65,30 +65,6 @@ impl ClientInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ClientInfo {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
if self.mac != other.mac {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.ip != other.ip {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.transport != other.transport {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.port != other.port {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/* this next case should never occur with TCP and UDP,
|
|
||||||
* but this implementation tries to remain transport-protocol-agnostic
|
|
||||||
**/
|
|
||||||
if self.cookie != other.cookie {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for ClientInfo {}
|
impl Eq for ClientInfo {}
|
||||||
|
|
||||||
impl Display for ClientInfo {
|
impl Display for ClientInfo {
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,18 @@ pub fn repl<'a, 'b>(
|
||||||
arp_req: &'a ArpPacket,
|
arp_req: &'a ArpPacket,
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
) -> Option<MutableArpPacket<'b>> {
|
) -> Option<MutableArpPacket<'b>> {
|
||||||
|
masscanned.log.arp_recv(arp_req);
|
||||||
let mut arp_repl =
|
let mut arp_repl =
|
||||||
MutableArpPacket::owned(arp_req.packet().to_vec()).expect("error parsing ARP packet");
|
MutableArpPacket::owned(arp_req.packet().to_vec()).expect("error parsing ARP packet");
|
||||||
/* Build ARP answer depending of the type of request */
|
/* Build ARP answer depending of the type of request */
|
||||||
match arp_req.get_operation() {
|
match arp_req.get_operation() {
|
||||||
ArpOperations::Request => {
|
ArpOperations::Request => {
|
||||||
|
masscanned.log.arp_recv(arp_req);
|
||||||
let ip = IpAddr::V4(arp_req.get_target_proto_addr());
|
let ip = IpAddr::V4(arp_req.get_target_proto_addr());
|
||||||
/* Ignore ARP requests for IP addresses not handled by masscanned */
|
/* Ignore ARP requests for IP addresses not handled by masscanned */
|
||||||
if let Some(ip_addr_list) = masscanned.ip_addresses {
|
if let Some(ip_addr_list) = masscanned.self_ip_list {
|
||||||
if !ip_addr_list.contains(&ip) {
|
if !ip_addr_list.contains(&ip) {
|
||||||
info!(
|
masscanned.log.arp_drop(arp_req);
|
||||||
"Ignoring ARP request from {} for IP {}",
|
|
||||||
arp_req.get_sender_hw_addr(),
|
|
||||||
ip
|
|
||||||
);
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -53,17 +51,15 @@ pub fn repl<'a, 'b>(
|
||||||
arp_repl.set_target_hw_addr(arp_req.get_sender_hw_addr().to_owned());
|
arp_repl.set_target_hw_addr(arp_req.get_sender_hw_addr().to_owned());
|
||||||
arp_repl.set_target_proto_addr(arp_req.get_sender_proto_addr().to_owned());
|
arp_repl.set_target_proto_addr(arp_req.get_sender_proto_addr().to_owned());
|
||||||
arp_repl.set_sender_proto_addr(arp_req.get_target_proto_addr().to_owned());
|
arp_repl.set_sender_proto_addr(arp_req.get_target_proto_addr().to_owned());
|
||||||
warn!(
|
masscanned.log.arp_send(&arp_repl);
|
||||||
"ARP-Reply to {} for IP {}",
|
|
||||||
arp_req.get_sender_hw_addr(),
|
|
||||||
arp_repl.get_sender_proto_addr()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
info!("ARP Operation not handled: {:?}", arp_repl.get_operation());
|
info!("ARP Operation not handled: {:?}", arp_repl.get_operation());
|
||||||
|
masscanned.log.arp_drop(arp_req);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
masscanned.log.arp_send(&arp_repl);
|
||||||
Some(arp_repl)
|
Some(arp_repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +72,8 @@ mod tests {
|
||||||
|
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arp_reply() {
|
fn test_arp_reply() {
|
||||||
let mut ips = HashSet::new();
|
let mut ips = HashSet::new();
|
||||||
|
|
@ -85,7 +83,9 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let mut arp_req =
|
let mut arp_req =
|
||||||
MutableArpPacket::owned([0; 28].to_vec()).expect("error constructing ARP request");
|
MutableArpPacket::owned([0; 28].to_vec()).expect("error constructing ARP request");
|
||||||
|
|
|
||||||
|
|
@ -104,30 +104,32 @@ pub fn reply<'a, 'b>(
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut client_info: &mut ClientInfo,
|
mut client_info: &mut ClientInfo,
|
||||||
) -> Option<MutableEthernetPacket<'b>> {
|
) -> Option<MutableEthernetPacket<'b>> {
|
||||||
debug!("receiving Ethernet packet: {:?}", eth_req);
|
/* Fill client information for this packet with MAC addresses (src and dst) */
|
||||||
|
client_info.mac.src = Some(eth_req.get_source());
|
||||||
|
client_info.mac.dst = Some(eth_req.get_destination());
|
||||||
|
masscanned.log.eth_recv(eth_req, &client_info);
|
||||||
let mut eth_repl;
|
let mut eth_repl;
|
||||||
/* First, check if the destination MAC address is one of those masscanned
|
/* First, check if the destination MAC address is one of those masscanned
|
||||||
* is authorized to answer to (avoid answering to packets addressed to
|
* is authorized to answer to (avoid answering to packets addressed to
|
||||||
* other machines)
|
* other machines)
|
||||||
**/
|
**/
|
||||||
if !get_authorized_eth_addr(&masscanned.mac, masscanned.ip_addresses)
|
if !get_authorized_eth_addr(&masscanned.mac, masscanned.self_ip_list)
|
||||||
.contains(ð_req.get_destination())
|
.contains(ð_req.get_destination())
|
||||||
{
|
{
|
||||||
info!(
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
"Ignoring Ethernet packet from {} to {}",
|
|
||||||
eth_req.get_source(),
|
|
||||||
eth_req.get_destination(),
|
|
||||||
);
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
/* Fill client information for this packet with MAC addresses (src and dst) */
|
|
||||||
client_info.mac.src = Some(eth_req.get_source());
|
|
||||||
client_info.mac.dst = Some(eth_req.get_destination());
|
|
||||||
/* Build next layer payload for answer depending on the incoming packet */
|
/* Build next layer payload for answer depending on the incoming packet */
|
||||||
match eth_req.get_ethertype() {
|
match eth_req.get_ethertype() {
|
||||||
/* Construct answer to ARP request */
|
/* Construct answer to ARP request */
|
||||||
EtherTypes::Arp => {
|
EtherTypes::Arp => {
|
||||||
let arp_req = ArpPacket::new(eth_req.payload()).expect("error parsing ARP packet");
|
let arp_req = if let Some(p) = ArpPacket::new(eth_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing ARP packet");
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(arp_repl) = arp::repl(&arp_req, masscanned) {
|
if let Some(arp_repl) = arp::repl(&arp_req, masscanned) {
|
||||||
let arp_len = arp_repl.packet().len();
|
let arp_len = arp_repl.packet().len();
|
||||||
let eth_len = EthernetPacket::minimum_packet_size() + arp_len;
|
let eth_len = EthernetPacket::minimum_packet_size() + arp_len;
|
||||||
|
|
@ -136,6 +138,7 @@ pub fn reply<'a, 'b>(
|
||||||
eth_repl.set_ethertype(EtherTypes::Arp);
|
eth_repl.set_ethertype(EtherTypes::Arp);
|
||||||
eth_repl.set_payload(arp_repl.packet());
|
eth_repl.set_payload(arp_repl.packet());
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +148,7 @@ pub fn reply<'a, 'b>(
|
||||||
p
|
p
|
||||||
} else {
|
} else {
|
||||||
warn!("error parsing IPv4 packet");
|
warn!("error parsing IPv4 packet");
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if let Some(mut ipv4_repl) =
|
if let Some(mut ipv4_repl) =
|
||||||
|
|
@ -158,12 +162,19 @@ pub fn reply<'a, 'b>(
|
||||||
eth_repl.set_ethertype(EtherTypes::Ipv4);
|
eth_repl.set_ethertype(EtherTypes::Ipv4);
|
||||||
eth_repl.set_payload(ipv4_repl.packet());
|
eth_repl.set_payload(ipv4_repl.packet());
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Construct answer to IPv6 packet */
|
/* Construct answer to IPv6 packet */
|
||||||
EtherTypes::Ipv6 => {
|
EtherTypes::Ipv6 => {
|
||||||
let ipv6_req = Ipv6Packet::new(eth_req.payload()).expect("error parsing IPv6 packet");
|
let ipv6_req = if let Some(p) = Ipv6Packet::new(eth_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing IPv6 packet");
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(ipv6_repl) = layer_3::ipv6::repl(&ipv6_req, masscanned, &mut client_info) {
|
if let Some(ipv6_repl) = layer_3::ipv6::repl(&ipv6_req, masscanned, &mut client_info) {
|
||||||
let ipv6_len = ipv6_repl.packet().len();
|
let ipv6_len = ipv6_repl.packet().len();
|
||||||
let eth_len = EthernetPacket::minimum_packet_size() + ipv6_len;
|
let eth_len = EthernetPacket::minimum_packet_size() + ipv6_len;
|
||||||
|
|
@ -172,18 +183,20 @@ pub fn reply<'a, 'b>(
|
||||||
eth_repl.set_ethertype(EtherTypes::Ipv6);
|
eth_repl.set_ethertype(EtherTypes::Ipv6);
|
||||||
eth_repl.set_payload(ipv6_repl.packet());
|
eth_repl.set_payload(ipv6_repl.packet());
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Log & drop unknown network protocol */
|
/* Log & drop unknown network protocol */
|
||||||
_ => {
|
_ => {
|
||||||
info!("Ethernet type not handled: {:?}", eth_req.get_ethertype());
|
info!("Ethernet type not handled: {:?}", eth_req.get_ethertype());
|
||||||
|
masscanned.log.eth_drop(eth_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
eth_repl.set_source(masscanned.mac);
|
eth_repl.set_source(masscanned.mac);
|
||||||
eth_repl.set_destination(eth_req.get_source());
|
eth_repl.set_destination(eth_req.get_source());
|
||||||
debug!("sending Ethernet packet: {:?}", eth_repl);
|
masscanned.log.eth_send(ð_repl, &client_info);
|
||||||
Some(eth_repl)
|
Some(eth_repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,6 +206,46 @@ mod tests {
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eth_empty() {
|
||||||
|
let payload = b"";
|
||||||
|
let test_mac_addr =
|
||||||
|
MacAddr::from_str("55:44:33:22:11:00").expect("error parsing MAC address");
|
||||||
|
let mac = MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address");
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(Ipv4Addr::new(0xaa, 0x99, 0x88, 0x77)));
|
||||||
|
ips.insert(IpAddr::V6(Ipv6Addr::new(
|
||||||
|
0x7777, 0x7777, 0x7777, 0x7777, 0x7777, 0x7777, 0xaabb, 0xccdd,
|
||||||
|
)));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: mac,
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
for proto in [EtherTypes::Ipv4, EtherTypes::Ipv6, EtherTypes::Arp] {
|
||||||
|
let mut eth_req = MutableEthernetPacket::owned(vec![
|
||||||
|
0;
|
||||||
|
EthernetPacket::minimum_packet_size(
|
||||||
|
) + payload.len()
|
||||||
|
])
|
||||||
|
.expect("error constructing ethernet packet");
|
||||||
|
eth_req.set_source(test_mac_addr);
|
||||||
|
eth_req.set_payload(payload);
|
||||||
|
eth_req.set_ethertype(proto);
|
||||||
|
eth_req.set_destination(mac);
|
||||||
|
if let Some(_) = reply(ð_req.to_immutable(), &masscanned, &mut client_info) {
|
||||||
|
panic!("expected no Ethernet answer, got one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_eth_reply() {
|
fn test_eth_reply() {
|
||||||
/* test payload is IP(src="3.2.1.0", dst=".".join(str(b) for b in [0xaa, 0x99,
|
/* test payload is IP(src="3.2.1.0", dst=".".join(str(b) for b in [0xaa, 0x99,
|
||||||
|
|
@ -211,7 +264,9 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let mut eth_req = MutableEthernetPacket::owned(vec![
|
let mut eth_req = MutableEthernetPacket::owned(vec![
|
||||||
0;
|
0;
|
||||||
|
|
|
||||||
|
|
@ -39,31 +39,43 @@ pub fn repl<'a, 'b>(
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut client_info: &mut ClientInfo,
|
mut client_info: &mut ClientInfo,
|
||||||
) -> Option<MutableIpv4Packet<'b>> {
|
) -> Option<MutableIpv4Packet<'b>> {
|
||||||
debug!("receiving IPv4 packet: {:?}", ip_req);
|
/* Fill client info with source and dest. IP addresses */
|
||||||
|
client_info.ip.src = Some(IpAddr::V4(ip_req.get_source()));
|
||||||
|
client_info.ip.dst = Some(IpAddr::V4(ip_req.get_destination()));
|
||||||
|
masscanned.log.ipv4_recv(&ip_req, &client_info);
|
||||||
/* If masscanned is configured with IP addresses, then
|
/* If masscanned is configured with IP addresses, then
|
||||||
* check that the dest. IP address of the packet is one of
|
* check that the dest. IP address of the packet is one of
|
||||||
* those handled by masscanned - otherwise, drop the packet.
|
* those handled by masscanned - otherwise, drop the packet.
|
||||||
**/
|
**/
|
||||||
if let Some(ip_addr_list) = masscanned.ip_addresses {
|
if let Some(ip_addr_list) = masscanned.self_ip_list {
|
||||||
if !ip_addr_list.contains(&IpAddr::V4(ip_req.get_destination())) {
|
if !ip_addr_list.contains(&IpAddr::V4(ip_req.get_destination())) {
|
||||||
info!(
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
"Ignoring IP packet from {} for {}",
|
return None;
|
||||||
ip_req.get_source(),
|
}
|
||||||
ip_req.get_destination()
|
}
|
||||||
);
|
/* If masscanned is configured with a remote ip deny list, then
|
||||||
|
* check if the src. IP address of the packet is one of
|
||||||
|
* those ignored by masscanned - if so, drop the packet.
|
||||||
|
**/
|
||||||
|
if let Some(remote_ip_deny_list) = masscanned.remote_ip_deny_list {
|
||||||
|
if remote_ip_deny_list.contains(&IpAddr::V4(ip_req.get_source())) {
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Fill client info with source and dest. IP addresses */
|
|
||||||
client_info.ip.src = Some(IpAddr::V4(ip_req.get_source()));
|
|
||||||
client_info.ip.dst = Some(IpAddr::V4(ip_req.get_destination()));
|
|
||||||
/* Fill client info with transport layer procotol */
|
/* Fill client info with transport layer procotol */
|
||||||
client_info.transport = Some(ip_req.get_next_level_protocol());
|
client_info.transport = Some(ip_req.get_next_level_protocol());
|
||||||
let mut ip_repl;
|
let mut ip_repl;
|
||||||
match ip_req.get_next_level_protocol() {
|
match ip_req.get_next_level_protocol() {
|
||||||
/* Answer to an ICMP packet */
|
/* Answer to an ICMP packet */
|
||||||
IpNextHeaderProtocols::Icmp => {
|
IpNextHeaderProtocols::Icmp => {
|
||||||
let icmp_req = IcmpPacket::new(ip_req.payload()).expect("error parsing ICMP packet");
|
let icmp_req = if let Some(p) = IcmpPacket::new(ip_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing ICMP packet");
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(mut icmp_repl) = layer_4::icmpv4::repl(&icmp_req, masscanned, &client_info)
|
if let Some(mut icmp_repl) = layer_4::icmpv4::repl(&icmp_req, masscanned, &client_info)
|
||||||
{
|
{
|
||||||
icmp_repl.set_checksum(ipv4_checksum_icmp(&icmp_repl.to_immutable()));
|
icmp_repl.set_checksum(ipv4_checksum_icmp(&icmp_repl.to_immutable()));
|
||||||
|
|
@ -77,12 +89,19 @@ pub fn repl<'a, 'b>(
|
||||||
ip_repl.set_payload(icmp_repl.packet());
|
ip_repl.set_payload(icmp_repl.packet());
|
||||||
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
|
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Answer to a TCP packet */
|
/* Answer to a TCP packet */
|
||||||
IpNextHeaderProtocols::Tcp => {
|
IpNextHeaderProtocols::Tcp => {
|
||||||
let tcp_req = TcpPacket::new(ip_req.payload()).expect("error parsing TCP packet");
|
let tcp_req = if let Some(p) = TcpPacket::new(ip_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing TCP packet");
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
|
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
|
||||||
tcp_repl.set_checksum(ipv4_checksum_tcp(
|
tcp_repl.set_checksum(ipv4_checksum_tcp(
|
||||||
&tcp_repl.to_immutable(),
|
&tcp_repl.to_immutable(),
|
||||||
|
|
@ -99,12 +118,19 @@ pub fn repl<'a, 'b>(
|
||||||
ip_repl.set_payload(tcp_repl.packet());
|
ip_repl.set_payload(tcp_repl.packet());
|
||||||
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
|
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Answer to an UDP packet */
|
/* Answer to an UDP packet */
|
||||||
IpNextHeaderProtocols::Udp => {
|
IpNextHeaderProtocols::Udp => {
|
||||||
let udp_req = UdpPacket::new(ip_req.payload()).expect("error parsing UDP packet");
|
let udp_req = if let Some(p) = UdpPacket::new(ip_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing UDP packet");
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
|
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
|
||||||
udp_repl.set_checksum(ipv4_checksum_udp(
|
udp_repl.set_checksum(ipv4_checksum_udp(
|
||||||
&udp_repl.to_immutable(),
|
&udp_repl.to_immutable(),
|
||||||
|
|
@ -123,15 +149,13 @@ pub fn repl<'a, 'b>(
|
||||||
ip_repl.set_payload(udp_repl.packet());
|
ip_repl.set_payload(udp_repl.packet());
|
||||||
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
ip_repl.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Next layer protocol not handled (yet) - dropping packet */
|
/* Next layer protocol not handled (yet) - dropping packet */
|
||||||
_ => {
|
_ => {
|
||||||
info!(
|
masscanned.log.ipv4_drop(&ip_req, &client_info);
|
||||||
"IPv4 upper layer not handled: {:?}",
|
|
||||||
ip_req.get_next_level_protocol()
|
|
||||||
);
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -150,7 +174,7 @@ pub fn repl<'a, 'b>(
|
||||||
/* FIXME when dest. was a multicast IP address */
|
/* FIXME when dest. was a multicast IP address */
|
||||||
ip_repl.set_source(ip_req.get_destination());
|
ip_repl.set_source(ip_req.get_destination());
|
||||||
ip_repl.set_destination(ip_req.get_source());
|
ip_repl.set_destination(ip_req.get_source());
|
||||||
debug!("sending IPv4 packet: {:?}", ip_repl);
|
masscanned.log.ipv4_send(&ip_repl, &client_info);
|
||||||
Some(ip_repl)
|
Some(ip_repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,10 +187,11 @@ mod tests {
|
||||||
|
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ipv4_reply() {
|
fn test_ipv4_empty() {
|
||||||
/* test payload is scapy> ICMP() */
|
let payload = b"";
|
||||||
let payload = b"\x08\x00\xf7\xff\x00\x00\x00\x00";
|
|
||||||
let mut client_info = ClientInfo::new();
|
let mut client_info = ClientInfo::new();
|
||||||
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
|
@ -177,7 +202,60 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
for proto in [
|
||||||
|
IpNextHeaderProtocols::Tcp,
|
||||||
|
IpNextHeaderProtocols::Udp,
|
||||||
|
IpNextHeaderProtocols::Icmp,
|
||||||
|
] {
|
||||||
|
let mut ip_req = MutableIpv4Packet::owned(vec![
|
||||||
|
0;
|
||||||
|
Ipv4Packet::minimum_packet_size()
|
||||||
|
+ payload.len()
|
||||||
|
])
|
||||||
|
.expect("error constructing IPv4 packet");
|
||||||
|
ip_req.set_version(4);
|
||||||
|
ip_req.set_ttl(64);
|
||||||
|
ip_req.set_identification(0);
|
||||||
|
ip_req.set_flags(Ipv4Flags::DontFragment);
|
||||||
|
ip_req.set_source(test_ip_addr);
|
||||||
|
ip_req.set_header_length(5);
|
||||||
|
/* Set test payload for layer 4 */
|
||||||
|
ip_req.set_total_length(ip_req.packet().len() as u16);
|
||||||
|
ip_req.set_payload(payload);
|
||||||
|
/* Set next protocol */
|
||||||
|
ip_req.set_next_level_protocol(proto);
|
||||||
|
/* Send to a legitimate IP address */
|
||||||
|
ip_req.set_destination(masscanned_ip_addr);
|
||||||
|
if let Some(_) = repl(&ip_req.to_immutable(), &masscanned, &mut client_info) {
|
||||||
|
panic!("expected no IP answer, got one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ipv4_reply() {
|
||||||
|
/* test payload is scapy> ICMP() */
|
||||||
|
let payload = b"\x08\x00\xf7\xff\x00\x00\x00\x00";
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
let blacklist_ip_addr = Ipv4Addr::new(3, 3, 3, 3);
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
let mut blacklist_ips = HashSet::new();
|
||||||
|
blacklist_ips.insert(IpAddr::V4(blacklist_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: Some(&blacklist_ips),
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let mut ip_req =
|
let mut ip_req =
|
||||||
MutableIpv4Packet::owned(vec![0; Ipv4Packet::minimum_packet_size() + payload.len()])
|
MutableIpv4Packet::owned(vec![0; Ipv4Packet::minimum_packet_size() + payload.len()])
|
||||||
|
|
@ -206,5 +284,9 @@ mod tests {
|
||||||
/* Send to a non-legitimate IP address */
|
/* Send to a non-legitimate IP address */
|
||||||
ip_req.set_destination(Ipv4Addr::new(2, 2, 2, 2));
|
ip_req.set_destination(Ipv4Addr::new(2, 2, 2, 2));
|
||||||
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||||
|
/* Send from a non-legitimate IP address */
|
||||||
|
ip_req.set_source(blacklist_ip_addr);
|
||||||
|
ip_req.set_destination(masscanned_ip_addr);
|
||||||
|
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,18 +35,31 @@ pub fn repl<'a, 'b>(
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut client_info: &mut ClientInfo,
|
mut client_info: &mut ClientInfo,
|
||||||
) -> Option<MutableIpv6Packet<'b>> {
|
) -> Option<MutableIpv6Packet<'b>> {
|
||||||
debug!("receiving IPv6 packet: {:?}", ip_req);
|
/* Fill client info with source and dest. IP address */
|
||||||
|
client_info.ip.src = Some(IpAddr::V6(ip_req.get_source()));
|
||||||
|
client_info.ip.dst = Some(IpAddr::V6(ip_req.get_destination()));
|
||||||
|
masscanned.log.ipv6_recv(ip_req, client_info);
|
||||||
let src = ip_req.get_source();
|
let src = ip_req.get_source();
|
||||||
let mut dst = ip_req.get_destination();
|
let mut dst = ip_req.get_destination();
|
||||||
/* If masscanned is configured with IP addresses, check that
|
/* If masscanned is configured with IP addresses, then
|
||||||
* the dest. IP address corresponds to one of those
|
* check that the dest. IP address of the packet is one of
|
||||||
* Otherwise, drop the packet.
|
* those handled by masscanned - otherwise, drop the packet.
|
||||||
**/
|
**/
|
||||||
if let Some(ip_addr_list) = masscanned.ip_addresses {
|
if let Some(ip_addr_list) = masscanned.self_ip_list {
|
||||||
if !ip_addr_list.contains(&IpAddr::V6(dst))
|
if !ip_addr_list.contains(&IpAddr::V6(dst))
|
||||||
&& ip_req.get_next_header() != IpNextHeaderProtocols::Icmpv6
|
&& ip_req.get_next_header() != IpNextHeaderProtocols::Icmpv6
|
||||||
{
|
{
|
||||||
info!("Ignoring IP packet from {} for {}", &src, &dst);
|
masscanned.log.ipv6_drop(ip_req, client_info);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* If masscanned is configured with a remote ip deny list, then
|
||||||
|
* check if the src. IP address of the packet is one of
|
||||||
|
* those ignored by masscanned - if so, drop the packet.
|
||||||
|
**/
|
||||||
|
if let Some(remote_ip_deny_list) = masscanned.remote_ip_deny_list {
|
||||||
|
if remote_ip_deny_list.contains(&IpAddr::V6(src)) {
|
||||||
|
masscanned.log.ipv6_drop(ip_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +72,13 @@ pub fn repl<'a, 'b>(
|
||||||
match ip_req.get_next_header() {
|
match ip_req.get_next_header() {
|
||||||
/* Answer to ICMPv6 */
|
/* Answer to ICMPv6 */
|
||||||
IpNextHeaderProtocols::Icmpv6 => {
|
IpNextHeaderProtocols::Icmpv6 => {
|
||||||
let icmp_req =
|
let icmp_req = if let Some(p) = Icmpv6Packet::new(ip_req.payload()) {
|
||||||
Icmpv6Packet::new(ip_req.payload()).expect("error parsing ICMPv6 packet");
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing ICMPv6 packet");
|
||||||
|
masscanned.log.ipv6_drop(&ip_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let (Some(mut icmp_repl), dst_addr) =
|
if let (Some(mut icmp_repl), dst_addr) =
|
||||||
layer_4::icmpv6::repl(&icmp_req, masscanned, &client_info)
|
layer_4::icmpv6::repl(&icmp_req, masscanned, &client_info)
|
||||||
{
|
{
|
||||||
|
|
@ -84,12 +102,19 @@ pub fn repl<'a, 'b>(
|
||||||
ip_repl.set_hop_limit(255);
|
ip_repl.set_hop_limit(255);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.ipv6_drop(ip_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Answer to TCP */
|
/* Answer to TCP */
|
||||||
IpNextHeaderProtocols::Tcp => {
|
IpNextHeaderProtocols::Tcp => {
|
||||||
let tcp_req = TcpPacket::new(ip_req.payload()).expect("error parsing TCP packet");
|
let tcp_req = if let Some(p) = TcpPacket::new(ip_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing TCP packet");
|
||||||
|
masscanned.log.ipv6_drop(&ip_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
|
if let Some(mut tcp_repl) = layer_4::tcp::repl(&tcp_req, masscanned, &mut client_info) {
|
||||||
/* Compute and set TCP checksum */
|
/* Compute and set TCP checksum */
|
||||||
tcp_repl.set_checksum(ipv6_checksum_tcp(
|
tcp_repl.set_checksum(ipv6_checksum_tcp(
|
||||||
|
|
@ -108,12 +133,19 @@ pub fn repl<'a, 'b>(
|
||||||
ip_repl.set_payload_length(tcp_len as u16);
|
ip_repl.set_payload_length(tcp_len as u16);
|
||||||
ip_repl.set_payload(&tcp_repl.packet());
|
ip_repl.set_payload(&tcp_repl.packet());
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.ipv6_drop(ip_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Answer to UDP */
|
/* Answer to UDP */
|
||||||
IpNextHeaderProtocols::Udp => {
|
IpNextHeaderProtocols::Udp => {
|
||||||
let udp_req = UdpPacket::new(ip_req.payload()).expect("error parsing UDP packet");
|
let udp_req = if let Some(p) = UdpPacket::new(ip_req.payload()) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
warn!("error parsing UDP packet");
|
||||||
|
masscanned.log.ipv6_drop(&ip_req, &client_info);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
|
if let Some(mut udp_repl) = layer_4::udp::repl(&udp_req, masscanned, &mut client_info) {
|
||||||
/* Compute and set UDP checksum */
|
/* Compute and set UDP checksum */
|
||||||
udp_repl.set_checksum(ipv6_checksum_udp(
|
udp_repl.set_checksum(ipv6_checksum_udp(
|
||||||
|
|
@ -132,15 +164,13 @@ pub fn repl<'a, 'b>(
|
||||||
ip_repl.set_payload_length(udp_len as u16);
|
ip_repl.set_payload_length(udp_len as u16);
|
||||||
ip_repl.set_payload(&udp_repl.packet());
|
ip_repl.set_payload(&udp_repl.packet());
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.ipv6_drop(ip_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Other protocols are not handled (yet) - dropping */
|
/* Other protocols are not handled (yet) - dropping */
|
||||||
_ => {
|
_ => {
|
||||||
info!(
|
masscanned.log.ipv6_drop(ip_req, client_info);
|
||||||
"IPv6 upper layer not handled: {:?}",
|
|
||||||
ip_req.get_next_header()
|
|
||||||
);
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -153,7 +183,7 @@ pub fn repl<'a, 'b>(
|
||||||
/* Set packet source and dest. */
|
/* Set packet source and dest. */
|
||||||
ip_repl.set_source(dst);
|
ip_repl.set_source(dst);
|
||||||
ip_repl.set_destination(src);
|
ip_repl.set_destination(src);
|
||||||
debug!("sending IPv6 packet: {:?}", ip_repl);
|
masscanned.log.ipv6_send(&ip_repl, client_info);
|
||||||
Some(ip_repl)
|
Some(ip_repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,12 +196,11 @@ mod tests {
|
||||||
|
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ipv6_reply() {
|
fn test_ipv6_empty() {
|
||||||
/* test payload is scapy> IPv6(src="7777:6666:5555:4444:3333:2222:1111:0000",
|
let payload = b"";
|
||||||
* dst="0000:1111:2222:3333:4444:5555:6666:7777")/TCP(sport=12345, dport=54321,
|
|
||||||
* flags="S"))[TCP] */
|
|
||||||
let payload = b"09\xd41\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcf\xbc\x00\x00";
|
|
||||||
let mut client_info = ClientInfo::new();
|
let mut client_info = ClientInfo::new();
|
||||||
let test_ip_addr = Ipv6Addr::new(
|
let test_ip_addr = Ipv6Addr::new(
|
||||||
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
|
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
|
||||||
|
|
@ -186,7 +215,64 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
for proto in [
|
||||||
|
IpNextHeaderProtocols::Tcp,
|
||||||
|
IpNextHeaderProtocols::Udp,
|
||||||
|
IpNextHeaderProtocols::Icmp,
|
||||||
|
] {
|
||||||
|
let mut ip_req = MutableIpv6Packet::owned(vec![
|
||||||
|
0;
|
||||||
|
Ipv6Packet::minimum_packet_size()
|
||||||
|
+ payload.len()
|
||||||
|
])
|
||||||
|
.expect("error constructing IPv6 packet");
|
||||||
|
ip_req.set_version(6);
|
||||||
|
ip_req.set_source(test_ip_addr);
|
||||||
|
/* Set test payload for layer 4 */
|
||||||
|
ip_req.set_payload_length(payload.len() as u16);
|
||||||
|
ip_req.set_payload(payload);
|
||||||
|
/* Set next protocol */
|
||||||
|
ip_req.set_next_header(proto);
|
||||||
|
/* Send to a legitimate IP address */
|
||||||
|
ip_req.set_destination(masscanned_ip_addr);
|
||||||
|
if let Some(_) = repl(&ip_req.to_immutable(), &masscanned, &mut client_info) {
|
||||||
|
panic!("expected no IP answer, got one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ipv6_reply() {
|
||||||
|
/* test payload is scapy> IPv6(src="7777:6666:5555:4444:3333:2222:1111:0000",
|
||||||
|
* dst="0000:1111:2222:3333:4444:5555:6666:7777")/TCP(sport=12345, dport=54321,
|
||||||
|
* flags="S"))[TCP] */
|
||||||
|
let payload = b"09\xd41\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcf\xbc\x00\x00";
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv6Addr::new(
|
||||||
|
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
|
||||||
|
);
|
||||||
|
let masscanned_ip_addr = Ipv6Addr::new(
|
||||||
|
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777,
|
||||||
|
);
|
||||||
|
let blacklist_ip_addr = Ipv6Addr::new(
|
||||||
|
0x1111, 0x1111, 0x1111, 0x1111, 0x1111, 0x1111, 0x1111, 0x1111,
|
||||||
|
);
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V6(masscanned_ip_addr));
|
||||||
|
let mut blacklist_ips = HashSet::new();
|
||||||
|
blacklist_ips.insert(IpAddr::V6(blacklist_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: Some(&blacklist_ips),
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let mut ip_req =
|
let mut ip_req =
|
||||||
MutableIpv6Packet::owned(vec![0; Ipv6Packet::minimum_packet_size() + payload.len()])
|
MutableIpv6Packet::owned(vec![0; Ipv6Packet::minimum_packet_size() + payload.len()])
|
||||||
|
|
@ -213,5 +299,9 @@ mod tests {
|
||||||
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7778,
|
0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7778,
|
||||||
));
|
));
|
||||||
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||||
|
/* Send from a non-legitimate IP address */
|
||||||
|
ip_req.set_source(blacklist_ip_addr);
|
||||||
|
ip_req.set_destination(masscanned_ip_addr);
|
||||||
|
assert!(repl(&ip_req.to_immutable(), &masscanned, &mut client_info) == None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
use pnet::packet::{
|
use pnet::packet::{
|
||||||
icmp::{IcmpCode, IcmpPacket, IcmpTypes, MutableIcmpPacket},
|
icmp::{IcmpCode, IcmpPacket, IcmpTypes, MutableIcmpPacket},
|
||||||
Packet,
|
Packet,
|
||||||
|
|
@ -26,16 +24,16 @@ use crate::Masscanned;
|
||||||
|
|
||||||
pub fn repl<'a, 'b>(
|
pub fn repl<'a, 'b>(
|
||||||
icmp_req: &'a IcmpPacket,
|
icmp_req: &'a IcmpPacket,
|
||||||
_masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut _client_info: &ClientInfo,
|
client_info: &ClientInfo,
|
||||||
) -> Option<MutableIcmpPacket<'b>> {
|
) -> Option<MutableIcmpPacket<'b>> {
|
||||||
debug!("receiving ICMPv4 packet: {:?}", icmp_req);
|
masscanned.log.icmpv4_recv(icmp_req, client_info);
|
||||||
let mut icmp_repl;
|
let mut icmp_repl;
|
||||||
match icmp_req.get_icmp_type() {
|
match icmp_req.get_icmp_type() {
|
||||||
IcmpTypes::EchoRequest => {
|
IcmpTypes::EchoRequest => {
|
||||||
/* Check code of ICMP packet */
|
/* Check code of ICMP packet */
|
||||||
if icmp_req.get_icmp_code() != IcmpCode(0) {
|
if icmp_req.get_icmp_code() != IcmpCode(0) {
|
||||||
info!("ICMP code not handled: {:?}", icmp_req.get_icmp_code());
|
masscanned.log.icmpv4_drop(icmp_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
/* Compute answer length */
|
/* Compute answer length */
|
||||||
|
|
@ -53,13 +51,13 @@ pub fn repl<'a, 'b>(
|
||||||
* reply message."
|
* reply message."
|
||||||
**/
|
**/
|
||||||
icmp_repl.set_payload(icmp_req.payload());
|
icmp_repl.set_payload(icmp_req.payload());
|
||||||
warn!("ICMP-Echo-Reply to ICMP-Echo-Request");
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
masscanned.log.icmpv4_drop(icmp_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug!("sending ICMPv4 packet: {:?}", icmp_repl);
|
masscanned.log.icmpv4_send(&icmp_repl, client_info);
|
||||||
Some(icmp_repl)
|
Some(icmp_repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,6 +68,8 @@ mod tests {
|
||||||
|
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_icmpv4_reply() {
|
fn test_icmpv4_reply() {
|
||||||
/* test payload is scapy> ICMP() */
|
/* test payload is scapy> ICMP() */
|
||||||
|
|
@ -80,7 +80,9 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: None,
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let mut icmp_req =
|
let mut icmp_req =
|
||||||
MutableIcmpPacket::owned(vec![0; IcmpPacket::minimum_packet_size() + payload.len()])
|
MutableIcmpPacket::owned(vec![0; IcmpPacket::minimum_packet_size() + payload.len()])
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ pub fn nd_ns_repl<'a, 'b>(
|
||||||
* check that the dest. IP address of the packet is one of
|
* check that the dest. IP address of the packet is one of
|
||||||
* those handled by masscanned - otherwise, drop the packet.
|
* those handled by masscanned - otherwise, drop the packet.
|
||||||
**/
|
**/
|
||||||
if let Some(addresses) = masscanned.ip_addresses {
|
if let Some(addresses) = masscanned.self_ip_list {
|
||||||
if !addresses.contains(&IpAddr::V6(nd_ns_req.get_target_addr())) {
|
if !addresses.contains(&IpAddr::V6(nd_ns_req.get_target_addr())) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ pub fn repl<'a, 'b>(
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
client_info: &ClientInfo,
|
client_info: &ClientInfo,
|
||||||
) -> (Option<MutableIcmpv6Packet<'b>>, Option<Ipv6Addr>) {
|
) -> (Option<MutableIcmpv6Packet<'b>>, Option<Ipv6Addr>) {
|
||||||
debug!("receiving ICMPv6 packet: {:?}", icmp_req);
|
masscanned.log.icmpv6_recv(icmp_req, client_info);
|
||||||
let mut dst_ip = None;
|
let mut dst_ip = None;
|
||||||
if icmp_req.get_icmpv6_code() != Icmpv6Codes::NoCode {
|
if icmp_req.get_icmpv6_code() != Icmpv6Codes::NoCode {
|
||||||
return (None, None);
|
return (None, None);
|
||||||
|
|
@ -120,6 +120,7 @@ pub fn repl<'a, 'b>(
|
||||||
icmp_repl = MutableIcmpv6Packet::owned(nd_na_repl.packet().to_vec())
|
icmp_repl = MutableIcmpv6Packet::owned(nd_na_repl.packet().to_vec())
|
||||||
.expect("error constructing an ICMPv6 packet");
|
.expect("error constructing an ICMPv6 packet");
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.icmpv6_drop(icmp_req, client_info);
|
||||||
return (None, None);
|
return (None, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,17 +137,13 @@ pub fn repl<'a, 'b>(
|
||||||
icmp_repl = MutableIcmpv6Packet::owned(vec![0; Icmpv6Packet::packet_size(&echo_repl)])
|
icmp_repl = MutableIcmpv6Packet::owned(vec![0; Icmpv6Packet::packet_size(&echo_repl)])
|
||||||
.expect("error constructing an ICMPv6 packet");
|
.expect("error constructing an ICMPv6 packet");
|
||||||
icmp_repl.populate(&echo_repl);
|
icmp_repl.populate(&echo_repl);
|
||||||
warn!("ICMPv6-Echo-Reply to ICMPv6-Echo-Request");
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
info!(
|
masscanned.log.icmpv6_drop(icmp_req, client_info);
|
||||||
"ICMPv6 packet not handled: {:?}",
|
|
||||||
icmp_req.get_icmpv6_type()
|
|
||||||
);
|
|
||||||
return (None, None);
|
return (None, None);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug!("sending ICMPv6 packet: {:?}", icmp_repl);
|
masscanned.log.icmpv6_send(&icmp_repl, client_info);
|
||||||
(Some(icmp_repl), dst_ip)
|
(Some(icmp_repl), dst_ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +157,8 @@ mod tests {
|
||||||
use pnet::packet::icmpv6::ndp::{MutableNeighborSolicitPacket, NeighborSolicit};
|
use pnet::packet::icmpv6::ndp::{MutableNeighborSolicitPacket, NeighborSolicit};
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nd_na_reply() {
|
fn test_nd_na_reply() {
|
||||||
let client_info = ClientInfo::new();
|
let client_info = ClientInfo::new();
|
||||||
|
|
@ -173,7 +172,9 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
/* Legitimate solicitation */
|
/* Legitimate solicitation */
|
||||||
let ndp_ns = NeighborSolicit {
|
let ndp_ns = NeighborSolicit {
|
||||||
|
|
@ -245,7 +246,9 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let mut icmpv6_echo_req = MutableIcmpv6Packet::owned(vec![
|
let mut icmpv6_echo_req = MutableIcmpv6Packet::owned(vec![
|
||||||
0;
|
0;
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ pub fn repl<'a, 'b>(
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut client_info: &mut ClientInfo,
|
mut client_info: &mut ClientInfo,
|
||||||
) -> Option<MutableTcpPacket<'b>> {
|
) -> Option<MutableTcpPacket<'b>> {
|
||||||
debug!("receiving TCP packet: {:?}", tcp_req);
|
|
||||||
/* Fill client info with source and dest. TCP port */
|
/* Fill client info with source and dest. TCP port */
|
||||||
client_info.port.src = Some(tcp_req.get_source());
|
client_info.port.src = Some(tcp_req.get_source());
|
||||||
client_info.port.dst = Some(tcp_req.get_destination());
|
client_info.port.dst = Some(tcp_req.get_destination());
|
||||||
|
masscanned.log.tcp_recv(tcp_req, client_info);
|
||||||
/* Construct response TCP packet */
|
/* Construct response TCP packet */
|
||||||
let mut tcp_repl;
|
let mut tcp_repl;
|
||||||
match tcp_req.get_flags() {
|
match tcp_req.get_flags() {
|
||||||
|
|
@ -49,16 +49,24 @@ pub fn repl<'a, 'b>(
|
||||||
};
|
};
|
||||||
/* Compute syncookie */
|
/* Compute syncookie */
|
||||||
if let Ok(cookie) = synackcookie::generate(&client_info, &masscanned.synack_key) {
|
if let Ok(cookie) = synackcookie::generate(&client_info, &masscanned.synack_key) {
|
||||||
if cookie != ackno {
|
|
||||||
info!("PSH-ACK ignored: synackcookie not valid");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
client_info.cookie = Some(cookie);
|
client_info.cookie = Some(cookie);
|
||||||
|
if !proto::is_tcb_set(cookie) {
|
||||||
|
/* First Ack: check syncookie, create tcb */
|
||||||
|
if cookie != ackno {
|
||||||
|
masscanned.log.tcp_drop(tcp_req, client_info);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
proto::add_tcb(cookie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
warn!("ACK to PSH-ACK on port {}", tcp_req.get_destination());
|
warn!("ACK to PSH-ACK on port {}", tcp_req.get_destination());
|
||||||
let payload = tcp_req.payload();
|
let payload = tcp_req.payload();
|
||||||
/* Any answer to upper-layer protocol? */
|
/* Any answer to upper-layer protocol? */
|
||||||
if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info) {
|
let mut payload_repl = None;
|
||||||
|
proto::get_tcb(client_info.cookie.unwrap(), |tcb| {
|
||||||
|
payload_repl = proto::repl(&payload, masscanned, &mut client_info, tcb);
|
||||||
|
});
|
||||||
|
if let Some(repl) = payload_repl {
|
||||||
tcp_repl = MutableTcpPacket::owned(
|
tcp_repl = MutableTcpPacket::owned(
|
||||||
[vec![0; MutableTcpPacket::minimum_packet_size()], repl].concat(),
|
[vec![0; MutableTcpPacket::minimum_packet_size()], repl].concat(),
|
||||||
)
|
)
|
||||||
|
|
@ -70,33 +78,52 @@ pub fn repl<'a, 'b>(
|
||||||
.expect("error constructing a TCP packet");
|
.expect("error constructing a TCP packet");
|
||||||
tcp_repl.set_flags(TcpFlags::ACK);
|
tcp_repl.set_flags(TcpFlags::ACK);
|
||||||
}
|
}
|
||||||
tcp_repl.set_acknowledgement(tcp_req.get_sequence() + (tcp_req.payload().len() as u32));
|
tcp_repl.set_acknowledgement(
|
||||||
|
tcp_req
|
||||||
|
.get_sequence()
|
||||||
|
.wrapping_add(tcp_req.payload().len() as u32),
|
||||||
|
);
|
||||||
tcp_repl.set_sequence(tcp_req.get_acknowledgement());
|
tcp_repl.set_sequence(tcp_req.get_acknowledgement());
|
||||||
}
|
}
|
||||||
/* Answer to ACK: nothing */
|
/* Answer to ACK: nothing */
|
||||||
flags if flags == TcpFlags::ACK => {
|
flags if flags == TcpFlags::ACK => {
|
||||||
/* answer here when server needs to speak first after handshake */
|
/* answer here when server needs to speak first after handshake */
|
||||||
|
masscanned.log.tcp_drop(tcp_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
/* Answer to RST and FIN: nothing */
|
/* Answer to RST: nothing */
|
||||||
flags if (flags == TcpFlags::RST || flags == (TcpFlags::FIN | TcpFlags::ACK)) => {
|
flags if flags == TcpFlags::RST => {
|
||||||
|
masscanned.log.tcp_drop(tcp_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
/* Answer to SYN */
|
/* Answer to FIN,ACK with FIN,ACK */
|
||||||
flags if flags & TcpFlags::SYN == TcpFlags::SYN => {
|
flags if flags == (TcpFlags::FIN | TcpFlags::ACK) => {
|
||||||
|
tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
|
||||||
|
.expect("error constructing a TCP packet");
|
||||||
|
tcp_repl.set_flags(TcpFlags::FIN | TcpFlags::ACK);
|
||||||
|
tcp_repl.set_acknowledgement(tcp_req.get_sequence().wrapping_add(1));
|
||||||
|
tcp_repl.set_sequence(tcp_req.get_acknowledgement());
|
||||||
|
}
|
||||||
|
/* Answer to SYN + P|U|C|E + !(C && E) to imitate Linux network stack */
|
||||||
|
flags
|
||||||
|
if (flags & TcpFlags::SYN) == TcpFlags::SYN &&
|
||||||
|
/* no other flag than S,P,U,C,E */
|
||||||
|
(flags & !(TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::CWR | TcpFlags::ECE)) == 0 &&
|
||||||
|
/* not C && E */
|
||||||
|
((flags & TcpFlags::CWR == 0) || (flags & TcpFlags::ECE == 0)) =>
|
||||||
|
{
|
||||||
tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
|
tcp_repl = MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()])
|
||||||
.expect("error constructing a TCP packet");
|
.expect("error constructing a TCP packet");
|
||||||
tcp_repl.set_flags(TcpFlags::ACK);
|
tcp_repl.set_flags(TcpFlags::ACK);
|
||||||
tcp_repl.set_flags(TcpFlags::SYN | TcpFlags::ACK);
|
tcp_repl.set_flags(TcpFlags::SYN | TcpFlags::ACK);
|
||||||
tcp_repl.set_acknowledgement(tcp_req.get_sequence() + 1);
|
tcp_repl.set_acknowledgement(tcp_req.get_sequence().wrapping_add(1));
|
||||||
/* generate a SYNACK-cookie (same as masscan) */
|
/* generate a SYNACK-cookie (same as masscan) */
|
||||||
tcp_repl.set_sequence(
|
tcp_repl.set_sequence(
|
||||||
synackcookie::generate(&client_info, &masscanned.synack_key).unwrap(),
|
synackcookie::generate(&client_info, &masscanned.synack_key).unwrap(),
|
||||||
);
|
);
|
||||||
warn!("SYN-ACK to ACK on port {}", tcp_req.get_destination());
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
info!("TCP flag not handled: {}", tcp_req.get_flags());
|
masscanned.log.tcp_drop(tcp_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +134,7 @@ pub fn repl<'a, 'b>(
|
||||||
/* Set TCP headers */
|
/* Set TCP headers */
|
||||||
tcp_repl.set_data_offset(5);
|
tcp_repl.set_data_offset(5);
|
||||||
tcp_repl.set_window(65535);
|
tcp_repl.set_window(65535);
|
||||||
debug!("sending TCP packet: {:?}", tcp_repl);
|
masscanned.log.tcp_send(&tcp_repl, client_info);
|
||||||
Some(tcp_repl)
|
Some(tcp_repl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,13 +145,212 @@ mod tests {
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tcp_syn() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||||
|
iface: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
/* reference */
|
||||||
|
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
|
||||||
|
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
|
||||||
|
let tcp_sport = 65500;
|
||||||
|
let tcp_dport = 80;
|
||||||
|
let seq = 1234567;
|
||||||
|
let ack = 0;
|
||||||
|
let mut client_info = ClientInfo {
|
||||||
|
mac: ClientInfoSrcDst {
|
||||||
|
src: None,
|
||||||
|
dst: None,
|
||||||
|
},
|
||||||
|
ip: ClientInfoSrcDst {
|
||||||
|
src: Some(ip_src),
|
||||||
|
dst: Some(ip_dst),
|
||||||
|
},
|
||||||
|
transport: None,
|
||||||
|
port: ClientInfoSrcDst {
|
||||||
|
src: Some(tcp_sport),
|
||||||
|
dst: Some(tcp_dport),
|
||||||
|
},
|
||||||
|
cookie: None,
|
||||||
|
};
|
||||||
|
/* flags OK - list is exhaustive */
|
||||||
|
/* aim at imitating a Linux network stack */
|
||||||
|
let flags_ok = [
|
||||||
|
TcpFlags::SYN,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH,
|
||||||
|
TcpFlags::SYN | TcpFlags::URG,
|
||||||
|
TcpFlags::SYN | TcpFlags::CWR,
|
||||||
|
TcpFlags::SYN | TcpFlags::ECE,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::CWR,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::ECE,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::ECE,
|
||||||
|
TcpFlags::SYN | TcpFlags::URG | TcpFlags::CWR,
|
||||||
|
TcpFlags::SYN | TcpFlags::URG | TcpFlags::ECE,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::CWR,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::ECE,
|
||||||
|
];
|
||||||
|
for flags in flags_ok {
|
||||||
|
let mut tcp_req =
|
||||||
|
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
|
||||||
|
tcp_req.set_source(tcp_sport);
|
||||||
|
tcp_req.set_destination(tcp_dport);
|
||||||
|
tcp_req.set_sequence(seq);
|
||||||
|
tcp_req.set_acknowledgement(ack);
|
||||||
|
tcp_req.set_flags(flags);
|
||||||
|
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
|
||||||
|
if some_tcp_repl == None {
|
||||||
|
panic!("expected a reply, got none for flags: {:?}", flags);
|
||||||
|
}
|
||||||
|
let tcp_repl = some_tcp_repl.unwrap();
|
||||||
|
/* check reply flags */
|
||||||
|
assert!(tcp_repl.get_flags() == (TcpFlags::SYN | TcpFlags::ACK));
|
||||||
|
/* check reply seq and ack */
|
||||||
|
assert!(tcp_repl.get_acknowledgement() == seq.wrapping_add(1));
|
||||||
|
}
|
||||||
|
/* flags KO - list is *not* exhaustive */
|
||||||
|
let flags_ko = [
|
||||||
|
TcpFlags::SYN | TcpFlags::ACK,
|
||||||
|
TcpFlags::SYN | TcpFlags::FIN,
|
||||||
|
TcpFlags::SYN | TcpFlags::CWR | TcpFlags::ECE,
|
||||||
|
TcpFlags::SYN | TcpFlags::PSH | TcpFlags::URG | TcpFlags::CWR | TcpFlags::ECE,
|
||||||
|
TcpFlags::PSH,
|
||||||
|
];
|
||||||
|
for flags in flags_ko {
|
||||||
|
let mut tcp_req =
|
||||||
|
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
|
||||||
|
tcp_req.set_source(tcp_sport);
|
||||||
|
tcp_req.set_destination(tcp_dport);
|
||||||
|
tcp_req.set_sequence(seq);
|
||||||
|
tcp_req.set_acknowledgement(ack);
|
||||||
|
tcp_req.set_flags(flags);
|
||||||
|
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
|
||||||
|
if some_tcp_repl != None {
|
||||||
|
panic!("expected no reply, got one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tcp_fin_ack() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||||
|
iface: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
/* reference */
|
||||||
|
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
|
||||||
|
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
|
||||||
|
let tcp_sport = 65500;
|
||||||
|
let tcp_dport = 80;
|
||||||
|
let seq = 1234567;
|
||||||
|
let ack = 7654321;
|
||||||
|
let mut client_info = ClientInfo {
|
||||||
|
mac: ClientInfoSrcDst {
|
||||||
|
src: None,
|
||||||
|
dst: None,
|
||||||
|
},
|
||||||
|
ip: ClientInfoSrcDst {
|
||||||
|
src: Some(ip_src),
|
||||||
|
dst: Some(ip_dst),
|
||||||
|
},
|
||||||
|
transport: None,
|
||||||
|
port: ClientInfoSrcDst {
|
||||||
|
src: Some(tcp_sport),
|
||||||
|
dst: Some(tcp_dport),
|
||||||
|
},
|
||||||
|
cookie: None,
|
||||||
|
};
|
||||||
|
let mut tcp_req =
|
||||||
|
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
|
||||||
|
tcp_req.set_source(tcp_sport);
|
||||||
|
tcp_req.set_destination(tcp_dport);
|
||||||
|
tcp_req.set_sequence(seq);
|
||||||
|
tcp_req.set_acknowledgement(ack);
|
||||||
|
tcp_req.set_flags(TcpFlags::FIN | TcpFlags::ACK);
|
||||||
|
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
|
||||||
|
if some_tcp_repl == None {
|
||||||
|
panic!("expected a reply, got none");
|
||||||
|
}
|
||||||
|
let tcp_repl = some_tcp_repl.unwrap();
|
||||||
|
/* check reply flags */
|
||||||
|
assert!(tcp_repl.get_flags() == (TcpFlags::FIN | TcpFlags::ACK));
|
||||||
|
/* check reply seq and ack */
|
||||||
|
assert!(tcp_repl.get_sequence() == ack);
|
||||||
|
assert!(tcp_repl.get_acknowledgement() == seq.wrapping_add(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tcp_fin_ack_wrap() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||||
|
iface: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
/* reference */
|
||||||
|
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
|
||||||
|
let ip_dst = IpAddr::V4(Ipv4Addr::new(90, 64, 122, 203));
|
||||||
|
let tcp_sport = 65500;
|
||||||
|
let tcp_dport = 80;
|
||||||
|
let seq = 0xffffffff;
|
||||||
|
let ack = 0xffffffff;
|
||||||
|
let mut client_info = ClientInfo {
|
||||||
|
mac: ClientInfoSrcDst {
|
||||||
|
src: None,
|
||||||
|
dst: None,
|
||||||
|
},
|
||||||
|
ip: ClientInfoSrcDst {
|
||||||
|
src: Some(ip_src),
|
||||||
|
dst: Some(ip_dst),
|
||||||
|
},
|
||||||
|
transport: None,
|
||||||
|
port: ClientInfoSrcDst {
|
||||||
|
src: Some(tcp_sport),
|
||||||
|
dst: Some(tcp_dport),
|
||||||
|
},
|
||||||
|
cookie: None,
|
||||||
|
};
|
||||||
|
let mut tcp_req =
|
||||||
|
MutableTcpPacket::owned(vec![0; MutableTcpPacket::minimum_packet_size()]).unwrap();
|
||||||
|
tcp_req.set_source(tcp_sport);
|
||||||
|
tcp_req.set_destination(tcp_dport);
|
||||||
|
tcp_req.set_sequence(seq);
|
||||||
|
tcp_req.set_acknowledgement(ack);
|
||||||
|
tcp_req.set_flags(TcpFlags::FIN | TcpFlags::ACK);
|
||||||
|
let some_tcp_repl = repl(&tcp_req.to_immutable(), &masscanned, &mut client_info);
|
||||||
|
if some_tcp_repl == None {
|
||||||
|
panic!("expected a reply, got none");
|
||||||
|
}
|
||||||
|
let tcp_repl = some_tcp_repl.unwrap();
|
||||||
|
/* check reply flags */
|
||||||
|
assert!(tcp_repl.get_flags() == (TcpFlags::FIN | TcpFlags::ACK));
|
||||||
|
/* check reply seq and ack */
|
||||||
|
assert!(tcp_repl.get_sequence() == ack);
|
||||||
|
assert!(tcp_repl.get_acknowledgement() == seq.wrapping_add(1));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_synack_cookie_ipv4() {
|
fn test_synack_cookie_ipv4() {
|
||||||
let masscanned = Masscanned {
|
let masscanned = Masscanned {
|
||||||
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||||
ip_addresses: None,
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||||
iface: None,
|
iface: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
/* reference */
|
/* reference */
|
||||||
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
|
let ip_src = IpAddr::V4(Ipv4Addr::new(27, 198, 143, 1));
|
||||||
|
|
@ -171,9 +397,11 @@ mod tests {
|
||||||
fn test_synack_cookie_ipv6() {
|
fn test_synack_cookie_ipv6() {
|
||||||
let masscanned = Masscanned {
|
let masscanned = Masscanned {
|
||||||
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
mac: MacAddr(0, 0, 0, 0, 0, 0),
|
||||||
ip_addresses: None,
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
synack_key: [0x06a0a1d63f305e9b, 0xd4d4bcbb7304875f],
|
||||||
iface: None,
|
iface: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
/* reference */
|
/* reference */
|
||||||
let ip_src = IpAddr::V6(Ipv6Addr::new(234, 52, 183, 47, 184, 172, 64, 141));
|
let ip_src = IpAddr::V6(Ipv6Addr::new(234, 52, 183, 47, 184, 172, 64, 141));
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use log::*;
|
|
||||||
|
|
||||||
use pnet::packet::{
|
use pnet::packet::{
|
||||||
udp::{MutableUdpPacket, UdpPacket},
|
udp::{MutableUdpPacket, UdpPacket},
|
||||||
Packet,
|
Packet,
|
||||||
|
|
@ -30,25 +28,26 @@ pub fn repl<'a, 'b>(
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut client_info: &mut ClientInfo,
|
mut client_info: &mut ClientInfo,
|
||||||
) -> Option<MutableUdpPacket<'b>> {
|
) -> Option<MutableUdpPacket<'b>> {
|
||||||
debug!("receiving UDP packet: {:?}", udp_req);
|
|
||||||
/* Fill client info with source and dest. UDP port */
|
/* Fill client info with source and dest. UDP port */
|
||||||
client_info.port.src = Some(udp_req.get_source());
|
client_info.port.src = Some(udp_req.get_source());
|
||||||
client_info.port.dst = Some(udp_req.get_destination());
|
client_info.port.dst = Some(udp_req.get_destination());
|
||||||
|
masscanned.log.udp_recv(udp_req, client_info);
|
||||||
let payload = udp_req.payload();
|
let payload = udp_req.payload();
|
||||||
let mut udp_repl;
|
let mut udp_repl;
|
||||||
if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info) {
|
if let Some(repl) = proto::repl(&payload, masscanned, &mut client_info, None) {
|
||||||
udp_repl = MutableUdpPacket::owned(
|
udp_repl = MutableUdpPacket::owned(
|
||||||
[vec![0; MutableUdpPacket::minimum_packet_size()], repl].concat(),
|
[vec![0; MutableUdpPacket::minimum_packet_size()], repl].concat(),
|
||||||
)
|
)
|
||||||
.expect("error constructing a UDP packet");
|
.expect("error constructing a UDP packet");
|
||||||
udp_repl.set_length(udp_repl.packet().len() as u16);
|
udp_repl.set_length(udp_repl.packet().len() as u16);
|
||||||
} else {
|
} else {
|
||||||
|
masscanned.log.udp_drop(udp_req, client_info);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
/* Set source and dest. port for response packet from client info */
|
/* Set source and dest. port for response packet from client info */
|
||||||
/* Note: client info could have been modified by upper layers (e.g., STUN) */
|
/* Note: client info could have been modified by upper layers (e.g., STUN) */
|
||||||
udp_repl.set_source(client_info.port.dst.unwrap());
|
udp_repl.set_source(client_info.port.dst.unwrap());
|
||||||
udp_repl.set_destination(client_info.port.src.unwrap());
|
udp_repl.set_destination(client_info.port.src.unwrap());
|
||||||
debug!("sending UDP packet: {:?}", udp_repl);
|
masscanned.log.udp_send(&udp_repl, client_info);
|
||||||
Some(udp_repl)
|
Some(udp_repl)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
308
src/logger/console.rs
Normal file
308
src/logger/console.rs
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use pnet::packet::{
|
||||||
|
arp::{ArpPacket, MutableArpPacket},
|
||||||
|
ethernet::{EthernetPacket, MutableEthernetPacket},
|
||||||
|
icmp::{IcmpPacket, MutableIcmpPacket},
|
||||||
|
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
|
||||||
|
ipv4::{Ipv4Packet, MutableIpv4Packet},
|
||||||
|
ipv6::{Ipv6Packet, MutableIpv6Packet},
|
||||||
|
tcp::{MutableTcpPacket, TcpPacket},
|
||||||
|
udp::{MutableUdpPacket, UdpPacket},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
use crate::logger::Logger;
|
||||||
|
|
||||||
|
pub struct ConsoleLogger {
|
||||||
|
arp: bool,
|
||||||
|
eth: bool,
|
||||||
|
ipv4: bool,
|
||||||
|
ipv6: bool,
|
||||||
|
icmpv4: bool,
|
||||||
|
icmpv6: bool,
|
||||||
|
tcp: bool,
|
||||||
|
udp: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConsoleLogger {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ConsoleLogger {
|
||||||
|
arp: true,
|
||||||
|
eth: true,
|
||||||
|
ipv4: true,
|
||||||
|
ipv6: true,
|
||||||
|
icmpv4: true,
|
||||||
|
icmpv6: true,
|
||||||
|
tcp: true,
|
||||||
|
udp: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn prolog(&self, proto: &str, verb: &str, crlf: bool) {
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap();
|
||||||
|
print!(
|
||||||
|
"{}.{}\t{}\t{}{}",
|
||||||
|
now.as_secs(),
|
||||||
|
now.subsec_millis(),
|
||||||
|
proto,
|
||||||
|
verb,
|
||||||
|
if crlf { "\n" } else { "\t" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn client_info(&self, c: &ClientInfo) {
|
||||||
|
print!(
|
||||||
|
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t",
|
||||||
|
if let Some(m) = c.mac.src {
|
||||||
|
format!("{}", m)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(m) = c.mac.dst {
|
||||||
|
format!("{}", m)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(i) = c.ip.src {
|
||||||
|
format!("{}", i)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(i) = c.ip.dst {
|
||||||
|
format!("{}", i)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(t) = c.transport {
|
||||||
|
format!("{}", t)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(p) = c.port.src {
|
||||||
|
format!("{}", p)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(p) = c.port.dst {
|
||||||
|
format!("{}", p)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logger for ConsoleLogger {
|
||||||
|
fn init(&self) {
|
||||||
|
self.prolog("arp", "init", true);
|
||||||
|
self.prolog("eth", "init", true);
|
||||||
|
self.prolog("ipv4", "init", true);
|
||||||
|
self.prolog("ipv6", "init", true);
|
||||||
|
self.prolog("icmpv4", "init", true);
|
||||||
|
self.prolog("icmpv6", "init", true);
|
||||||
|
self.prolog("tcp", "init", true);
|
||||||
|
self.prolog("udp", "init", true);
|
||||||
|
}
|
||||||
|
/* ARP */
|
||||||
|
fn arp_enabled(&self) -> bool {
|
||||||
|
self.arp
|
||||||
|
}
|
||||||
|
fn arp_recv(&self, p: &ArpPacket) {
|
||||||
|
self.prolog("arp", "recv", false);
|
||||||
|
println!(
|
||||||
|
"{:}\t{:}\t{:}\t{:}\t{:?}",
|
||||||
|
p.get_sender_hw_addr(),
|
||||||
|
p.get_target_hw_addr(),
|
||||||
|
p.get_sender_proto_addr(),
|
||||||
|
p.get_target_proto_addr(),
|
||||||
|
p.get_operation(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn arp_drop(&self, p: &ArpPacket) {
|
||||||
|
self.prolog("arp", "drop", false);
|
||||||
|
println!(
|
||||||
|
"{:}\t{:}\t{:}\t{:}\t{:?}",
|
||||||
|
p.get_sender_hw_addr(),
|
||||||
|
p.get_target_hw_addr(),
|
||||||
|
p.get_sender_proto_addr(),
|
||||||
|
p.get_target_proto_addr(),
|
||||||
|
p.get_operation(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn arp_send(&self, p: &MutableArpPacket) {
|
||||||
|
self.prolog("arp", "send", false);
|
||||||
|
println!(
|
||||||
|
"{:}\t{:}\t{:}\t{:}\t{:?}",
|
||||||
|
p.get_target_hw_addr(),
|
||||||
|
p.get_sender_hw_addr(),
|
||||||
|
p.get_target_proto_addr(),
|
||||||
|
p.get_sender_proto_addr(),
|
||||||
|
p.get_operation(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Ethernet */
|
||||||
|
fn eth_enabled(&self) -> bool {
|
||||||
|
self.eth
|
||||||
|
}
|
||||||
|
fn eth_recv(&self, p: &EthernetPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("eth", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_ethertype(),);
|
||||||
|
}
|
||||||
|
fn eth_drop(&self, p: &EthernetPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("eth", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_ethertype(),);
|
||||||
|
}
|
||||||
|
fn eth_send(&self, p: &MutableEthernetPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("eth", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_ethertype(),);
|
||||||
|
}
|
||||||
|
/* IPv4 */
|
||||||
|
fn ipv4_enabled(&self) -> bool {
|
||||||
|
self.ipv4
|
||||||
|
}
|
||||||
|
fn ipv4_recv(&self, p: &Ipv4Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv4", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_next_level_protocol(),);
|
||||||
|
}
|
||||||
|
fn ipv4_drop(&self, p: &Ipv4Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv4", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_next_level_protocol(),);
|
||||||
|
}
|
||||||
|
fn ipv4_send(&self, p: &MutableIpv4Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv4", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_next_level_protocol(),);
|
||||||
|
}
|
||||||
|
/* IPv6 */
|
||||||
|
fn ipv6_enabled(&self) -> bool {
|
||||||
|
self.ipv6
|
||||||
|
}
|
||||||
|
fn ipv6_recv(&self, p: &Ipv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv6", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_next_header(),);
|
||||||
|
}
|
||||||
|
fn ipv6_drop(&self, p: &Ipv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv6", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_next_header(),);
|
||||||
|
}
|
||||||
|
fn ipv6_send(&self, p: &MutableIpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv6", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:}", p.get_next_header(),);
|
||||||
|
}
|
||||||
|
/* ICMPv4 */
|
||||||
|
fn icmpv4_enabled(&self) -> bool {
|
||||||
|
self.icmpv4
|
||||||
|
}
|
||||||
|
fn icmpv4_recv(&self, p: &IcmpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv4", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:?}\t{:?}", p.get_icmp_type(), p.get_icmp_code(),);
|
||||||
|
}
|
||||||
|
fn icmpv4_drop(&self, p: &IcmpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv4", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:?}\t{:?}", p.get_icmp_type(), p.get_icmp_code(),);
|
||||||
|
}
|
||||||
|
fn icmpv4_send(&self, p: &MutableIcmpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv4", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:?}\t{:?}", p.get_icmp_type(), p.get_icmp_code(),);
|
||||||
|
}
|
||||||
|
/* ICMPv6 */
|
||||||
|
fn icmpv6_enabled(&self) -> bool {
|
||||||
|
self.icmpv6
|
||||||
|
}
|
||||||
|
fn icmpv6_recv(&self, p: &Icmpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv6", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:?}\t{:?}", p.get_icmpv6_type(), p.get_icmpv6_code(),);
|
||||||
|
}
|
||||||
|
fn icmpv6_drop(&self, p: &Icmpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv6", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:?}\t{:?}", p.get_icmpv6_type(), p.get_icmpv6_code(),);
|
||||||
|
}
|
||||||
|
fn icmpv6_send(&self, p: &MutableIcmpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv6", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("{:?}\t{:?}", p.get_icmpv6_type(), p.get_icmpv6_code(),);
|
||||||
|
}
|
||||||
|
/* TCP */
|
||||||
|
fn tcp_enabled(&self) -> bool {
|
||||||
|
self.tcp
|
||||||
|
}
|
||||||
|
fn tcp_recv(&self, p: &TcpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("tcp", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
"{:?}\t{:}\t{:}",
|
||||||
|
p.get_flags(),
|
||||||
|
p.get_sequence(),
|
||||||
|
p.get_acknowledgement(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn tcp_drop(&self, p: &TcpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("tcp", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
"{:?}\t{:}\t{:}",
|
||||||
|
p.get_flags(),
|
||||||
|
p.get_sequence(),
|
||||||
|
p.get_acknowledgement(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn tcp_send(&self, p: &MutableTcpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("tcp", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
"{:?}\t{:}\t{:}",
|
||||||
|
p.get_flags(),
|
||||||
|
p.get_sequence(),
|
||||||
|
p.get_acknowledgement(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* UDP */
|
||||||
|
fn udp_enabled(&self) -> bool {
|
||||||
|
self.udp
|
||||||
|
}
|
||||||
|
fn udp_recv(&self, _p: &UdpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("udp", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
fn udp_drop(&self, _p: &UdpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("udp", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
fn udp_send(&self, _p: &MutableUdpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("udp", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
332
src/logger/logfmt.rs
Normal file
332
src/logger/logfmt.rs
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use pnet::packet::{
|
||||||
|
arp::{ArpPacket, MutableArpPacket},
|
||||||
|
ethernet::{EthernetPacket, MutableEthernetPacket},
|
||||||
|
icmp::{IcmpPacket, MutableIcmpPacket},
|
||||||
|
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
|
||||||
|
ipv4::{Ipv4Packet, MutableIpv4Packet},
|
||||||
|
ipv6::{Ipv6Packet, MutableIpv6Packet},
|
||||||
|
tcp::{MutableTcpPacket, TcpPacket},
|
||||||
|
udp::{MutableUdpPacket, UdpPacket},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
use crate::logger::Logger;
|
||||||
|
|
||||||
|
pub struct LogfmtLogger {
|
||||||
|
arp: bool,
|
||||||
|
eth: bool,
|
||||||
|
ipv4: bool,
|
||||||
|
ipv6: bool,
|
||||||
|
icmpv4: bool,
|
||||||
|
icmpv6: bool,
|
||||||
|
tcp: bool,
|
||||||
|
udp: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogfmtLogger {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
LogfmtLogger {
|
||||||
|
arp: true,
|
||||||
|
eth: true,
|
||||||
|
ipv4: true,
|
||||||
|
ipv6: true,
|
||||||
|
icmpv4: true,
|
||||||
|
icmpv6: true,
|
||||||
|
tcp: true,
|
||||||
|
udp: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn prolog(&self, proto: &str, verb: &str, crlf: bool) {
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap();
|
||||||
|
print!(
|
||||||
|
"ts={}.{} proto={} verb={}{}",
|
||||||
|
now.as_secs(),
|
||||||
|
now.subsec_millis(),
|
||||||
|
proto,
|
||||||
|
verb,
|
||||||
|
if crlf { "\n" } else { " " },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn client_info(&self, c: &ClientInfo) {
|
||||||
|
print!(
|
||||||
|
"{}{}{}{}{}{}{}",
|
||||||
|
if let Some(m) = c.mac.src {
|
||||||
|
format!(" mac_src={}", m)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(m) = c.mac.dst {
|
||||||
|
format!(" mac_dst={}", m)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(i) = c.ip.src {
|
||||||
|
format!(" ip_src={}", i)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(i) = c.ip.dst {
|
||||||
|
format!(" ip_dst={}", i)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(t) = c.transport {
|
||||||
|
format!(" transport={}", t)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(p) = c.port.src {
|
||||||
|
format!(" port_src={}", p)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
if let Some(p) = c.port.dst {
|
||||||
|
format!(" port_dst={}", p)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logger for LogfmtLogger {
|
||||||
|
fn init(&self) {
|
||||||
|
self.prolog("arp", "init", true);
|
||||||
|
self.prolog("eth", "init", true);
|
||||||
|
self.prolog("ipv4", "init", true);
|
||||||
|
self.prolog("ipv6", "init", true);
|
||||||
|
self.prolog("icmpv4", "init", true);
|
||||||
|
self.prolog("icmpv6", "init", true);
|
||||||
|
self.prolog("tcp", "init", true);
|
||||||
|
self.prolog("udp", "init", true);
|
||||||
|
}
|
||||||
|
/* ARP */
|
||||||
|
fn arp_enabled(&self) -> bool {
|
||||||
|
self.arp
|
||||||
|
}
|
||||||
|
fn arp_recv(&self, p: &ArpPacket) {
|
||||||
|
self.prolog("arp", "recv", false);
|
||||||
|
println!(
|
||||||
|
" mac_src={:} mac_dst={:} ip_src={:} ip_dst={:} op={:?}",
|
||||||
|
p.get_sender_hw_addr(),
|
||||||
|
p.get_target_hw_addr(),
|
||||||
|
p.get_sender_proto_addr(),
|
||||||
|
p.get_target_proto_addr(),
|
||||||
|
p.get_operation(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn arp_drop(&self, p: &ArpPacket) {
|
||||||
|
self.prolog("arp", "drop", false);
|
||||||
|
println!(
|
||||||
|
" mac_src={:} mac_dst={:} ip_src={:} ip_dst={:} op={:?}",
|
||||||
|
p.get_sender_hw_addr(),
|
||||||
|
p.get_target_hw_addr(),
|
||||||
|
p.get_sender_proto_addr(),
|
||||||
|
p.get_target_proto_addr(),
|
||||||
|
p.get_operation(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn arp_send(&self, p: &MutableArpPacket) {
|
||||||
|
self.prolog("arp", "send", false);
|
||||||
|
println!(
|
||||||
|
" mac_dst={:} mac_src={:} ip_dst={:} ip_src={:} op={:?}",
|
||||||
|
p.get_target_hw_addr(),
|
||||||
|
p.get_sender_hw_addr(),
|
||||||
|
p.get_target_proto_addr(),
|
||||||
|
p.get_sender_proto_addr(),
|
||||||
|
p.get_operation(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Ethernet */
|
||||||
|
fn eth_enabled(&self) -> bool {
|
||||||
|
self.eth
|
||||||
|
}
|
||||||
|
fn eth_recv(&self, p: &EthernetPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("eth", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" eth_type={:}", p.get_ethertype(),);
|
||||||
|
}
|
||||||
|
fn eth_drop(&self, p: &EthernetPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("eth", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" eth_type={:}", p.get_ethertype(),);
|
||||||
|
}
|
||||||
|
fn eth_send(&self, p: &MutableEthernetPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("eth", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" eth_type={:}", p.get_ethertype(),);
|
||||||
|
}
|
||||||
|
/* IPv4 */
|
||||||
|
fn ipv4_enabled(&self) -> bool {
|
||||||
|
self.ipv4
|
||||||
|
}
|
||||||
|
fn ipv4_recv(&self, p: &Ipv4Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv4", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" next_proto={:}", p.get_next_level_protocol(),);
|
||||||
|
}
|
||||||
|
fn ipv4_drop(&self, p: &Ipv4Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv4", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" next_proto={:}", p.get_next_level_protocol(),);
|
||||||
|
}
|
||||||
|
fn ipv4_send(&self, p: &MutableIpv4Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv4", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" next_proto={:}", p.get_next_level_protocol(),);
|
||||||
|
}
|
||||||
|
/* IPv6 */
|
||||||
|
fn ipv6_enabled(&self) -> bool {
|
||||||
|
self.ipv6
|
||||||
|
}
|
||||||
|
fn ipv6_recv(&self, p: &Ipv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv6", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" next_proto={:}", p.get_next_header(),);
|
||||||
|
}
|
||||||
|
fn ipv6_drop(&self, p: &Ipv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv6", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" next_proto={:}", p.get_next_header(),);
|
||||||
|
}
|
||||||
|
fn ipv6_send(&self, p: &MutableIpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("ipv6", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(" next_proto={:}", p.get_next_header(),);
|
||||||
|
}
|
||||||
|
/* ICMPv4 */
|
||||||
|
fn icmpv4_enabled(&self) -> bool {
|
||||||
|
self.icmpv4
|
||||||
|
}
|
||||||
|
fn icmpv4_recv(&self, p: &IcmpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv4", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" icmp_type={:?} icmp_code={:?}",
|
||||||
|
p.get_icmp_type(),
|
||||||
|
p.get_icmp_code(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn icmpv4_drop(&self, p: &IcmpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv4", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" icmp_type={:?} icmp_code={:?}",
|
||||||
|
p.get_icmp_type(),
|
||||||
|
p.get_icmp_code(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn icmpv4_send(&self, p: &MutableIcmpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv4", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" icmp_type={:?} icmp_code={:?}",
|
||||||
|
p.get_icmp_type(),
|
||||||
|
p.get_icmp_code(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* ICMPv6 */
|
||||||
|
fn icmpv6_enabled(&self) -> bool {
|
||||||
|
self.icmpv6
|
||||||
|
}
|
||||||
|
fn icmpv6_recv(&self, p: &Icmpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv6", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" icmpv6_type={:?} icmpv6_code={:?}",
|
||||||
|
p.get_icmpv6_type(),
|
||||||
|
p.get_icmpv6_code(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn icmpv6_drop(&self, p: &Icmpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv6", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" icmpv6_type={:?} icmpv6_code={:?}",
|
||||||
|
p.get_icmpv6_type(),
|
||||||
|
p.get_icmpv6_code(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn icmpv6_send(&self, p: &MutableIcmpv6Packet, c: &ClientInfo) {
|
||||||
|
self.prolog("icmpv6", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" icmpv6_type={:?} icmpv6_code={:?}",
|
||||||
|
p.get_icmpv6_type(),
|
||||||
|
p.get_icmpv6_code(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* TCP */
|
||||||
|
fn tcp_enabled(&self) -> bool {
|
||||||
|
self.tcp
|
||||||
|
}
|
||||||
|
fn tcp_recv(&self, p: &TcpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("tcp", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" flags={:?} seq={:} ack={:}",
|
||||||
|
p.get_flags(),
|
||||||
|
p.get_sequence(),
|
||||||
|
p.get_acknowledgement(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn tcp_drop(&self, p: &TcpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("tcp", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" flags={:?} seq={:} ack={:}",
|
||||||
|
p.get_flags(),
|
||||||
|
p.get_sequence(),
|
||||||
|
p.get_acknowledgement(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn tcp_send(&self, p: &MutableTcpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("tcp", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!(
|
||||||
|
" flags={:?} seq={:} ack={:}",
|
||||||
|
p.get_flags(),
|
||||||
|
p.get_sequence(),
|
||||||
|
p.get_acknowledgement(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* UDP */
|
||||||
|
fn udp_enabled(&self) -> bool {
|
||||||
|
self.udp
|
||||||
|
}
|
||||||
|
fn udp_recv(&self, _p: &UdpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("udp", "recv", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
fn udp_drop(&self, _p: &UdpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("udp", "drop", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
fn udp_send(&self, _p: &MutableUdpPacket, c: &ClientInfo) {
|
||||||
|
self.prolog("udp", "send", false);
|
||||||
|
self.client_info(c);
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
225
src/logger/meta.rs
Normal file
225
src/logger/meta.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use pnet::packet::{
|
||||||
|
arp::{ArpPacket, MutableArpPacket},
|
||||||
|
ethernet::{EthernetPacket, MutableEthernetPacket},
|
||||||
|
icmp::{IcmpPacket, MutableIcmpPacket},
|
||||||
|
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
|
||||||
|
ipv4::{Ipv4Packet, MutableIpv4Packet},
|
||||||
|
ipv6::{Ipv6Packet, MutableIpv6Packet},
|
||||||
|
tcp::{MutableTcpPacket, TcpPacket},
|
||||||
|
udp::{MutableUdpPacket, UdpPacket},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
use crate::logger::Logger;
|
||||||
|
|
||||||
|
pub struct MetaLogger {
|
||||||
|
loggers: Vec<Box<dyn Logger>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaLogger {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MetaLogger {
|
||||||
|
loggers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn add(&mut self, log: Box<dyn Logger>) {
|
||||||
|
self.loggers.push(log);
|
||||||
|
}
|
||||||
|
pub fn init(&self) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
l.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ARP */
|
||||||
|
pub fn arp_recv(&self, p: &ArpPacket) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.arp_enabled() {
|
||||||
|
l.arp_recv(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn arp_drop(&self, p: &ArpPacket) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.arp_enabled() {
|
||||||
|
l.arp_drop(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn arp_send(&self, p: &MutableArpPacket) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.arp_enabled() {
|
||||||
|
l.arp_send(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Ethernet */
|
||||||
|
pub fn eth_recv(&self, p: &EthernetPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.eth_enabled() {
|
||||||
|
l.eth_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn eth_drop(&self, p: &EthernetPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.eth_enabled() {
|
||||||
|
l.eth_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn eth_send(&self, p: &MutableEthernetPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.eth_enabled() {
|
||||||
|
l.eth_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* IPv4 */
|
||||||
|
pub fn ipv4_recv(&self, p: &Ipv4Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.ipv4_enabled() {
|
||||||
|
l.ipv4_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ipv4_drop(&self, p: &Ipv4Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.ipv4_enabled() {
|
||||||
|
l.ipv4_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ipv4_send(&self, p: &MutableIpv4Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.ipv4_enabled() {
|
||||||
|
l.ipv4_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* IPv6 */
|
||||||
|
pub fn ipv6_recv(&self, p: &Ipv6Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.ipv6_enabled() {
|
||||||
|
l.ipv6_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ipv6_drop(&self, p: &Ipv6Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.ipv6_enabled() {
|
||||||
|
l.ipv6_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ipv6_send(&self, p: &MutableIpv6Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.ipv6_enabled() {
|
||||||
|
l.ipv6_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ICMPv4 */
|
||||||
|
pub fn icmpv4_recv(&self, p: &IcmpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.icmpv4_enabled() {
|
||||||
|
l.icmpv4_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn icmpv4_drop(&self, p: &IcmpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.icmpv4_enabled() {
|
||||||
|
l.icmpv4_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn icmpv4_send(&self, p: &MutableIcmpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.icmpv4_enabled() {
|
||||||
|
l.icmpv4_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ICMPv6 */
|
||||||
|
pub fn icmpv6_recv(&self, p: &Icmpv6Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.icmpv6_enabled() {
|
||||||
|
l.icmpv6_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn icmpv6_drop(&self, p: &Icmpv6Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.icmpv6_enabled() {
|
||||||
|
l.icmpv6_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn icmpv6_send(&self, p: &MutableIcmpv6Packet, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.icmpv6_enabled() {
|
||||||
|
l.icmpv6_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* TCP */
|
||||||
|
pub fn tcp_recv(&self, p: &TcpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.tcp_enabled() {
|
||||||
|
l.tcp_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tcp_drop(&self, p: &TcpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.tcp_enabled() {
|
||||||
|
l.tcp_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tcp_send(&self, p: &MutableTcpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.tcp_enabled() {
|
||||||
|
l.tcp_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* UDP */
|
||||||
|
pub fn udp_recv(&self, p: &UdpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.udp_enabled() {
|
||||||
|
l.udp_recv(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn udp_drop(&self, p: &UdpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.udp_enabled() {
|
||||||
|
l.udp_drop(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn udp_send(&self, p: &MutableUdpPacket, c: &ClientInfo) {
|
||||||
|
for l in &self.loggers {
|
||||||
|
if l.udp_enabled() {
|
||||||
|
l.udp_send(p, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/logger/mod.rs
Normal file
97
src/logger/mod.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use pnet::packet::{
|
||||||
|
arp::{ArpPacket, MutableArpPacket},
|
||||||
|
ethernet::{EthernetPacket, MutableEthernetPacket},
|
||||||
|
icmp::{IcmpPacket, MutableIcmpPacket},
|
||||||
|
icmpv6::{Icmpv6Packet, MutableIcmpv6Packet},
|
||||||
|
ipv4::{Ipv4Packet, MutableIpv4Packet},
|
||||||
|
ipv6::{Ipv6Packet, MutableIpv6Packet},
|
||||||
|
tcp::{MutableTcpPacket, TcpPacket},
|
||||||
|
udp::{MutableUdpPacket, UdpPacket},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
|
||||||
|
mod console;
|
||||||
|
mod logfmt;
|
||||||
|
mod meta;
|
||||||
|
|
||||||
|
pub use console::ConsoleLogger;
|
||||||
|
pub use logfmt::LogfmtLogger;
|
||||||
|
pub use meta::MetaLogger;
|
||||||
|
|
||||||
|
pub trait Logger {
|
||||||
|
fn init(&self);
|
||||||
|
/* list of notifications that a logger might or might not implement */
|
||||||
|
/* ARP */
|
||||||
|
fn arp_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn arp_recv(&self, _p: &ArpPacket) {}
|
||||||
|
fn arp_drop(&self, _p: &ArpPacket) {}
|
||||||
|
fn arp_send(&self, _p: &MutableArpPacket) {}
|
||||||
|
/* Ethernet */
|
||||||
|
fn eth_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn eth_recv(&self, _p: &EthernetPacket, _c: &ClientInfo) {}
|
||||||
|
fn eth_drop(&self, _p: &EthernetPacket, _c: &ClientInfo) {}
|
||||||
|
fn eth_send(&self, _p: &MutableEthernetPacket, _c: &ClientInfo) {}
|
||||||
|
/* IPv4 */
|
||||||
|
fn ipv4_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn ipv4_recv(&self, _p: &Ipv4Packet, _c: &ClientInfo) {}
|
||||||
|
fn ipv4_drop(&self, _p: &Ipv4Packet, _c: &ClientInfo) {}
|
||||||
|
fn ipv4_send(&self, _p: &MutableIpv4Packet, _c: &ClientInfo) {}
|
||||||
|
/* IPv6 */
|
||||||
|
fn ipv6_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn ipv6_recv(&self, _p: &Ipv6Packet, _c: &ClientInfo) {}
|
||||||
|
fn ipv6_drop(&self, _p: &Ipv6Packet, _c: &ClientInfo) {}
|
||||||
|
fn ipv6_send(&self, _p: &MutableIpv6Packet, _c: &ClientInfo) {}
|
||||||
|
/* ICMPv4 */
|
||||||
|
fn icmpv4_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn icmpv4_recv(&self, _p: &IcmpPacket, _c: &ClientInfo) {}
|
||||||
|
fn icmpv4_drop(&self, _p: &IcmpPacket, _c: &ClientInfo) {}
|
||||||
|
fn icmpv4_send(&self, _p: &MutableIcmpPacket, _c: &ClientInfo) {}
|
||||||
|
/* ICMPv6 */
|
||||||
|
fn icmpv6_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn icmpv6_recv(&self, _p: &Icmpv6Packet, _c: &ClientInfo) {}
|
||||||
|
fn icmpv6_drop(&self, _p: &Icmpv6Packet, _c: &ClientInfo) {}
|
||||||
|
fn icmpv6_send(&self, _p: &MutableIcmpv6Packet, _c: &ClientInfo) {}
|
||||||
|
/* TCP */
|
||||||
|
fn tcp_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn tcp_recv(&self, _p: &TcpPacket, _c: &ClientInfo) {}
|
||||||
|
fn tcp_drop(&self, _p: &TcpPacket, _c: &ClientInfo) {}
|
||||||
|
fn tcp_send(&self, _p: &MutableTcpPacket, _c: &ClientInfo) {}
|
||||||
|
/* UDP */
|
||||||
|
fn udp_enabled(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn udp_recv(&self, _p: &UdpPacket, _c: &ClientInfo) {}
|
||||||
|
fn udp_drop(&self, _p: &UdpPacket, _c: &ClientInfo) {}
|
||||||
|
fn udp_send(&self, _p: &MutableUdpPacket, _c: &ClientInfo) {}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// This file is part of masscanned.
|
// This file is part of masscanned.
|
||||||
// Copyright 2021 - The IVRE project
|
// Copyright 2021 - 2022 The IVRE project
|
||||||
//
|
//
|
||||||
// Masscanned is free software: you can redistribute it and/or modify it
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
// under the terms of the GNU General Public License as published by
|
// under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -24,7 +24,7 @@ use std::fs::File;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{builder::PossibleValuesParser, Arg, ArgAction, Command};
|
||||||
use log::*;
|
use log::*;
|
||||||
use pnet::{
|
use pnet::{
|
||||||
datalink::{self, Channel::Ethernet, DataLinkReceiver, DataLinkSender, NetworkInterface},
|
datalink::{self, Channel::Ethernet, DataLinkReceiver, DataLinkSender, NetworkInterface},
|
||||||
|
|
@ -35,12 +35,14 @@ use pnet::{
|
||||||
util::MacAddr,
|
util::MacAddr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::logger::{ConsoleLogger, LogfmtLogger, Logger, MetaLogger};
|
||||||
use crate::utils::IpAddrParser;
|
use crate::utils::IpAddrParser;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod layer_2;
|
mod layer_2;
|
||||||
mod layer_3;
|
mod layer_3;
|
||||||
mod layer_4;
|
mod layer_4;
|
||||||
|
mod logger;
|
||||||
mod proto;
|
mod proto;
|
||||||
mod smack;
|
mod smack;
|
||||||
mod synackcookie;
|
mod synackcookie;
|
||||||
|
|
@ -54,7 +56,10 @@ pub struct Masscanned<'a> {
|
||||||
pub mac: MacAddr,
|
pub mac: MacAddr,
|
||||||
/* iface is an Option to make tests easier */
|
/* iface is an Option to make tests easier */
|
||||||
pub iface: Option<&'a NetworkInterface>,
|
pub iface: Option<&'a NetworkInterface>,
|
||||||
pub ip_addresses: Option<&'a HashSet<IpAddr>>,
|
pub self_ip_list: Option<&'a HashSet<IpAddr>>,
|
||||||
|
pub remote_ip_deny_list: Option<&'a HashSet<IpAddr>>,
|
||||||
|
/* loggers */
|
||||||
|
pub log: MetaLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the L2 network interface from its name */
|
/* Get the L2 network interface from its name */
|
||||||
|
|
@ -98,40 +103,75 @@ fn reply<'a, 'b>(packet: &'a [u8], masscanned: &Masscanned) -> Option<MutableEth
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
/* parse arguments from CLI */
|
/* parse arguments from CLI */
|
||||||
let args = App::new("Network responder - answer them all")
|
let args = Command::new("Network responder - answer them all")
|
||||||
.version(VERSION)
|
.version(VERSION)
|
||||||
.about("Network answering machine for various network protocols (L2-L3-L4 + applications)")
|
.about("Network answering machine for various network protocols (L2-L3-L4 + applications)")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("interface")
|
Arg::new("interface")
|
||||||
.short("i")
|
.short('i')
|
||||||
.long("iface")
|
.long("iface")
|
||||||
.value_name("iface")
|
.value_name("iface")
|
||||||
.help("the interface to use for receiving/sending packets")
|
.help("the interface to use for receiving/sending packets")
|
||||||
.required(true)
|
.required(true)
|
||||||
.takes_value(true),
|
.num_args(1),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("mac")
|
Arg::new("mac")
|
||||||
.short("a")
|
.short('m')
|
||||||
.long("mac-addr")
|
.long("mac-addr")
|
||||||
.help("MAC address to use in the response packets")
|
.help("MAC address to use in the response packets")
|
||||||
.takes_value(true),
|
.num_args(1),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("ip")
|
Arg::new("selfipfile")
|
||||||
.short("f")
|
.long("self-ip-file")
|
||||||
.long("ip-addr-file")
|
.help("File with the list of IP addresses handled by masscanned")
|
||||||
.help("File with the list of IP addresses to impersonate")
|
.num_args(1),
|
||||||
.takes_value(true),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("verbosity")
|
Arg::new("selfiplist")
|
||||||
.short("v")
|
.long("self-ip-list")
|
||||||
.multiple(true)
|
.help("Inline list of IP addresses handled by masscanned, comma-separated")
|
||||||
|
.num_args(1),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("remoteipdenyfile")
|
||||||
|
.long("remote-ip-deny-file")
|
||||||
|
.help(
|
||||||
|
"File with the list of IP addresses from which masscanned will ignore packets",
|
||||||
|
)
|
||||||
|
.num_args(1),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("remoteipdenylist")
|
||||||
|
.long("remote-ip-deny-list")
|
||||||
|
.help("Inline list of IP addresses from which masscanned will ignore packets")
|
||||||
|
.num_args(1),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("verbosity")
|
||||||
|
.short('v')
|
||||||
|
.action(ArgAction::Count)
|
||||||
.help("Increase message verbosity"),
|
.help("Increase message verbosity"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("quiet")
|
||||||
|
.long("quiet")
|
||||||
|
.short('q')
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.required(false)
|
||||||
|
.help("Quiet mode: do not output anything on stdout"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("format")
|
||||||
|
.long("format")
|
||||||
|
.help("Format in which to output logs")
|
||||||
|
.default_value("console")
|
||||||
|
.value_parser(PossibleValuesParser::new(["console", "logfmt"]))
|
||||||
|
.num_args(1),
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
let verbose = args.occurrences_of("verbosity") as usize;
|
let verbose = args.value_source("verbosity").unwrap() as usize;
|
||||||
/* initialise logger */
|
/* initialise logger */
|
||||||
stderrlog::new()
|
stderrlog::new()
|
||||||
.module(module_path!())
|
.module(module_path!())
|
||||||
|
|
@ -143,27 +183,24 @@ fn main() {
|
||||||
debug!("debug messages enabled");
|
debug!("debug messages enabled");
|
||||||
trace!("trace messages enabled");
|
trace!("trace messages enabled");
|
||||||
info!("Command line arguments:");
|
info!("Command line arguments:");
|
||||||
for arg in &args.args {
|
|
||||||
info!("....{:?}", arg);
|
|
||||||
}
|
|
||||||
let iface = if let Some(i) = get_interface(
|
let iface = if let Some(i) = get_interface(
|
||||||
args.value_of("interface")
|
args.get_one::<String>("interface")
|
||||||
.expect("error parsing iface argument"),
|
.expect("error parsing iface argument"),
|
||||||
) {
|
) {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
"Cannot open interface \"{}\" - are you sure it exists?",
|
"Cannot open interface \"{}\" - are you sure it exists?",
|
||||||
args.value_of("interface")
|
args.get_one::<String>("interface")
|
||||||
.expect("error parsing iface argument")
|
.expect("error parsing iface argument")
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if iface.flags & (netdevice::IFF_UP.bits() as u32) == 0 {
|
if !iface.is_up() {
|
||||||
error!("specified interface is DOWN");
|
error!("specified interface is DOWN");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mac = if let Some(m) = args.value_of("mac") {
|
let mac = if let Some(m) = args.get_one::<String>("mac") {
|
||||||
MacAddr::from_str(m).expect("error parsing provided MAC address")
|
MacAddr::from_str(m).expect("error parsing provided MAC address")
|
||||||
} else if let Some(m) = iface.mac {
|
} else if let Some(m) = iface.mac {
|
||||||
m
|
m
|
||||||
|
|
@ -172,9 +209,9 @@ fn main() {
|
||||||
};
|
};
|
||||||
/* Parse ip address file specified */
|
/* Parse ip address file specified */
|
||||||
/* FIXME: .and_then(|path| File::open(path).map(|file| )).unwrap_or_default() ? */
|
/* FIXME: .and_then(|path| File::open(path).map(|file| )).unwrap_or_default() ? */
|
||||||
let ip_list = if let Some(ref path) = args.value_of("ip") {
|
let mut ip_list = if let Some(ref path) = args.get_one::<String>("selfipfile") {
|
||||||
if let Ok(file) = File::open(path) {
|
if let Ok(file) = File::open(path) {
|
||||||
info!("parsing ip address file: {}", &path);
|
info!("parsing self ip file: {}", &path);
|
||||||
file.extract_ip_addresses_only(None)
|
file.extract_ip_addresses_only(None)
|
||||||
} else {
|
} else {
|
||||||
HashSet::new()
|
HashSet::new()
|
||||||
|
|
@ -182,23 +219,74 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
HashSet::new()
|
HashSet::new()
|
||||||
};
|
};
|
||||||
let ip_addresses = if !ip_list.is_empty() {
|
if let Some(ip_inline) = args.get_one::<String>("selfiplist") {
|
||||||
|
ip_list.extend(ip_inline.extract_ip_addresses_only(None));
|
||||||
|
}
|
||||||
|
let self_ip_list = if !ip_list.is_empty() {
|
||||||
|
for ip in &ip_list {
|
||||||
|
info!("binding........{}", ip);
|
||||||
|
}
|
||||||
|
Some(&ip_list)
|
||||||
|
} else {
|
||||||
|
info!("binding........0.0.0.0");
|
||||||
|
info!("binding........::");
|
||||||
|
None
|
||||||
|
};
|
||||||
|
/* Parse remote ip deny file specified */
|
||||||
|
let mut ip_list = if let Some(ref path) = args.get_one::<String>("remoteipdenyfile") {
|
||||||
|
if let Ok(file) = File::open(path) {
|
||||||
|
info!("parsing remote ip deny file: {}", &path);
|
||||||
|
file.extract_ip_addresses_only(None)
|
||||||
|
} else {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HashSet::new()
|
||||||
|
};
|
||||||
|
if let Some(ip_inline) = args.get_one::<String>("remoteipdenylist") {
|
||||||
|
ip_list.extend(ip_inline.extract_ip_addresses_only(None));
|
||||||
|
}
|
||||||
|
let remote_ip_deny_list = if !ip_list.is_empty() {
|
||||||
|
for ip in &ip_list {
|
||||||
|
info!("ignoring.......{}", ip);
|
||||||
|
}
|
||||||
Some(&ip_list)
|
Some(&ip_list)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let masscanned = Masscanned {
|
|
||||||
|
let mut masscanned = Masscanned {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac,
|
mac,
|
||||||
iface: Some(&iface),
|
iface: Some(&iface),
|
||||||
ip_addresses,
|
self_ip_list,
|
||||||
|
remote_ip_deny_list,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
info!("interface......{}", masscanned.iface.unwrap().name);
|
info!("interface......{}", masscanned.iface.unwrap().name);
|
||||||
info!("mac address....{}", masscanned.mac);
|
info!("mac address....{}", masscanned.mac);
|
||||||
|
if !args
|
||||||
|
.get_one::<bool>("quiet")
|
||||||
|
.expect("unexpected error parsing argument")
|
||||||
|
{
|
||||||
|
if let Some(format) = args.get_one::<String>("format") {
|
||||||
|
let chosen_logger: Box<dyn Logger> = match format.as_str() {
|
||||||
|
"console" => Box::new(ConsoleLogger::new()),
|
||||||
|
"logfmt" => Box::new(LogfmtLogger::new()),
|
||||||
|
|
||||||
|
// clap should already ensure we're using a valid format
|
||||||
|
_ => panic!("illegal format"),
|
||||||
|
};
|
||||||
|
masscanned.log.add(chosen_logger);
|
||||||
|
} else {
|
||||||
|
masscanned.log.add(Box::new(ConsoleLogger::new()));
|
||||||
|
}
|
||||||
|
masscanned.log.init();
|
||||||
|
}
|
||||||
let (mut tx, mut rx) = get_channel(masscanned.iface.unwrap());
|
let (mut tx, mut rx) = get_channel(masscanned.iface.unwrap());
|
||||||
loop {
|
loop {
|
||||||
/* check if network interface is still up */
|
/* check if network interface is still up */
|
||||||
if masscanned.iface.unwrap().flags & (netdevice::IFF_UP.bits() as u32) == 0 {
|
if !masscanned.iface.unwrap().is_up() {
|
||||||
error!("interface is DOWN - aborting");
|
error!("interface is DOWN - aborting");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
src/proto/dissector.rs
Normal file
92
src/proto/dissector.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::proto::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
////////////
|
||||||
|
// Common //
|
||||||
|
////////////
|
||||||
|
|
||||||
|
/// ### PacketDissector
|
||||||
|
/// A util class used to dissect fields.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PacketDissector<T> {
|
||||||
|
pub i: usize,
|
||||||
|
pub state: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PacketDissector<T> {
|
||||||
|
pub fn new(initial_state: T) -> PacketDissector<T> {
|
||||||
|
return PacketDissector {
|
||||||
|
i: 0,
|
||||||
|
state: initial_state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn next_state(&mut self, state: T) {
|
||||||
|
self.state = state;
|
||||||
|
self.i = 0;
|
||||||
|
}
|
||||||
|
pub fn next_state_when_i_reaches(&mut self, state: T, i: usize) {
|
||||||
|
if self.i == i {
|
||||||
|
self.next_state(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn _read_usize(&mut self, byte: &u8, value: usize, next_state: T, size: usize) -> usize {
|
||||||
|
self.i += 1;
|
||||||
|
self.next_state_when_i_reaches(next_state, size);
|
||||||
|
(value << 8) + *byte as usize
|
||||||
|
}
|
||||||
|
fn _read_ulesize(&mut self, byte: &u8, value: usize, next_state: T, size: usize) -> usize {
|
||||||
|
let ret = value + ((*byte as usize) << (8 * self.i));
|
||||||
|
self.i += 1;
|
||||||
|
self.next_state_when_i_reaches(next_state, size);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
pub fn read_u16(&mut self, byte: &u8, value: u16, next_state: T) -> u16 {
|
||||||
|
self._read_usize(byte, value as usize, next_state, 2) as u16
|
||||||
|
}
|
||||||
|
pub fn read_ule16(&mut self, byte: &u8, value: u16, next_state: T) -> u16 {
|
||||||
|
self._read_ulesize(byte, value as usize, next_state, 2) as u16
|
||||||
|
}
|
||||||
|
pub fn read_u32(&mut self, byte: &u8, value: u32, next_state: T) -> u32 {
|
||||||
|
self._read_usize(byte, value as usize, next_state, 4) as u32
|
||||||
|
}
|
||||||
|
pub fn read_ule32(&mut self, byte: &u8, value: u32, next_state: T) -> u32 {
|
||||||
|
self._read_ulesize(byte, value as usize, next_state, 4) as u32
|
||||||
|
}
|
||||||
|
pub fn read_ule64(&mut self, byte: &u8, value: u64, next_state: T) -> u64 {
|
||||||
|
self._read_ulesize(byte, value as usize, next_state, 8) as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MPacket {
|
||||||
|
fn new() -> Self;
|
||||||
|
fn repl(
|
||||||
|
&self,
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
_client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>>;
|
||||||
|
fn parse(&mut self, byte: &u8);
|
||||||
|
|
||||||
|
fn parse_all(&mut self, bytes: &[u8]) {
|
||||||
|
for byte in bytes {
|
||||||
|
self.parse(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/proto/dns/cst.rs
Normal file
93
src/proto/dns/cst.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2022 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone, Copy, EnumIter)]
|
||||||
|
pub enum DNSType {
|
||||||
|
NONE,
|
||||||
|
A,
|
||||||
|
TXT, // value: 16 - text strings
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for DNSType {
|
||||||
|
fn from(item: u16) -> Self {
|
||||||
|
match item {
|
||||||
|
1 => DNSType::A,
|
||||||
|
16 => DNSType::TXT,
|
||||||
|
_ => DNSType::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DNSType> for u16 {
|
||||||
|
fn from(item: DNSType) -> Self {
|
||||||
|
match item {
|
||||||
|
DNSType::A => 1,
|
||||||
|
DNSType::TXT => 16,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone, Copy, EnumIter)]
|
||||||
|
pub enum DNSClass {
|
||||||
|
NONE,
|
||||||
|
IN, // value: 1 - the Internet
|
||||||
|
CH, // value: 3 - the CHAOS class
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for DNSClass {
|
||||||
|
fn from(item: u16) -> Self {
|
||||||
|
match item {
|
||||||
|
1 => DNSClass::IN,
|
||||||
|
3 => DNSClass::CH,
|
||||||
|
_ => DNSClass::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DNSClass> for u16 {
|
||||||
|
fn from(item: DNSClass) -> Self {
|
||||||
|
match item {
|
||||||
|
DNSClass::IN => 1,
|
||||||
|
DNSClass::CH => 3,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_parse() {
|
||||||
|
/* type TXT */
|
||||||
|
assert!(DNSType::from(1) == DNSType::A);
|
||||||
|
assert!(1 as u16 == DNSType::A.into());
|
||||||
|
assert!(DNSType::from(16) == DNSType::TXT);
|
||||||
|
assert!(16 as u16 == DNSType::TXT.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn class_parse() {
|
||||||
|
assert!(DNSClass::from(1) == DNSClass::IN);
|
||||||
|
assert!(1 as u16 == DNSClass::IN.into());
|
||||||
|
assert!(DNSClass::from(3) == DNSClass::CH);
|
||||||
|
assert!(3 as u16 == DNSClass::CH.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
387
src/proto/dns/header.rs
Normal file
387
src/proto/dns/header.rs
Normal file
|
|
@ -0,0 +1,387 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2022 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use crate::proto::dissector::{MPacket, PacketDissector};
|
||||||
|
use crate::proto::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum DNSHeaderState {
|
||||||
|
Id,
|
||||||
|
Flags,
|
||||||
|
QDCount,
|
||||||
|
ANCount,
|
||||||
|
NSCount,
|
||||||
|
ARCount,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DNSHeader {
|
||||||
|
pub d: PacketDissector<DNSHeaderState>,
|
||||||
|
pub id: u16,
|
||||||
|
pub flags: u16,
|
||||||
|
pub _qr: bool,
|
||||||
|
pub _opcode: u8,
|
||||||
|
pub _aa: bool,
|
||||||
|
pub _tc: bool,
|
||||||
|
pub _rd: bool,
|
||||||
|
pub _ra: bool,
|
||||||
|
pub _z: u8,
|
||||||
|
pub _rcode: u8,
|
||||||
|
pub qdcount: u16,
|
||||||
|
pub ancount: u16,
|
||||||
|
pub nscount: u16,
|
||||||
|
pub arcount: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for DNSHeader {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
for b in item {
|
||||||
|
hdr.parse(&b);
|
||||||
|
}
|
||||||
|
if hdr.d.state == DNSHeaderState::End {
|
||||||
|
Ok(hdr)
|
||||||
|
} else {
|
||||||
|
Err("packet is incomplete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&DNSHeader> for Vec<u8> {
|
||||||
|
fn from(item: &DNSHeader) -> Self {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
/* id */
|
||||||
|
v.push((item.id >> 8) as u8);
|
||||||
|
v.push((item.id & 0xFF) as u8);
|
||||||
|
|
||||||
|
/* flags */
|
||||||
|
/* QR | OPCODE | AA | TC | RD */
|
||||||
|
v.push(
|
||||||
|
((item._qr as u8) << 7)
|
||||||
|
| (item._opcode << 3)
|
||||||
|
| ((item._aa as u8) << 2)
|
||||||
|
| ((item._tc as u8) << 1)
|
||||||
|
| (item._rd as u8),
|
||||||
|
);
|
||||||
|
/* AA | ZZZ | RCODE */
|
||||||
|
v.push(0);
|
||||||
|
|
||||||
|
/* qdcount */
|
||||||
|
v.push((item.qdcount >> 8) as u8);
|
||||||
|
v.push((item.qdcount & 0xFF) as u8);
|
||||||
|
|
||||||
|
/* ancount */
|
||||||
|
v.push((item.ancount >> 8) as u8);
|
||||||
|
v.push((item.ancount & 0xFF) as u8);
|
||||||
|
|
||||||
|
/* nscount */
|
||||||
|
v.push((item.nscount >> 8) as u8);
|
||||||
|
v.push((item.nscount & 0xFF) as u8);
|
||||||
|
|
||||||
|
/* arcount */
|
||||||
|
v.push((item.arcount >> 8) as u8);
|
||||||
|
v.push((item.arcount & 0xFF) as u8);
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MPacket for DNSHeader {
|
||||||
|
fn new() -> Self {
|
||||||
|
DNSHeader {
|
||||||
|
d: PacketDissector::new(DNSHeaderState::Id),
|
||||||
|
id: 0,
|
||||||
|
flags: 0,
|
||||||
|
_qr: false,
|
||||||
|
_opcode: 0,
|
||||||
|
_aa: false,
|
||||||
|
_tc: false,
|
||||||
|
_rd: false,
|
||||||
|
_ra: false,
|
||||||
|
_z: 0,
|
||||||
|
_rcode: 0,
|
||||||
|
qdcount: 0,
|
||||||
|
ancount: 0,
|
||||||
|
nscount: 0,
|
||||||
|
arcount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self, byte: &u8) {
|
||||||
|
match self.d.state {
|
||||||
|
DNSHeaderState::Id => {
|
||||||
|
self.id = self.d.read_u16(byte, self.id, DNSHeaderState::Flags);
|
||||||
|
}
|
||||||
|
DNSHeaderState::Flags => {
|
||||||
|
self.flags = self.d.read_u16(byte, self.flags, DNSHeaderState::QDCount);
|
||||||
|
}
|
||||||
|
DNSHeaderState::QDCount => {
|
||||||
|
self.qdcount = self.d.read_u16(byte, self.qdcount, DNSHeaderState::ANCount);
|
||||||
|
}
|
||||||
|
DNSHeaderState::ANCount => {
|
||||||
|
self.ancount = self.d.read_u16(byte, self.ancount, DNSHeaderState::NSCount);
|
||||||
|
}
|
||||||
|
DNSHeaderState::NSCount => {
|
||||||
|
self.nscount = self.d.read_u16(byte, self.nscount, DNSHeaderState::ARCount);
|
||||||
|
}
|
||||||
|
DNSHeaderState::ARCount => {
|
||||||
|
self.arcount = self.d.read_u16(byte, self.arcount, DNSHeaderState::End);
|
||||||
|
}
|
||||||
|
DNSHeaderState::End => {}
|
||||||
|
}
|
||||||
|
/* we need this to be executed at the same call
|
||||||
|
* the state changes to End, hence it is not in the
|
||||||
|
* match structure
|
||||||
|
**/
|
||||||
|
if self.d.state == DNSHeaderState::End {
|
||||||
|
self._qr = (self.flags >> 15) == 1;
|
||||||
|
self._opcode = ((self.flags >> 11) & 0x0F) as u8;
|
||||||
|
self._aa = (self.flags >> 10) & 0x01 == 1;
|
||||||
|
self._tc = (self.flags >> 9) & 0x01 == 1;
|
||||||
|
self._rd = (self.flags >> 8) & 0x01 == 1;
|
||||||
|
self._ra = (self.flags >> 7) & 0x01 == 1;
|
||||||
|
self._z = ((self.flags >> 4) & 0x07) as u8;
|
||||||
|
self._rcode = (self.flags & 0x0F) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repl(
|
||||||
|
&self,
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
_client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let mut r = DNSHeader::new();
|
||||||
|
r.id = self.id;
|
||||||
|
r._qr = true;
|
||||||
|
r._opcode = self._opcode;
|
||||||
|
r._aa = true;
|
||||||
|
r._tc = false;
|
||||||
|
/* RFC1035
|
||||||
|
* Recursion Desired - this bit may be set in a query and
|
||||||
|
* is copied into the response. */
|
||||||
|
r._rd = self._rd;
|
||||||
|
r._ra = false;
|
||||||
|
r.qdcount = self.qdcount;
|
||||||
|
r.ancount = self.qdcount;
|
||||||
|
Some(Vec::<u8>::from(&r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use pnet::util::MacAddr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_all() {
|
||||||
|
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00";
|
||||||
|
let hdr = match DNSHeader::try_from(payload.to_vec()) {
|
||||||
|
Ok(_hdr) => _hdr,
|
||||||
|
Err(e) => panic!("error while parsing DNS header: {}", e),
|
||||||
|
};
|
||||||
|
assert!(hdr.d.state == DNSHeaderState::End);
|
||||||
|
assert!(hdr.id == 0xb307);
|
||||||
|
assert!(hdr.flags == 0x0100);
|
||||||
|
assert!(hdr._qr == false);
|
||||||
|
assert!(hdr._opcode == 0);
|
||||||
|
assert!(hdr._aa == false);
|
||||||
|
assert!(hdr._tc == false);
|
||||||
|
assert!(hdr._rd == true);
|
||||||
|
assert!(hdr._ra == false);
|
||||||
|
assert!(hdr._z == 0);
|
||||||
|
assert!(hdr._rcode == 0);
|
||||||
|
assert!(hdr.qdcount == 1);
|
||||||
|
assert!(hdr.ancount == 0);
|
||||||
|
assert!(hdr.nscount == 0);
|
||||||
|
assert!(hdr.arcount == 0);
|
||||||
|
assert!(Vec::<u8>::from(&hdr) == payload.to_vec());
|
||||||
|
/* KO */
|
||||||
|
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00";
|
||||||
|
match DNSHeader::try_from(payload.to_vec()) {
|
||||||
|
Ok(_) => panic!("parsing should have failed"),
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_byte_by_byte() {
|
||||||
|
/* OK */
|
||||||
|
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00";
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
for b in payload {
|
||||||
|
assert!(hdr.d.state != DNSHeaderState::End);
|
||||||
|
hdr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(hdr.d.state == DNSHeaderState::End);
|
||||||
|
assert!(hdr.id == 0xb307);
|
||||||
|
assert!(hdr.flags == 0x0100);
|
||||||
|
assert!(hdr._qr == false);
|
||||||
|
assert!(hdr._opcode == 0);
|
||||||
|
assert!(hdr._aa == false);
|
||||||
|
assert!(hdr._tc == false);
|
||||||
|
assert!(hdr._rd == true);
|
||||||
|
assert!(hdr._ra == false);
|
||||||
|
assert!(hdr._z == 0);
|
||||||
|
assert!(hdr._rcode == 0);
|
||||||
|
assert!(hdr.qdcount == 1);
|
||||||
|
assert!(hdr.ancount == 0);
|
||||||
|
assert!(hdr.nscount == 0);
|
||||||
|
assert!(hdr.arcount == 0);
|
||||||
|
assert!(Vec::<u8>::from(&hdr) == payload.to_vec());
|
||||||
|
/* KO */
|
||||||
|
let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00";
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
for b in payload {
|
||||||
|
hdr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(hdr.d.state != DNSHeaderState::End);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consistency_qd_rr(qd: &DNSHeader, rr: &DNSHeader) {
|
||||||
|
assert!(rr.id == qd.id);
|
||||||
|
assert!(rr._qr == true);
|
||||||
|
assert!(rr._opcode == qd._opcode);
|
||||||
|
assert!(rr._aa == true);
|
||||||
|
assert!(rr._tc == false);
|
||||||
|
assert!(rr._rd == qd._rd);
|
||||||
|
assert!(rr._ra == false);
|
||||||
|
assert!(rr._z == 0);
|
||||||
|
assert!(rr._rcode == 0);
|
||||||
|
/* check flags */
|
||||||
|
assert!(rr.flags >> 15 == rr._qr as u16);
|
||||||
|
assert!((rr.flags >> 11) & 0xF == rr._opcode as u16);
|
||||||
|
assert!((rr.flags >> 10) & 0x1 == rr._aa as u16);
|
||||||
|
assert!((rr.flags >> 9) & 0x1 == rr._tc as u16);
|
||||||
|
assert!((rr.flags >> 8) & 0x1 == rr._rd as u16);
|
||||||
|
assert!((rr.flags >> 7) & 0x1 == rr._ra as u16);
|
||||||
|
assert!((rr.flags >> 4) & 0x7 == rr._z as u16);
|
||||||
|
assert!(rr.flags & 0xF == rr._rcode as u16);
|
||||||
|
assert!(rr.qdcount == qd.qdcount);
|
||||||
|
assert!(rr.ancount == qd.qdcount);
|
||||||
|
assert!(rr.nscount == 0);
|
||||||
|
assert!(rr.arcount == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repl_id() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let client_info = ClientInfo::new();
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
hdr._qr = false;
|
||||||
|
for id in [0x1234, 0x4321, 0xffff, 0x0, 0x1337] {
|
||||||
|
hdr.id = id;
|
||||||
|
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
|
||||||
|
DNSHeader::try_from(r).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("expected DNS header answer, got None");
|
||||||
|
};
|
||||||
|
consistency_qd_rr(&hdr, &hdr_repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repl_opcode() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let client_info = ClientInfo::new();
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
hdr._qr = false;
|
||||||
|
/* opcode */
|
||||||
|
for opcode in 0..3 {
|
||||||
|
hdr._opcode = opcode;
|
||||||
|
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
|
||||||
|
DNSHeader::try_from(r).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("expected DNS header answer, got None");
|
||||||
|
};
|
||||||
|
consistency_qd_rr(&hdr, &hdr_repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repl_rd() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let client_info = ClientInfo::new();
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
hdr._qr = false;
|
||||||
|
/* rd */
|
||||||
|
for rd in [false, true] {
|
||||||
|
hdr._rd = rd;
|
||||||
|
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
|
||||||
|
DNSHeader::try_from(r).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("expected DNS header answer, got None");
|
||||||
|
};
|
||||||
|
consistency_qd_rr(&hdr, &hdr_repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repl_ancount() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let client_info = ClientInfo::new();
|
||||||
|
let mut hdr = DNSHeader::new();
|
||||||
|
hdr._qr = false;
|
||||||
|
/* rd */
|
||||||
|
for qdcount in 0..16 {
|
||||||
|
hdr.qdcount = qdcount;
|
||||||
|
let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) {
|
||||||
|
DNSHeader::try_from(r).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("expected DNS header answer, got None");
|
||||||
|
};
|
||||||
|
consistency_qd_rr(&hdr, &hdr_repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
688
src/proto/dns/mod.rs
Normal file
688
src/proto/dns/mod.rs
Normal file
|
|
@ -0,0 +1,688 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2022 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
mod cst;
|
||||||
|
|
||||||
|
mod header;
|
||||||
|
use header::{DNSHeader, DNSHeaderState};
|
||||||
|
|
||||||
|
mod query;
|
||||||
|
use query::{DNSQuery, DNSQueryState};
|
||||||
|
|
||||||
|
mod rr;
|
||||||
|
use rr::{DNSRRState, DNSRR};
|
||||||
|
|
||||||
|
use crate::proto::dissector::{MPacket, PacketDissector};
|
||||||
|
use crate::proto::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum DNSState {
|
||||||
|
Header,
|
||||||
|
Query,
|
||||||
|
Answer,
|
||||||
|
Authority,
|
||||||
|
Additional,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DNSPacket {
|
||||||
|
d: PacketDissector<DNSState>,
|
||||||
|
header: DNSHeader,
|
||||||
|
qd: Vec<DNSQuery>,
|
||||||
|
rr: Vec<DNSRR>,
|
||||||
|
ns: Vec<DNSRR>,
|
||||||
|
ar: Vec<DNSRR>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for DNSPacket {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let mut dns = DNSPacket::new();
|
||||||
|
for b in item {
|
||||||
|
dns.parse(&b);
|
||||||
|
}
|
||||||
|
if dns.d.state == DNSState::End {
|
||||||
|
Ok(dns)
|
||||||
|
} else {
|
||||||
|
Err("packet is incomplete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&DNSPacket> for Vec<u8> {
|
||||||
|
fn from(item: &DNSPacket) -> Self {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
v.extend(Vec::<u8>::from(&item.header));
|
||||||
|
for qd in &item.qd {
|
||||||
|
v.extend(Vec::<u8>::from(qd));
|
||||||
|
}
|
||||||
|
for rr in &item.rr {
|
||||||
|
v.extend(Vec::<u8>::from(rr));
|
||||||
|
}
|
||||||
|
for ns in &item.ns {
|
||||||
|
v.extend(Vec::<u8>::from(ns));
|
||||||
|
}
|
||||||
|
for ar in &item.ar {
|
||||||
|
v.extend(Vec::<u8>::from(ar));
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MPacket for DNSPacket {
|
||||||
|
fn new() -> Self {
|
||||||
|
DNSPacket {
|
||||||
|
d: PacketDissector::new(DNSState::Header),
|
||||||
|
header: DNSHeader::new(),
|
||||||
|
qd: Vec::new(),
|
||||||
|
rr: Vec::new(),
|
||||||
|
ns: Vec::new(),
|
||||||
|
ar: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self, byte: &u8) {
|
||||||
|
match self.d.state {
|
||||||
|
DNSState::Header => {
|
||||||
|
self.header.parse(byte);
|
||||||
|
if self.header.d.state == DNSHeaderState::End {
|
||||||
|
if self.header.qdcount > 0 {
|
||||||
|
self.qd.push(DNSQuery::new());
|
||||||
|
self.d.next_state(DNSState::Query);
|
||||||
|
} else if self.header.ancount > 0 {
|
||||||
|
self.rr.push(DNSRR::new());
|
||||||
|
self.d.next_state(DNSState::Answer);
|
||||||
|
} else if self.header.nscount > 0 {
|
||||||
|
self.d.next_state(DNSState::Authority);
|
||||||
|
} else if self.header.arcount > 0 {
|
||||||
|
self.d.next_state(DNSState::Additional);
|
||||||
|
} else {
|
||||||
|
self.d.next_state(DNSState::End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DNSState::Query => {
|
||||||
|
let qdcount = self.qd.len();
|
||||||
|
self.qd[qdcount - 1].parse(byte);
|
||||||
|
if self.qd[qdcount - 1].d.state == DNSQueryState::End {
|
||||||
|
if self.header.qdcount as usize > self.qd.len() {
|
||||||
|
self.qd.push(DNSQuery::new());
|
||||||
|
} else if self.header.ancount > 0 {
|
||||||
|
self.rr.push(DNSRR::new());
|
||||||
|
self.d.next_state(DNSState::Answer);
|
||||||
|
} else if self.header.nscount > 0 {
|
||||||
|
self.d.next_state(DNSState::Authority);
|
||||||
|
} else if self.header.arcount > 0 {
|
||||||
|
self.d.next_state(DNSState::Additional);
|
||||||
|
} else {
|
||||||
|
self.d.next_state(DNSState::End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DNSState::Answer => {
|
||||||
|
let ancount = self.rr.len();
|
||||||
|
self.rr[ancount - 1].parse(byte);
|
||||||
|
if self.rr[ancount - 1].d.state == DNSRRState::End {
|
||||||
|
if self.header.ancount as usize > self.rr.len() {
|
||||||
|
self.rr.push(DNSRR::new());
|
||||||
|
} else if self.header.nscount > 0 {
|
||||||
|
self.d.next_state(DNSState::Authority);
|
||||||
|
} else if self.header.arcount > 0 {
|
||||||
|
self.d.next_state(DNSState::Additional);
|
||||||
|
} else {
|
||||||
|
self.d.next_state(DNSState::End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repl(
|
||||||
|
&self,
|
||||||
|
masscanned: &Masscanned,
|
||||||
|
client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let mut ans = DNSPacket::new();
|
||||||
|
ans.header = if let Some(hdr) = self.header.repl(&masscanned, &client_info, None) {
|
||||||
|
if let Ok(h) = DNSHeader::try_from(hdr) {
|
||||||
|
h
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
/* reply to qd */
|
||||||
|
for qd in &self.qd {
|
||||||
|
if let Ok(q) = DNSQuery::try_from(Vec::<u8>::from(qd)) {
|
||||||
|
ans.qd.push(q);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(raw_rr) = qd.repl(&masscanned, &client_info, None) {
|
||||||
|
if let Ok(rr) = DNSRR::try_from(raw_rr) {
|
||||||
|
ans.rr.push(rr);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Vec::<u8>::from(&ans))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::cst::{DNSClass, DNSType};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use pnet::util::MacAddr;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_qd_all() {
|
||||||
|
/* OK */
|
||||||
|
/* scapy: DNS(id=0x1337,
|
||||||
|
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"))
|
||||||
|
**/
|
||||||
|
let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01";
|
||||||
|
let dns = match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_dns) => _dns,
|
||||||
|
Err(e) => panic!("error while parsing DNS packet: {}", e),
|
||||||
|
};
|
||||||
|
assert!(dns.header.id == 0x1337);
|
||||||
|
assert!(dns.header._qr == false);
|
||||||
|
assert!(dns.header._opcode == 0);
|
||||||
|
assert!(dns.header._aa == false);
|
||||||
|
assert!(dns.header._tc == false);
|
||||||
|
assert!(dns.header._rd == true);
|
||||||
|
assert!(dns.header._ra == false);
|
||||||
|
assert!(dns.header._z == 0);
|
||||||
|
assert!(dns.header._rcode == 0);
|
||||||
|
assert!(dns.header.qdcount == 3);
|
||||||
|
assert!(dns.header.ancount == 0);
|
||||||
|
assert!(dns.header.nscount == 0);
|
||||||
|
assert!(dns.header.arcount == 0);
|
||||||
|
assert!(dns.qd.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.qd[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[0].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[1].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[2].class == DNSClass::IN);
|
||||||
|
/* KO */
|
||||||
|
let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00";
|
||||||
|
match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_) => panic!("parsing should have failed"),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
let payload = b"xxx";
|
||||||
|
match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_) => panic!("parsing should have failed"),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_qd_byte_by_byte() {
|
||||||
|
/* scapy: DNS(id=0x1337,
|
||||||
|
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"))
|
||||||
|
**/
|
||||||
|
let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01";
|
||||||
|
let mut dns = DNSPacket::new();
|
||||||
|
for b in payload {
|
||||||
|
assert!(dns.d.state != DNSState::End);
|
||||||
|
dns.parse(&b);
|
||||||
|
}
|
||||||
|
assert!(dns.d.state == DNSState::End);
|
||||||
|
assert!(dns.header.id == 0x1337);
|
||||||
|
assert!(dns.header._qr == false);
|
||||||
|
assert!(dns.header._opcode == 0);
|
||||||
|
assert!(dns.header._aa == false);
|
||||||
|
assert!(dns.header._tc == false);
|
||||||
|
assert!(dns.header._rd == true);
|
||||||
|
assert!(dns.header._ra == false);
|
||||||
|
assert!(dns.header._z == 0);
|
||||||
|
assert!(dns.header._rcode == 0);
|
||||||
|
assert!(dns.header.qdcount == 3);
|
||||||
|
assert!(dns.header.ancount == 0);
|
||||||
|
assert!(dns.header.nscount == 0);
|
||||||
|
assert!(dns.header.arcount == 0);
|
||||||
|
assert!(dns.qd.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.qd[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[0].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[1].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[2].class == DNSClass::IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_rr_all() {
|
||||||
|
/* OK */
|
||||||
|
/* scapy: DNS(id=1234, qr=True, aa=True, qd=None,
|
||||||
|
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
|
||||||
|
**/
|
||||||
|
let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
|
||||||
|
let dns = match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_dns) => _dns,
|
||||||
|
Err(e) => panic!("error while parsing DNS packet: {}", e),
|
||||||
|
};
|
||||||
|
assert!(dns.header.id == 1234);
|
||||||
|
assert!(dns.header._qr == true);
|
||||||
|
assert!(dns.header._opcode == 0);
|
||||||
|
assert!(dns.header._aa == true);
|
||||||
|
assert!(dns.header._tc == false);
|
||||||
|
assert!(dns.header._rd == true);
|
||||||
|
assert!(dns.header._ra == false);
|
||||||
|
assert!(dns.header._z == 0);
|
||||||
|
assert!(dns.header._rcode == 0);
|
||||||
|
assert!(dns.header.qdcount == 0);
|
||||||
|
assert!(dns.header.ancount == 3);
|
||||||
|
assert!(dns.header.nscount == 0);
|
||||||
|
assert!(dns.header.arcount == 0);
|
||||||
|
assert!(dns.rr.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.rr[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[0].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[1].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[2].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
|
||||||
|
/* KO */
|
||||||
|
let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x04\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
|
||||||
|
match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_) => panic!("parsing should have failed"),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
let payload = b"xxx";
|
||||||
|
match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_) => panic!("parsing should have failed"),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_rr_byte_by_byte() {
|
||||||
|
/* scapy: DNS(id=1234, qr=True, aa=True, qd=None,
|
||||||
|
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
|
||||||
|
**/
|
||||||
|
let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
|
||||||
|
let mut dns = DNSPacket::new();
|
||||||
|
for b in payload {
|
||||||
|
assert!(dns.d.state != DNSState::End);
|
||||||
|
dns.parse(&b);
|
||||||
|
}
|
||||||
|
assert!(dns.d.state == DNSState::End);
|
||||||
|
assert!(dns.header.id == 1234);
|
||||||
|
assert!(dns.header._qr == true);
|
||||||
|
assert!(dns.header._opcode == 0);
|
||||||
|
assert!(dns.header._aa == true);
|
||||||
|
assert!(dns.header._tc == false);
|
||||||
|
assert!(dns.header._rd == true);
|
||||||
|
assert!(dns.header._ra == false);
|
||||||
|
assert!(dns.header._z == 0);
|
||||||
|
assert!(dns.header._rcode == 0);
|
||||||
|
assert!(dns.header.qdcount == 0);
|
||||||
|
assert!(dns.header.ancount == 3);
|
||||||
|
assert!(dns.header.nscount == 0);
|
||||||
|
assert!(dns.header.arcount == 0);
|
||||||
|
assert!(dns.rr.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.rr[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[0].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[1].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[2].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_qd_rr_all() {
|
||||||
|
/* scapy: DNS(id=1234, qr=True, aa=True,
|
||||||
|
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"),
|
||||||
|
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
|
||||||
|
*/
|
||||||
|
let payload = b"\x04\xd2\x85\x00\x00\x03\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
|
||||||
|
let dns = match DNSPacket::try_from(payload.to_vec()) {
|
||||||
|
Ok(_dns) => _dns,
|
||||||
|
Err(e) => panic!("error while parsing DNS packet: {}", e),
|
||||||
|
};
|
||||||
|
assert!(dns.header.id == 1234);
|
||||||
|
assert!(dns.header._qr == true);
|
||||||
|
assert!(dns.header._opcode == 0);
|
||||||
|
assert!(dns.header._aa == true);
|
||||||
|
assert!(dns.header._tc == false);
|
||||||
|
assert!(dns.header._rd == true);
|
||||||
|
assert!(dns.header._ra == false);
|
||||||
|
assert!(dns.header._z == 0);
|
||||||
|
assert!(dns.header._rcode == 0);
|
||||||
|
assert!(dns.header.qdcount == 3);
|
||||||
|
assert!(dns.header.ancount == 3);
|
||||||
|
assert!(dns.header.nscount == 0);
|
||||||
|
assert!(dns.header.arcount == 0);
|
||||||
|
assert!(dns.qd.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.qd[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[0].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[1].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[2].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.rr[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[0].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[1].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[2].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_qr_rr_byte_by_byte() {
|
||||||
|
/* scapy: DNS(id=1234, qr=True, aa=True,
|
||||||
|
* qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"),
|
||||||
|
* an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3"))
|
||||||
|
*/
|
||||||
|
let payload = b"\x04\xd2\x85\x00\x00\x03\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03";
|
||||||
|
let mut dns = DNSPacket::new();
|
||||||
|
for b in payload {
|
||||||
|
assert!(dns.d.state != DNSState::End);
|
||||||
|
dns.parse(&b);
|
||||||
|
}
|
||||||
|
assert!(dns.d.state == DNSState::End);
|
||||||
|
assert!(dns.header.id == 1234);
|
||||||
|
assert!(dns.header._qr == true);
|
||||||
|
assert!(dns.header._opcode == 0);
|
||||||
|
assert!(dns.header._aa == true);
|
||||||
|
assert!(dns.header._tc == false);
|
||||||
|
assert!(dns.header._rd == true);
|
||||||
|
assert!(dns.header._ra == false);
|
||||||
|
assert!(dns.header._z == 0);
|
||||||
|
assert!(dns.header._rcode == 0);
|
||||||
|
assert!(dns.header.qdcount == 3);
|
||||||
|
assert!(dns.header.ancount == 3);
|
||||||
|
assert!(dns.header.nscount == 0);
|
||||||
|
assert!(dns.header.arcount == 0);
|
||||||
|
assert!(dns.qd.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.qd[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[0].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[1].class == DNSClass::IN);
|
||||||
|
assert!(
|
||||||
|
dns.qd[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.qd[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.qd[2].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr.len() == 3);
|
||||||
|
assert!(
|
||||||
|
dns.rr[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[0].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[0].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[1].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[1].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[1].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]);
|
||||||
|
assert!(
|
||||||
|
dns.rr[2].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(dns.rr[2].type_ == DNSType::A);
|
||||||
|
assert!(dns.rr[2].class == DNSClass::IN);
|
||||||
|
assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_in_a() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
/* scapy: DNS(id=0x1337,
|
||||||
|
* qd=DNSQR(qname="www.example.com"))
|
||||||
|
**/
|
||||||
|
let payload = b"\x137\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x07example\x03com\x00\x00\x01\x00\x01";
|
||||||
|
let dns = DNSPacket::try_from(payload.to_vec()).unwrap();
|
||||||
|
for ip in [
|
||||||
|
Ipv4Addr::new(127, 0, 0, 1),
|
||||||
|
Ipv4Addr::new(0, 0, 0, 0),
|
||||||
|
Ipv4Addr::new(4, 3, 2, 1),
|
||||||
|
] {
|
||||||
|
client_info.ip.dst = Some(IpAddr::V4(ip));
|
||||||
|
let ans = if let Some(a) = dns.repl(&masscanned, &client_info, None) {
|
||||||
|
DNSPacket::try_from(a).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("expected a reply, got None");
|
||||||
|
};
|
||||||
|
assert!(ans.header.id == 0x1337);
|
||||||
|
assert!(ans.header._qr == true);
|
||||||
|
assert!(ans.header._opcode == 0);
|
||||||
|
assert!(ans.header._aa == true);
|
||||||
|
assert!(ans.header._tc == false);
|
||||||
|
assert!(ans.header._rd == dns.header._rd);
|
||||||
|
assert!(ans.header._ra == false);
|
||||||
|
assert!(ans.header._z == 0);
|
||||||
|
assert!(ans.header._rcode == 0);
|
||||||
|
assert!(ans.header.qdcount == 1);
|
||||||
|
assert!(ans.header.ancount == 1);
|
||||||
|
assert!(ans.header.nscount == 0);
|
||||||
|
assert!(ans.header.arcount == 0);
|
||||||
|
assert!(ans.qd.len() == 1);
|
||||||
|
assert!(
|
||||||
|
ans.qd[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(ans.qd[0].type_ == DNSType::A);
|
||||||
|
assert!(ans.qd[0].class == DNSClass::IN);
|
||||||
|
assert!(ans.rr.len() == 1);
|
||||||
|
assert!(
|
||||||
|
ans.rr[0].name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
|
||||||
|
0x03, 0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(ans.rr[0].type_ == DNSType::A);
|
||||||
|
assert!(ans.rr[0].class == DNSClass::IN);
|
||||||
|
assert!(ans.rr[0].rdata == ip.octets());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
337
src/proto/dns/query.rs
Normal file
337
src/proto/dns/query.rs
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2022 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::cst::{DNSClass, DNSType};
|
||||||
|
use super::rr::DNSRR;
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use crate::proto::dissector::{MPacket, PacketDissector};
|
||||||
|
use crate::proto::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum DNSQueryState {
|
||||||
|
Name,
|
||||||
|
Type,
|
||||||
|
Class,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DNSQuery {
|
||||||
|
pub d: PacketDissector<DNSQueryState>,
|
||||||
|
/* RFC 1035 - Section 4.1.2 */
|
||||||
|
pub name: Vec<u8>,
|
||||||
|
_u_type: u16,
|
||||||
|
pub type_: DNSType,
|
||||||
|
_u_class: u16,
|
||||||
|
pub class: DNSClass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for DNSQuery {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let mut query = DNSQuery::new();
|
||||||
|
for b in item {
|
||||||
|
query.parse(&b);
|
||||||
|
}
|
||||||
|
if query.d.state == DNSQueryState::End {
|
||||||
|
Ok(query)
|
||||||
|
} else {
|
||||||
|
Err("packet is incomplete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&DNSQuery> for Vec<u8> {
|
||||||
|
fn from(item: &DNSQuery) -> Self {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
/* name */
|
||||||
|
v.extend(&item.name);
|
||||||
|
/* type */
|
||||||
|
v.push(((u16::from(item.type_)) >> 8) as u8);
|
||||||
|
v.push(((u16::from(item.type_)) & 0xFF) as u8);
|
||||||
|
/* class */
|
||||||
|
v.push(((u16::from(item.class)) >> 8) as u8);
|
||||||
|
v.push(((u16::from(item.class)) & 0xFF) as u8);
|
||||||
|
/* return */
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MPacket for DNSQuery {
|
||||||
|
fn new() -> Self {
|
||||||
|
DNSQuery {
|
||||||
|
d: PacketDissector::new(DNSQueryState::Name),
|
||||||
|
name: Vec::new(),
|
||||||
|
_u_type: 0,
|
||||||
|
type_: DNSType::NONE,
|
||||||
|
_u_class: 0,
|
||||||
|
class: DNSClass::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self, byte: &u8) {
|
||||||
|
match self.d.state {
|
||||||
|
DNSQueryState::Name => {
|
||||||
|
self.name.push(*byte);
|
||||||
|
if *byte == 0 {
|
||||||
|
self.d.next_state(DNSQueryState::Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DNSQueryState::Type => {
|
||||||
|
self._u_type = self.d.read_u16(byte, self._u_type, DNSQueryState::Class);
|
||||||
|
}
|
||||||
|
DNSQueryState::Class => {
|
||||||
|
self._u_class = self.d.read_u16(byte, self._u_class, DNSQueryState::End);
|
||||||
|
}
|
||||||
|
DNSQueryState::End => {}
|
||||||
|
}
|
||||||
|
/* we need this to be executed at the same call
|
||||||
|
* the state changes to End, hence it is not in the
|
||||||
|
* match structure
|
||||||
|
**/
|
||||||
|
if self.d.state == DNSQueryState::End {
|
||||||
|
self.type_ = DNSType::from(self._u_type);
|
||||||
|
self.class = DNSClass::from(self._u_class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repl(
|
||||||
|
&self,
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
match self.class {
|
||||||
|
DNSClass::IN => {
|
||||||
|
match self.type_ {
|
||||||
|
DNSType::A => {
|
||||||
|
let mut rr = DNSRR::new();
|
||||||
|
/* copy request */
|
||||||
|
for b in &self.name {
|
||||||
|
rr.name.push(*b);
|
||||||
|
}
|
||||||
|
rr.type_ = DNSType::A;
|
||||||
|
rr.class = DNSClass::IN;
|
||||||
|
rr.ttl = 43200;
|
||||||
|
rr.rdata = match client_info.ip.dst {
|
||||||
|
Some(IpAddr::V4(ip)) => ip.octets().to_vec(),
|
||||||
|
Some(IpAddr::V6(_)) => Vec::new(),
|
||||||
|
None => Vec::new(),
|
||||||
|
};
|
||||||
|
rr.rdlen = rr.rdata.len() as u16;
|
||||||
|
Some(Vec::<u8>::from(&rr))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use pnet::util::MacAddr;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::client::ClientInfoSrcDst;
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_in_a_all() {
|
||||||
|
/* A */
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01";
|
||||||
|
let qr = match DNSQuery::try_from(payload.to_vec()) {
|
||||||
|
Ok(_qr) => _qr,
|
||||||
|
Err(e) => panic!("error while parsing DNS query: {}", e),
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
qr.name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
|
||||||
|
0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(qr.type_ == DNSType::A);
|
||||||
|
assert!(qr.class == DNSClass::IN);
|
||||||
|
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
|
||||||
|
/* TXT */
|
||||||
|
let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03";
|
||||||
|
let qr = match DNSQuery::try_from(payload.to_vec()) {
|
||||||
|
Ok(_qr) => _qr,
|
||||||
|
Err(e) => panic!("error while parsing DNS query: {}", e),
|
||||||
|
};
|
||||||
|
assert!(qr.type_ == DNSType::TXT);
|
||||||
|
assert!(qr.class == DNSClass::CH);
|
||||||
|
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
|
||||||
|
/* KO */
|
||||||
|
let payload = b"xxx";
|
||||||
|
match DNSQuery::try_from(payload.to_vec()) {
|
||||||
|
Ok(_) => panic!("parsing should have failed"),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_in_a_byte_by_byte() {
|
||||||
|
/* A */
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01";
|
||||||
|
let mut qr = DNSQuery::new();
|
||||||
|
for b in payload {
|
||||||
|
qr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(qr.d.state == DNSQueryState::End);
|
||||||
|
assert!(
|
||||||
|
qr.name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
|
||||||
|
0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(qr.type_ == DNSType::A);
|
||||||
|
assert!(qr.class == DNSClass::IN);
|
||||||
|
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
|
||||||
|
/* TXT */
|
||||||
|
let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03";
|
||||||
|
let mut qr = DNSQuery::new();
|
||||||
|
for b in payload {
|
||||||
|
qr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(qr.d.state == DNSQueryState::End);
|
||||||
|
assert!(qr.type_ == DNSType::TXT);
|
||||||
|
assert!(qr.class == DNSClass::CH);
|
||||||
|
assert!(Vec::<u8>::from(&qr) == payload.to_vec());
|
||||||
|
/* KO */
|
||||||
|
let payload = b"xxx";
|
||||||
|
let mut qr = DNSQuery::new();
|
||||||
|
for b in payload {
|
||||||
|
qr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(qr.d.state != DNSQueryState::End);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_in_a() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let ip_src = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
|
let ip_dst = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2));
|
||||||
|
let client_info = ClientInfo {
|
||||||
|
mac: ClientInfoSrcDst {
|
||||||
|
src: None,
|
||||||
|
dst: None,
|
||||||
|
},
|
||||||
|
ip: ClientInfoSrcDst {
|
||||||
|
src: Some(ip_src),
|
||||||
|
dst: Some(ip_dst),
|
||||||
|
},
|
||||||
|
transport: None,
|
||||||
|
port: ClientInfoSrcDst {
|
||||||
|
src: None,
|
||||||
|
dst: None,
|
||||||
|
},
|
||||||
|
cookie: None,
|
||||||
|
};
|
||||||
|
/* TXT */
|
||||||
|
let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03";
|
||||||
|
let mut qr = DNSQuery::new();
|
||||||
|
for b in payload {
|
||||||
|
qr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(qr.type_ == DNSType::TXT);
|
||||||
|
assert!(qr.class == DNSClass::CH);
|
||||||
|
/* A */
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01";
|
||||||
|
let mut qr = DNSQuery::new();
|
||||||
|
for b in payload {
|
||||||
|
qr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
qr.name
|
||||||
|
== [
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
|
||||||
|
0x63, 0x6f, 0x6d, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(qr.type_ == DNSType::A);
|
||||||
|
assert!(qr.class == DNSClass::IN);
|
||||||
|
let rr_raw = match qr.repl(&masscanned, &client_info, None) {
|
||||||
|
None => {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
Some(r) => r,
|
||||||
|
};
|
||||||
|
let mut rr = DNSRR::new();
|
||||||
|
for b in rr_raw {
|
||||||
|
rr.parse(&b);
|
||||||
|
}
|
||||||
|
assert!(rr.name == qr.name);
|
||||||
|
assert!(rr.type_ == DNSType::A);
|
||||||
|
assert!(rr.class == DNSClass::IN);
|
||||||
|
assert!(rr.ttl == 43200);
|
||||||
|
assert!(rr.rdata == [127, 0, 0, 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repl() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let client_info = ClientInfo::new();
|
||||||
|
/* exhaustive tests */
|
||||||
|
let supported: Vec<(DNSClass, DNSType)> = vec![(DNSClass::IN, DNSType::A)];
|
||||||
|
let mut qd = DNSQuery::new();
|
||||||
|
qd.name = vec![
|
||||||
|
0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63,
|
||||||
|
0x6f, 0x6d, 0x00,
|
||||||
|
];
|
||||||
|
for c in DNSClass::iter() {
|
||||||
|
qd.class = c;
|
||||||
|
for t in DNSType::iter() {
|
||||||
|
qd.type_ = t;
|
||||||
|
if supported.contains(&(c, t)) {
|
||||||
|
if qd.repl(&masscanned, &client_info, None) == None {
|
||||||
|
panic!("expected reply, got None");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if qd.repl(&masscanned, &client_info, None) != None {
|
||||||
|
panic!("expected no reply, got one for {:?}, {:?}", c, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
251
src/proto/dns/rr.rs
Normal file
251
src/proto/dns/rr.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2022 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::cst::{DNSClass, DNSType};
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use crate::proto::dissector::{MPacket, PacketDissector};
|
||||||
|
use crate::proto::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum DNSRRState {
|
||||||
|
Name,
|
||||||
|
Type,
|
||||||
|
Class,
|
||||||
|
TTL,
|
||||||
|
RDLength,
|
||||||
|
RData,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DNSRR {
|
||||||
|
pub d: PacketDissector<DNSRRState>,
|
||||||
|
/* RFC 1035 - Section 3.2.1 */
|
||||||
|
pub name: Vec<u8>,
|
||||||
|
_u_type: u16,
|
||||||
|
pub type_: DNSType,
|
||||||
|
_u_class: u16,
|
||||||
|
pub class: DNSClass,
|
||||||
|
pub ttl: u32,
|
||||||
|
pub rdlen: u16,
|
||||||
|
pub rdata: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&DNSRR> for Vec<u8> {
|
||||||
|
fn from(item: &DNSRR) -> Self {
|
||||||
|
/* CAUTION: for the rdlen field:
|
||||||
|
* - if item.rdlen is not 0, its value is packed
|
||||||
|
* - if item.rdlen = 0, then the length of item.rdata is used instead
|
||||||
|
*/
|
||||||
|
let mut v = Vec::new();
|
||||||
|
/* name */
|
||||||
|
for b in &item.name {
|
||||||
|
v.push(b.clone());
|
||||||
|
}
|
||||||
|
/* type */
|
||||||
|
let type_: u16 = item.type_.into();
|
||||||
|
v.push((type_ >> 8) as u8);
|
||||||
|
v.push((type_ & 0xFF) as u8);
|
||||||
|
/* class */
|
||||||
|
let class: u16 = item.class.into();
|
||||||
|
v.push((class >> 8) as u8);
|
||||||
|
v.push((class & 0xFF) as u8);
|
||||||
|
/* ttl */
|
||||||
|
v.push((item.ttl >> 24) as u8);
|
||||||
|
v.push((item.ttl >> 16) as u8);
|
||||||
|
v.push((item.ttl >> 8) as u8);
|
||||||
|
v.push((item.ttl & 0xFF) as u8);
|
||||||
|
/* rdlen */
|
||||||
|
let rdlen = if item.rdlen == 0 {
|
||||||
|
item.rdata.len() as u16
|
||||||
|
} else {
|
||||||
|
item.rdlen
|
||||||
|
};
|
||||||
|
v.push((rdlen >> 8) as u8);
|
||||||
|
v.push((rdlen & 0xFF) as u8);
|
||||||
|
/* rdata */
|
||||||
|
for b in &item.rdata {
|
||||||
|
v.push(b.clone());
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for DNSRR {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let mut rr = DNSRR::new();
|
||||||
|
for b in item {
|
||||||
|
rr.parse(&b);
|
||||||
|
}
|
||||||
|
if rr.d.state == DNSRRState::End {
|
||||||
|
Ok(rr)
|
||||||
|
} else {
|
||||||
|
Err("packet is incomplete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MPacket for DNSRR {
|
||||||
|
fn new() -> Self {
|
||||||
|
DNSRR {
|
||||||
|
d: PacketDissector::new(DNSRRState::Name),
|
||||||
|
name: Vec::new(),
|
||||||
|
_u_type: 0,
|
||||||
|
type_: DNSType::NONE,
|
||||||
|
_u_class: 0,
|
||||||
|
class: DNSClass::NONE,
|
||||||
|
rdlen: 0,
|
||||||
|
ttl: 0,
|
||||||
|
rdata: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self, byte: &u8) {
|
||||||
|
match self.d.state {
|
||||||
|
DNSRRState::Name => {
|
||||||
|
self.name.push(*byte);
|
||||||
|
if *byte == 0 {
|
||||||
|
self.d.next_state(DNSRRState::Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DNSRRState::Type => {
|
||||||
|
self._u_type = self.d.read_u16(byte, self._u_type, DNSRRState::Class);
|
||||||
|
}
|
||||||
|
DNSRRState::Class => {
|
||||||
|
self._u_class = self.d.read_u16(byte, self._u_class, DNSRRState::TTL);
|
||||||
|
}
|
||||||
|
DNSRRState::TTL => {
|
||||||
|
self.ttl = self.d.read_u32(byte, self.ttl, DNSRRState::RDLength);
|
||||||
|
}
|
||||||
|
DNSRRState::RDLength => {
|
||||||
|
self.rdlen = self.d.read_u16(byte, self.rdlen, DNSRRState::RData);
|
||||||
|
/* when read the rdlen, check if len is 0 */
|
||||||
|
if self.d.state == DNSRRState::RData && self.rdlen == 0 {
|
||||||
|
self.d.state = DNSRRState::End;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DNSRRState::RData => {
|
||||||
|
self.rdata.push(*byte);
|
||||||
|
if self.rdata.len() == self.rdlen as usize {
|
||||||
|
self.d.next_state(DNSRRState::End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DNSRRState::End => {}
|
||||||
|
}
|
||||||
|
/* we need this to be executed at the same call
|
||||||
|
* the state changes to End, hence it is not in the
|
||||||
|
* match structure
|
||||||
|
**/
|
||||||
|
if self.d.state == DNSRRState::End {
|
||||||
|
self.type_ = DNSType::from(self._u_type);
|
||||||
|
self.class = DNSClass::from(self._u_class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repl(
|
||||||
|
&self,
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
_client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build() {
|
||||||
|
let mut rr = DNSRR::new();
|
||||||
|
rr.name = b"\x03www\x07example\x03com\x00".to_vec();
|
||||||
|
rr.class = DNSClass::IN;
|
||||||
|
rr.type_ = DNSType::A;
|
||||||
|
rr.ttl = 1234;
|
||||||
|
rr.rdlen = 4;
|
||||||
|
rr.rdata = b"\x7f\x00\x00\x01".to_vec();
|
||||||
|
assert!(Vec::<u8>::from(&rr) == b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x04\xd2\x00\x04\x7f\x00\x00\x01");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_all() {
|
||||||
|
/*
|
||||||
|
* raw(DNSRR(rrname="www.example.com", rdata="127.0.0.1"))
|
||||||
|
*/
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01";
|
||||||
|
let rr = match DNSRR::try_from(payload.to_vec()) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => panic!("error while parsing DNS RR: {}", e),
|
||||||
|
};
|
||||||
|
assert!(rr.name == b"\x03www\x07example\x03com\x00");
|
||||||
|
assert!(rr.class == DNSClass::IN);
|
||||||
|
assert!(rr.type_ == DNSType::A);
|
||||||
|
assert!(rr.rdata == b"\x7f\x00\x00\x01");
|
||||||
|
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
|
||||||
|
/*
|
||||||
|
* empty data
|
||||||
|
*/
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00";
|
||||||
|
let rr = match DNSRR::try_from(payload.to_vec()) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => panic!("error while parsing DNS RR: {}", e),
|
||||||
|
};
|
||||||
|
assert!(rr.name == b"\x03www\x07example\x03com\x00");
|
||||||
|
assert!(rr.class == DNSClass::IN);
|
||||||
|
assert!(rr.type_ == DNSType::A);
|
||||||
|
assert!(rr.rdata == b"");
|
||||||
|
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_byte_by_byte() {
|
||||||
|
/*
|
||||||
|
* raw(DNSRR(rrname="www.example.com", rdata="127.0.0.1"))
|
||||||
|
*/
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01";
|
||||||
|
let mut rr = DNSRR::new();
|
||||||
|
for b in payload {
|
||||||
|
assert!(rr.d.state != DNSRRState::End);
|
||||||
|
rr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(rr.d.state == DNSRRState::End);
|
||||||
|
assert!(rr.name == b"\x03www\x07example\x03com\x00");
|
||||||
|
assert!(rr.class == DNSClass::IN);
|
||||||
|
assert!(rr.type_ == DNSType::A);
|
||||||
|
assert!(rr.rdata == b"\x7f\x00\x00\x01");
|
||||||
|
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
|
||||||
|
/*
|
||||||
|
* empty data
|
||||||
|
*/
|
||||||
|
let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00";
|
||||||
|
let mut rr = DNSRR::new();
|
||||||
|
for b in payload {
|
||||||
|
assert!(rr.d.state != DNSRRState::End);
|
||||||
|
rr.parse(b);
|
||||||
|
}
|
||||||
|
assert!(rr.name == b"\x03www\x07example\x03com\x00");
|
||||||
|
assert!(rr.class == DNSClass::IN);
|
||||||
|
assert!(rr.type_ == DNSType::A);
|
||||||
|
assert!(rr.rdata == b"");
|
||||||
|
assert!(Vec::<u8>::from(&rr) == payload.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/proto/ghost.rs
Normal file
59
src/proto/ghost.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use log::*;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use flate2::write::ZlibEncoder;
|
||||||
|
use flate2::Compression;
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
pub const GHOST_PATTERN_SIGNATURE: &[u8; 5] = b"Gh0st";
|
||||||
|
|
||||||
|
pub fn repl<'a>(
|
||||||
|
_data: &'a [u8],
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
_client_info: &mut ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
debug!("receiving Gh0st data, sending one null byte payload");
|
||||||
|
// Packet structure:
|
||||||
|
// GHOST_PATTERN_SIGNATURE + [ packet size ] + [ uncompressed payload size ] + payload
|
||||||
|
let mut result = GHOST_PATTERN_SIGNATURE.to_vec();
|
||||||
|
let uncompressed_data = b"\x00";
|
||||||
|
let mut compressed_data = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||||
|
compressed_data
|
||||||
|
.write_all(uncompressed_data)
|
||||||
|
.expect("Ghost: cannot decompress payload");
|
||||||
|
let mut compressed_data = compressed_data
|
||||||
|
.finish()
|
||||||
|
.expect("Ghost: cannot decompress payload");
|
||||||
|
let mut packet_len = compressed_data.len() + GHOST_PATTERN_SIGNATURE.len() + 4 * 2;
|
||||||
|
for _ in 0..4 {
|
||||||
|
result.push((packet_len % 256) as u8);
|
||||||
|
packet_len /= 256;
|
||||||
|
}
|
||||||
|
let mut uncompressed_len = uncompressed_data.len();
|
||||||
|
for _ in 0..4 {
|
||||||
|
result.push((uncompressed_len % 256) as u8);
|
||||||
|
uncompressed_len /= 256;
|
||||||
|
}
|
||||||
|
result.append(&mut compressed_data);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ use lazy_static::lazy_static;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use crate::client::ClientInfo;
|
use crate::client::ClientInfo;
|
||||||
|
use crate::proto::{ProtocolState as GenericProtocolState, TCPControlBlock};
|
||||||
use crate::smack::{
|
use crate::smack::{
|
||||||
Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_INSENSITIVE, UNANCHORED_STATE,
|
Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_INSENSITIVE, UNANCHORED_STATE,
|
||||||
};
|
};
|
||||||
|
|
@ -62,7 +63,7 @@ const HTTP_STATE_CONTENT: usize = 64;
|
||||||
|
|
||||||
const HTTP_STATE_FAIL: usize = 0xFFFF;
|
const HTTP_STATE_FAIL: usize = 0xFFFF;
|
||||||
|
|
||||||
struct ProtocolState {
|
pub struct ProtocolState {
|
||||||
state: usize,
|
state: usize,
|
||||||
state_bis: usize,
|
state_bis: usize,
|
||||||
smack_state: usize,
|
smack_state: usize,
|
||||||
|
|
@ -223,15 +224,39 @@ pub fn repl<'a>(
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
_masscanned: &Masscanned,
|
_masscanned: &Masscanned,
|
||||||
_client_info: &ClientInfo,
|
_client_info: &ClientInfo,
|
||||||
|
tcb: Option<&mut TCPControlBlock>,
|
||||||
) -> Option<Vec<u8>> {
|
) -> Option<Vec<u8>> {
|
||||||
debug!("receiving HTTP data");
|
debug!("receiving HTTP data");
|
||||||
let mut pstate = ProtocolState::new();
|
let mut state = ProtocolState::new();
|
||||||
|
let mut pstate = {
|
||||||
|
if let Some(t) = tcb {
|
||||||
|
match t.proto_state {
|
||||||
|
None => t.proto_state = Some(GenericProtocolState::HTTP(ProtocolState::new())),
|
||||||
|
Some(GenericProtocolState::HTTP(_)) => {}
|
||||||
|
_ => {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(GenericProtocolState::HTTP(p)) = &mut t.proto_state {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
&mut state
|
||||||
|
}
|
||||||
|
};
|
||||||
http_parse(&mut pstate, data);
|
http_parse(&mut pstate, data);
|
||||||
if pstate.state == HTTP_STATE_FAIL {
|
if pstate.state == HTTP_STATE_FAIL {
|
||||||
debug!("data in not correctly formatted - not responding");
|
debug!("data in not correctly formatted - not responding");
|
||||||
debug!("pstate: {}", pstate.state);
|
debug!("pstate: {}", pstate.state);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
/* if not in CONTENT state, not responding yet (it means the client
|
||||||
|
* has not finished sending headers yet) */
|
||||||
|
if pstate.state != HTTP_STATE_CONTENT {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let content = "\
|
let content = "\
|
||||||
<html>
|
<html>
|
||||||
<head><title>401 Authorization Required</title></head>
|
<head><title>401 Authorization Required</title></head>
|
||||||
|
|
@ -267,122 +292,127 @@ WWW-Authenticate: Basic realm=\"Access to admin page\"
|
||||||
Some(repl_data)
|
Some(repl_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn test_http_verb() {
|
mod tests {
|
||||||
/* all at once */
|
use super::*;
|
||||||
for verb in HTTP_VERBS.iter() {
|
|
||||||
let mut pstate = ProtocolState::new();
|
|
||||||
assert!(pstate.state == HTTP_STATE_START);
|
|
||||||
assert!(pstate.smack_state == BASE_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
http_parse(&mut pstate, &verb.as_bytes());
|
|
||||||
assert!(pstate.state == HTTP_STATE_SPACE);
|
|
||||||
assert!(pstate.smack_id == (HttpField::Verb as usize));
|
|
||||||
assert!(pstate.http_verb == verb.as_bytes());
|
|
||||||
}
|
|
||||||
/* byte by byte */
|
|
||||||
for verb in HTTP_VERBS.iter() {
|
|
||||||
let mut pstate = ProtocolState::new();
|
|
||||||
assert!(pstate.state == HTTP_STATE_START);
|
|
||||||
assert!(pstate.smack_state == BASE_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
for i in 0..verb.len() {
|
|
||||||
if i > 0 {
|
|
||||||
assert!(pstate.state == HTTP_STATE_VERB);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
}
|
|
||||||
http_parse(&mut pstate, &verb.as_bytes()[i..i + 1]);
|
|
||||||
}
|
|
||||||
assert!(pstate.state == HTTP_STATE_SPACE);
|
|
||||||
assert!(pstate.smack_id == (HttpField::Verb as usize));
|
|
||||||
assert!(pstate.http_verb == verb.as_bytes());
|
|
||||||
}
|
|
||||||
/* KO test: XXX */
|
|
||||||
let mut pstate = ProtocolState::new();
|
|
||||||
assert!(pstate.state == HTTP_STATE_START);
|
|
||||||
assert!(pstate.smack_state == BASE_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
http_parse(&mut pstate, "XXX".as_bytes());
|
|
||||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
|
||||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
/* KO test: XGET */
|
|
||||||
let mut pstate = ProtocolState::new();
|
|
||||||
assert!(pstate.state == HTTP_STATE_START);
|
|
||||||
assert!(pstate.smack_state == BASE_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
http_parse(&mut pstate, "XGET".as_bytes());
|
|
||||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
|
||||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
/* KO test: GEX */
|
|
||||||
let mut pstate = ProtocolState::new();
|
|
||||||
assert!(pstate.state == HTTP_STATE_START);
|
|
||||||
assert!(pstate.smack_state == BASE_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
http_parse(&mut pstate, "GEX".as_bytes());
|
|
||||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
|
||||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
/* KO test: GE T */
|
|
||||||
let mut pstate = ProtocolState::new();
|
|
||||||
assert!(pstate.state == HTTP_STATE_START);
|
|
||||||
assert!(pstate.smack_state == BASE_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
http_parse(&mut pstate, "GE T".as_bytes());
|
|
||||||
assert!(pstate.state == HTTP_STATE_FAIL);
|
|
||||||
assert!(pstate.smack_state == UNANCHORED_STATE);
|
|
||||||
assert!(pstate.smack_id == NO_MATCH);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_http_request_line() {
|
fn test_http_verb() {
|
||||||
let mut pstate = ProtocolState::new();
|
/* all at once */
|
||||||
let data = "GET /index.php HTTP/1.1\r\n".as_bytes();
|
for verb in HTTP_VERBS.iter() {
|
||||||
for i in 0..data.len() {
|
let mut pstate = ProtocolState::new();
|
||||||
http_parse(&mut pstate, &data[i..i + 1]);
|
assert!(pstate.state == HTTP_STATE_START);
|
||||||
if i < 2 {
|
assert!(pstate.smack_state == BASE_STATE);
|
||||||
assert!(pstate.state == HTTP_STATE_VERB);
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
} else if i == 2 {
|
http_parse(&mut pstate, &verb.as_bytes());
|
||||||
assert!(pstate.state == HTTP_STATE_SPACE);
|
assert!(pstate.state == HTTP_STATE_SPACE);
|
||||||
} else if 3 <= i && i <= 13 {
|
assert!(pstate.smack_id == (HttpField::Verb as usize));
|
||||||
assert!(pstate.state == HTTP_STATE_URI);
|
assert!(pstate.http_verb == verb.as_bytes());
|
||||||
} else if 14 <= i && i <= 19 {
|
}
|
||||||
assert!(pstate.state == HTTP_STATE_H + (i - 14));
|
/* byte by byte */
|
||||||
} else if i == 20 {
|
for verb in HTTP_VERBS.iter() {
|
||||||
assert!(pstate.state == HTTP_STATE_VERSION_MAJ);
|
let mut pstate = ProtocolState::new();
|
||||||
} else if 21 <= i && i <= 23 {
|
assert!(pstate.state == HTTP_STATE_START);
|
||||||
assert!(pstate.state == HTTP_STATE_VERSION_MIN);
|
assert!(pstate.smack_state == BASE_STATE);
|
||||||
} else if i == 24 {
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
for i in 0..verb.len() {
|
||||||
|
if i > 0 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_VERB);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
}
|
||||||
|
http_parse(&mut pstate, &verb.as_bytes()[i..i + 1]);
|
||||||
|
}
|
||||||
|
assert!(pstate.state == HTTP_STATE_SPACE);
|
||||||
|
assert!(pstate.smack_id == (HttpField::Verb as usize));
|
||||||
|
assert!(pstate.http_verb == verb.as_bytes());
|
||||||
|
}
|
||||||
|
/* KO test: XXX */
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == HTTP_STATE_START);
|
||||||
|
assert!(pstate.smack_state == BASE_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
http_parse(&mut pstate, "XXX".as_bytes());
|
||||||
|
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||||
|
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
/* KO test: XGET */
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == HTTP_STATE_START);
|
||||||
|
assert!(pstate.smack_state == BASE_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
http_parse(&mut pstate, "XGET".as_bytes());
|
||||||
|
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||||
|
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
/* KO test: GEX */
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == HTTP_STATE_START);
|
||||||
|
assert!(pstate.smack_state == BASE_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
http_parse(&mut pstate, "GEX".as_bytes());
|
||||||
|
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||||
|
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
/* KO test: GE T */
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == HTTP_STATE_START);
|
||||||
|
assert!(pstate.smack_state == BASE_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
http_parse(&mut pstate, "GE T".as_bytes());
|
||||||
|
assert!(pstate.state == HTTP_STATE_FAIL);
|
||||||
|
assert!(pstate.smack_state == UNANCHORED_STATE);
|
||||||
|
assert!(pstate.smack_id == NO_MATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_http_request_line() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
let data = "GET /index.php HTTP/1.1\r\n".as_bytes();
|
||||||
|
for i in 0..data.len() {
|
||||||
|
http_parse(&mut pstate, &data[i..i + 1]);
|
||||||
|
if i < 2 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_VERB);
|
||||||
|
} else if i == 2 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_SPACE);
|
||||||
|
} else if 3 <= i && i <= 13 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_URI);
|
||||||
|
} else if 14 <= i && i <= 19 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_H + (i - 14));
|
||||||
|
} else if i == 20 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_VERSION_MAJ);
|
||||||
|
} else if 21 <= i && i <= 23 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_VERSION_MIN);
|
||||||
|
} else if i == 24 {
|
||||||
|
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_http_request_field() {
|
fn test_http_request_field() {
|
||||||
let mut pstate = ProtocolState::new();
|
let mut pstate = ProtocolState::new();
|
||||||
let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
|
let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
|
||||||
http_parse(&mut pstate, req);
|
http_parse(&mut pstate, req);
|
||||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||||
let field = b"Content-Length";
|
let field = b"Content-Length";
|
||||||
http_parse(&mut pstate, field);
|
http_parse(&mut pstate, field);
|
||||||
assert!(pstate.state == HTTP_STATE_FIELD_NAME);
|
assert!(pstate.state == HTTP_STATE_FIELD_NAME);
|
||||||
let dot = b": ";
|
let dot = b": ";
|
||||||
http_parse(&mut pstate, dot);
|
http_parse(&mut pstate, dot);
|
||||||
assert!(pstate.state == HTTP_STATE_FIELD_VALUE);
|
assert!(pstate.state == HTTP_STATE_FIELD_VALUE);
|
||||||
let value = b": 0\r\n";
|
let value = b": 0\r\n";
|
||||||
http_parse(&mut pstate, value);
|
http_parse(&mut pstate, value);
|
||||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_http_request_no_field() {
|
fn test_http_request_no_field() {
|
||||||
let mut pstate = ProtocolState::new();
|
let mut pstate = ProtocolState::new();
|
||||||
let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
|
let req = "POST /index.php HTTP/2.0\r\n".as_bytes();
|
||||||
http_parse(&mut pstate, req);
|
http_parse(&mut pstate, req);
|
||||||
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
assert!(pstate.state == HTTP_STATE_FIELD_START);
|
||||||
let crlf = "\r\n".as_bytes();
|
let crlf = "\r\n".as_bytes();
|
||||||
http_parse(&mut pstate, crlf);
|
http_parse(&mut pstate, crlf);
|
||||||
assert!(pstate.state == HTTP_STATE_CONTENT);
|
assert!(pstate.state == HTTP_STATE_CONTENT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
269
src/proto/mod.rs
269
src/proto/mod.rs
|
|
@ -17,13 +17,15 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::*;
|
use log::*;
|
||||||
use pnet::packet::ip::IpNextHeaderProtocols;
|
use pnet::packet::ip::IpNextHeaderProtocols;
|
||||||
use std::collections::HashMap;
|
use std::convert::TryFrom;
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
use crate::client::ClientInfo;
|
use crate::client::ClientInfo;
|
||||||
use crate::smack::{Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_SENSITIVE};
|
use crate::smack::{Smack, SmackFlags, BASE_STATE, NO_MATCH, SMACK_CASE_SENSITIVE};
|
||||||
use crate::Masscanned;
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
mod dns;
|
||||||
|
use dns::DNSPacket;
|
||||||
|
|
||||||
mod http;
|
mod http;
|
||||||
use http::HTTP_VERBS;
|
use http::HTTP_VERBS;
|
||||||
|
|
||||||
|
|
@ -31,19 +33,38 @@ mod stun;
|
||||||
use stun::{STUN_PATTERN_CHANGE_REQUEST, STUN_PATTERN_EMPTY, STUN_PATTERN_MAGIC};
|
use stun::{STUN_PATTERN_CHANGE_REQUEST, STUN_PATTERN_EMPTY, STUN_PATTERN_MAGIC};
|
||||||
|
|
||||||
mod ssh;
|
mod ssh;
|
||||||
use ssh::SSH_PATTERN_CLIENT_PROTOCOL;
|
use ssh::{SSH_PATTERN_CLIENT_PROTOCOL_1, SSH_PATTERN_CLIENT_PROTOCOL_2};
|
||||||
|
|
||||||
|
mod ghost;
|
||||||
|
use ghost::GHOST_PATTERN_SIGNATURE;
|
||||||
|
|
||||||
|
mod rpc;
|
||||||
|
use rpc::{RPC_CALL_TCP, RPC_CALL_UDP};
|
||||||
|
|
||||||
|
mod smb;
|
||||||
|
use smb::{SMB1_PATTERN_MAGIC, SMB2_PATTERN_MAGIC};
|
||||||
|
|
||||||
|
mod dissector;
|
||||||
|
use dissector::MPacket;
|
||||||
|
|
||||||
|
// mod dissector;
|
||||||
|
// pub use dissector::PacketDissector;
|
||||||
|
//
|
||||||
|
mod tcb;
|
||||||
|
pub use tcb::{add_tcb, get_tcb, is_tcb_set, ProtocolState, TCPControlBlock};
|
||||||
|
|
||||||
|
const PROTO_NONE: usize = 0;
|
||||||
const PROTO_HTTP: usize = 1;
|
const PROTO_HTTP: usize = 1;
|
||||||
const PROTO_STUN: usize = 2;
|
const PROTO_STUN: usize = 2;
|
||||||
const PROTO_SSH: usize = 3;
|
const PROTO_SSH: usize = 3;
|
||||||
|
const PROTO_GHOST: usize = 4;
|
||||||
struct TCPControlBlock {
|
const PROTO_RPC_TCP: usize = 5;
|
||||||
proto_state: usize,
|
const PROTO_RPC_UDP: usize = 6;
|
||||||
}
|
const PROTO_SMB1: usize = 7;
|
||||||
|
const PROTO_SMB2: usize = 8;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PROTO_SMACK: Smack = proto_init();
|
static ref PROTO_SMACK: Smack = proto_init();
|
||||||
static ref CONTABLE: Mutex<HashMap<u32, TCPControlBlock>> = Mutex::new(HashMap::new());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn proto_init() -> Smack {
|
fn proto_init() -> Smack {
|
||||||
|
|
@ -72,10 +93,40 @@ fn proto_init() -> Smack {
|
||||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::ANCHOR_END | SmackFlags::WILDCARDS,
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::ANCHOR_END | SmackFlags::WILDCARDS,
|
||||||
);
|
);
|
||||||
smack.add_pattern(
|
smack.add_pattern(
|
||||||
SSH_PATTERN_CLIENT_PROTOCOL,
|
SSH_PATTERN_CLIENT_PROTOCOL_2,
|
||||||
PROTO_SSH,
|
PROTO_SSH,
|
||||||
SmackFlags::ANCHOR_BEGIN,
|
SmackFlags::ANCHOR_BEGIN,
|
||||||
);
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
SSH_PATTERN_CLIENT_PROTOCOL_1,
|
||||||
|
PROTO_SSH,
|
||||||
|
SmackFlags::ANCHOR_BEGIN,
|
||||||
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
GHOST_PATTERN_SIGNATURE,
|
||||||
|
PROTO_GHOST,
|
||||||
|
SmackFlags::ANCHOR_BEGIN,
|
||||||
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
RPC_CALL_TCP,
|
||||||
|
PROTO_RPC_TCP,
|
||||||
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||||
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
RPC_CALL_UDP,
|
||||||
|
PROTO_RPC_UDP,
|
||||||
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||||
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
SMB1_PATTERN_MAGIC,
|
||||||
|
PROTO_SMB1,
|
||||||
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||||
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
SMB2_PATTERN_MAGIC,
|
||||||
|
PROTO_SMB2,
|
||||||
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||||
|
);
|
||||||
smack.compile();
|
smack.compile();
|
||||||
smack
|
smack
|
||||||
}
|
}
|
||||||
|
|
@ -84,50 +135,59 @@ pub fn repl<'a>(
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
masscanned: &Masscanned,
|
masscanned: &Masscanned,
|
||||||
mut client_info: &mut ClientInfo,
|
mut client_info: &mut ClientInfo,
|
||||||
|
mut tcb: Option<&mut TCPControlBlock>,
|
||||||
) -> Option<Vec<u8>> {
|
) -> Option<Vec<u8>> {
|
||||||
debug!("packet payload: {:?}", data);
|
debug!("packet payload: {:?}", data);
|
||||||
let mut id;
|
let mut id;
|
||||||
if client_info.transport == Some(IpNextHeaderProtocols::Tcp) && client_info.cookie == None {
|
if client_info.transport == Some(IpNextHeaderProtocols::Tcp) && client_info.cookie == None {
|
||||||
error!("Unexpected empty cookie");
|
error!("Unexpected empty cookie");
|
||||||
return None;
|
return None;
|
||||||
} else if client_info.cookie != None {
|
} else if let Some(t) = &mut tcb {
|
||||||
/* proto over TCP */
|
/* proto over TCP */
|
||||||
let cookie = client_info.cookie.unwrap();
|
|
||||||
let mut ct = CONTABLE.lock().unwrap();
|
|
||||||
if !ct.contains_key(&cookie) {
|
|
||||||
ct.insert(
|
|
||||||
cookie,
|
|
||||||
TCPControlBlock {
|
|
||||||
proto_state: BASE_STATE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut tcb = ct.get_mut(&cookie).unwrap();
|
if t.proto_id == PROTO_NONE {
|
||||||
let mut state = tcb.proto_state;
|
let mut state = t.smack_state;
|
||||||
id = PROTO_SMACK.search_next(&mut state, &data.to_vec(), &mut i);
|
t.proto_id = PROTO_SMACK.search_next(&mut state, data, &mut i);
|
||||||
tcb.proto_state = state;
|
t.smack_state = state;
|
||||||
|
}
|
||||||
|
id = t.proto_id;
|
||||||
} else {
|
} else {
|
||||||
/* proto over else (e.g., UDP) */
|
/* proto over else (e.g., UDP) */
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut state = BASE_STATE;
|
let mut state = BASE_STATE;
|
||||||
id = PROTO_SMACK.search_next(&mut state, &data.to_vec(), &mut i);
|
id = PROTO_SMACK.search_next(&mut state, data, &mut i);
|
||||||
/* because we are not over TCP, we can afford to assume end of pattern */
|
/* because we are not over TCP, we can afford to assume end of pattern */
|
||||||
if id == NO_MATCH {
|
if id == NO_MATCH {
|
||||||
id = PROTO_SMACK.search_next_end(&mut state);
|
id = PROTO_SMACK.search_next_end(&mut state);
|
||||||
}
|
}
|
||||||
|
/* still no match: let us try to parse packet with protocoles
|
||||||
|
* that are not matched with a regex */
|
||||||
|
if id == NO_MATCH {
|
||||||
|
/* try to parse data as a DNS packet */
|
||||||
|
if let Ok(dns) = DNSPacket::try_from(data.to_vec()) {
|
||||||
|
if let Some(r) = dns.repl(&masscanned, &client_info, None) {
|
||||||
|
return Some(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* proto over else (e.g., UDP) */
|
/* proto over else (e.g., UDP) */
|
||||||
if id == PROTO_HTTP {
|
match id {
|
||||||
return http::repl(data, masscanned, client_info);
|
PROTO_HTTP => http::repl(data, masscanned, client_info, tcb),
|
||||||
} else if id == PROTO_STUN {
|
PROTO_STUN => stun::repl(data, masscanned, &mut client_info, tcb),
|
||||||
return stun::repl(data, masscanned, &mut client_info);
|
PROTO_SSH => ssh::repl(data, masscanned, &mut client_info, tcb),
|
||||||
} else if id == PROTO_SSH {
|
PROTO_GHOST => ghost::repl(data, masscanned, &mut client_info, tcb),
|
||||||
return ssh::repl(data, masscanned, &mut client_info);
|
PROTO_RPC_TCP => rpc::repl_tcp(data, masscanned, &mut client_info, tcb),
|
||||||
} else {
|
PROTO_RPC_UDP => rpc::repl_udp(data, masscanned, &mut client_info, tcb),
|
||||||
debug!("id: {}", id);
|
PROTO_SMB1 => smb::repl_smb1(data, masscanned, &mut client_info, tcb),
|
||||||
|
PROTO_SMB2 => smb::repl_smb2(data, masscanned, &mut client_info, tcb),
|
||||||
|
_ => {
|
||||||
|
if let Some(t) = &mut tcb {
|
||||||
|
t.proto_id = PROTO_NONE;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -139,6 +199,8 @@ mod tests {
|
||||||
|
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_proto_dispatch_stun() {
|
fn test_proto_dispatch_stun() {
|
||||||
let mut client_info = ClientInfo::new();
|
let mut client_info = ClientInfo::new();
|
||||||
|
|
@ -153,7 +215,9 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
/***** TEST STUN - MAGIC *****/
|
/***** TEST STUN - MAGIC *****/
|
||||||
/* test payload is:
|
/* test payload is:
|
||||||
|
|
@ -164,7 +228,7 @@ mod tests {
|
||||||
*/
|
*/
|
||||||
let payload =
|
let payload =
|
||||||
b"\x00\x01\x00\x00\x21\x12\xa4\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
b"\x00\x01\x00\x00\x21\x12\xa4\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got nothing");
|
panic!("expected an answer, got nothing");
|
||||||
|
|
@ -178,7 +242,7 @@ mod tests {
|
||||||
*/
|
*/
|
||||||
let payload =
|
let payload =
|
||||||
b"\x00\x01\x00\x00\xaa\xbb\xcc\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
b"\x00\x01\x00\x00\xaa\xbb\xcc\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got nothing");
|
panic!("expected an answer, got nothing");
|
||||||
|
|
@ -191,7 +255,7 @@ mod tests {
|
||||||
*/
|
*/
|
||||||
let payload =
|
let payload =
|
||||||
b"\x00\x01\x00\x08\x01\xdb\xd4]4\x9f\xe2RQ\x19\x05,\x93\x14f4\x00\x03\x00\x04\x00\x00\x00\x00";
|
b"\x00\x01\x00\x08\x01\xdb\xd4]4\x9f\xe2RQ\x19\x05,\x93\x14f4\x00\x03\x00\x04\x00\x00\x00\x00";
|
||||||
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let _stun_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got nothing");
|
panic!("expected an answer, got nothing");
|
||||||
|
|
@ -212,28 +276,123 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
/***** TEST SSH *****/
|
/***** TEST SSH *****/
|
||||||
let payloads = [
|
let payloads = [
|
||||||
"SSH-2.0-PUTTY",
|
"SSH-2.0-PUTTY\r\n",
|
||||||
"SSH-2.0-Go",
|
"SSH-2.0-Go\r\n",
|
||||||
"SSH-2.0-libssh2_1.4.3",
|
"SSH-2.0-libssh2_1.4.3\r\n",
|
||||||
"SSH-2.0-PuTTY",
|
"SSH-2.0-PuTTY\r\n",
|
||||||
"SSH-2.0-AsyncSSH_2.1.0",
|
"SSH-2.0-AsyncSSH_2.1.0\r\n",
|
||||||
"SSH-2.0-libssh2_1.9.0",
|
"SSH-2.0-libssh2_1.9.0\r\n",
|
||||||
"SSH-2.0-libssh2_1.7.0",
|
"SSH-2.0-libssh2_1.7.0\r\n",
|
||||||
"SSH-2.0-8.35 FlowSsh: FlowSshNet_SftpStress54.38.116.473",
|
"SSH-2.0-8.35 FlowSsh: FlowSshNet_SftpStress54.38.116.473\r\n",
|
||||||
"SSH-2.0-libssh_0.9.5",
|
"SSH-2.0-libssh_0.9.5\r\n",
|
||||||
"SSH-2.0-OpenSSH_6.7p1 Raspbian-5+deb8u3",
|
"SSH-2.0-OpenSSH_6.7p1 Raspbian-5+deb8u3\r\n",
|
||||||
|
"SSH-1.99-Cisco-1.25\r\n",
|
||||||
];
|
];
|
||||||
for payload in payloads.iter() {
|
for payload in payloads.iter() {
|
||||||
let _ssh_resp = if let Some(r) = repl(payload.as_bytes(), &masscanned, &mut client_info)
|
let _ssh_resp =
|
||||||
{
|
if let Some(r) = repl(payload.as_bytes(), &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got nothing");
|
panic!("expected an answer, got nothing");
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proto_dispatch_ghost() {
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
|
client_info.port.src = Some(65000);
|
||||||
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
/***** TEST GHOST *****/
|
||||||
|
let payloads = [
|
||||||
|
b"Gh0st\xad\x00\x00\x00\xe0\x00\x00\x00x\x9cKS``\x98\xc3\xc0\xc0\xc0\x06\xc4\x8c@\xbcQ\x96\x81\x81\tH\x07\xa7\x16\x95e&\xa7*\x04$&g+\x182\x94\xf6\xb000\xac\xa8rc\x00\x01\x11\xa0\x82\x1f\\`&\x83\xc7K7\x86\x19\xe5n\x0c9\x95n\x0c;\x84\x0f3\xac\xe8sch\xa8^\xcf4'J\x97\xa9\x82\xe30\xc3\x91h]&\x90\xf8\xce\x97S\xcbA4L?2=\xe1\xc4\x92\x86\x0b@\xf5`\x0cT\x1f\xae\xaf]\nr\x0b\x03#\xa3\xdc\x02~\x06\x86\x03+\x18m\xc2=\xfdtC,C\xfdL<<==\\\x9d\x19\x88\x00\xe5 \x02\x00T\xf5+\\"
|
||||||
|
];
|
||||||
|
for payload in payloads.iter() {
|
||||||
|
let _ghost_resp =
|
||||||
|
if let Some(r) = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
panic!("expected an answer, got nothing");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proto_dispatch_http() {
|
||||||
|
/* ensure that HTTP FSM does not answer until completion of request
|
||||||
|
* (at least headers) */
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
|
client_info.port.src = Some(65000);
|
||||||
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
/***** TEST COMPLETE REQUEST *****/
|
||||||
|
let payload = b"GET / HTTP/1.1\r\n\r\n";
|
||||||
|
if let None = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
|
||||||
|
panic!("expected an answer, got nothing");
|
||||||
|
}
|
||||||
|
/***** TEST INCOMPLETE REQUEST *****/
|
||||||
|
let payload = b"GET / HTTP/1.1\r\n";
|
||||||
|
if let Some(_) = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
|
||||||
|
panic!("expected no answer, got one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dispatch_dns() {
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: None,
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
client_info.ip.dst = Some(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)));
|
||||||
|
let payloads = [
|
||||||
|
b"\x04\xd2\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01",
|
||||||
|
];
|
||||||
|
for payload in payloads.iter() {
|
||||||
|
let dns_resp =
|
||||||
|
if let Some(r) = repl(&payload.to_vec(), &masscanned, &mut client_info, None) {
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
panic!("expected an answer, got nothing");
|
||||||
|
};
|
||||||
|
if let Err(e) = DNSPacket::try_from(dns_resp) {
|
||||||
|
panic!("error trying to parse the DNS answer: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
561
src/proto/rpc.rs
Normal file
561
src/proto/rpc.rs
Normal file
|
|
@ -0,0 +1,561 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
use crate::proto::{ProtocolState as GenericProtocolState, TCPControlBlock};
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
// last fragment (1 bit) + fragment len (31 bits) / length XID (random) / message type: call (0) / RPC version (0-255) / Program: Portmap (99840 - 100095) / Program version (*, random versions used, see below) / / Procedure: ??? (0-255)
|
||||||
|
pub const RPC_CALL_TCP: &[u8; 28] =
|
||||||
|
b"********\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
|
||||||
|
// UDP: last fragment and fragment len are missing
|
||||||
|
pub const RPC_CALL_UDP: &[u8; 24] =
|
||||||
|
b"****\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum RpcState {
|
||||||
|
Frag,
|
||||||
|
Xid,
|
||||||
|
MessageType,
|
||||||
|
RpcVersion,
|
||||||
|
Program,
|
||||||
|
ProgramVersion,
|
||||||
|
Procedure,
|
||||||
|
CredsFlavor,
|
||||||
|
CredsLen,
|
||||||
|
Creds,
|
||||||
|
VerifFlavor,
|
||||||
|
VerifLen,
|
||||||
|
Verif,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProtocolState {
|
||||||
|
state: RpcState,
|
||||||
|
last_frag: bool,
|
||||||
|
frag_len: u32,
|
||||||
|
xid: u32,
|
||||||
|
message_type: u32,
|
||||||
|
rpc_version: u32,
|
||||||
|
program: u32,
|
||||||
|
prog_version: u32,
|
||||||
|
procedure: u32,
|
||||||
|
creds_flavor: u32,
|
||||||
|
creds_data: Vec<u8>,
|
||||||
|
verif_flavor: u32,
|
||||||
|
verif_data: Vec<u8>,
|
||||||
|
payload: Vec<u8>,
|
||||||
|
cur_len: u32,
|
||||||
|
data_len: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Rpcb {
|
||||||
|
program: u32,
|
||||||
|
version: u32,
|
||||||
|
netid: String,
|
||||||
|
addr: String,
|
||||||
|
port: u16,
|
||||||
|
owner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtocolState {
|
||||||
|
fn new() -> Self {
|
||||||
|
ProtocolState {
|
||||||
|
state: RpcState::Frag,
|
||||||
|
last_frag: false,
|
||||||
|
frag_len: 0,
|
||||||
|
xid: 0,
|
||||||
|
message_type: 0,
|
||||||
|
rpc_version: 0,
|
||||||
|
program: 0,
|
||||||
|
prog_version: 0,
|
||||||
|
procedure: 0,
|
||||||
|
creds_flavor: 0,
|
||||||
|
creds_data: Vec::<u8>::new(),
|
||||||
|
verif_flavor: 0,
|
||||||
|
verif_data: Vec::<u8>::new(),
|
||||||
|
payload: Vec::<u8>::new(),
|
||||||
|
cur_len: 0,
|
||||||
|
data_len: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u32(pstate: &mut ProtocolState, byte: u8, value: u32, next_state: RpcState) -> u32 {
|
||||||
|
pstate.cur_len += 1;
|
||||||
|
if pstate.cur_len == 4 {
|
||||||
|
pstate.state = next_state;
|
||||||
|
pstate.cur_len = 0;
|
||||||
|
}
|
||||||
|
value * 256 + byte as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_string(pstate: &mut ProtocolState, next_state: RpcState) {
|
||||||
|
pstate.data_len -= 1;
|
||||||
|
if pstate.data_len == 0 {
|
||||||
|
pstate.state = next_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rpc_parse(pstate: &mut ProtocolState, data: &[u8]) {
|
||||||
|
for byte in data {
|
||||||
|
match pstate.state {
|
||||||
|
RpcState::Frag => {
|
||||||
|
if pstate.cur_len == 0 {
|
||||||
|
match byte & 128 {
|
||||||
|
0 => pstate.last_frag = false,
|
||||||
|
_ => pstate.last_frag = true,
|
||||||
|
};
|
||||||
|
pstate.frag_len = (*byte & 127) as u32;
|
||||||
|
} else {
|
||||||
|
pstate.frag_len = *byte as u32;
|
||||||
|
}
|
||||||
|
pstate.cur_len += 1;
|
||||||
|
if pstate.cur_len == 4 {
|
||||||
|
pstate.state = RpcState::Xid;
|
||||||
|
pstate.cur_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RpcState::Xid => {
|
||||||
|
pstate.xid = read_u32(pstate, *byte, pstate.xid, RpcState::MessageType)
|
||||||
|
}
|
||||||
|
RpcState::MessageType => {
|
||||||
|
pstate.message_type =
|
||||||
|
read_u32(pstate, *byte, pstate.message_type, RpcState::RpcVersion)
|
||||||
|
}
|
||||||
|
RpcState::RpcVersion => {
|
||||||
|
pstate.rpc_version = read_u32(pstate, *byte, pstate.rpc_version, RpcState::Program)
|
||||||
|
}
|
||||||
|
RpcState::Program => {
|
||||||
|
pstate.program = read_u32(pstate, *byte, pstate.program, RpcState::ProgramVersion)
|
||||||
|
}
|
||||||
|
RpcState::ProgramVersion => {
|
||||||
|
pstate.prog_version =
|
||||||
|
read_u32(pstate, *byte, pstate.prog_version, RpcState::Procedure)
|
||||||
|
}
|
||||||
|
RpcState::Procedure => {
|
||||||
|
pstate.procedure = read_u32(pstate, *byte, pstate.procedure, RpcState::CredsFlavor)
|
||||||
|
}
|
||||||
|
RpcState::CredsFlavor => {
|
||||||
|
pstate.creds_flavor =
|
||||||
|
read_u32(pstate, *byte, pstate.creds_flavor, RpcState::CredsLen)
|
||||||
|
}
|
||||||
|
RpcState::CredsLen => {
|
||||||
|
pstate.data_len = read_u32(pstate, *byte, pstate.data_len, RpcState::Creds);
|
||||||
|
if matches!(pstate.state, RpcState::Creds) && pstate.data_len == 0 {
|
||||||
|
pstate.state = RpcState::VerifFlavor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RpcState::Creds => {
|
||||||
|
pstate.creds_data.push(*byte);
|
||||||
|
read_string(pstate, RpcState::VerifFlavor)
|
||||||
|
}
|
||||||
|
RpcState::VerifFlavor => {
|
||||||
|
pstate.verif_flavor =
|
||||||
|
read_u32(pstate, *byte, pstate.verif_flavor, RpcState::VerifLen)
|
||||||
|
}
|
||||||
|
RpcState::VerifLen => {
|
||||||
|
pstate.data_len = read_u32(pstate, *byte, pstate.data_len, RpcState::Verif);
|
||||||
|
if matches!(pstate.state, RpcState::Verif) && pstate.cur_len == 0 {
|
||||||
|
pstate.state = RpcState::End
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RpcState::Verif => {
|
||||||
|
pstate.verif_data.push(*byte);
|
||||||
|
read_string(pstate, RpcState::End)
|
||||||
|
}
|
||||||
|
RpcState::End => {
|
||||||
|
pstate.payload.push(*byte);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_nth_byte(value: u32, nth: u8) -> u8 {
|
||||||
|
let shift = 8 * (3 - nth);
|
||||||
|
((value & (0xff << shift)) >> shift).try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_u32(buffer: &mut Vec<u8>, data: u32) {
|
||||||
|
for i in 0..4 {
|
||||||
|
buffer.push(get_nth_byte(data, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_string_pad(buffer: &mut Vec<u8>, data: String) {
|
||||||
|
let len: u32 = data.len().try_into().unwrap();
|
||||||
|
push_u32(buffer, len);
|
||||||
|
buffer.append(&mut data.as_bytes().to_vec());
|
||||||
|
if len % 4 != 0 {
|
||||||
|
for _ in 0..(4 - (len % 4)) {
|
||||||
|
buffer.append(&mut b"\x00".to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_repl_portmap(pstate: &mut ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
|
||||||
|
let mut resp = Vec::<u8>::new();
|
||||||
|
match pstate.procedure {
|
||||||
|
// 0 => {}
|
||||||
|
3 => {
|
||||||
|
// getaddr / getport
|
||||||
|
// accepted state: 0 (RPC executed successfully)
|
||||||
|
resp.extend([0, 0, 0, 0]);
|
||||||
|
let localport = client_info.port.dst.unwrap();
|
||||||
|
match pstate.prog_version {
|
||||||
|
2 => {
|
||||||
|
push_u32(&mut resp, localport as u32);
|
||||||
|
}
|
||||||
|
3 | 4 => {
|
||||||
|
let addr = format!(
|
||||||
|
"{}.{}.{}",
|
||||||
|
client_info.ip.dst.unwrap(),
|
||||||
|
localport >> 8,
|
||||||
|
localport % 256
|
||||||
|
);
|
||||||
|
push_string_pad(&mut resp, addr);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong RPC version"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
// dump
|
||||||
|
// accepted state: 0 (RPC executed successfully)
|
||||||
|
resp.extend([0, 0, 0, 0]);
|
||||||
|
let localaddr = client_info.ip.dst.unwrap();
|
||||||
|
let localport = client_info.port.dst.unwrap();
|
||||||
|
let netid = match localaddr {
|
||||||
|
IpAddr::V4(_) => "tcp",
|
||||||
|
IpAddr::V6(_) => "tcp6",
|
||||||
|
};
|
||||||
|
for rpcb in [
|
||||||
|
Rpcb {
|
||||||
|
program: 100000,
|
||||||
|
version: 2,
|
||||||
|
netid: netid.to_string(),
|
||||||
|
addr: format!("{}", localaddr),
|
||||||
|
port: localport,
|
||||||
|
owner: "superuser".to_string(),
|
||||||
|
},
|
||||||
|
Rpcb {
|
||||||
|
program: 100000,
|
||||||
|
version: 3,
|
||||||
|
netid: netid.to_string(),
|
||||||
|
addr: format!("{}", localaddr),
|
||||||
|
port: localport,
|
||||||
|
owner: "superuser".to_string(),
|
||||||
|
},
|
||||||
|
Rpcb {
|
||||||
|
program: 100000,
|
||||||
|
version: 4,
|
||||||
|
netid: netid.to_string(),
|
||||||
|
addr: format!("{}", localaddr),
|
||||||
|
port: localport,
|
||||||
|
owner: "superuser".to_string(),
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
resp.append(&mut b"\x00\x00\x00\x01".to_vec()); // value follows: yes
|
||||||
|
push_u32(&mut resp, rpcb.program);
|
||||||
|
push_u32(&mut resp, rpcb.version);
|
||||||
|
match pstate.prog_version {
|
||||||
|
2 => {
|
||||||
|
push_u32(
|
||||||
|
&mut resp,
|
||||||
|
match rpcb.netid.as_str() {
|
||||||
|
"tcp" => 6,
|
||||||
|
"tcp6" => 6,
|
||||||
|
"udp" => 17,
|
||||||
|
"udp6" => 17,
|
||||||
|
_ => 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
push_u32(&mut resp, localport as u32);
|
||||||
|
}
|
||||||
|
3 | 4 => {
|
||||||
|
push_string_pad(&mut resp, rpcb.netid);
|
||||||
|
push_string_pad(
|
||||||
|
&mut resp,
|
||||||
|
format!("{}.{}.{}", rpcb.addr, rpcb.port >> 8, rpcb.port & 0xff),
|
||||||
|
);
|
||||||
|
push_string_pad(&mut resp, rpcb.owner);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong RPC version"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.append(&mut b"\x00\x00\x00\x00".to_vec()); // value follows: no
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// accepted state: 5 (program can't support procedure)
|
||||||
|
resp.extend([0, 0, 0, 5]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn!(
|
||||||
|
"RPC: Portmap version {}, procedure {}",
|
||||||
|
pstate.prog_version, pstate.procedure
|
||||||
|
);
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_repl_unknownprog(pstate: &mut ProtocolState, _client_info: &ClientInfo) -> Vec<u8> {
|
||||||
|
warn!(
|
||||||
|
"Unknown program {}, procedure {}: accepted state 1",
|
||||||
|
pstate.program, pstate.procedure
|
||||||
|
);
|
||||||
|
// accepted state: 1 (remote hasn't exported program)
|
||||||
|
vec![0, 0, 0, 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_repl(pstate: &mut ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
|
||||||
|
// TODO: test RPC versions, drop non calls?
|
||||||
|
let mut resp = Vec::<u8>::new();
|
||||||
|
push_u32(&mut resp, pstate.xid);
|
||||||
|
// message_type: 1 (reply)
|
||||||
|
// reply_state: 0 (accepted)
|
||||||
|
// verifier: 0 (auth null)
|
||||||
|
// verifier length: 0
|
||||||
|
resp.extend([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
if pstate.prog_version < 2 || pstate.prog_version > 4 {
|
||||||
|
/*
|
||||||
|
* Scanners (e.g., Nmap script rpc-grind) often use random
|
||||||
|
* values for program version to find out if a program is
|
||||||
|
* supported, so for any program, we answer with "remote can't
|
||||||
|
* support version" accepted state.
|
||||||
|
*/
|
||||||
|
// accepted state: 2 (remote can't support version)
|
||||||
|
// prog_version min: 2
|
||||||
|
// prog_version max: 4
|
||||||
|
let prog_version = match pstate.prog_version {
|
||||||
|
104316 => "104316 (Nmap probe TCP RPCCheck)".to_string(),
|
||||||
|
x => x.to_string(),
|
||||||
|
};
|
||||||
|
warn!(
|
||||||
|
"RPC: unsupported version {} for program {}",
|
||||||
|
prog_version, pstate.program
|
||||||
|
);
|
||||||
|
resp.extend([0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 4]);
|
||||||
|
} else if pstate.procedure == 0 {
|
||||||
|
/*
|
||||||
|
* RPC clients (e.g., Linux kernel NFS client, rpcbind CLI
|
||||||
|
* tool) would often send a NULL procedure (0) call before any
|
||||||
|
* real operation .
|
||||||
|
*/
|
||||||
|
// accepted state: 0 (RPC executed successfully)
|
||||||
|
warn!("RPC: NULL procedure call for program {}", pstate.program);
|
||||||
|
resp.extend([0, 0, 0, 0]);
|
||||||
|
} else {
|
||||||
|
let mut specif_resp = match pstate.program {
|
||||||
|
100000 => build_repl_portmap(pstate, client_info),
|
||||||
|
_ => build_repl_unknownprog(pstate, client_info),
|
||||||
|
};
|
||||||
|
resp.append(&mut specif_resp);
|
||||||
|
}
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repl_tcp<'a>(
|
||||||
|
data: &'a [u8],
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
client_info: &ClientInfo,
|
||||||
|
tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let mut state = ProtocolState::new();
|
||||||
|
let mut pstate = {
|
||||||
|
if let Some(t) = tcb {
|
||||||
|
match t.proto_state {
|
||||||
|
None => t.proto_state = Some(GenericProtocolState::RPC(ProtocolState::new())),
|
||||||
|
Some(GenericProtocolState::RPC(_)) => {}
|
||||||
|
_ => {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(GenericProtocolState::RPC(p)) = &mut t.proto_state {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
&mut state
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rpc_parse(&mut pstate, data);
|
||||||
|
// warn!("RPC {:#?}", pstate);
|
||||||
|
let resp = match pstate.state {
|
||||||
|
RpcState::End => Some(build_repl(pstate, client_info)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
match resp {
|
||||||
|
Some(mut resp) => {
|
||||||
|
let length: u32 = resp.len().try_into().unwrap();
|
||||||
|
let mut final_resp = Vec::<u8>::new();
|
||||||
|
for i in 0..4 {
|
||||||
|
match i {
|
||||||
|
0 => final_resp.push(get_nth_byte(length, i) | 0x80),
|
||||||
|
_ => final_resp.push(get_nth_byte(length, i)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
final_resp.append(&mut resp);
|
||||||
|
Some(final_resp)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repl_udp<'a>(
|
||||||
|
data: &'a [u8],
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
pstate.state = RpcState::Xid;
|
||||||
|
pstate.last_frag = true;
|
||||||
|
pstate.frag_len = data.len().try_into().unwrap();
|
||||||
|
rpc_parse(&mut pstate, data);
|
||||||
|
// warn!("RPC {:#?}", pstate);
|
||||||
|
match pstate.state {
|
||||||
|
RpcState::End => Some(build_repl(&mut pstate, client_info)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::client::ClientInfoSrcDst;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
const CLIENT_INFO: ClientInfo = ClientInfo {
|
||||||
|
mac: ClientInfoSrcDst {
|
||||||
|
src: None,
|
||||||
|
dst: None,
|
||||||
|
},
|
||||||
|
ip: ClientInfoSrcDst {
|
||||||
|
src: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 0))),
|
||||||
|
dst: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 1))),
|
||||||
|
},
|
||||||
|
transport: None,
|
||||||
|
port: ClientInfoSrcDst {
|
||||||
|
src: Some(12345),
|
||||||
|
dst: Some(111),
|
||||||
|
},
|
||||||
|
cookie: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_probe_nmap() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
rpc_parse(&mut pstate, b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
|
||||||
|
assert!(matches!(pstate.state, RpcState::End));
|
||||||
|
assert!(pstate.xid == 0x72fe1d13);
|
||||||
|
assert!(pstate.rpc_version == 2);
|
||||||
|
assert!(pstate.program == 100000);
|
||||||
|
assert!(pstate.prog_version == 104316);
|
||||||
|
assert!(pstate.procedure == 0);
|
||||||
|
assert!(pstate.creds_flavor == 0);
|
||||||
|
assert!(pstate.creds_data.len() == 0);
|
||||||
|
assert!(pstate.verif_flavor == 0);
|
||||||
|
assert!(pstate.verif_data.len() == 0);
|
||||||
|
let resp = build_repl(&mut pstate, &CLIENT_INFO);
|
||||||
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_probe_nmap_udp() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
pstate.state = RpcState::Xid;
|
||||||
|
rpc_parse(&mut pstate, b"\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
|
||||||
|
assert!(matches!(pstate.state, RpcState::End));
|
||||||
|
assert!(pstate.xid == 0x72fe1d13);
|
||||||
|
assert!(pstate.rpc_version == 2);
|
||||||
|
assert!(pstate.program == 100000);
|
||||||
|
assert!(pstate.prog_version == 104316);
|
||||||
|
assert!(pstate.procedure == 0);
|
||||||
|
assert!(pstate.creds_flavor == 0);
|
||||||
|
assert!(pstate.creds_data.len() == 0);
|
||||||
|
assert!(pstate.verif_flavor == 0);
|
||||||
|
assert!(pstate.verif_data.len() == 0);
|
||||||
|
let resp = build_repl(&mut pstate, &CLIENT_INFO);
|
||||||
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_probe_nmap_split1() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
for byte in b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" {
|
||||||
|
rpc_parse(&mut pstate, &[*byte]);
|
||||||
|
}
|
||||||
|
assert!(matches!(pstate.state, RpcState::End));
|
||||||
|
assert!(pstate.xid == 0x72fe1d13);
|
||||||
|
assert!(pstate.rpc_version == 2);
|
||||||
|
assert!(pstate.program == 100000);
|
||||||
|
assert!(pstate.prog_version == 104316);
|
||||||
|
assert!(pstate.procedure == 0);
|
||||||
|
assert!(pstate.creds_flavor == 0);
|
||||||
|
assert!(pstate.creds_data.len() == 0);
|
||||||
|
assert!(pstate.verif_flavor == 0);
|
||||||
|
assert!(pstate.verif_data.len() == 0);
|
||||||
|
let resp = build_repl(&mut pstate, &CLIENT_INFO);
|
||||||
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_probe_nmap_split2() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
for data in [
|
||||||
|
b"\x80\x00\x00\x28\x72\xfe\x1d",
|
||||||
|
b"\x13\x00\x00\x00\x00\x00\x00",
|
||||||
|
b"\x00\x02\x00\x01\x86\xa0\x00",
|
||||||
|
b"\x01\x97\x7c\x00\x00\x00\x00",
|
||||||
|
b"\x00\x00\x00\x00\x00\x00\x00",
|
||||||
|
b"\x00\x00\x00\x00\x00\x00\x00",
|
||||||
|
] {
|
||||||
|
rpc_parse(&mut pstate, data);
|
||||||
|
}
|
||||||
|
rpc_parse(&mut pstate, b"\x00\x00");
|
||||||
|
assert!(matches!(pstate.state, RpcState::End));
|
||||||
|
assert!(pstate.xid == 0x72fe1d13);
|
||||||
|
assert!(pstate.rpc_version == 2);
|
||||||
|
assert!(pstate.program == 100000);
|
||||||
|
assert!(pstate.prog_version == 104316);
|
||||||
|
assert!(pstate.procedure == 0);
|
||||||
|
assert!(pstate.creds_flavor == 0);
|
||||||
|
assert!(pstate.creds_data.len() == 0);
|
||||||
|
assert!(pstate.verif_flavor == 0);
|
||||||
|
assert!(pstate.verif_data.len() == 0);
|
||||||
|
let resp = build_repl(&mut pstate, &CLIENT_INFO);
|
||||||
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_probe_portmap_v4_dump() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
rpc_parse(&mut pstate, b"\x80\x00\x00\x28\x01\x1b\x60\xa6\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
|
||||||
|
assert!(matches!(pstate.state, RpcState::End));
|
||||||
|
assert!(pstate.rpc_version == 2);
|
||||||
|
assert!(pstate.program == 100000);
|
||||||
|
assert!(pstate.prog_version == 4);
|
||||||
|
assert!(pstate.procedure == 4); // dump
|
||||||
|
assert!(pstate.creds_flavor == 0);
|
||||||
|
assert!(pstate.creds_data.len() == 0);
|
||||||
|
assert!(pstate.verif_flavor == 0);
|
||||||
|
assert!(pstate.verif_data.len() == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
1418
src/proto/smb.rs
Normal file
1418
src/proto/smb.rs
Normal file
File diff suppressed because it is too large
Load diff
626
src/proto/ssh.rs
626
src/proto/ssh.rs
|
|
@ -16,21 +16,635 @@
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use crate::client::ClientInfo;
|
use crate::client::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
|
use crate::utils::byte2str;
|
||||||
use crate::Masscanned;
|
use crate::Masscanned;
|
||||||
|
|
||||||
pub const SSH_PATTERN_CLIENT_PROTOCOL: &[u8; 7] = b"SSH-2.0";
|
pub const SSH_PATTERN_CLIENT_PROTOCOL_2: &[u8; 7] = b"SSH-2.0";
|
||||||
|
pub const SSH_PATTERN_CLIENT_PROTOCOL_1: &[u8; 8] = b"SSH-1.99";
|
||||||
|
|
||||||
|
const SSH_STATE_START: usize = 0;
|
||||||
|
const SSH_STATE_S1: usize = 1;
|
||||||
|
const SSH_STATE_S2: usize = 2;
|
||||||
|
const SSH_STATE_H: usize = 3;
|
||||||
|
const SSH_STATE_DASH: usize = 4;
|
||||||
|
const SSH_STATE_VERSION: usize = 5;
|
||||||
|
const SSH_STATE_SOFTWARE: usize = 6;
|
||||||
|
const SSH_STATE_COMMENT: usize = 7;
|
||||||
|
const SSH_STATE_EOB: usize = 8;
|
||||||
|
const SSH_STATE_LF: usize = 9;
|
||||||
|
|
||||||
|
const SSH_STATE_FAIL: usize = 0xFFFF;
|
||||||
|
|
||||||
|
struct ProtocolState {
|
||||||
|
state: usize,
|
||||||
|
prev_state: usize,
|
||||||
|
ssh_version: Vec<u8>,
|
||||||
|
ssh_software: Vec<u8>,
|
||||||
|
ssh_comment: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtocolState {
|
||||||
|
fn new() -> Self {
|
||||||
|
ProtocolState {
|
||||||
|
state: SSH_STATE_START,
|
||||||
|
prev_state: SSH_STATE_START,
|
||||||
|
ssh_version: Vec::<u8>::new(),
|
||||||
|
ssh_software: Vec::<u8>::new(),
|
||||||
|
ssh_comment: Vec::<u8>::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ssh_parse(pstate: &mut ProtocolState, data: &[u8]) {
|
||||||
|
/* RFC 4253:
|
||||||
|
*
|
||||||
|
* 4.2. Protocol Version Exchange
|
||||||
|
*
|
||||||
|
* When the connection has been established, both sides MUST send an
|
||||||
|
* identification string. This identification string MUST be
|
||||||
|
*
|
||||||
|
* SSH-protoversion-softwareversion SP comments CR LF
|
||||||
|
*
|
||||||
|
* Since the protocol being defined in this set of documents is version
|
||||||
|
* 2.0, the 'protoversion' MUST be "2.0". The 'comments' string is
|
||||||
|
* OPTIONAL. If the 'comments' string is included, a 'space' character
|
||||||
|
* (denoted above as SP, ASCII 32) MUST separate the 'softwareversion'
|
||||||
|
* and 'comments' strings. The identification MUST be terminated by a
|
||||||
|
* single Carriage Return (CR) and a single Line Feed (LF) character
|
||||||
|
* (ASCII 13 and 10, respectively). Implementers who wish to maintain
|
||||||
|
* compatibility with older, undocumented versions of this protocol may
|
||||||
|
* want to process the identification string without expecting the
|
||||||
|
* presence of the carriage return character for reasons described in
|
||||||
|
* Section 5 of this document. The null character MUST NOT be sent.
|
||||||
|
* The maximum length of the string is 255 characters, including the
|
||||||
|
* Carriage Return and Line Feed.
|
||||||
|
*/
|
||||||
|
let mut i = 0;
|
||||||
|
while i < data.len() {
|
||||||
|
match pstate.state {
|
||||||
|
SSH_STATE_START => {
|
||||||
|
pstate.state = SSH_STATE_S1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* first bytes should be "SSH-" */
|
||||||
|
SSH_STATE_S1 | SSH_STATE_S2 | SSH_STATE_H | SSH_STATE_DASH => {
|
||||||
|
if data[i] != b"SSH-"[pstate.state - SSH_STATE_S1] {
|
||||||
|
pstate.state = SSH_STATE_FAIL;
|
||||||
|
} else {
|
||||||
|
pstate.state += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* expect LF after a CR was read */
|
||||||
|
SSH_STATE_LF => {
|
||||||
|
if data[i] == b'\n' {
|
||||||
|
pstate.state = SSH_STATE_EOB;
|
||||||
|
} else {
|
||||||
|
if pstate.prev_state == SSH_STATE_SOFTWARE {
|
||||||
|
/* when reading software, \r can be followed by something else than \n */
|
||||||
|
pstate.state = pstate.prev_state;
|
||||||
|
/* cancel the read of this char */
|
||||||
|
i -= 1;
|
||||||
|
/* add the previously read \r to the software string */
|
||||||
|
pstate.ssh_software.push(b'\r');
|
||||||
|
} else if pstate.prev_state == SSH_STATE_COMMENT {
|
||||||
|
/* when reading comment, \r can be followed by something else than \n */
|
||||||
|
pstate.state = pstate.prev_state;
|
||||||
|
/* cancel the read of this char */
|
||||||
|
i -= 1;
|
||||||
|
/* add the previously read \r to the software string */
|
||||||
|
pstate.ssh_comment.push(b'\r');
|
||||||
|
} else {
|
||||||
|
/* in some other cases, it fails */
|
||||||
|
pstate.state = SSH_STATE_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSH_STATE_VERSION => {
|
||||||
|
if data[i] == b'-' {
|
||||||
|
pstate.state = SSH_STATE_SOFTWARE;
|
||||||
|
} else if !data[i].is_ascii_digit() && data[i] != b'.' {
|
||||||
|
pstate.state = SSH_STATE_FAIL;
|
||||||
|
} else {
|
||||||
|
pstate.ssh_version.push(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSH_STATE_SOFTWARE => {
|
||||||
|
if data[i] == b'\r' {
|
||||||
|
/* look for LF in the next char */
|
||||||
|
pstate.prev_state = pstate.state;
|
||||||
|
pstate.state = SSH_STATE_LF;
|
||||||
|
} else if data[i] == b' ' {
|
||||||
|
pstate.state = SSH_STATE_COMMENT;
|
||||||
|
} else {
|
||||||
|
pstate.ssh_software.push(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSH_STATE_COMMENT => {
|
||||||
|
if data[i] == b'\r' {
|
||||||
|
/* look for LF in the next char */
|
||||||
|
pstate.prev_state = pstate.state;
|
||||||
|
pstate.state = SSH_STATE_LF;
|
||||||
|
} else {
|
||||||
|
pstate.ssh_comment.push(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SSH_STATE_FAIL => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SSH_STATE_EOB => { /* so far, do not parse after banner */ }
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn repl<'a>(
|
pub fn repl<'a>(
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
_masscanned: &Masscanned,
|
_masscanned: &Masscanned,
|
||||||
mut _client_info: &mut ClientInfo,
|
mut _client_info: &ClientInfo,
|
||||||
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
) -> Option<Vec<u8>> {
|
) -> Option<Vec<u8>> {
|
||||||
debug!("receiving SSH data");
|
debug!("receiving SSH data");
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
ssh_parse(&mut pstate, data);
|
||||||
|
if pstate.state != SSH_STATE_EOB {
|
||||||
|
debug!("data in not correctly formatted - not responding");
|
||||||
|
debug!("pstate: {}", pstate.state);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let repl_data = b"SSH-2.0-1\r\n".to_vec();
|
let repl_data = b"SSH-2.0-1\r\n".to_vec();
|
||||||
debug!("sending SSH answer");
|
debug!("sending SSH answer");
|
||||||
warn!("SSH server banner to {}", str::from_utf8(&data).unwrap().trim_end());
|
warn!("SSH server banner to {}", byte2str(&pstate.ssh_software));
|
||||||
return Some(repl_data);
|
Some(repl_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/* Reconstruct client's banner from the parsed information */
|
||||||
|
fn ssh_banner(pstate: &ProtocolState) -> Vec<u8> {
|
||||||
|
let mut banner = b"SSH-".to_vec();
|
||||||
|
for b in &pstate.ssh_version {
|
||||||
|
banner.push(*b);
|
||||||
|
}
|
||||||
|
banner.push(b'-');
|
||||||
|
for b in &pstate.ssh_software {
|
||||||
|
banner.push(*b);
|
||||||
|
}
|
||||||
|
if pstate.ssh_comment.len() > 0 {
|
||||||
|
banner.push(b' ');
|
||||||
|
for b in &pstate.ssh_comment {
|
||||||
|
banner.push(*b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
banner.push(b'\r');
|
||||||
|
banner.push(b'\n');
|
||||||
|
banner
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_2_banner_parse() {
|
||||||
|
/* all at once */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
/* byte by byte */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
for i in 0..test_banner.len() {
|
||||||
|
if i == 0 {
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
} else if i > 0 && i < 4 {
|
||||||
|
assert!(pstate.state == SSH_STATE_S1 + i);
|
||||||
|
} else if i >= 4 && i < 8 {
|
||||||
|
assert!(pstate.state == SSH_STATE_VERSION);
|
||||||
|
} else if i >= 8 && i < 17 {
|
||||||
|
assert!(pstate.state == SSH_STATE_SOFTWARE);
|
||||||
|
} else if i >= 17 && i < test_banner.len() - 1 {
|
||||||
|
assert!(pstate.state == SSH_STATE_COMMENT);
|
||||||
|
} else {
|
||||||
|
assert!(pstate.state == SSH_STATE_LF);
|
||||||
|
}
|
||||||
|
ssh_parse(&mut pstate, &test_banner[i..i + 1]);
|
||||||
|
}
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_1_banner_parse() {
|
||||||
|
/* all at once */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* byte by byte */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
for i in 0..test_banner.len() {
|
||||||
|
if i == 0 {
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
} else if i > 0 && i < 4 {
|
||||||
|
assert!(pstate.state == SSH_STATE_S1 + i);
|
||||||
|
} else if i >= 4 && i < 9 {
|
||||||
|
assert!(pstate.state == SSH_STATE_VERSION);
|
||||||
|
} else if i >= 9 && i < 18 {
|
||||||
|
assert!(pstate.state == SSH_STATE_SOFTWARE);
|
||||||
|
} else if i >= 18 && i < test_banner.len() - 1 {
|
||||||
|
assert!(pstate.state == SSH_STATE_COMMENT);
|
||||||
|
} else {
|
||||||
|
assert!(pstate.state == SSH_STATE_LF);
|
||||||
|
}
|
||||||
|
ssh_parse(&mut pstate, &test_banner[i..i + 1]);
|
||||||
|
}
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_2_banner_space() {
|
||||||
|
/* space in SSH */
|
||||||
|
let test_banner = b"S SH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* space in VERSION */
|
||||||
|
let test_banner = b"SSH-2. 0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* space in software */
|
||||||
|
let test_banner = b"SSH-2.0-SOFT WARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT");
|
||||||
|
assert!(pstate.ssh_comment == b"WARE COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* space in comment */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COM MENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM MENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* double space */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b" COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_1_banner_space() {
|
||||||
|
/* space in SSH */
|
||||||
|
let test_banner = b"S SH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* space in VERSION */
|
||||||
|
let test_banner = b"SSH-1. 99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* space in software */
|
||||||
|
let test_banner = b"SSH-1.99-SOFT WARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT");
|
||||||
|
assert!(pstate.ssh_comment == b"WARE COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* space in comment */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COM MENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM MENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* double space */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b" COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_2_banner_cr() {
|
||||||
|
/* CR in SSH */
|
||||||
|
let test_banner = b"S\rSH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CR in VERSION */
|
||||||
|
let test_banner = b"SSH-2.\r0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CR in SOFTWARE */
|
||||||
|
let test_banner = b"SSH-2.0-SOFT\rWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT\rWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* CR in COMMENT */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COM\rMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM\rMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* CR at the end */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT\r");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_1_banner_cr() {
|
||||||
|
/* CR in SSH */
|
||||||
|
let test_banner = b"S\rSH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CR in VERSION */
|
||||||
|
let test_banner = b"SSH-1.\r99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CR in SOFTWARE */
|
||||||
|
let test_banner = b"SSH-1.99-SOFT\rWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT\rWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* CR in COMMENT */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COM\rMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM\rMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* CR at the end */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT\r");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_2_banner_lf() {
|
||||||
|
/* LF in SSH */
|
||||||
|
let test_banner = b"S\nSH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* LF in VERSION */
|
||||||
|
let test_banner = b"SSH-2.\n0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* LF in SOFTWARE */
|
||||||
|
let test_banner = b"SSH-2.0-SOFT\nWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT\nWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* LF in COMMENT */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COM\nMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM\nMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* LF at the end */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\n\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT\n");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_1_banner_lf() {
|
||||||
|
/* LF in SSH */
|
||||||
|
let test_banner = b"S\nSH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* LF in VERSION */
|
||||||
|
let test_banner = b"SSH-1.\n99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* LF in SOFTWARE */
|
||||||
|
let test_banner = b"SSH-1.99-SOFT\nWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT\nWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* LF in COMMENT */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COM\nMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM\nMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
/* LF at the end */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\n\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT\n");
|
||||||
|
assert!(ssh_banner(&pstate) == test_banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_2_banner_crlf() {
|
||||||
|
/* CRLF in SSH */
|
||||||
|
let test_banner = b"S\r\nSH-2.0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CRLF in VERSION */
|
||||||
|
let test_banner = b"SSH-2.\r\n0-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CRLF in SOFTWARE */
|
||||||
|
let test_banner = b"SSH-2.0-SOFT\r\nWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT");
|
||||||
|
assert!(pstate.ssh_comment == b"");
|
||||||
|
assert!(ssh_banner(&pstate) == b"SSH-2.0-SOFT\r\n");
|
||||||
|
/* CRLF in COMMENT */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COM\r\nMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM");
|
||||||
|
assert!(ssh_banner(&pstate) == b"SSH-2.0-SOFTWARE COM\r\n");
|
||||||
|
/* CRLF at the end */
|
||||||
|
let test_banner = b"SSH-2.0-SOFTWARE COMMENT\r\n\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"2.0");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == b"SSH-2.0-SOFTWARE COMMENT\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ssh_1_banner_crlf() {
|
||||||
|
/* CRLF in SSH */
|
||||||
|
let test_banner = b"S\r\nSH-1.99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CRLF in VERSION */
|
||||||
|
let test_banner = b"SSH-1.\r\n99-SOFTWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_FAIL);
|
||||||
|
/* CRLF in SOFTWARE */
|
||||||
|
let test_banner = b"SSH-1.99-SOFT\r\nWARE COMMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFT");
|
||||||
|
assert!(pstate.ssh_comment == b"");
|
||||||
|
assert!(ssh_banner(&pstate) == b"SSH-1.99-SOFT\r\n");
|
||||||
|
/* CRLF in COMMENT */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COM\r\nMENT\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COM");
|
||||||
|
assert!(ssh_banner(&pstate) == b"SSH-1.99-SOFTWARE COM\r\n");
|
||||||
|
/* CRLF at the end */
|
||||||
|
let test_banner = b"SSH-1.99-SOFTWARE COMMENT\r\n\r\n";
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
assert!(pstate.state == SSH_STATE_START);
|
||||||
|
ssh_parse(&mut pstate, test_banner);
|
||||||
|
assert!(pstate.state == SSH_STATE_EOB);
|
||||||
|
assert!(pstate.ssh_version == b"1.99");
|
||||||
|
assert!(pstate.ssh_software == b"SOFTWARE");
|
||||||
|
assert!(pstate.ssh_comment == b"COMMENT");
|
||||||
|
assert!(ssh_banner(&pstate) == b"SSH-1.99-SOFTWARE COMMENT\r\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ use byteorder::{BigEndian, ByteOrder};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::client::ClientInfo;
|
use crate::client::ClientInfo;
|
||||||
|
use crate::proto::TCPControlBlock;
|
||||||
use crate::Masscanned;
|
use crate::Masscanned;
|
||||||
|
|
||||||
/* RFC 5389: The magic cookie field MUST contain the fixed value 0x2112A442 in
|
/* RFC 5389: The magic cookie field MUST contain the fixed value 0x2112A442 in
|
||||||
|
|
@ -257,107 +258,6 @@ impl Into<Vec<u8>> for &StunAttribute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
struct StunPacket {
|
|
||||||
class: u8,
|
|
||||||
method: u16,
|
|
||||||
length: u16,
|
|
||||||
magic: u32,
|
|
||||||
id: u128,
|
|
||||||
data: Vec<u8>,
|
|
||||||
attributes: Vec<StunAttribute>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StunPacket {
|
|
||||||
fn new(data: &[u8]) -> Result<Self, io::Error> {
|
|
||||||
if data.len() < 20 {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"not enough data",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let class: u8 = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4);
|
|
||||||
let method: u16 = (((data[0] & 0b00111110) << 7) as u16) | ((data[1] & 0b11101111) as u16);
|
|
||||||
let length: u16 = BigEndian::read_u16(&data[2..4]);
|
|
||||||
let magic: u32 = BigEndian::read_u32(&data[4..8]);
|
|
||||||
let id: u128 = ((BigEndian::read_u64(&data[8..16]) as u128) << 32)
|
|
||||||
| (BigEndian::read_u32(&data[16..20]) as u128);
|
|
||||||
if data.len() < 20 + length as usize {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"not enough data",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let data: Vec<u8> = data[20..(20 + length) as usize].to_vec();
|
|
||||||
let mut stun = StunPacket {
|
|
||||||
class,
|
|
||||||
method,
|
|
||||||
length,
|
|
||||||
magic,
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
attributes: Vec::<StunAttribute>::new(),
|
|
||||||
};
|
|
||||||
stun.attributes = stun.get_attributes();
|
|
||||||
Ok(stun)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn empty() -> Self {
|
|
||||||
StunPacket {
|
|
||||||
class: 0,
|
|
||||||
method: 0,
|
|
||||||
length: 0,
|
|
||||||
magic: 0,
|
|
||||||
id: 0,
|
|
||||||
data: Vec::new(),
|
|
||||||
attributes: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_attributes(&self) -> Vec<StunAttribute> {
|
|
||||||
let mut i = 0;
|
|
||||||
let mut attributes = Vec::<StunAttribute>::new();
|
|
||||||
while i + 4 < self.data.len() {
|
|
||||||
let attr = StunAttribute::from(self.data[i..].to_vec());
|
|
||||||
i += 4 + attr.len() as usize;
|
|
||||||
attributes.push(attr);
|
|
||||||
}
|
|
||||||
attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_length(&mut self) {
|
|
||||||
self.length = 0;
|
|
||||||
for attr in &self.attributes {
|
|
||||||
self.length += 4 + attr.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Vec<u8>> for StunPacket {
|
|
||||||
fn into(self) -> Vec<u8> {
|
|
||||||
let mut v = Vec::<u8>::new();
|
|
||||||
// first cocktail with class and method bits
|
|
||||||
v.push(
|
|
||||||
TryInto::<u8>::try_into((self.method >> 7) & 0b00111110).unwrap()
|
|
||||||
| TryInto::<u8>::try_into((self.class & 0b10) >> 1).unwrap(),
|
|
||||||
);
|
|
||||||
// second cocktail with class and method bits
|
|
||||||
v.push(
|
|
||||||
TryInto::<u8>::try_into((self.method & 0b01110000) << 1).unwrap()
|
|
||||||
| TryInto::<u8>::try_into((self.class & 0b01) << 4).unwrap()
|
|
||||||
| TryInto::<u8>::try_into(self.method & 0b00001111).unwrap(),
|
|
||||||
);
|
|
||||||
v.append(&mut self.length.to_be_bytes().to_vec());
|
|
||||||
v.append(&mut self.magic.to_be_bytes().to_vec());
|
|
||||||
v.append(&mut self.id.to_be_bytes()[4..].to_vec());
|
|
||||||
for attr in &self.attributes {
|
|
||||||
v.append(&mut attr.into());
|
|
||||||
}
|
|
||||||
v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct StunPacket {
|
struct StunPacket {
|
||||||
class: u8,
|
class: u8,
|
||||||
method: u16,
|
method: u16,
|
||||||
|
|
@ -451,67 +351,11 @@ impl Into<Vec<u8>> for StunPacket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
pub fn repl<'a>(
|
pub fn repl<'a>(
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
_masscanned: &Masscanned,
|
_masscanned: &Masscanned,
|
||||||
client_info: ClientInfo,
|
client_info: &mut ClientInfo,
|
||||||
) -> Option<Vec<u8>> {
|
_tcb: Option<&mut TCPControlBlock>,
|
||||||
debug!("receiving STUN data");
|
|
||||||
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
if stun_req.class != STUN_CLASS_REQUEST {
|
|
||||||
info!(
|
|
||||||
"STUN packet not handled (class unknown: 0b{:b})",
|
|
||||||
stun_req.class
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if stun_req.method != STUN_METHOD_BINDING {
|
|
||||||
info!(
|
|
||||||
"STUN packet not handled (method unknown: 0x{:03x})",
|
|
||||||
stun_req.method
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* To be compatible with RFC3489: ignore magic
|
|
||||||
if stun_req.magic != STUN_MAGIC {
|
|
||||||
info!(
|
|
||||||
"STUN packet not handled (magic unknown: 0x{:04x})",
|
|
||||||
stun_req.magic
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if client_info.ip.src == None {
|
|
||||||
error!("STUN packet not handled (expected client ip address not found)");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if client_info.port.src == None {
|
|
||||||
error!("STUN packet not handled (expected client port address not found)");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut stun_resp: StunPacket = StunPacket::empty();
|
|
||||||
stun_resp.class = STUN_CLASS_SUCCESS_RESPONSE;
|
|
||||||
stun_resp.method = STUN_METHOD_BINDING;
|
|
||||||
stun_resp.id = stun_req.id;
|
|
||||||
stun_resp.attributes = Vec::<StunAttribute>::new();
|
|
||||||
stun_resp.attributes.push(StunAttribute::MappedAddress(
|
|
||||||
StunMappedAddressAttribute::new(client_info.ip.src.unwrap(), client_info.port.src.unwrap()),
|
|
||||||
));
|
|
||||||
stun_resp.set_length();
|
|
||||||
return Some(stun_resp.into());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub fn repl<'a>(
|
|
||||||
data: &'a [u8],
|
|
||||||
_masscanned: &Masscanned,
|
|
||||||
mut client_info: &mut ClientInfo,
|
|
||||||
) -> Option<Vec<u8>> {
|
) -> Option<Vec<u8>> {
|
||||||
debug!("receiving STUN data");
|
debug!("receiving STUN data");
|
||||||
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
|
let stun_req: StunPacket = if let Ok(s) = StunPacket::new(&data) {
|
||||||
|
|
@ -571,6 +415,8 @@ mod tests {
|
||||||
|
|
||||||
use pnet::util::MacAddr;
|
use pnet::util::MacAddr;
|
||||||
|
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_proto_stun_ipv4() {
|
fn test_proto_stun_ipv4() {
|
||||||
/* test payload is:
|
/* test payload is:
|
||||||
|
|
@ -596,9 +442,11 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got None");
|
panic!("expected an answer, got None");
|
||||||
|
|
@ -655,13 +503,15 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
client_info.ip.src = Some(IpAddr::V6(test_ip_addr));
|
client_info.ip.src = Some(IpAddr::V6(test_ip_addr));
|
||||||
client_info.ip.dst = Some(IpAddr::V6(masscanned_ip_addr));
|
client_info.ip.dst = Some(IpAddr::V6(masscanned_ip_addr));
|
||||||
client_info.port.src = Some(55000);
|
client_info.port.src = Some(55000);
|
||||||
client_info.port.dst = Some(65000);
|
client_info.port.dst = Some(65000);
|
||||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got None");
|
panic!("expected an answer, got None");
|
||||||
|
|
@ -706,13 +556,15 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||||
client_info.port.src = Some(55000);
|
client_info.port.src = Some(55000);
|
||||||
client_info.port.dst = Some(65000);
|
client_info.port.dst = Some(65000);
|
||||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got None");
|
panic!("expected an answer, got None");
|
||||||
|
|
@ -755,13 +607,15 @@ mod tests {
|
||||||
synack_key: [0, 0],
|
synack_key: [0, 0],
|
||||||
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
iface: None,
|
iface: None,
|
||||||
ip_addresses: Some(&ips),
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
};
|
};
|
||||||
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||||
client_info.port.src = Some(55000);
|
client_info.port.src = Some(55000);
|
||||||
client_info.port.dst = Some(65535);
|
client_info.port.dst = Some(65535);
|
||||||
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info) {
|
let payload_resp = if let Some(r) = repl(payload, &masscanned, &mut client_info, None) {
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
panic!("expected an answer, got None");
|
panic!("expected an answer, got None");
|
||||||
|
|
|
||||||
270
src/proto/tcb.rs
Normal file
270
src/proto/tcb.rs
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use super::http::ProtocolState as HTTPProtocolState;
|
||||||
|
use super::rpc::ProtocolState as RPCProtocolState;
|
||||||
|
use crate::proto::{BASE_STATE, PROTO_NONE};
|
||||||
|
|
||||||
|
pub enum ProtocolState {
|
||||||
|
HTTP(HTTPProtocolState),
|
||||||
|
RPC(RPCProtocolState),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TCPControlBlock {
|
||||||
|
/* state used to detect protocols (not specific) */
|
||||||
|
pub smack_state: usize,
|
||||||
|
/* detected protocol */
|
||||||
|
pub proto_id: usize,
|
||||||
|
/* internal state of protocol parser (e.g., HTTP parsing) */
|
||||||
|
pub proto_state: Option<ProtocolState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CONTABLE: Mutex<HashMap<u32, TCPControlBlock>> = Mutex::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_tcb_set(cookie: u32) -> bool {
|
||||||
|
CONTABLE.lock().unwrap().contains_key(&cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tcb<F>(cookie: u32, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(Option<&mut TCPControlBlock>),
|
||||||
|
{
|
||||||
|
f(CONTABLE.lock().unwrap().get_mut(&cookie));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tcb(cookie: u32) {
|
||||||
|
let mut ct = CONTABLE.lock().unwrap();
|
||||||
|
let tcb = TCPControlBlock {
|
||||||
|
smack_state: BASE_STATE,
|
||||||
|
proto_id: PROTO_NONE,
|
||||||
|
proto_state: None,
|
||||||
|
};
|
||||||
|
if !ct.contains_key(&cookie) {
|
||||||
|
ct.insert(cookie, tcb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use pnet::{
|
||||||
|
packet::{ip::IpNextHeaderProtocols, tcp::TcpPacket},
|
||||||
|
util::MacAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::client::ClientInfo;
|
||||||
|
use crate::layer_4::tcp;
|
||||||
|
use crate::logger::MetaLogger;
|
||||||
|
use crate::proto::{PROTO_HTTP, PROTO_RPC_TCP};
|
||||||
|
use crate::synackcookie;
|
||||||
|
use crate::Masscanned;
|
||||||
|
|
||||||
|
fn get_dummy_tcp(&client_info: &ClientInfo) -> Vec<u8> {
|
||||||
|
/* Craft a TCP ACK+PUSH packet with correct ports and ack */
|
||||||
|
let mut pkt = Vec::new();
|
||||||
|
pkt.extend_from_slice(&client_info.port.src.unwrap().to_be_bytes());
|
||||||
|
pkt.extend_from_slice(&client_info.port.dst.unwrap().to_be_bytes());
|
||||||
|
pkt.extend_from_slice(b"\x00\x00\x00\x00");
|
||||||
|
pkt.extend_from_slice(&(client_info.cookie.unwrap() + 1).to_be_bytes());
|
||||||
|
pkt.extend_from_slice(b"P\x18 \x00\x00\x00\x00\x00");
|
||||||
|
pkt
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proto_tcb_proto_id() {
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
|
client_info.port.src = Some(65000);
|
||||||
|
client_info.port.dst = Some(80);
|
||||||
|
client_info.transport = Some(IpNextHeaderProtocols::Tcp);
|
||||||
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
|
||||||
|
client_info.cookie = Some(cookie);
|
||||||
|
assert!(!is_tcb_set(cookie), "expected no TCB entry, found one");
|
||||||
|
/***** TEST PROTOCOL ID IN TCB *****/
|
||||||
|
let payload = [get_dummy_tcp(&client_info), b"GET / HTTP/1.1\r\n".to_vec()].concat();
|
||||||
|
tcp::repl(
|
||||||
|
&TcpPacket::new(&payload).unwrap(),
|
||||||
|
&masscanned,
|
||||||
|
&mut client_info,
|
||||||
|
);
|
||||||
|
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
|
||||||
|
get_tcb(cookie, |t| {
|
||||||
|
let t = t.unwrap();
|
||||||
|
assert!(t.proto_id == PROTO_HTTP);
|
||||||
|
});
|
||||||
|
|
||||||
|
/***** SENDING MORE DATA *****/
|
||||||
|
let payload = [
|
||||||
|
get_dummy_tcp(&client_info),
|
||||||
|
b"garbage data with no specific format (no protocol)\r\n\r\n".to_vec(),
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
tcp::repl(
|
||||||
|
&TcpPacket::new(&payload).unwrap(),
|
||||||
|
&masscanned,
|
||||||
|
&mut client_info,
|
||||||
|
);
|
||||||
|
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
|
||||||
|
get_tcb(cookie, |t| {
|
||||||
|
let t = t.unwrap();
|
||||||
|
assert!(t.proto_id == PROTO_HTTP);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proto_tcb_proto_state_http() {
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
|
client_info.port.src = Some(65001);
|
||||||
|
client_info.port.dst = Some(80);
|
||||||
|
client_info.transport = Some(IpNextHeaderProtocols::Tcp);
|
||||||
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
|
||||||
|
client_info.cookie = Some(cookie);
|
||||||
|
assert!(!is_tcb_set(cookie), "expected no TCB entry, found one");
|
||||||
|
/***** TEST PROTOCOL ID IN TCB *****/
|
||||||
|
let payload = [get_dummy_tcp(&client_info), b"GET / HTTP/1.1\r\n".to_vec()].concat();
|
||||||
|
tcp::repl(
|
||||||
|
&TcpPacket::new(&payload).unwrap(),
|
||||||
|
&masscanned,
|
||||||
|
&mut client_info,
|
||||||
|
);
|
||||||
|
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
|
||||||
|
get_tcb(cookie, |t| {
|
||||||
|
let t = t.unwrap();
|
||||||
|
assert!(t.proto_id == PROTO_HTTP);
|
||||||
|
if let Some(ProtocolState::HTTP(_)) = t.proto_state {
|
||||||
|
} else {
|
||||||
|
panic!("expected a HTTP protocole state, found None");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/***** SENDING MORE DATA *****/
|
||||||
|
let payload = [
|
||||||
|
get_dummy_tcp(&client_info),
|
||||||
|
b"Field: empty\r\n\r\n".to_vec(),
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
/* Should have an answer here */
|
||||||
|
if let None = tcp::repl(
|
||||||
|
&TcpPacket::new(&payload).unwrap(),
|
||||||
|
&masscanned,
|
||||||
|
&mut client_info,
|
||||||
|
) {
|
||||||
|
panic!("expected an HTTP response, got nothing");
|
||||||
|
}
|
||||||
|
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
|
||||||
|
get_tcb(cookie, |t| {
|
||||||
|
let t = t.unwrap();
|
||||||
|
assert!(t.proto_id == PROTO_HTTP);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proto_tcb_proto_state_rpc() {
|
||||||
|
let mut client_info = ClientInfo::new();
|
||||||
|
let test_ip_addr = Ipv4Addr::new(3, 2, 1, 0);
|
||||||
|
client_info.ip.src = Some(IpAddr::V4(test_ip_addr));
|
||||||
|
client_info.port.src = Some(65002);
|
||||||
|
client_info.port.dst = Some(80);
|
||||||
|
client_info.transport = Some(IpNextHeaderProtocols::Tcp);
|
||||||
|
let masscanned_ip_addr = Ipv4Addr::new(0, 1, 2, 3);
|
||||||
|
client_info.ip.dst = Some(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
let mut ips = HashSet::new();
|
||||||
|
ips.insert(IpAddr::V4(masscanned_ip_addr));
|
||||||
|
/* Construct masscanned context object */
|
||||||
|
let masscanned = Masscanned {
|
||||||
|
synack_key: [0, 0],
|
||||||
|
mac: MacAddr::from_str("00:11:22:33:44:55").expect("error parsing MAC address"),
|
||||||
|
iface: None,
|
||||||
|
self_ip_list: Some(&ips),
|
||||||
|
remote_ip_deny_list: None,
|
||||||
|
log: MetaLogger::new(),
|
||||||
|
};
|
||||||
|
let cookie = synackcookie::generate(&client_info, &masscanned.synack_key).unwrap();
|
||||||
|
client_info.cookie = Some(cookie);
|
||||||
|
assert!(!is_tcb_set(cookie), "expected no TCB entry, found one");
|
||||||
|
/***** TEST PROTOCOL ID IN TCB *****/
|
||||||
|
let full_payload = b"\x80\x00\x00\x28\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
|
let payload = [get_dummy_tcp(&client_info), full_payload[0..28].to_vec()].concat();
|
||||||
|
tcp::repl(
|
||||||
|
&TcpPacket::new(&payload).unwrap(),
|
||||||
|
&masscanned,
|
||||||
|
&mut client_info,
|
||||||
|
);
|
||||||
|
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
|
||||||
|
get_tcb(cookie, |t| {
|
||||||
|
let t = t.unwrap();
|
||||||
|
assert!(t.proto_id == PROTO_RPC_TCP);
|
||||||
|
if let Some(ProtocolState::RPC(_)) = t.proto_state {
|
||||||
|
} else {
|
||||||
|
panic!("expected a RPC protocole state, found None");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/***** SENDING MORE DATA *****/
|
||||||
|
/* Should have an answer here */
|
||||||
|
let payload = [get_dummy_tcp(&client_info), full_payload[28..].to_vec()].concat();
|
||||||
|
if let None = tcp::repl(
|
||||||
|
&TcpPacket::new(&payload).unwrap(),
|
||||||
|
&masscanned,
|
||||||
|
&mut client_info,
|
||||||
|
) {
|
||||||
|
panic!("expected a RPC response, got nothing");
|
||||||
|
}
|
||||||
|
assert!(is_tcb_set(cookie), "expected a TCB entry, not found");
|
||||||
|
get_tcb(cookie, |t| {
|
||||||
|
let t = t.unwrap();
|
||||||
|
assert!(t.proto_id == PROTO_RPC_TCP);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ pub struct Smack {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_copy_of_pattern(pattern: &[u8], is_nocase: bool) -> Vec<u8> {
|
fn make_copy_of_pattern(pattern: &[u8], is_nocase: bool) -> Vec<u8> {
|
||||||
let mut p = pattern.clone().to_vec();
|
let mut p = pattern.to_vec();
|
||||||
for i in 0..p.len() {
|
for i in 0..p.len() {
|
||||||
if is_nocase {
|
if is_nocase {
|
||||||
p[i] = p[i].to_ascii_lowercase();
|
p[i] = p[i].to_ascii_lowercase();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct SmackFlags: usize {
|
pub struct SmackFlags: usize {
|
||||||
const EMPTY = 0x00;
|
const EMPTY = 0x00;
|
||||||
const ANCHOR_BEGIN = 0x01;
|
const ANCHOR_BEGIN = 0x01;
|
||||||
|
|
|
||||||
48
src/utils/display.rs
Normal file
48
src/utils/display.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// This file is part of masscanned.
|
||||||
|
// Copyright 2021 - 2022 - The IVRE project
|
||||||
|
//
|
||||||
|
// Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
// License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
static CHARS: [&'static str; 256] = [
|
||||||
|
"\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07", "\\x08", "\\x09",
|
||||||
|
"\\x0a", "\\x0b", "\\x0c", "\\x0d", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13",
|
||||||
|
"\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d",
|
||||||
|
"\\x1e", "\\x1f", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".",
|
||||||
|
"/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A",
|
||||||
|
"B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
|
||||||
|
"U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g",
|
||||||
|
"h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||||
|
"{", "|", "}", "~", "\\x7f", "\\x80", "\\x81", "\\x82", "\\x83", "\\x84", "\\x85", "\\x86",
|
||||||
|
"\\x87", "\\x88", "\\x89", "\\x8a", "\\x8b", "\\x8c", "\\x8d", "\\x8e", "\\x8f", "\\x90",
|
||||||
|
"\\x91", "\\x92", "\\x93", "\\x94", "\\x95", "\\x96", "\\x97", "\\x98", "\\x99", "\\x9a",
|
||||||
|
"\\x9b", "\\x9c", "\\x9d", "\\x9e", "\\x9f", "\\xa0", "\\xa1", "\\xa2", "\\xa3", "\\xa4",
|
||||||
|
"\\xa5", "\\xa6", "\\xa7", "\\xa8", "\\xa9", "\\xaa", "\\xab", "\\xac", "\\xad", "\\xae",
|
||||||
|
"\\xaf", "\\xb0", "\\xb1", "\\xb2", "\\xb3", "\\xb4", "\\xb5", "\\xb6", "\\xb7", "\\xb8",
|
||||||
|
"\\xb9", "\\xba", "\\xbb", "\\xbc", "\\xbd", "\\xbe", "\\xbf", "\\xc0", "\\xc1", "\\xc2",
|
||||||
|
"\\xc3", "\\xc4", "\\xc5", "\\xc6", "\\xc7", "\\xc8", "\\xc9", "\\xca", "\\xcb", "\\xcc",
|
||||||
|
"\\xcd", "\\xce", "\\xcf", "\\xd0", "\\xd1", "\\xd2", "\\xd3", "\\xd4", "\\xd5", "\\xd6",
|
||||||
|
"\\xd7", "\\xd8", "\\xd9", "\\xda", "\\xdb", "\\xdc", "\\xdd", "\\xde", "\\xdf", "\\xe0",
|
||||||
|
"\\xe1", "\\xe2", "\\xe3", "\\xe4", "\\xe5", "\\xe6", "\\xe7", "\\xe8", "\\xe9", "\\xea",
|
||||||
|
"\\xeb", "\\xec", "\\xed", "\\xee", "\\xef", "\\xf0", "\\xf1", "\\xf2", "\\xf3", "\\xf4",
|
||||||
|
"\\xf5", "\\xf6", "\\xf7", "\\xf8", "\\xf9", "\\xfa", "\\xfb", "\\xfc", "\\xfd", "\\xfe",
|
||||||
|
"\\xff",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn byte2str(data: &[u8]) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
for byte in data {
|
||||||
|
result.push_str(CHARS[usize::from(*byte)]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
mod parsers;
|
mod parsers;
|
||||||
|
|
||||||
pub use parsers::IpAddrParser;
|
pub use parsers::IpAddrParser;
|
||||||
|
|
||||||
|
mod display;
|
||||||
|
|
||||||
|
pub use display::byte2str;
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ use std::io::BufReader;
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use pcap_file::pcap::{Packet, PcapReader};
|
use pcap_file::pcap::{PcapPacket, PcapReader};
|
||||||
use pnet::packet::{
|
use pnet::packet::{
|
||||||
ethernet::{EtherTypes, EthernetPacket},
|
ethernet::{EtherTypes, EthernetPacket},
|
||||||
ipv4::Ipv4Packet,
|
ipv4::Ipv4Packet,
|
||||||
ipv6::Ipv6Packet,
|
ipv6::Ipv6Packet,
|
||||||
Packet as Pkt,
|
Packet,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Generic IP packet (either IPv4 or IPv6) */
|
/* Generic IP packet (either IPv4 or IPv6) */
|
||||||
|
|
@ -134,9 +134,46 @@ impl IpAddrParser for File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Parse IP addresses from a comma-separated list in a string */
|
||||||
|
impl IpAddrParser for &str {
|
||||||
|
fn extract_ip_addresses_with_count(
|
||||||
|
self,
|
||||||
|
_blacklist: Option<HashSet<IpAddr>>,
|
||||||
|
) -> HashMap<IpAddr, u32> {
|
||||||
|
panic!("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_ip_addresses_only(self, blacklist: Option<HashSet<IpAddr>>) -> HashSet<IpAddr> {
|
||||||
|
let mut ip_addresses = HashSet::new();
|
||||||
|
for line in self.split(",") {
|
||||||
|
/* Should never occur */
|
||||||
|
if line.is_empty() {
|
||||||
|
warn!("cannot parse line: {}", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ip: IpAddr;
|
||||||
|
if let Ok(val) = line.parse::<Ipv4Addr>() {
|
||||||
|
ip = IpAddr::V4(val);
|
||||||
|
} else if let Ok(val) = line.parse::<Ipv6Addr>() {
|
||||||
|
ip = IpAddr::V6(val);
|
||||||
|
} else {
|
||||||
|
warn!("cannot parse IP address from line: {}", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(ref b) = blacklist {
|
||||||
|
if b.contains(&ip) {
|
||||||
|
info!("[blacklist] ignoring {}", &ip);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ip_addresses.insert(ip);
|
||||||
|
}
|
||||||
|
ip_addresses
|
||||||
|
}
|
||||||
|
}
|
||||||
/* Get the IP address of source and dest. from an IP packet.
|
/* Get the IP address of source and dest. from an IP packet.
|
||||||
* works with both IPv4 and IPv6 packets/addresses */
|
* works with both IPv4 and IPv6 packets/addresses */
|
||||||
fn extract_ip(pkt: Packet) -> Option<(IpAddr, IpAddr)> {
|
fn extract_ip(pkt: PcapPacket) -> Option<(IpAddr, IpAddr)> {
|
||||||
let eth = EthernetPacket::new(&pkt.data).expect("error parsing Ethernet packet");
|
let eth = EthernetPacket::new(&pkt.data).expect("error parsing Ethernet packet");
|
||||||
let payload = eth.payload();
|
let payload = eth.payload();
|
||||||
let ip = match eth.get_ethertype() {
|
let ip = match eth.get_ethertype() {
|
||||||
|
|
@ -169,13 +206,13 @@ impl IpAddrParser for PcapReader<std::fs::File> {
|
||||||
/* Extract IP addresses (v4 and v6) from a capture and count occurrences of
|
/* Extract IP addresses (v4 and v6) from a capture and count occurrences of
|
||||||
* each. */
|
* each. */
|
||||||
fn extract_ip_addresses_with_count(
|
fn extract_ip_addresses_with_count(
|
||||||
self: PcapReader<std::fs::File>,
|
mut self: PcapReader<std::fs::File>,
|
||||||
blacklist: Option<HashSet<IpAddr>>,
|
blacklist: Option<HashSet<IpAddr>>,
|
||||||
) -> HashMap<IpAddr, u32> {
|
) -> HashMap<IpAddr, u32> {
|
||||||
let mut ip_addresses = HashMap::new();
|
let mut ip_addresses = HashMap::new();
|
||||||
// pcap.map(fn) , map_Ok
|
// pcap.map(fn) , map_Ok
|
||||||
// .iter, into_iter
|
// .iter, into_iter
|
||||||
for pkt in self {
|
while let Some(pkt) = self.next_packet() {
|
||||||
match pkt {
|
match pkt {
|
||||||
Ok(pkt) => {
|
Ok(pkt) => {
|
||||||
// map_Some map_None
|
// map_Some map_None
|
||||||
|
|
@ -209,13 +246,13 @@ impl IpAddrParser for PcapReader<std::fs::File> {
|
||||||
ip_addresses
|
ip_addresses
|
||||||
}
|
}
|
||||||
fn extract_ip_addresses_only(
|
fn extract_ip_addresses_only(
|
||||||
self: PcapReader<std::fs::File>,
|
mut self: PcapReader<std::fs::File>,
|
||||||
blacklist: Option<HashSet<IpAddr>>,
|
blacklist: Option<HashSet<IpAddr>>,
|
||||||
) -> HashSet<IpAddr> {
|
) -> HashSet<IpAddr> {
|
||||||
let mut ip_addresses = HashSet::new();
|
let mut ip_addresses = HashSet::new();
|
||||||
// pcap.map(fn) , map_Ok
|
// pcap.map(fn) , map_Ok
|
||||||
// .iter, into_iter
|
// .iter, into_iter
|
||||||
for pkt in self {
|
while let Some(pkt) = self.next_packet() {
|
||||||
match pkt {
|
match pkt {
|
||||||
Ok(pkt) => {
|
Ok(pkt) => {
|
||||||
// map_Some map_None
|
// map_Some map_None
|
||||||
|
|
|
||||||
3
test/requirements.txt
Normal file
3
test/requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ivre
|
||||||
|
scapy
|
||||||
|
requests
|
||||||
597
test/src/all.py
597
test/src/all.py
|
|
@ -14,580 +14,31 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from scapy.all import *
|
import importlib
|
||||||
import requests
|
import os
|
||||||
import requests.packages.urllib3.util.connection as urllib3_cn
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from .conf import *
|
# Export / other tests
|
||||||
|
from .core import test_all # noqa: F401
|
||||||
|
|
||||||
fmt = logging.Formatter("%(levelname)s\t%(message)s")
|
DEFAULT_TESTS = [
|
||||||
ch = logging.StreamHandler()
|
"arp",
|
||||||
ch.setFormatter(fmt)
|
"dns",
|
||||||
ch.setLevel(logging.DEBUG)
|
"ghost",
|
||||||
LOG = logging.getLogger(__name__)
|
"http",
|
||||||
LOG.setLevel(logging.DEBUG)
|
"icmpv4",
|
||||||
LOG.addHandler(ch)
|
"icmpv6",
|
||||||
|
"ip",
|
||||||
|
"rpc",
|
||||||
|
"smb",
|
||||||
|
"ssh",
|
||||||
|
"stun",
|
||||||
|
"tcp",
|
||||||
|
"udp",
|
||||||
|
]
|
||||||
|
|
||||||
tests = list()
|
ENABLED_TESTS = DEFAULT_TESTS
|
||||||
|
if tests := os.environ.get("TESTS"):
|
||||||
|
ENABLED_TESTS = [x.strip() for x in tests.split(",")]
|
||||||
|
|
||||||
# decorator to automatically add a function to tests
|
for test in ENABLED_TESTS:
|
||||||
def test(f):
|
importlib.import_module(".tests." + test, package="src")
|
||||||
OK = "\033[1mOK\033[0m"
|
|
||||||
KO = "\033[1m\033[1;%dmKO\033[0m" % 31
|
|
||||||
global tests
|
|
||||||
fname = f.__name__.ljust(50, '.')
|
|
||||||
def w(iface):
|
|
||||||
try:
|
|
||||||
f(iface)
|
|
||||||
LOG.info("{}{}".format(fname, OK))
|
|
||||||
except AssertionError as e:
|
|
||||||
LOG.info("{}{}: {}".format(fname, KO, e))
|
|
||||||
tests.append(w)
|
|
||||||
return w
|
|
||||||
|
|
||||||
def multicast(ip6):
|
|
||||||
a, b = ip6.split(":")[-2:]
|
|
||||||
mac = ["33", "33", "ff"]
|
|
||||||
if len(a) == 4:
|
|
||||||
mac.append(a[2:])
|
|
||||||
else:
|
|
||||||
mac.append("00")
|
|
||||||
if len(b) >= 2:
|
|
||||||
mac.append(b[:2])
|
|
||||||
else:
|
|
||||||
mac.append("00")
|
|
||||||
if len(b) >= 4:
|
|
||||||
mac.append(b[2:])
|
|
||||||
else:
|
|
||||||
mac.append("00")
|
|
||||||
return ":".join(mac)
|
|
||||||
|
|
||||||
def check_ip_checksum(pkt):
|
|
||||||
assert(IP in pkt), "no IP layer found"
|
|
||||||
ip_pkt = pkt[IP]
|
|
||||||
chksum = ip_pkt.chksum
|
|
||||||
del ip_pkt.chksum
|
|
||||||
assert(IP(raw(ip_pkt)).chksum == chksum), "bad IPv4 checksum"
|
|
||||||
|
|
||||||
def check_ipv6_checksum(pkt):
|
|
||||||
assert(IPv6 in pkt), "no IP layer found"
|
|
||||||
ip_pkt = pkt[IPv6]
|
|
||||||
chksum = ip_pkt.chksum
|
|
||||||
del ip_pkt.chksum
|
|
||||||
assert(IPv6(raw(ip_pkt)).chksum == chksum), "bad IPv6 checksum"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_arp_req(iface):
|
|
||||||
##### ARP #####
|
|
||||||
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst=IPV4_ADDR)
|
|
||||||
arp_repl = iface.sr1(arp_req, timeout=1)
|
|
||||||
assert(arp_repl is not None), "expecting answer, got nothing"
|
|
||||||
assert(ARP in arp_repl), "no ARP layer found"
|
|
||||||
arp_repl = arp_repl[ARP]
|
|
||||||
# check answer
|
|
||||||
## op is "is-at"
|
|
||||||
assert(arp_repl.op == 2), "unexpected ARP op: {}".format(arp_repl.op)
|
|
||||||
## answer for the requested IP
|
|
||||||
assert(arp_repl.psrc == arp_req.pdst), "unexpected ARP psrc: {}".format(arp_repl.psrc)
|
|
||||||
assert(arp_repl.pdst == arp_req.psrc), "unexpected ARP pdst: {}".format(arp_repl.pdst)
|
|
||||||
## answer is expected MAC address
|
|
||||||
assert(arp_repl.hwsrc == MAC_ADDR), "unexpected ARP hwsrc: {}".format(arp_repl.hwsrc)
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_arp_req_other_ip(iface):
|
|
||||||
##### ARP #####
|
|
||||||
arp_req = Ether()/ARP(psrc='192.0.0.2', pdst='1.2.3.4')
|
|
||||||
arp_repl = iface.sr1(arp_req, timeout=1)
|
|
||||||
assert(arp_repl is None), "responding to ARP requests for other IP addresses"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_req(iface):
|
|
||||||
##### IP #####
|
|
||||||
ip_req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR, id=0x1337)/ICMP(type=8, code=0)
|
|
||||||
ip_repl = iface.sr1(ip_req, timeout=1)
|
|
||||||
assert(ip_repl is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(ip_repl)
|
|
||||||
assert(IP in ip_repl), "no IP layer in response"
|
|
||||||
ip_repl = ip_repl[IP]
|
|
||||||
assert(ip_repl.id == 0), "IP identification unexpected"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_eth_req_other_mac(iface):
|
|
||||||
#### ETH ####
|
|
||||||
ip_req = Ether(dst="00:00:00:11:11:11")/IP(dst=IPV4_ADDR)/ICMP(type=8, code=0)
|
|
||||||
ip_repl = iface.sr1(ip_req, timeout=1)
|
|
||||||
assert(ip_repl is None), "responding to other MAC addresses"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_req_other_ip(iface):
|
|
||||||
##### IP #####
|
|
||||||
ip_req = Ether(dst=MAC_ADDR)/IP(dst="1.2.3.4")/ICMP(type=8, code=0)
|
|
||||||
ip_repl = iface.sr1(ip_req, timeout=1)
|
|
||||||
assert(ip_repl is None), "responding to other IP addresses"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_icmpv4_echo_req(iface):
|
|
||||||
##### ICMPv4 #####
|
|
||||||
icmp_req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/ICMP(type=8, code=0)/Raw("idrinkwaytoomuchcoffee")
|
|
||||||
icmp_repl = iface.sr1(icmp_req, timeout=1)
|
|
||||||
assert(icmp_repl is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(icmp_repl)
|
|
||||||
assert(ICMP in icmp_repl)
|
|
||||||
icmp_repl = icmp_repl[ICMP]
|
|
||||||
# check answer
|
|
||||||
## type is "echo-reply"
|
|
||||||
assert(icmp_repl.type == 0)
|
|
||||||
assert(icmp_repl.code == 0)
|
|
||||||
## data is the same as sent
|
|
||||||
assert(icmp_repl.load == icmp_req.load)
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_icmpv6_neighbor_solicitation(iface):
|
|
||||||
##### IPv6 Neighbor Solicitation #####
|
|
||||||
for mac in ["ff:ff:ff:ff:ff:ff", "33:33:00:00:00:01", MAC_ADDR, multicast(IPV6_ADDR)]:
|
|
||||||
nd_ns = Ether(dst=mac)/IPv6()/ICMPv6ND_NS(tgt=IPV6_ADDR)
|
|
||||||
nd_na = iface.sr1(nd_ns, timeout=1)
|
|
||||||
assert(nd_na is not None), "expecting answer, got nothing"
|
|
||||||
assert(ICMPv6ND_NA in nd_na)
|
|
||||||
nd_na = nd_na[ICMPv6ND_NA]
|
|
||||||
# check answer content
|
|
||||||
assert(nd_na.code == 0)
|
|
||||||
assert(nd_na.R == 0)
|
|
||||||
assert(nd_na.S == 1)
|
|
||||||
assert(nd_na.O == 1)
|
|
||||||
assert(nd_na.tgt == IPV6_ADDR)
|
|
||||||
# check ND Option
|
|
||||||
assert(nd_na.haslayer(ICMPv6NDOptDstLLAddr))
|
|
||||||
assert(nd_na.getlayer(ICMPv6NDOptDstLLAddr).lladdr == MAC_ADDR)
|
|
||||||
for mac in ["00:00:00:00:00:00", "33:33:33:00:00:01"]:
|
|
||||||
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff")/IPv6()/ICMPv6ND_NS(tgt=IPV6_ADDR)
|
|
||||||
nd_na = iface.sr1(nd_ns, timeout=1)
|
|
||||||
assert(nd_na is not None), "expecting no answer, got one"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_icmpv6_neighbor_solicitation_other_ip(iface):
|
|
||||||
##### IPv6 Neighbor Solicitation #####
|
|
||||||
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff")/IPv6()/ICMPv6ND_NS(tgt="2020:4141:3030:2020::bdbd")
|
|
||||||
nd_na = iface.sr1(nd_ns, timeout=1)
|
|
||||||
assert(nd_na is None), "responding to ND_NS for other IP addresses"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_icmpv6_echo_req(iface):
|
|
||||||
##### IPv6 Ping #####
|
|
||||||
echo_req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/ICMPv6EchoRequest(data="waytoomanynapkins")
|
|
||||||
echo_repl = iface.sr1(echo_req, timeout=1)
|
|
||||||
assert(echo_repl is not None), "expecting answer, got nothing"
|
|
||||||
assert(ICMPv6EchoReply in echo_repl)
|
|
||||||
echo_repl = echo_repl[ICMPv6EchoReply]
|
|
||||||
# check answer content
|
|
||||||
assert(echo_repl.code == 0)
|
|
||||||
assert(echo_repl.data == echo_req.data)
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_tcp_syn(iface):
|
|
||||||
##### SYN-ACK #####
|
|
||||||
# test a list of ports, randomly generated once
|
|
||||||
ports_to_test = [
|
|
||||||
1152, 2003, 2193, 3709, 4054, 6605, 6737, 6875, 7320, 8898, 9513, 9738, 10623, 10723,
|
|
||||||
11253, 12125, 12189, 12873, 14648, 14659, 16242, 16243, 17209, 17492, 17667, 17838,
|
|
||||||
18081, 18682, 18790, 19124, 19288, 19558, 19628, 19789, 20093, 21014, 21459, 21740,
|
|
||||||
24070, 24312, 24576, 26939, 27136, 27165, 27361, 29971, 31088, 33011, 33068, 34990,
|
|
||||||
35093, 35958, 36626, 36789, 37130, 37238, 37256, 37697, 37890, 38958, 42131, 43864,
|
|
||||||
44420, 44655, 44868, 45157, 46213, 46497, 46955, 49049, 49067, 49452, 49480, 50498,
|
|
||||||
50945, 51181, 52890, 53301, 53407, 53417, 53980, 55827, 56483, 58552, 58713, 58836,
|
|
||||||
59362, 59560, 60534, 60555, 60660, 61615, 62402, 62533, 62941, 63240, 63339, 63616,
|
|
||||||
64380, 65438,
|
|
||||||
]
|
|
||||||
for p in ports_to_test:
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", dport=p)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_tcp_psh_ack(iface):
|
|
||||||
##### PSH-ACK #####
|
|
||||||
sport = 26695
|
|
||||||
port = 445
|
|
||||||
# send PSH-ACK first
|
|
||||||
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port)/Raw("payload")
|
|
||||||
syn_ack = iface.sr1(psh_ack, timeout=1)
|
|
||||||
assert(syn_ack is None), "no answer expected, got one"
|
|
||||||
# test the anti-injection mechanism
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", dport=port)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", dport=port)
|
|
||||||
# should fail because no ack given
|
|
||||||
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port)
|
|
||||||
ack = iface.sr1(psh_ack, timeout=1)
|
|
||||||
assert(ack is None), "no answer expected, got one"
|
|
||||||
# should get an answer this time
|
|
||||||
psh_ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", dport=port, ack=syn_ack.seq + 1)
|
|
||||||
ack = iface.sr1(psh_ack, timeout=1)
|
|
||||||
assert(ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(ack)
|
|
||||||
assert(TCP in ack)
|
|
||||||
ack = ack[TCP]
|
|
||||||
assert(ack.flags == "A")
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_tcp_psh_ack(iface):
|
|
||||||
##### PSH-ACK #####
|
|
||||||
sport = 26695
|
|
||||||
port = 445
|
|
||||||
# send PSH-ACK first
|
|
||||||
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port)/Raw("payload")
|
|
||||||
syn_ack = iface.sr1(psh_ack, timeout=1)
|
|
||||||
assert(syn_ack is None), "no answer expected, got one"
|
|
||||||
# test the anti-injection mechanism
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", dport=port)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", dport=port)
|
|
||||||
# should fail because no ack given
|
|
||||||
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port)
|
|
||||||
ack = iface.sr1(psh_ack, timeout=1)
|
|
||||||
assert(ack is None), "no answer expected, got one"
|
|
||||||
# should get an answer this time
|
|
||||||
psh_ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", dport=port, ack=syn_ack.seq + 1)
|
|
||||||
ack = iface.sr1(psh_ack, timeout=1)
|
|
||||||
assert(ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(ack)
|
|
||||||
assert(TCP in ack)
|
|
||||||
ack = ack[TCP]
|
|
||||||
assert(ack.flags == "A")
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_tcp_http(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
|
||||||
_ = iface.sr1(ack, timeout=1)
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(TCP in resp)
|
|
||||||
tcp = resp[TCP]
|
|
||||||
assert(tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_tcp_http(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
|
||||||
_ = iface.sr1(ack, timeout=1)
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(TCP in resp)
|
|
||||||
tcp = resp[TCP]
|
|
||||||
assert(tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_udp_http(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(UDP in resp)
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_udp_http(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw("GET / HTTP/1.1\r\n\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(UDP in resp)
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n"))
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_tcp_http_ko(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
|
||||||
_ = iface.sr1(ack, timeout=1)
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(TCP in resp)
|
|
||||||
assert("P" not in resp[TCP].flags)
|
|
||||||
assert(len(resp[TCP].payload) == 0)
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_udp_http_ko(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is None), "expecting no answer, got one"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_tcp_http_ko(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
|
||||||
_ = iface.sr1(ack, timeout=1)
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(TCP in resp)
|
|
||||||
assert("P" not in resp[TCP].flags)
|
|
||||||
assert(len(resp[TCP].payload) == 0)
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_udp_http_ko(iface):
|
|
||||||
sport = 24592
|
|
||||||
dports = [80, 443, 5000, 53228]
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(bytes.fromhex("4f5054494f4e53"))
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is None), "expecting no answer, got one"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_udp_stun(iface):
|
|
||||||
sports = [12345, 55555, 80, 43273]
|
|
||||||
dports = [80, 800, 8000, 3478]
|
|
||||||
payload = bytes.fromhex("000100002112a442000000000000000000000000")
|
|
||||||
for sport in sports:
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(UDP in resp), "no UDP layer found"
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(udp.sport == dport), "unexpected UDP sport: {}".format(udp.sport)
|
|
||||||
assert(udp.dport == sport), "unexpected UDP dport: {}".format(udp.dport)
|
|
||||||
resp_payload = udp.payload.load
|
|
||||||
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
|
|
||||||
tid = resp_payload[8:20]
|
|
||||||
data = resp_payload[20:]
|
|
||||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
|
||||||
assert(length == 12), "expected length 12, got {}".format(length)
|
|
||||||
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
|
|
||||||
assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
|
|
||||||
assert(data == bytes.fromhex("000100080001") + struct.pack(">H", sport) + bytes.fromhex("00000000")), "unexpected data"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_udp_stun(iface):
|
|
||||||
sports = [12345, 55555, 80, 43273]
|
|
||||||
dports = [80, 800, 8000, 3478]
|
|
||||||
payload = bytes.fromhex("000100002112a442000000000000000000000000")
|
|
||||||
for sport in sports:
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(UDP in resp)
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(udp.sport == dport)
|
|
||||||
assert(udp.dport == sport)
|
|
||||||
resp_payload = udp.payload.load
|
|
||||||
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
|
|
||||||
tid = resp_payload[8:20]
|
|
||||||
data = resp_payload[20:]
|
|
||||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
|
||||||
assert(length == 24), "expected length 24, got {}".format(length)
|
|
||||||
assert(magic == 0x2112a442), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
|
|
||||||
assert(tid == b'\x00' * 12), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
|
|
||||||
assert(data == bytes.fromhex("000100140002") + struct.pack(">H", sport) + bytes.fromhex("00000000" * 4)), "unexpected data: {}".format(data)
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_udp_stun_change_port(iface):
|
|
||||||
sports = [12345, 55555, 80, 43273]
|
|
||||||
dports = [80, 800, 8000, 3478, 65535]
|
|
||||||
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
|
|
||||||
for sport in sports:
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(UDP in resp), "no UDP layer found"
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(udp.sport == (dport + 1) % 2**16), "expected answer from UDP/{}, got it from UDP/{}".format((dport + 1) % 2**16, udp.sport)
|
|
||||||
assert(udp.dport == sport), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
|
|
||||||
resp_payload = udp.payload.load
|
|
||||||
type_, length = struct.unpack(">HH", resp_payload[:4])
|
|
||||||
tid = resp_payload[4:20]
|
|
||||||
data = resp_payload[20:]
|
|
||||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
|
||||||
assert(length == 12), "expected length 12, got {}".format(length)
|
|
||||||
assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
|
|
||||||
assert(data == bytes.fromhex("000100080001") + struct.pack(">H", sport) + bytes.fromhex("00000000")), "unexpected data"
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_udp_stun_change_port(iface):
|
|
||||||
sports = [12345, 55555, 80, 43273]
|
|
||||||
dports = [80, 800, 8000, 3478, 65535]
|
|
||||||
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
|
|
||||||
for sport in sports:
|
|
||||||
for dport in dports:
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(payload)
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(UDP in resp), "expecting UDP layer in answer, got nothing"
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(udp.sport == (dport + 1) % 2**16), "expected answer from UDP/{}, got it from UDP/{}".format((dport + 1) % 2**16, udp.sport)
|
|
||||||
assert(udp.dport == sport), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
|
|
||||||
resp_payload = udp.payload.load
|
|
||||||
type_, length = struct.unpack(">HH", resp_payload[:4])
|
|
||||||
tid = resp_payload[4:20]
|
|
||||||
data = resp_payload[20:]
|
|
||||||
assert(type_ == 0x0101), "expected type 0X0101, got 0x{:04x}".format(type_)
|
|
||||||
assert(length == 24), "expected length 12, got {}".format(length)
|
|
||||||
assert(tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c")), "expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
|
|
||||||
assert(data == bytes.fromhex("000100140002") + struct.pack(">H", sport) + bytes.fromhex("00000000" * 4))
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_tcp_ssh(iface):
|
|
||||||
sport = 37183
|
|
||||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
|
||||||
for i, dport in enumerate(dports):
|
|
||||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
|
||||||
_ = iface.sr1(ack, timeout=1)
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(TCP in resp)
|
|
||||||
tcp = resp[TCP]
|
|
||||||
assert("A" in tcp.flags), "expecting ACK flag, not set (%r)" % tcp.flags
|
|
||||||
assert("P" in tcp.flags), "expecting PSH flag, not set (%r)" % tcp.flags
|
|
||||||
assert(len(tcp.payload) > 0), "expecting payload, got none"
|
|
||||||
assert(tcp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % tcp.payload.load
|
|
||||||
assert(tcp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % tcp.payload.load
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv4_udp_ssh(iface):
|
|
||||||
sport = 37183
|
|
||||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
|
||||||
for i, dport in enumerate(dports):
|
|
||||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
|
||||||
req = Ether(dst=MAC_ADDR)/IP(dst=IPV4_ADDR)/UDP(sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ip_checksum(resp)
|
|
||||||
assert(UDP in resp)
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(len(udp.payload) > 0), "expecting payload, got none"
|
|
||||||
assert(udp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % udp.payload.load
|
|
||||||
assert(udp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % udp.payload.load
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_tcp_ssh(iface):
|
|
||||||
sport = 37183
|
|
||||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
|
||||||
for i, dport in enumerate(dports):
|
|
||||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
|
||||||
syn = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="S", sport=sport, dport=dport)
|
|
||||||
syn_ack = iface.sr1(syn, timeout=1)
|
|
||||||
assert(syn_ack is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(syn_ack)
|
|
||||||
assert(TCP in syn_ack)
|
|
||||||
syn_ack = syn_ack[TCP]
|
|
||||||
assert(syn_ack.flags == "SA")
|
|
||||||
ack = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="A", sport=sport, dport=dport, ack=syn_ack.seq + 1)
|
|
||||||
_ = iface.sr1(ack, timeout=1)
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/TCP(flags="PA", ack=syn_ack.seq + 1, sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(TCP in resp)
|
|
||||||
tcp = resp[TCP]
|
|
||||||
assert("A" in tcp.flags), "expecting ACK flag, not set (%r)" % tcp.flags
|
|
||||||
assert("P" in tcp.flags), "expecting PSH flag, not set (%r)" % tcp.flags
|
|
||||||
assert(len(tcp.payload) > 0), "expecting payload, got none"
|
|
||||||
assert(tcp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % tcp.payload.load
|
|
||||||
assert(tcp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % tcp.payload.load
|
|
||||||
|
|
||||||
@test
|
|
||||||
def test_ipv6_udp_ssh(iface):
|
|
||||||
sport = 37183
|
|
||||||
dports = [22, 80, 2222, 2022, 23874, 50000]
|
|
||||||
for i, dport in enumerate(dports):
|
|
||||||
banner = [b"SSH-2.0-AsyncSSH_2.1.0", b"SSH-2.0-PuTTY", b"SSH-2.0-libssh2_1.4.3", b"SSH-2.0-Go", b"SSH-2.0-PUTTY"][i % 5]
|
|
||||||
req = Ether(dst=MAC_ADDR)/IPv6(dst=IPV6_ADDR)/UDP(sport=sport, dport=dport)/Raw(banner + b"\r\n")
|
|
||||||
resp = iface.sr1(req, timeout=1)
|
|
||||||
assert(resp is not None), "expecting answer, got nothing"
|
|
||||||
check_ipv6_checksum(resp)
|
|
||||||
assert(UDP in resp)
|
|
||||||
udp = resp[UDP]
|
|
||||||
assert(len(udp.payload) > 0), "expecting payload, got none"
|
|
||||||
assert(udp.payload.load.startswith(b"SSH-2.0-")), "unexpected banner: %r" % udp.payload.load
|
|
||||||
assert(udp.payload.load.endswith(b"\r\n")), "unexpected banner: %r" % udp.payload.load
|
|
||||||
|
|
||||||
def test_all(iface):
|
|
||||||
global tests
|
|
||||||
# execute tests
|
|
||||||
for t in tests:
|
|
||||||
t(iface)
|
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,5 @@
|
||||||
|
|
||||||
IPV4_ADDR = "192.0.0.1"
|
IPV4_ADDR = "192.0.0.1"
|
||||||
IPV6_ADDR = "2001:41d0::ab32:bdb8"
|
IPV6_ADDR = "2001:41d0::ab32:bdb8"
|
||||||
MAC_ADDR = "52:1c:4e:c2:a4:1f"
|
MAC_ADDR = "52:1c:4e:c2:a4:1f"
|
||||||
OUTDIR = "test/res/"
|
OUTDIR = "test/res/"
|
||||||
|
|
|
||||||
102
test/src/core.py
Normal file
102
test/src/core.py
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - 2025 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from scapy.compat import raw
|
||||||
|
from scapy.layers.inet import IP
|
||||||
|
from scapy.layers.inet6 import IPv6
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logs():
|
||||||
|
log = logging.getLogger()
|
||||||
|
log.setLevel(logging.DEBUG)
|
||||||
|
if not log.handlers:
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setFormatter(logging.Formatter("%(levelname)s\t%(message)s"))
|
||||||
|
ch.setLevel(logging.DEBUG)
|
||||||
|
log.addHandler(ch)
|
||||||
|
return log
|
||||||
|
|
||||||
|
|
||||||
|
LOG = setup_logs()
|
||||||
|
TESTS = []
|
||||||
|
ERRORS = []
|
||||||
|
|
||||||
|
|
||||||
|
# decorator to automatically add a function to tests
|
||||||
|
def test(f):
|
||||||
|
OK = "\033[1mOK\033[0m"
|
||||||
|
KO = "\033[1m\033[1;%dmKO\033[0m" % 31
|
||||||
|
fname = f.__name__.ljust(50, ".")
|
||||||
|
|
||||||
|
def w(m):
|
||||||
|
try:
|
||||||
|
# check that masscanned is still running
|
||||||
|
assert m.poll() is None, "masscanned not running"
|
||||||
|
f()
|
||||||
|
# check that masscanned is still running
|
||||||
|
assert m.poll() is None, "masscanned terminated unexpectedly"
|
||||||
|
LOG.info("{}{}".format(fname, OK))
|
||||||
|
except AssertionError as e:
|
||||||
|
LOG.error("{}{}: {}".format(fname, KO, e))
|
||||||
|
ERRORS.append(fname)
|
||||||
|
|
||||||
|
TESTS.append(w)
|
||||||
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def test_all(m):
|
||||||
|
# execute tests
|
||||||
|
for t in TESTS:
|
||||||
|
# perform unit test
|
||||||
|
t(m)
|
||||||
|
LOG.info(f"\033[1mRan {len(TESTS)} tests with {len(ERRORS)} errors\033[0m")
|
||||||
|
return len(ERRORS)
|
||||||
|
|
||||||
|
|
||||||
|
def multicast(ip6):
|
||||||
|
a, b = ip6.split(":")[-2:]
|
||||||
|
mac = ["33", "33", "ff"]
|
||||||
|
if len(a) == 4:
|
||||||
|
mac.append(a[2:])
|
||||||
|
else:
|
||||||
|
mac.append("00")
|
||||||
|
if len(b) >= 2:
|
||||||
|
mac.append(b[:2])
|
||||||
|
else:
|
||||||
|
mac.append("00")
|
||||||
|
if len(b) >= 4:
|
||||||
|
mac.append(b[2:])
|
||||||
|
else:
|
||||||
|
mac.append("00")
|
||||||
|
return ":".join(mac)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ip_checksum(pkt):
|
||||||
|
assert IP in pkt, "no IP layer found"
|
||||||
|
ip_pkt = pkt[IP]
|
||||||
|
chksum = ip_pkt.chksum
|
||||||
|
del ip_pkt.chksum
|
||||||
|
assert IP(raw(ip_pkt)).chksum == chksum, "bad IPv4 checksum"
|
||||||
|
|
||||||
|
|
||||||
|
def check_ipv6_checksum(pkt):
|
||||||
|
assert IPv6 in pkt, "no IP layer found"
|
||||||
|
ip_pkt = pkt[IPv6]
|
||||||
|
chksum = ip_pkt.chksum
|
||||||
|
del ip_pkt.chksum
|
||||||
|
assert IPv6(raw(ip_pkt)).chksum == chksum, "bad IPv6 checksum"
|
||||||
15
test/src/tests/__init__.py
Normal file
15
test/src/tests/__init__.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
51
test/src/tests/arp.py
Normal file
51
test/src/tests/arp.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.l2 import Ether, ARP, ETHER_BROADCAST
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, MAC_ADDR
|
||||||
|
from ..core import test
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_arp_req():
|
||||||
|
##### ARP #####
|
||||||
|
arp_req = Ether(dst=ETHER_BROADCAST) / ARP(pdst=IPV4_ADDR)
|
||||||
|
arp_repl = srp1(arp_req, timeout=1)
|
||||||
|
assert arp_repl is not None, "expecting answer, got nothing"
|
||||||
|
assert ARP in arp_repl, "no ARP layer found"
|
||||||
|
arp_repl = arp_repl[ARP]
|
||||||
|
# check answer
|
||||||
|
## op is "is-at"
|
||||||
|
assert arp_repl.op == 2, "unexpected ARP op: {}".format(arp_repl.op)
|
||||||
|
## answer for the requested IP
|
||||||
|
assert arp_repl.psrc == arp_req.pdst, "unexpected ARP psrc: {}".format(
|
||||||
|
arp_repl.psrc
|
||||||
|
)
|
||||||
|
assert arp_repl.pdst == arp_req.psrc, "unexpected ARP pdst: {}".format(
|
||||||
|
arp_repl.pdst
|
||||||
|
)
|
||||||
|
## answer is expected MAC address
|
||||||
|
assert arp_repl.hwsrc == MAC_ADDR, "unexpected ARP hwsrc: {}".format(arp_repl.hwsrc)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_arp_req_other_ip():
|
||||||
|
##### ARP #####
|
||||||
|
arp_req = Ether(dst=ETHER_BROADCAST) / ARP(pdst="1.2.3.4")
|
||||||
|
arp_repl = srp1(arp_req, timeout=1)
|
||||||
|
assert arp_repl is None, "responding to ARP requests for other IP addresses"
|
||||||
158
test/src/tests/dns.py
Normal file
158
test/src/tests/dns.py
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2022 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.compat import raw
|
||||||
|
from scapy.layers.dns import DNS, DNSQR
|
||||||
|
from scapy.layers.inet import IP, UDP
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_dns_in_a():
|
||||||
|
sports = [53, 13274, 0]
|
||||||
|
dports = [53, 5353, 80, 161, 24732]
|
||||||
|
for sport in sports:
|
||||||
|
for dport in dports:
|
||||||
|
for domain in ["example.com", "www.example.com", "masscan.ned"]:
|
||||||
|
qd = DNSQR(qname=domain, qtype="A", qclass="IN")
|
||||||
|
dns_req = DNS(id=1234, rd=False, opcode=0, qd=qd)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ dns_req
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert UDP in resp, "no UDP layer found"
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert (
|
||||||
|
udp.sport == dport
|
||||||
|
), f"unexpected UDP sport: {udp.sport!r} ({domain})"
|
||||||
|
assert (
|
||||||
|
udp.dport == sport
|
||||||
|
), f"unexpected UDP dport: {udp.dport!r} ({domain})"
|
||||||
|
if DNS not in udp:
|
||||||
|
try:
|
||||||
|
dns_rep = DNS(udp.load)
|
||||||
|
except Exception:
|
||||||
|
raise AssertionError("no DNS layer found")
|
||||||
|
else:
|
||||||
|
dns_rep = udp[DNS]
|
||||||
|
assert (
|
||||||
|
dns_rep.id == 1234
|
||||||
|
), f"unexpected id value: {dns_rep.id!r} ({domain})"
|
||||||
|
assert dns_rep.qr, "unexpected qr value"
|
||||||
|
assert dns_rep.opcode == 0, "unexpected opcode value"
|
||||||
|
assert dns_rep.aa, "unexpected aa value"
|
||||||
|
assert not dns_rep.tc, "unexpected tc value"
|
||||||
|
assert not dns_rep.rd, "unexpected rd value"
|
||||||
|
assert not dns_rep.ra, "unexpected ra value"
|
||||||
|
assert dns_rep.z == 0, "unexpected z value"
|
||||||
|
assert dns_rep.rcode == 0, "unexpected rcode value"
|
||||||
|
assert (
|
||||||
|
dns_rep.qdcount == 1
|
||||||
|
), f"unexpected qdcount value: {dns_rep.qdcount!r} vs 1 ({domain})"
|
||||||
|
assert dns_rep.ancount == 1, "unexpected ancount value"
|
||||||
|
assert dns_rep.nscount == 0, "unexpected nscount value"
|
||||||
|
assert dns_rep.arcount == 0, "unexpected arcount value"
|
||||||
|
assert raw(dns_rep.qd[0]) == raw(
|
||||||
|
dns_req.qd[0]
|
||||||
|
), "query in request and response do not match"
|
||||||
|
assert raw(dns_rep.qd[0].qname) == raw(
|
||||||
|
dns_req.qd[0].qname
|
||||||
|
), "qname query in request and response do not match"
|
||||||
|
assert (
|
||||||
|
dns_rep.an[0].rrname == dns_req.qd[0].qname
|
||||||
|
), "rrname in answer does not match qname in request"
|
||||||
|
assert (
|
||||||
|
dns_rep.an[0].rclass == dns_req.qd[0].qclass
|
||||||
|
), "class in answer does not match query"
|
||||||
|
assert (
|
||||||
|
dns_rep.an[0].type == dns_req.qd[0].qtype
|
||||||
|
), "type in answer does not match query"
|
||||||
|
assert dns_rep.an[0].rdata == IPV4_ADDR
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_dns_in_a_multiple_queries():
|
||||||
|
sports = [53, 13274, 12198, 888, 0]
|
||||||
|
dports = [53, 5353, 80, 161, 24732]
|
||||||
|
for sport in sports:
|
||||||
|
for dport in dports:
|
||||||
|
qd = [
|
||||||
|
DNSQR(qname="www.example1.com", qtype="A", qclass="IN"),
|
||||||
|
DNSQR(qname="www.example2.com", qtype="A", qclass="IN"),
|
||||||
|
DNSQR(qname="www.example3.com", qtype="A", qclass="IN"),
|
||||||
|
]
|
||||||
|
dns_req = DNS(id=1234, rd=False, opcode=0, qd=qd)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ dns_req
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert UDP in resp, "no UDP layer found"
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert udp.sport == dport, "unexpected UDP sport: {}".format(udp.sport)
|
||||||
|
assert udp.dport == sport, "unexpected UDP dport: {}".format(udp.dport)
|
||||||
|
if DNS not in udp:
|
||||||
|
try:
|
||||||
|
dns_rep = DNS(udp.load)
|
||||||
|
except Exception:
|
||||||
|
raise AssertionError("no DNS layer found")
|
||||||
|
else:
|
||||||
|
dns_rep = udp[DNS]
|
||||||
|
assert dns_rep.id == 1234, f"unexpected id value: {dns_rep.id}"
|
||||||
|
assert dns_rep.qr, "unexpected qr value"
|
||||||
|
assert dns_rep.opcode == 0, "unexpected opcode value"
|
||||||
|
assert dns_rep.aa, "unexpected aa value"
|
||||||
|
assert not dns_rep.tc, "unexpected tc value"
|
||||||
|
assert not dns_rep.rd, "unexpected rd value"
|
||||||
|
assert not dns_rep.ra, "unexpected ra value"
|
||||||
|
assert dns_rep.z == 0, "unexpected z value"
|
||||||
|
assert dns_rep.rcode == 0, "unexpected rcode value"
|
||||||
|
assert (
|
||||||
|
dns_rep.qdcount == 3
|
||||||
|
), f"unexpected qdcount value: {dns_rep.qdcount} vs 3"
|
||||||
|
assert dns_rep.ancount == 3, "unexpected ancount value"
|
||||||
|
assert dns_rep.nscount == 0, "unexpected nscount value"
|
||||||
|
assert dns_rep.arcount == 0, "unexpected arcount value"
|
||||||
|
for i, q in enumerate(qd):
|
||||||
|
assert raw(dns_rep.qd[i]) == raw(
|
||||||
|
dns_req.qd[i]
|
||||||
|
), f"query in request and response do not match ({i})"
|
||||||
|
assert raw(dns_rep.qd[i].qname) == raw(
|
||||||
|
dns_req.qd[i].qname
|
||||||
|
), f"qname query in request and response do not match ({i})"
|
||||||
|
assert (
|
||||||
|
dns_rep.an[i].rrname == dns_req.qd[i].qname
|
||||||
|
), f"rrname in answer does not match qname in request ({i})"
|
||||||
|
assert (
|
||||||
|
dns_rep.an[i].rclass == dns_req.qd[i].qclass
|
||||||
|
), f"class in answer does not match query ({i})"
|
||||||
|
assert (
|
||||||
|
dns_rep.an[i].type == dns_req.qd[i].qtype
|
||||||
|
), f"type in answer does not match query ({i})"
|
||||||
|
assert dns_rep.an[i].rdata == IPV4_ADDR
|
||||||
87
test/src/tests/ghost.py
Normal file
87
test/src/tests/ghost.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
from scapy.compat import raw
|
||||||
|
from scapy.layers.inet import IP, TCP
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
from scapy.volatile import RandInt
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_ghost():
|
||||||
|
sport = 37184
|
||||||
|
dports = [22, 23874]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
banner = b"Gh0st\xad\x00\x00\x00\xe0\x00\x00\x00x\x9cKS``\x98\xc3\xc0\xc0\xc0\x06\xc4\x8c@\xbcQ\x96\x81\x81\tH\x07\xa7\x16\x95e&\xa7*\x04$&g+\x182\x94\xf6\xb000\xac\xa8rc\x00\x01\x11\xa0\x82\x1f\\`&\x83\xc7K7\x86\x19\xe5n\x0c9\x95n\x0c;\x84\x0f3\xac\xe8sch\xa8^\xcf4'J\x97\xa9\x82\xe30\xc3\x91h]&\x90\xf8\xce\x97S\xcbA4L?2=\xe1\xc4\x92\x86\x0b@\xf5`\x0cT\x1f\xae\xaf]\nr\x0b\x03#\xa3\xdc\x02~\x06\x86\x03+\x18m\xc2=\xfdtC,C\xfdL<<==\\\x9d\x19\x88\x00\xe5 \x02\x00T\xf5+\\"
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA"
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw(banner)
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert "A" in tcp.flags, "expecting ACK flag, not set (%r)" % tcp.flags
|
||||||
|
assert "P" in tcp.flags, "expecting PSH flag, not set (%r)" % tcp.flags
|
||||||
|
data = raw(tcp.payload)
|
||||||
|
assert data, "expecting payload, got none"
|
||||||
|
assert data.startswith(b"Gh0st"), "unexpected banner: %r" % tcp.payload.load
|
||||||
|
data_len, uncompressed_len = struct.unpack("<II", data[5:13])
|
||||||
|
assert len(data) == data_len, "invalid Ghost payload: %r" % data
|
||||||
|
assert len(zlib.decompress(data[13:])) == uncompressed_len, (
|
||||||
|
"invalid Ghost payload: %r" % data
|
||||||
|
)
|
||||||
409
test/src/tests/http.py
Normal file
409
test/src/tests/http.py
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP, TCP, UDP
|
||||||
|
from scapy.layers.inet6 import IPv6
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
from scapy.volatile import RandInt
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum, check_ipv6_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_http():
|
||||||
|
sport = 24592
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw("GET / HTTP/1.1\r\n\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_http_segmented():
|
||||||
|
sport = 24593
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
# request is not complete yet
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw("GET / HTTP/1.1\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
assert resp[TCP].flags == "A", (
|
||||||
|
'expecting TCP flag "A", got %r' % resp[TCP].flags
|
||||||
|
)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + len(req) + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw("\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert tcp.flags == "PA"
|
||||||
|
assert tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_http_incomplete():
|
||||||
|
sport = 24595
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
# purposedly incomplete request (missing additionnal ending \r\n)
|
||||||
|
/ Raw("GET / HTTP/1.1\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting an answer, got none"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert tcp.flags == "A", "expecting TCP flag A, got {}".format(tcp.flags)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_tcp_http():
|
||||||
|
sport = 24594
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA"
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw("GET / HTTP/1.1\r\n\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert tcp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_http():
|
||||||
|
sport = 24592
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw("GET / HTTP/1.1\r\n\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert UDP in resp
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_udp_http():
|
||||||
|
sport = 24592
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw("GET / HTTP/1.1\r\n\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert UDP in resp
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert udp.payload.load.startswith(b"HTTP/1.1 401 Unauthorized\n")
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_http_ko():
|
||||||
|
sport = 24596
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA"
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw(bytes.fromhex("4f5054494f4e53"))
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
assert "P" not in resp[TCP].flags
|
||||||
|
assert len(resp[TCP].payload) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_http_ko():
|
||||||
|
sport = 24592
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(bytes.fromhex("4f5054494f4e53"))
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is None, "expecting no answer, got one"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_tcp_http_ko():
|
||||||
|
sport = 24597
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA"
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw(bytes.fromhex("4f5054494f4e53"))
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
assert "P" not in resp[TCP].flags
|
||||||
|
assert len(resp[TCP].payload) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_udp_http_ko():
|
||||||
|
sport = 24592
|
||||||
|
dports = [80, 443, 5000, 53228]
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(bytes.fromhex("4f5054494f4e53"))
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is None, "expecting no answer, got one"
|
||||||
45
test/src/tests/icmpv4.py
Normal file
45
test/src/tests/icmpv4.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP, ICMP
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_icmpv4_echo_req():
|
||||||
|
##### ICMPv4 #####
|
||||||
|
icmp_req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ ICMP(type=8, code=0)
|
||||||
|
/ Raw("idrinkwaytoomuchcoffee")
|
||||||
|
)
|
||||||
|
icmp_repl = srp1(icmp_req, timeout=1)
|
||||||
|
assert icmp_repl is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(icmp_repl)
|
||||||
|
assert ICMP in icmp_repl
|
||||||
|
icmp_repl = icmp_repl[ICMP]
|
||||||
|
# check answer
|
||||||
|
## type is "echo-reply"
|
||||||
|
assert icmp_repl.type == 0
|
||||||
|
assert icmp_repl.code == 0
|
||||||
|
## data is the same as sent
|
||||||
|
assert icmp_repl.load == icmp_req.load
|
||||||
87
test/src/tests/icmpv6.py
Normal file
87
test/src/tests/icmpv6.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet6 import (
|
||||||
|
ICMPv6EchoReply,
|
||||||
|
ICMPv6EchoRequest,
|
||||||
|
ICMPv6NDOptDstLLAddr,
|
||||||
|
ICMPv6ND_NA,
|
||||||
|
ICMPv6ND_NS,
|
||||||
|
IPv6,
|
||||||
|
)
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV6_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, multicast
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_icmpv6_neighbor_solicitation():
|
||||||
|
##### IPv6 Neighbor Solicitation #####
|
||||||
|
for mac in [
|
||||||
|
"ff:ff:ff:ff:ff:ff",
|
||||||
|
"33:33:00:00:00:01",
|
||||||
|
MAC_ADDR,
|
||||||
|
multicast(IPV6_ADDR),
|
||||||
|
]:
|
||||||
|
nd_ns = Ether(dst=mac) / IPv6() / ICMPv6ND_NS(tgt=IPV6_ADDR)
|
||||||
|
nd_na = srp1(nd_ns, timeout=1)
|
||||||
|
assert nd_na is not None, "expecting answer, got nothing"
|
||||||
|
assert ICMPv6ND_NA in nd_na
|
||||||
|
nd_na = nd_na[ICMPv6ND_NA]
|
||||||
|
# check answer content
|
||||||
|
assert nd_na.code == 0
|
||||||
|
assert nd_na.R == 0
|
||||||
|
assert nd_na.S == 1
|
||||||
|
assert nd_na.O == 1 # noqa: E741
|
||||||
|
assert nd_na.tgt == IPV6_ADDR
|
||||||
|
# check ND Option
|
||||||
|
assert nd_na.haslayer(ICMPv6NDOptDstLLAddr)
|
||||||
|
assert nd_na.getlayer(ICMPv6NDOptDstLLAddr).lladdr == MAC_ADDR
|
||||||
|
for mac in ["00:00:00:00:00:00", "33:33:33:00:00:01"]:
|
||||||
|
nd_ns = Ether(dst="ff:ff:ff:ff:ff:ff") / IPv6() / ICMPv6ND_NS(tgt=IPV6_ADDR)
|
||||||
|
nd_na = srp1(nd_ns, timeout=1)
|
||||||
|
assert nd_na is not None, "expecting no answer, got one"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_icmpv6_neighbor_solicitation_other_ip():
|
||||||
|
##### IPv6 Neighbor Solicitation #####
|
||||||
|
nd_ns = (
|
||||||
|
Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||||
|
/ IPv6()
|
||||||
|
/ ICMPv6ND_NS(tgt="2020:4141:3030:2020::bdbd")
|
||||||
|
)
|
||||||
|
nd_na = srp1(nd_ns, timeout=1)
|
||||||
|
assert nd_na is None, "responding to ND_NS for other IP addresses"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_icmpv6_echo_req():
|
||||||
|
##### IPv6 Ping #####
|
||||||
|
echo_req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ ICMPv6EchoRequest(data="waytoomanynapkins")
|
||||||
|
)
|
||||||
|
echo_repl = srp1(echo_req, timeout=1)
|
||||||
|
assert echo_repl is not None, "expecting answer, got nothing"
|
||||||
|
assert ICMPv6EchoReply in echo_repl
|
||||||
|
echo_repl = echo_repl[ICMPv6EchoReply]
|
||||||
|
# check answer content
|
||||||
|
assert echo_repl.code == 0
|
||||||
|
assert echo_repl.data == echo_req.data
|
||||||
50
test/src/tests/ip.py
Normal file
50
test/src/tests/ip.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP, ICMP
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_req():
|
||||||
|
##### IP #####
|
||||||
|
ip_req = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR, id=0x1337) / ICMP(type=8, code=0)
|
||||||
|
ip_repl = srp1(ip_req, timeout=1)
|
||||||
|
assert ip_repl is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(ip_repl)
|
||||||
|
assert IP in ip_repl, "no IP layer in response"
|
||||||
|
ip_repl = ip_repl[IP]
|
||||||
|
assert ip_repl.id == 0, "IP identification unexpected"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_eth_req_other_mac():
|
||||||
|
#### ETH ####
|
||||||
|
ip_req = Ether(dst="00:00:00:11:11:11") / IP(dst=IPV4_ADDR) / ICMP(type=8, code=0)
|
||||||
|
ip_repl = srp1(ip_req, timeout=1)
|
||||||
|
assert ip_repl is None, "responding to other MAC addresses"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_req_other_ip():
|
||||||
|
##### IP #####
|
||||||
|
ip_req = Ether(dst=MAC_ADDR) / IP(dst="1.2.3.4") / ICMP(type=8, code=0)
|
||||||
|
ip_repl = srp1(ip_req, timeout=1)
|
||||||
|
assert ip_repl is None, "responding to other IP addresses"
|
||||||
112
test/src/tests/rpc.py
Normal file
112
test/src/tests/rpc.py
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from subprocess import check_call
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ivre.db import DBNmap
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR
|
||||||
|
from ..core import test
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_rpc_nmap():
|
||||||
|
for scan in "SU":
|
||||||
|
with NamedTemporaryFile(delete=False) as xml_result:
|
||||||
|
check_call(
|
||||||
|
[
|
||||||
|
"nmap",
|
||||||
|
"-n",
|
||||||
|
"-vv",
|
||||||
|
"-oX",
|
||||||
|
"-",
|
||||||
|
IPV4_ADDR,
|
||||||
|
f"-s{scan}V",
|
||||||
|
"-p",
|
||||||
|
"111",
|
||||||
|
"--script",
|
||||||
|
"rpcinfo,rpc-grind",
|
||||||
|
],
|
||||||
|
stdout=xml_result,
|
||||||
|
)
|
||||||
|
with NamedTemporaryFile(delete=False, mode="w") as json_result:
|
||||||
|
DBNmap(output=json_result).store_scan(xml_result.name)
|
||||||
|
os.unlink(xml_result.name)
|
||||||
|
with open(json_result.name) as fdesc:
|
||||||
|
results = [json.loads(line) for line in fdesc]
|
||||||
|
os.unlink(json_result.name)
|
||||||
|
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
|
||||||
|
result = results[0]
|
||||||
|
assert len(result["ports"]) == 1, f"Expected 1 port, got {len(result['ports'])}"
|
||||||
|
port = result["ports"][0]
|
||||||
|
assert port["port"] == 111, f"Expected port 111, got {port['port']}"
|
||||||
|
assert port["protocol"] == (
|
||||||
|
"tcp" if scan == "S" else "udp"
|
||||||
|
), f"Unexpected proto {port['protocol']} for scan {scan}"
|
||||||
|
assert port["service_name"] in {
|
||||||
|
"nfs",
|
||||||
|
"rpcbind",
|
||||||
|
"rstatd",
|
||||||
|
"rusersd",
|
||||||
|
}, f"Unexpected service_name: {port['service_name']}"
|
||||||
|
assert port["service_extrainfo"] in {
|
||||||
|
"RPC #100000",
|
||||||
|
"RPC #100001",
|
||||||
|
"RPC #100002",
|
||||||
|
"RPC #100003",
|
||||||
|
}, f"Unexpected service_extrainfo: {port['service_extrainfo']}"
|
||||||
|
assert (
|
||||||
|
len(port["scripts"]) == 1
|
||||||
|
), f"Expected 1 script, got {len(port['scripts'])}"
|
||||||
|
script = port["scripts"][0]
|
||||||
|
assert script["id"] == "rpcinfo", "Expected rpcinfo script, not found"
|
||||||
|
assert (
|
||||||
|
len(script["rpcinfo"]) == 1
|
||||||
|
), f"Expected 1 rpcinfo, got {len(script['rpcinfo'])}"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_rpcinfo():
|
||||||
|
with NamedTemporaryFile(delete=False) as rpcout:
|
||||||
|
check_call(["rpcinfo", "-p", IPV4_ADDR], stdout=rpcout)
|
||||||
|
with open(rpcout.name) as fdesc:
|
||||||
|
found = []
|
||||||
|
for line in fdesc:
|
||||||
|
line = line.split()
|
||||||
|
if line[0] == "program":
|
||||||
|
# header
|
||||||
|
continue
|
||||||
|
assert line[0] == "100000", f"Expected program 100000, got {line[0]}"
|
||||||
|
found.append(int(line[1]))
|
||||||
|
assert len(found) == 3, f"Expected three versions, got {found}"
|
||||||
|
for i in range(2, 5):
|
||||||
|
assert i in found, f"Missing version {i} in {found}"
|
||||||
|
os.unlink(rpcout.name)
|
||||||
|
with NamedTemporaryFile(delete=False) as rpcout:
|
||||||
|
check_call(["rpcinfo", "-u", IPV4_ADDR, "100000"], stdout=rpcout)
|
||||||
|
with open(rpcout.name) as fdesc:
|
||||||
|
found = []
|
||||||
|
expr = re.compile("^program 100000 version ([0-9]) ready and waiting$")
|
||||||
|
for line in fdesc:
|
||||||
|
found.append(int(expr.search(line.strip()).group(1)))
|
||||||
|
assert len(found) == 3, f"Expected three versions, got {found}"
|
||||||
|
for i in range(2, 5):
|
||||||
|
assert i in found, f"Missing version {i} in {found}"
|
||||||
|
os.unlink(rpcout.name)
|
||||||
65
test/src/tests/smb.py
Normal file
65
test/src/tests/smb.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ..core import test
|
||||||
|
from ..conf import IPV4_ADDR
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_smb1_network_req():
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
"smbclient",
|
||||||
|
"-U ''",
|
||||||
|
"-N",
|
||||||
|
"-d 6",
|
||||||
|
"-t 1",
|
||||||
|
"-L",
|
||||||
|
IPV4_ADDR,
|
||||||
|
"--option=client min protocol=NT1",
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
out, _ = proc.communicate()
|
||||||
|
assert f"Connecting to {IPV4_ADDR} at port 445" in out, "\n" + out
|
||||||
|
assert "session request ok" in out, "\n" + out
|
||||||
|
assert f"negotiated dialect[NT1] against server[{IPV4_ADDR}]" in out, "\n" + out
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_smb2_network_req():
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
"smbclient",
|
||||||
|
"-U ''",
|
||||||
|
"-N",
|
||||||
|
"-d 5",
|
||||||
|
"-t 1",
|
||||||
|
"-L",
|
||||||
|
IPV4_ADDR,
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
out, _ = proc.communicate()
|
||||||
|
assert f"Connecting to {IPV4_ADDR} at port 445" in out, "\n" + out
|
||||||
|
assert "session request ok" in out, "\n" + out
|
||||||
|
assert f"negotiated dialect[SMB2_02] against server[{IPV4_ADDR}]" in out, "\n" + out
|
||||||
217
test/src/tests/ssh.py
Normal file
217
test/src/tests/ssh.py
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP, TCP, UDP
|
||||||
|
from scapy.layers.inet6 import IPv6
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
from scapy.volatile import RandInt
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum, check_ipv6_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_ssh():
|
||||||
|
sport = 37183
|
||||||
|
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||||
|
for i, dport in enumerate(dports):
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
banner = [
|
||||||
|
b"SSH-2.0-AsyncSSH_2.1.0",
|
||||||
|
b"SSH-2.0-PuTTY",
|
||||||
|
b"SSH-2.0-libssh2_1.4.3",
|
||||||
|
b"SSH-2.0-Go",
|
||||||
|
b"SSH-2.0-PUTTY",
|
||||||
|
][i % 5]
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA"
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw(banner + b"\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert "A" in tcp.flags, "expecting ACK flag, not set (%r)" % tcp.flags
|
||||||
|
assert "P" in tcp.flags, "expecting PSH flag, not set (%r)" % tcp.flags
|
||||||
|
assert len(tcp.payload) > 0, "expecting payload, got none"
|
||||||
|
assert tcp.payload.load.startswith(b"SSH-2.0-"), (
|
||||||
|
"unexpected banner: %r" % tcp.payload.load
|
||||||
|
)
|
||||||
|
assert tcp.payload.load.endswith(b"\r\n"), (
|
||||||
|
"unexpected banner: %r" % tcp.payload.load
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_ssh():
|
||||||
|
sport = 37183
|
||||||
|
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||||
|
for i, dport in enumerate(dports):
|
||||||
|
banner = [
|
||||||
|
b"SSH-2.0-AsyncSSH_2.1.0",
|
||||||
|
b"SSH-2.0-PuTTY",
|
||||||
|
b"SSH-2.0-libssh2_1.4.3",
|
||||||
|
b"SSH-2.0-Go",
|
||||||
|
b"SSH-2.0-PUTTY",
|
||||||
|
][i % 5]
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(banner + b"\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert UDP in resp
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert len(udp.payload) > 0, "expecting payload, got none"
|
||||||
|
assert udp.payload.load.startswith(b"SSH-2.0-"), (
|
||||||
|
"unexpected banner: %r" % udp.payload.load
|
||||||
|
)
|
||||||
|
assert udp.payload.load.endswith(b"\r\n"), (
|
||||||
|
"unexpected banner: %r" % udp.payload.load
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_tcp_ssh():
|
||||||
|
sport = 37183
|
||||||
|
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||||
|
for i, dport in enumerate(dports):
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
banner = [
|
||||||
|
b"SSH-2.0-AsyncSSH_2.1.0",
|
||||||
|
b"SSH-2.0-PuTTY",
|
||||||
|
b"SSH-2.0-libssh2_1.4.3",
|
||||||
|
b"SSH-2.0-Go",
|
||||||
|
b"SSH-2.0-PUTTY",
|
||||||
|
][i % 5]
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=dport, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA"
|
||||||
|
ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="A",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_ = srp1(ack, timeout=1)
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA",
|
||||||
|
sport=sport,
|
||||||
|
dport=dport,
|
||||||
|
seq=seq_init + 1,
|
||||||
|
ack=syn_ack.seq + 1,
|
||||||
|
)
|
||||||
|
/ Raw(banner + b"\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert TCP in resp, "expecting TCP, got %r" % resp.summary()
|
||||||
|
tcp = resp[TCP]
|
||||||
|
assert "A" in tcp.flags, "expecting ACK flag, not set (%r)" % tcp.flags
|
||||||
|
assert "P" in tcp.flags, "expecting PSH flag, not set (%r)" % tcp.flags
|
||||||
|
assert len(tcp.payload) > 0, "expecting payload, got none"
|
||||||
|
assert tcp.payload.load.startswith(b"SSH-2.0-"), (
|
||||||
|
"unexpected banner: %r" % tcp.payload.load
|
||||||
|
)
|
||||||
|
assert tcp.payload.load.endswith(b"\r\n"), (
|
||||||
|
"unexpected banner: %r" % tcp.payload.load
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_udp_ssh():
|
||||||
|
sport = 37183
|
||||||
|
dports = [22, 80, 2222, 2022, 23874, 50000]
|
||||||
|
for i, dport in enumerate(dports):
|
||||||
|
banner = [
|
||||||
|
b"SSH-2.0-AsyncSSH_2.1.0",
|
||||||
|
b"SSH-2.0-PuTTY",
|
||||||
|
b"SSH-2.0-libssh2_1.4.3",
|
||||||
|
b"SSH-2.0-Go",
|
||||||
|
b"SSH-2.0-PUTTY",
|
||||||
|
][i % 5]
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(banner + b"\r\n")
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert UDP in resp
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert len(udp.payload) > 0, "expecting payload, got none"
|
||||||
|
assert udp.payload.load.startswith(b"SSH-2.0-"), (
|
||||||
|
"unexpected banner: %r" % udp.payload.load
|
||||||
|
)
|
||||||
|
assert udp.payload.load.endswith(b"\r\n"), (
|
||||||
|
"unexpected banner: %r" % udp.payload.load
|
||||||
|
)
|
||||||
196
test/src/tests/stun.py
Normal file
196
test/src/tests/stun.py
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from socket import AF_INET6
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP, UDP
|
||||||
|
from scapy.layers.inet6 import IPv6
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.pton_ntop import inet_pton
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum, check_ipv6_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_stun():
|
||||||
|
sports = [12345, 55555, 80, 43273]
|
||||||
|
dports = [80, 800, 8000, 3478]
|
||||||
|
payload = bytes.fromhex("000100002112a442000000000000000000000000")
|
||||||
|
for sport in sports:
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(payload)
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert UDP in resp, "no UDP layer found"
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert udp.sport == dport, "unexpected UDP sport: {}".format(udp.sport)
|
||||||
|
assert udp.dport == sport, "unexpected UDP dport: {}".format(udp.dport)
|
||||||
|
resp_payload = udp.payload.load
|
||||||
|
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
|
||||||
|
tid = resp_payload[8:20]
|
||||||
|
data = resp_payload[20:]
|
||||||
|
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||||
|
assert length == 12, "expected length 12, got {}".format(length)
|
||||||
|
assert (
|
||||||
|
magic == 0x2112A442
|
||||||
|
), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
|
||||||
|
assert (
|
||||||
|
tid == b"\x00" * 12
|
||||||
|
), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
|
||||||
|
expected_data = b"\x00\x01\x00\x08\x00\x01" + struct.pack(
|
||||||
|
">HBBBB", sport, 192, 0, 0, 0
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
data == expected_data
|
||||||
|
), f"unexpected data {data!r} != {expected_data!r}"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_udp_stun():
|
||||||
|
sports = [12345, 55555, 80, 43273]
|
||||||
|
dports = [80, 800, 8000, 3478]
|
||||||
|
payload = bytes.fromhex("000100002112a442000000000000000000000000")
|
||||||
|
for sport in sports:
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(payload)
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert UDP in resp
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert udp.sport == dport
|
||||||
|
assert udp.dport == sport
|
||||||
|
resp_payload = udp.payload.load
|
||||||
|
type_, length, magic = struct.unpack(">HHI", resp_payload[:8])
|
||||||
|
tid = resp_payload[8:20]
|
||||||
|
data = resp_payload[20:]
|
||||||
|
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||||
|
assert length == 24, "expected length 24, got {}".format(length)
|
||||||
|
assert (
|
||||||
|
magic == 0x2112A442
|
||||||
|
), "expected magic 0x2112a442, got 0x{:08x}".format(magic)
|
||||||
|
assert (
|
||||||
|
tid == b"\x00" * 12
|
||||||
|
), "expected tid 0x000000000000000000000000, got {:x}".format(tid)
|
||||||
|
expected_data = (
|
||||||
|
bytes.fromhex("000100140002")
|
||||||
|
+ struct.pack(">H", sport)
|
||||||
|
+ inet_pton(AF_INET6, "2001:41d0::1234:5678")
|
||||||
|
)
|
||||||
|
assert data == expected_data, "unexpected data: {}".format(data)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_stun_change_port():
|
||||||
|
sports = [12345, 55555, 80, 43273]
|
||||||
|
dports = [80, 800, 8000, 3478, 65535]
|
||||||
|
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
|
||||||
|
for sport in sports:
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(payload)
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(resp)
|
||||||
|
assert UDP in resp, "no UDP layer found"
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert (
|
||||||
|
udp.sport == (dport + 1) % 2**16
|
||||||
|
), "expected answer from UDP/{}, got it from UDP/{}".format(
|
||||||
|
(dport + 1) % 2**16, udp.sport
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
udp.dport == sport
|
||||||
|
), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
|
||||||
|
resp_payload = udp.payload.load
|
||||||
|
type_, length = struct.unpack(">HH", resp_payload[:4])
|
||||||
|
tid = resp_payload[4:20]
|
||||||
|
data = resp_payload[20:]
|
||||||
|
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||||
|
assert length == 12, "expected length 12, got {}".format(length)
|
||||||
|
assert tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c"), (
|
||||||
|
"expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
|
||||||
|
)
|
||||||
|
expected_data = b"\x00\x01\x00\x08\x00\x01" + struct.pack(
|
||||||
|
">HBBBB", sport, 192, 0, 0, 0
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
data == expected_data
|
||||||
|
), f"unexpected data {data!r} != {expected_data!r}"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_udp_stun_change_port():
|
||||||
|
sports = [12345, 55555, 80, 43273]
|
||||||
|
dports = [80, 800, 8000, 3478, 65535]
|
||||||
|
payload = bytes.fromhex("0001000803a3b9464dd8eb75e19481474293845c0003000400000002")
|
||||||
|
for sport in sports:
|
||||||
|
for dport in dports:
|
||||||
|
req = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ UDP(sport=sport, dport=dport)
|
||||||
|
/ Raw(payload)
|
||||||
|
)
|
||||||
|
resp = srp1(req, timeout=1)
|
||||||
|
assert resp is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(resp)
|
||||||
|
assert UDP in resp, "expecting UDP layer in answer, got nothing"
|
||||||
|
udp = resp[UDP]
|
||||||
|
assert (
|
||||||
|
udp.sport == (dport + 1) % 2**16
|
||||||
|
), "expected answer from UDP/{}, got it from UDP/{}".format(
|
||||||
|
(dport + 1) % 2**16, udp.sport
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
udp.dport == sport
|
||||||
|
), "expected answer to UDP/{}, got it to UDP/{}".format(sport, udp.dport)
|
||||||
|
resp_payload = udp.payload.load
|
||||||
|
type_, length = struct.unpack(">HH", resp_payload[:4])
|
||||||
|
tid = resp_payload[4:20]
|
||||||
|
data = resp_payload[20:]
|
||||||
|
assert type_ == 0x0101, "expected type 0X0101, got 0x{:04x}".format(type_)
|
||||||
|
assert length == 24, "expected length 12, got {}".format(length)
|
||||||
|
assert tid == bytes.fromhex("03a3b9464dd8eb75e19481474293845c"), (
|
||||||
|
"expected tid 0x03a3b9464dd8eb75e19481474293845c, got %r" % tid
|
||||||
|
)
|
||||||
|
expected_data = (
|
||||||
|
bytes.fromhex("000100140002")
|
||||||
|
+ struct.pack(">H", sport)
|
||||||
|
+ inet_pton(AF_INET6, "2001:41d0::1234:5678")
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
data == expected_data
|
||||||
|
), f"unexpected data {data!r} != {expected_data!r}"
|
||||||
303
test/src/tests/tcp.py
Normal file
303
test/src/tests/tcp.py
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP, TCP
|
||||||
|
from scapy.layers.inet6 import IPv6
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
from scapy.volatile import RandInt
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
|
||||||
|
from ..core import test, check_ip_checksum, check_ipv6_checksum
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_empty():
|
||||||
|
for p in [0, 80, 443]:
|
||||||
|
req = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR, proto=6) / Raw() # UDP
|
||||||
|
repl = srp1(req, timeout=1)
|
||||||
|
assert repl is None, "expecting no answer, got one"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_tcp_empty():
|
||||||
|
for p in [0, 80, 443]:
|
||||||
|
req = Ether(dst=MAC_ADDR) / IPv6(dst=IPV6_ADDR, nh=6) / Raw() # UDP
|
||||||
|
repl = srp1(req, timeout=1)
|
||||||
|
assert repl is None, "expecting no answer, got one"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_tcp_syn():
|
||||||
|
##### SYN-ACK #####
|
||||||
|
# test a list of ports, randomly generated once
|
||||||
|
ports_to_test = [
|
||||||
|
1152,
|
||||||
|
2003,
|
||||||
|
2193,
|
||||||
|
3709,
|
||||||
|
4054,
|
||||||
|
6605,
|
||||||
|
6737,
|
||||||
|
6875,
|
||||||
|
7320,
|
||||||
|
8898,
|
||||||
|
9513,
|
||||||
|
9738,
|
||||||
|
10623,
|
||||||
|
10723,
|
||||||
|
11253,
|
||||||
|
12125,
|
||||||
|
12189,
|
||||||
|
12873,
|
||||||
|
14648,
|
||||||
|
14659,
|
||||||
|
16242,
|
||||||
|
16243,
|
||||||
|
17209,
|
||||||
|
17492,
|
||||||
|
17667,
|
||||||
|
17838,
|
||||||
|
18081,
|
||||||
|
18682,
|
||||||
|
18790,
|
||||||
|
19124,
|
||||||
|
19288,
|
||||||
|
19558,
|
||||||
|
19628,
|
||||||
|
19789,
|
||||||
|
20093,
|
||||||
|
21014,
|
||||||
|
21459,
|
||||||
|
21740,
|
||||||
|
24070,
|
||||||
|
24312,
|
||||||
|
24576,
|
||||||
|
26939,
|
||||||
|
27136,
|
||||||
|
27165,
|
||||||
|
27361,
|
||||||
|
29971,
|
||||||
|
31088,
|
||||||
|
33011,
|
||||||
|
33068,
|
||||||
|
34990,
|
||||||
|
35093,
|
||||||
|
35958,
|
||||||
|
36626,
|
||||||
|
36789,
|
||||||
|
37130,
|
||||||
|
37238,
|
||||||
|
37256,
|
||||||
|
37697,
|
||||||
|
37890,
|
||||||
|
38958,
|
||||||
|
42131,
|
||||||
|
43864,
|
||||||
|
44420,
|
||||||
|
44655,
|
||||||
|
44868,
|
||||||
|
45157,
|
||||||
|
46213,
|
||||||
|
46497,
|
||||||
|
46955,
|
||||||
|
49049,
|
||||||
|
49067,
|
||||||
|
49452,
|
||||||
|
49480,
|
||||||
|
50498,
|
||||||
|
50945,
|
||||||
|
51181,
|
||||||
|
52890,
|
||||||
|
53301,
|
||||||
|
53407,
|
||||||
|
53417,
|
||||||
|
53980,
|
||||||
|
55827,
|
||||||
|
56483,
|
||||||
|
58552,
|
||||||
|
58713,
|
||||||
|
58836,
|
||||||
|
59362,
|
||||||
|
59560,
|
||||||
|
60534,
|
||||||
|
60555,
|
||||||
|
60660,
|
||||||
|
61615,
|
||||||
|
62402,
|
||||||
|
62533,
|
||||||
|
62941,
|
||||||
|
63240,
|
||||||
|
63339,
|
||||||
|
63616,
|
||||||
|
64380,
|
||||||
|
65438,
|
||||||
|
]
|
||||||
|
for p in ports_to_test:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", dport=p, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
|
||||||
|
assert syn_ack.ack == seq_init + 1, "wrong TCP ack value (%r != %r)" % (
|
||||||
|
syn_ack.ack,
|
||||||
|
seq_init + 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_tcp_psh_ack():
|
||||||
|
##### PSH-ACK #####
|
||||||
|
sport = 26695
|
||||||
|
port = 445
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
# send PSH-ACK first
|
||||||
|
psh_ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="PA", sport=sport, dport=port, seq=seq_init)
|
||||||
|
/ Raw("payload")
|
||||||
|
)
|
||||||
|
syn_ack = srp1(psh_ack, timeout=1)
|
||||||
|
assert syn_ack is None, "no answer expected, got one"
|
||||||
|
# test the anti-injection mechanism
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=port, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
|
||||||
|
assert syn_ack.ack == seq_init + 1, "wrong TCP ack value (%r != %r)" % (
|
||||||
|
syn_ack.ack,
|
||||||
|
seq_init + 1,
|
||||||
|
)
|
||||||
|
ack = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR) / TCP(flags="A", dport=port)
|
||||||
|
# should fail because no ack given
|
||||||
|
psh_ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags="PA", sport=sport, dport=port, ack=0, seq=seq_init + 1)
|
||||||
|
)
|
||||||
|
ack = srp1(psh_ack, timeout=1)
|
||||||
|
assert ack is None, "no answer expected, got one"
|
||||||
|
# should get an answer this time
|
||||||
|
psh_ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA", sport=sport, dport=port, ack=syn_ack.seq + 1, seq=seq_init + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ack = srp1(psh_ack, timeout=1)
|
||||||
|
assert ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ip_checksum(ack)
|
||||||
|
assert TCP in ack, "expecting TCP, got %r" % ack.summary()
|
||||||
|
ack = ack[TCP]
|
||||||
|
assert ack.flags == "A", "expecting TCP A, got %r" % syn_ack.flags
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_tcp_psh_ack():
|
||||||
|
##### PSH-ACK #####
|
||||||
|
sport = 26695
|
||||||
|
port = 445
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
# send PSH-ACK first
|
||||||
|
psh_ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(flags="PA", sport=sport, dport=port, seq=seq_init)
|
||||||
|
/ Raw("payload")
|
||||||
|
)
|
||||||
|
syn_ack = srp1(psh_ack, timeout=1)
|
||||||
|
assert syn_ack is None, "no answer expected, got one"
|
||||||
|
# test the anti-injection mechanism
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(flags="S", sport=sport, dport=port, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(syn_ack)
|
||||||
|
assert TCP in syn_ack, "expecting TCP, got %r" % syn_ack.summary()
|
||||||
|
syn_ack = syn_ack[TCP]
|
||||||
|
assert syn_ack.flags == "SA", "expecting TCP SA, got %r" % syn_ack.flags
|
||||||
|
assert syn_ack.ack == seq_init + 1, "wrong TCP ack value (%r != %r)" % (
|
||||||
|
syn_ack.ack,
|
||||||
|
seq_init + 1,
|
||||||
|
)
|
||||||
|
ack = Ether(dst=MAC_ADDR) / IPv6(dst=IPV6_ADDR) / TCP(flags="A", dport=port)
|
||||||
|
# should fail because no ack given
|
||||||
|
psh_ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(flags="PA", sport=sport, dport=port, ack=0, seq=seq_init + 1)
|
||||||
|
)
|
||||||
|
ack = srp1(psh_ack, timeout=1)
|
||||||
|
assert ack is None, "no answer expected, got one"
|
||||||
|
# should get an answer this time
|
||||||
|
psh_ack = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IPv6(dst=IPV6_ADDR)
|
||||||
|
/ TCP(
|
||||||
|
flags="PA", sport=sport, dport=port, ack=syn_ack.seq + 1, seq=seq_init + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ack = srp1(psh_ack, timeout=1)
|
||||||
|
assert ack is not None, "expecting answer, got nothing"
|
||||||
|
check_ipv6_checksum(ack)
|
||||||
|
assert TCP in ack, "expecting TCP, got %r" % ack.summary()
|
||||||
|
ack = ack[TCP]
|
||||||
|
assert ack.flags == "A", "expecting TCP A, got %r" % syn_ack.flags
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_tcp_syn_with_flags():
|
||||||
|
# send a SYN packet with other TCP flags, should not be answered
|
||||||
|
for flags in ["SA", "SR", "SF", "SPUCE"]:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags=flags, dport=80, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is None, "expecting no answer, got one"
|
||||||
|
# some should be accepted to imitate a Linux network stack
|
||||||
|
for flags in ["SP", "SU", "SC", "SE", "SPU", "SPC", "SPE", "SPUC", "SPUE"]:
|
||||||
|
seq_init = int(RandInt())
|
||||||
|
syn = (
|
||||||
|
Ether(dst=MAC_ADDR)
|
||||||
|
/ IP(dst=IPV4_ADDR)
|
||||||
|
/ TCP(flags=flags, dport=80, seq=seq_init)
|
||||||
|
)
|
||||||
|
syn_ack = srp1(syn, timeout=1)
|
||||||
|
assert syn_ack is not None, "expecting answer, got None"
|
||||||
40
test/src/tests/udp.py
Normal file
40
test/src/tests/udp.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# This file is part of masscanned.
|
||||||
|
# Copyright 2021 - The IVRE project
|
||||||
|
#
|
||||||
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Masscanned is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from scapy.layers.inet import IP
|
||||||
|
from scapy.layers.inet6 import IPv6
|
||||||
|
from scapy.layers.l2 import Ether
|
||||||
|
from scapy.packet import Raw
|
||||||
|
from scapy.sendrecv import srp1
|
||||||
|
|
||||||
|
from ..conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR
|
||||||
|
from ..core import test
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv4_udp_empty():
|
||||||
|
for p in [0, 53, 1000]:
|
||||||
|
req = Ether(dst=MAC_ADDR) / IP(dst=IPV4_ADDR, proto=17) / Raw() # UDP
|
||||||
|
repl = srp1(req, timeout=1)
|
||||||
|
assert repl is None, "expecting no answer, got one"
|
||||||
|
|
||||||
|
|
||||||
|
@test
|
||||||
|
def test_ipv6_udp_empty():
|
||||||
|
for p in [0, 53, 1000]:
|
||||||
|
req = Ether(dst=MAC_ADDR) / IPv6(dst=IPV6_ADDR, nh=17) / Raw() # UDP
|
||||||
|
repl = srp1(req, timeout=1)
|
||||||
|
assert repl is None, "expecting no answer, got one"
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# This file is part of masscanned.
|
# This file is part of masscanned.
|
||||||
# Copyright 2021 - The IVRE project
|
# Copyright 2021 - 2025 - The IVRE project
|
||||||
#
|
#
|
||||||
# Masscanned is free software: you can redistribute it and/or modify it
|
# Masscanned is free software: you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU General Public License as published by
|
# under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -16,63 +16,180 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
# along with Masscanned. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from scapy.all import *
|
import atexit
|
||||||
from time import sleep
|
import functools
|
||||||
from tempfile import _get_candidate_names as gen_tmp_filename
|
|
||||||
from tempfile import gettempdir
|
|
||||||
import subprocess
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
from signal import SIGINT
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from time import sleep
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ivre.config import guess_prefix
|
||||||
|
except ImportError:
|
||||||
|
HAS_IVRE = False
|
||||||
|
else:
|
||||||
|
HAS_IVRE = True
|
||||||
|
from scapy.config import conf
|
||||||
|
from scapy.interfaces import resolve_iface
|
||||||
|
|
||||||
from src.all import test_all
|
from src.all import test_all
|
||||||
from src.conf import *
|
from src.conf import IPV4_ADDR, IPV6_ADDR, MAC_ADDR, OUTDIR
|
||||||
|
|
||||||
# if args in CLI, they are passed to masscanned
|
|
||||||
if len(sys.argv) > 1:
|
def cleanup_net(iface):
|
||||||
args = " ".join(sys.argv[1:])
|
subprocess.check_call(["ip", "link", "delete", iface])
|
||||||
|
subprocess.check_call(
|
||||||
|
[
|
||||||
|
"iptables",
|
||||||
|
"-D",
|
||||||
|
"INPUT",
|
||||||
|
"-i",
|
||||||
|
iface,
|
||||||
|
"-m",
|
||||||
|
"state",
|
||||||
|
"--state",
|
||||||
|
"ESTABLISHED",
|
||||||
|
"-j",
|
||||||
|
"ACCEPT",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
subprocess.check_call(["iptables", "-D", "INPUT", "-i", iface, "-j", "DROP"])
|
||||||
|
try:
|
||||||
|
os.unlink(ipfile.name)
|
||||||
|
except NameError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def setup_net(iface):
|
||||||
|
# create the interfaces pair
|
||||||
|
atexit.register(functools.partial(cleanup_net, f"{iface}a"))
|
||||||
|
subprocess.check_call(
|
||||||
|
["ip", "link", "add", f"{iface}a", "type", "veth", "peer", f"{iface}b"]
|
||||||
|
)
|
||||||
|
for sub in "a", "b":
|
||||||
|
subprocess.check_call(["ip", "link", "set", f"{iface}{sub}", "up"])
|
||||||
|
subprocess.check_call(["ip", "addr", "add", "dev", f"{iface}a", "192.0.0.0/31"])
|
||||||
|
subprocess.check_call(
|
||||||
|
["ip", "addr", "add", "dev", f"{iface}a", "2001:41d0::1234:5678/96"]
|
||||||
|
)
|
||||||
|
subprocess.check_call(["ip", "route", "add", "1.2.3.4/32", "via", IPV4_ADDR])
|
||||||
|
# prevent problems between raw scanners (Scapy, Nmap, Masscan) and
|
||||||
|
# the host IP stack
|
||||||
|
subprocess.check_call(
|
||||||
|
[
|
||||||
|
"iptables",
|
||||||
|
"-A",
|
||||||
|
"INPUT",
|
||||||
|
"-i",
|
||||||
|
f"{iface}a",
|
||||||
|
"-m",
|
||||||
|
"state",
|
||||||
|
"--state",
|
||||||
|
"ESTABLISHED",
|
||||||
|
"-j",
|
||||||
|
"ACCEPT",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
subprocess.check_call(["iptables", "-A", "INPUT", "-i", f"{iface}a", "-j", "DROP"])
|
||||||
|
conf.route.resync()
|
||||||
|
conf.route6.resync()
|
||||||
|
|
||||||
|
|
||||||
|
IFACE = "masscanned"
|
||||||
|
setup_net(IFACE)
|
||||||
|
TCPDUMP = bool(os.environ.get("USE_TCPDUMP"))
|
||||||
|
if HAS_IVRE:
|
||||||
|
ZEEK_PASSIVERECON = bool(os.environ.get("USE_ZEEK"))
|
||||||
else:
|
else:
|
||||||
args = ""
|
ZEEK_PASSIVERECON = False
|
||||||
|
P0F = bool(os.environ.get("USE_P0F"))
|
||||||
fmt = logging.Formatter("%(levelname)s\t%(message)s")
|
|
||||||
ch = logging.StreamHandler()
|
|
||||||
ch.setFormatter(fmt)
|
|
||||||
ch.setLevel(logging.INFO)
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
LOG.setLevel(logging.INFO)
|
|
||||||
LOG.addHandler(ch)
|
|
||||||
|
|
||||||
conf.iface = 'tap0'
|
|
||||||
conf.verb = 0
|
conf.verb = 0
|
||||||
|
|
||||||
# prepare configuration file for masscanned
|
# prepare configuration file for masscanned
|
||||||
ipfile = os.path.join(gettempdir(), next(gen_tmp_filename()))
|
with NamedTemporaryFile(delete=False, mode="w") as ipfile:
|
||||||
with open(ipfile, "w") as f:
|
ipfile.write(f"{IPV4_ADDR}\n")
|
||||||
f.write("{}\n".format(IPV4_ADDR))
|
ipfile.write(f"{IPV6_ADDR}\n")
|
||||||
f.write("{}\n".format(IPV6_ADDR))
|
|
||||||
|
|
||||||
# create test interface
|
# create test interface
|
||||||
tap = TunTapInterface(resolve_iface(conf.iface))
|
conf.iface = resolve_iface(f"{IFACE}a")
|
||||||
|
|
||||||
# set interface
|
|
||||||
subprocess.run("ip a a dev {} 192.0.0.2".format(conf.iface), shell=True)
|
|
||||||
subprocess.run("ip link set {} up".format(conf.iface), shell=True)
|
|
||||||
|
|
||||||
# start capture
|
# start capture
|
||||||
tcpdump = subprocess.Popen("tcpdump -enli {} -w {}".format(conf.iface, os.path.join(OUTDIR, "test_capture.pcap")), shell=True,
|
if TCPDUMP:
|
||||||
stdin=None, stdout=None, stderr=None, close_fds=True)
|
tcpdump = subprocess.Popen(
|
||||||
|
[
|
||||||
|
"tcpdump",
|
||||||
|
"-enli",
|
||||||
|
f"{IFACE}a",
|
||||||
|
"-w",
|
||||||
|
os.path.join(OUTDIR, "test_capture.pcap"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if ZEEK_PASSIVERECON:
|
||||||
|
zeek = subprocess.Popen(
|
||||||
|
[
|
||||||
|
"zeek",
|
||||||
|
"-C",
|
||||||
|
"-b",
|
||||||
|
"-i",
|
||||||
|
f"{IFACE}a",
|
||||||
|
os.path.join(
|
||||||
|
guess_prefix("zeek"),
|
||||||
|
"ivre",
|
||||||
|
"passiverecon",
|
||||||
|
"bare.zeek",
|
||||||
|
),
|
||||||
|
"-e",
|
||||||
|
"redef tcp_content_deliver_all_resp = T; "
|
||||||
|
"redef tcp_content_deliver_all_orig = T; "
|
||||||
|
f"redef PassiveRecon::HONEYPOTS += {{ {IPV4_ADDR}, [{IPV6_ADDR}] }}",
|
||||||
|
],
|
||||||
|
stdout=open(os.path.join(OUTDIR, "zeek_passiverecon.stdout"), "w"),
|
||||||
|
stderr=open(os.path.join(OUTDIR, "zeek_passiverecon.stderr"), "w"),
|
||||||
|
)
|
||||||
|
if P0F:
|
||||||
|
p0f = subprocess.Popen(
|
||||||
|
["p0f", "-i", f"{IFACE}a", "-o", os.path.join(OUTDIR, "p0f_log.txt")],
|
||||||
|
stdout=open(os.path.join(OUTDIR, "p0f.stdout"), "w"),
|
||||||
|
stderr=open(os.path.join(OUTDIR, "p0f.stderr"), "w"),
|
||||||
|
)
|
||||||
# run masscanned
|
# run masscanned
|
||||||
masscanned = subprocess.Popen("RUST_BACKTRACE=1 ./target/debug/masscanned -vvvvv -i {} -f {} -a {} {}".format(conf.iface, ipfile, MAC_ADDR, args), shell=True,
|
masscanned = subprocess.Popen(
|
||||||
stdin=None, stdout=open("test/res/masscanned.stdout", "w"), stderr=open("test/res/masscanned.stderr", "w"), close_fds=True)
|
[
|
||||||
|
"./target/debug/masscanned",
|
||||||
|
"-vvvvv",
|
||||||
|
"-i",
|
||||||
|
f"{IFACE}b",
|
||||||
|
"--self-ip-file",
|
||||||
|
ipfile.name,
|
||||||
|
"-m",
|
||||||
|
MAC_ADDR,
|
||||||
|
]
|
||||||
|
# if args in CLI, they are passed to masscanned
|
||||||
|
+ sys.argv[1:],
|
||||||
|
env=dict(os.environ, RUST_BACKTRACE="1"),
|
||||||
|
stdout=open(os.path.join(OUTDIR, "masscanned.stdout"), "w"),
|
||||||
|
stderr=open(os.path.join(OUTDIR, "masscanned.stderr"), "w"),
|
||||||
|
)
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
test_all(tap)
|
result = test_all(masscanned)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
pass
|
result = -1
|
||||||
|
|
||||||
# terminate masscanned
|
# terminate masscanned
|
||||||
masscanned.kill()
|
masscanned.send_signal(SIGINT)
|
||||||
|
masscanned.wait()
|
||||||
# terminate capture
|
# terminate capture
|
||||||
sleep(2)
|
if TCPDUMP:
|
||||||
tcpdump.kill()
|
tcpdump.send_signal(SIGINT)
|
||||||
|
tcpdump.wait()
|
||||||
|
if ZEEK_PASSIVERECON:
|
||||||
|
zeek.send_signal(SIGINT)
|
||||||
|
zeek.wait()
|
||||||
|
if P0F:
|
||||||
|
p0f.send_signal(SIGINT)
|
||||||
|
p0f.wait()
|
||||||
|
sys.exit(result)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue