forked from robertdavidgraham/masscan
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathalgorithm.js
426 lines (350 loc) · 12.5 KB
/
algorithm.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/*
This is an implementation of the core Masscan scanning algorithm
in JavaScript/NodeJS. The core scanning algorithm is what makes
Masscan unique from other scanners, so it's worth highlighting
separately in a sample program.
REVIEW OF SCANNERS
The most famous port-scanner is "nmap". However, it is a
"host-at-a-time" scanner, and struggles at scanning large networks.
Masscan is an asynchronous, probe-at-a-time scanner. It spews out
probes to different ports, without caring if two probes happen to
be send to the same host. If the user wants a list of all ports
open on a single host, they have to post-process the masscan output
themselves, because masscan doesn't do it.
There are other asynchronous port-scanners, like scanrand, unicornscan,
and zmap. However, they have limitations in the way they do randomization
of their scans. They have limitations on the ranges of addresses and
ports that they'll accept, try to store an individual memory record
for everything scanned, or only partly randomize their scans.
THE WAY MASSCAN WORKS
Masscan first stores the targets as a "list of ranges". IP address
ranges are stored in one structure, and port ranges are stored
in another structure.
Then, a single index variable is used to enumerate the set of all
IP:port combinations. The scan works by simply incrementing the
index variable from 0 to the total number of probes (the 'range').
Then, before the enumeration step, the index is permuted into another
random index within the same range, in a 1-to-1 mapping. In other
words, the algorithm is theoretically reversable: given the output
of the permutation function, we can obtain the original index.
EXAMPLE
This program can be run like the following:
node patent.js 10.0.0.0-10.0.0.5 192.168.0.0/31 80,U:161
10.0.0.0-10.0.0.5
192.168.0.0-192.168.0.1
0.0.0.80-0.0.0.80
0.1.0.161-0.1.0.161
--> 10.0.0.4 udp:161
--> 10.0.0.0 udp:161
--> 10.0.0.1 udp:161
--> 10.0.0.4 tcp:80
--> 192.168.0.1 tcp:80
--> 10.0.0.0 tcp:80
--> 10.0.0.2 udp:161
--> 10.0.0.5 udp:161
--> 192.168.0.0 tcp:80
--> 192.168.0.0 udp:161
--> 10.0.0.1 tcp:80
--> 10.0.0.3 udp:161
--> 10.0.0.2 tcp:80
--> 10.0.0.5 tcp:80
--> 192.168.0.1 udp:161
--> 10.0.0.3 tcp:80
What you see first is the target ranges being echoed back that it scans,
first the IP address ranges, followed by the port ranges. The port ranges
are in weird decimal-dot notation because they share the same code
as for IPv4 addresses.
Then we see the randomized output, where individual probes are sent to a
random IP address and port.
TransmitThread
All the majic happens in the "TransmitThread()" function near the bottom
of this file.
We first see how the index variable 'i' is incremented from 0 to the
total number of packets that will be sent. We then see how first this
index is permuted to 'xXx', then this variable is separated into
one index for the IP address and another index for the port. Then,
those indexes are used to enumerate one of the IP addresses and
one of the ports.
Blackrock
This is the permutation function. It implements an encryption algorithm
based on DES (Data Encryption Standard). However, the use of real DES
would impose a restricting on the range that it be an even power of 2.
In the above example, with 14 total probes, this doesn't apply.
Therefore, we have to change binary operators like XOR with their
non-binary equivelents.
The upshot is that we first initialize Blackrock with the range (and
a seed/key), and then shuffle the index. The process is stateless,
meaning that any time we shuffle the number '5' we always get the
same result, regardless of what has happened before.
Targets, RangeList, Range
A Range is just a begin/end of an integer. We use this both for
IPv4 addresses (which are just 32-bit integers) and ports
(which are 16 bit integers).
A RangeList is just an array of Ranges. In Masscan, this object
sorts and combines ranges, making sure there are no duplicates,
but that isn't used in this example.
The RangeList object shows how an index can enumerate the
individual addresses/ports. This is down by walking the list
and subtracting from the index the size of each range, until
we reach a range that is larger than the index.
The Targets object just holds both the IP and port lists.
*/
function Range(begin, end) {
if (typeof begin == 'undefined' && typeof end == 'undefined') {
this.begin = 0xFFFFFFFF;
this.end = 0;
} else if (typeof end == 'undefined') {
this.begin = begin;
this.end = begin;
} else {
this.begin = begin;
this.end = end;
}
this.toString = function () {
return ((this.begin >> 24) & 0xFF)
+ "." + ((this.begin >> 16) & 0xFF)
+ "." + ((this.begin >> 8) & 0xFF)
+ "." + ((this.begin >> 0) & 0xFF)
+ "-" + ((this.end >> 24) & 0xFF)
+ "." + ((this.end >> 16) & 0xFF)
+ "." + ((this.end >> 8) & 0xFF)
+ "." + ((this.end >> 0) & 0xFF);
}
this.count = function () {
return this.end - this.begin + 1;
}
this.pick = function (index) {
return this.begin + index;
}
return this;
}
function RangeList() {
this.list = [];
this.total_count = 0;
this.push = function (range) {
this.list.push(range);
this.total_count += range.count();
}
this.count = function () {
return this.total_count;
}
this.pick = function (index) {
for (var i in this.list) {
var item = this.list[i];
if (index < item.count())
return item.pick(index);
else
index -= item.count();
}
return null;
}
}
function Targets() {
this.ports = new RangeList();
this.ips = new RangeList();
this.parse_ip = function (text) {
var x = text.split(".");
var result = 0;
result |= parseInt(x[0]) << 24;
result |= parseInt(x[1]) << 16;
result |= parseInt(x[2]) << 8;
result |= parseInt(x[3]) << 0;
return result;
}
this.parse_ports = function (arg) {
var offset = 0;
if (arg.indexOf(":") !== -1) {
var x = arg.split(":");
if (x[0] == "U")
offset = 65536;
else if (x[0] == "S")
offset = 65536 * 2;
arg = x[1];
}
var target;
if (arg.indexOf("-") !== -1) {
var x = arg.split("-");
target = new Range(parseInt(x[0]), parseInt(x[1]));
} else
target = new Range(parseInt(arg));
target.begin += offset;
target.end += offset;
this.ports.push(target);
}
this.parse_args = function (argv) {
for (var i in argv) {
var arg = argv[i];
if (arg.indexOf(",") !== -1) {
var x = arg.split(",");
for (var j in x)
this.parse_ports(x[j]);
} else if (arg.indexOf("/") !== -1) {
var x = arg.split("/");
var address = this.parse_ip(x[0]);
var prefix = parseInt(x[1]);
var mask = 0xFFFFFFFF << (32 - prefix);
address = address & mask;
var target = new Range(address, address | ~mask);
this.ips.push(target);
} else if (arg.indexOf("-") !== -1) {
var x = arg.split("-");
var begin = this.parse_ip(x[0]);
var end = this.parse_ip(x[1]);
var target = new Range(begin, end);
this.ips.push(target);
} else if (arg.indexOf(".") !== -1) {
var target = new Range(this.parse_ip(arg));
this.ips.push(target);
} else {
this.parse_ports(arg);
}
}
}
this.print = function () {
var i;
for (i in this.ips.list) {
console.log(this.ips.list[i].toString());
}
for (i in this.ports.list) {
console.log(this.ports.list[i].toString());
}
}
return this;
}
function Blackrock(range, seed) {
var split = Math.floor(Math.sqrt(range * 1.0));
this.rounds = 3;
this.seed = seed;
this.range = range;
this.a = split - 1;
this.b = split + 1;
while (this.a * this.b <= range)
this.b++;
/** Inner permutation function */
this.F = function (j, R, seed) {
var primes = [961752031, 982324657, 15485843, 961752031];
R = ((R << (R & 0x4)) + R + seed);
return Math.abs((((primes[j] * R + 25) ^ R) + j));
}
/** Outer feistal construction */
this.fe = function (r, a, b, m, seed) {
var L, R;
var j;
var tmp;
L = m % a;
R = Math.floor(m / a);
for (j = 1; j <= r; j++) {
if (j & 1) {
tmp = (L + this.F(j, R, seed)) % a;
} else {
tmp = (L + this.F(j, R, seed)) % b;
}
L = R;
R = tmp;
}
if (r & 1) {
return a * L + R;
} else {
return a * R + L;
}
}
/** Outer reverse feistal construction */
this.unfe = function (r, a, b, m, seed) {
var L, R;
var j;
var tmp;
if (r & 1) {
R = m % a;
L = Math.floor(m / a);
} else {
L = m % a;
R = Math.floor(m / a);
}
for (j = r; j >= 1; j--) {
if (j & 1) {
tmp = this.F(j, L, seed);
if (tmp > R) {
tmp = (tmp - R);
tmp = a - (tmp % a);
if (tmp == a)
tmp = 0;
} else {
tmp = (R - tmp);
tmp %= a;
}
} else {
tmp = this.F(j, L, seed);
if (tmp > R) {
tmp = (tmp - R);
tmp = b - (tmp % b);
if (tmp == b)
tmp = 0;
} else {
tmp = (R - tmp);
tmp %= b;
}
}
R = L;
L = tmp;
}
return a * R + L;
}
this.shuffle = function (m) {
var c;
c = this.fe(this.rounds, this.a, this.b, m, this.seed);
while (c >= this.range)
c = this.fe(this.rounds, this.a, this.b, c, this.seed);
return c;
}
this.unshuffle = function (m) {
var c;
c = unfe(this.rounds, this.a, this.b, m, this.seed);
while (c >= this.range)
c = unfe(this.rounds, this.a, this.b, c, this.seed);
return c;
}
return this;
}
function TransmitThread(targets, transmit, seed) {
var range = targets.ips.count() * targets.ports.count();
var b = Blackrock(range, seed);
for (var i = 0; i < range; i++) {
var xXx = b.shuffle(i);
var ip_index = Math.floor(xXx / targets.ports.count());
var port_index = Math.floor(xXx % targets.ports.count());
var ip = targets.ips.pick(ip_index);
var port = targets.ports.pick(port_index);
transmit(ip, port);
}
}
function Transmit2Thread(targets, transmit, seed, start, stop, increment) {
var range = targets.ips.count() * targets.ports.count();
var b = Blackrock(range, seed);
for (var i = start; i < range && i < stop; i += increment) {
var xXx = b.shuffle(i);
var ip_index = Math.floor(xXx / targets.ports.count());
var port_index = Math.floor(xXx % targets.ports.count());
var ip = targets.ips.pick(ip_index);
var port = targets.ports.pick(port_index);
transmit(ip, port);
}
}
function transmit(ip, port) {
var proto = "tcp";
if (port > 65536 * 2) {
proto = "sctp";
port -= 65536 * 2;
} else if (port > 65536) {
proto = "udp";
port -= 65536;
}
var ipstring = ((ip >> 24) & 0xFF)
+ "." + ((ip >> 16) & 0xFF)
+ "." + ((ip >> 8) & 0xFF)
+ "." + ((ip >> 0) & 0xFF)
console.log("--> " + ipstring + " " + proto + ":" + port);
}
var targets = new Targets();
targets.parse_args(process.argv.splice(2));
targets.print();
TransmitThread(targets, transmit, 42);