Module: Masscan::Parsers::Binary
- Defined in:
- lib/masscan/parsers/binary.rb
Overview
Parses the masscan -oB
output format.
Defined Under Namespace
Classes: CorruptedFile
Constant Summary collapse
- BUF_MAX =
Maximum buffer length for a single record.
1024 * 1024
- PSEUDO_RECORD_SIZE =
The "pseudo record" length
99
- MASSCAN_VERSION_FAMILY =
Masscan binary format version compatibility.
"1.1"
- MASSCAN_MAGIC =
The
masscan
binary format magic string. "masscan/#{MASSCAN_VERSION_FAMILY}"
- IP_PROTOCOLS =
Mapping of IP protocol numbers to keywords.
{ Socket::IPPROTO_ICMP => :icmp, Socket::IPPROTO_ICMPV6 => :icmp, Socket::IPPROTO_TCP => :tcp, Socket::IPPROTO_UDP => :udp, 132 => :sctp # Socket::IPPROTO_SCTP might not always be defined }
- APP_PROTOCOLS =
List of application protocol keywords.
[ nil, :heur, :ssh1, :ssh2, :http, :ftp, :dns_versionbind, :snmp, # simple network management protocol, udp/161 :nbtstat, # netbios, udp/137 :ssl3, :smb, # SMB tcp/139 and tcp/445 :smtp, :pop3, :imap4, :udp_zeroaccess, :x509_cert, :html_title, :html_full, :ntp, # network time protocol, udp/123 :vuln, :heartbleed, :ticketbleed, :vnc_rfb, :safe, :memcached, :scripting, :versioning, :coap, # constrained app proto, udp/5683, RFC7252 :telnet, :rdp, # Microsoft Remote Desktop Protocol tcp/3389 :http_server, # HTTP "Server:" field ]
Class Method Summary collapse
-
.decode_ipv4(ip) ⇒ IPAddr
Decodes an IPv4 address from an integer.
-
.decode_ipv6(ipv6_hi, ipv6_lo) ⇒ IPAddr
Decodes an IPv6 address from two 64bit integers.
-
.decode_reason(reason) ⇒ Array<:fin, :syn, :rst, :psh, :ack, :urg, :ece, :cwr>
Decodes a reason bitflag.
-
.decode_timestamp(timestamp) ⇒ Time
Decodes a timestamp from an integer.
-
.lookup_app_protocol(proto) ⇒ Symbol?
Looks up an application protocol number.
-
.lookup_ip_protocol(proto) ⇒ :icmp, ...
Looks up an IP protocol number.
-
.open(path) {|file| ... } ⇒ File
Opens a binary file for parsing.
-
.parse(io) {|record| ... } ⇒ Enumerator
Parses masscan binary data.
-
.parse_banner3(buffer) ⇒ Buffer
Parses a banner record.
-
.parse_banner4(buffer) ⇒ Buffer
Parses a banner record.
-
.parse_banner6(buffer) ⇒ Buffer
Parses a banner record.
-
.parse_banner9(buffer) ⇒ Buffer
Parses a banner record.
-
.parse_status(buffer, status) ⇒ Status
Parses a status record.
-
.parse_status2(buffer, status) ⇒ Status
Parses a status record.
-
.parse_status6(buffer, status) ⇒ Status
Parses a status record.
-
.read_multibyte_uint(io) ⇒ Integer?
Reads a multi-byte unsigned integer.
-
.read_pseudo_record(io) ⇒ String
Reads the "pseudo record" at the beginning of the file.
Class Method Details
.decode_ipv4(ip) ⇒ IPAddr
Decodes an IPv4 address from an integer.
218 219 220 |
# File 'lib/masscan/parsers/binary.rb', line 218 def self.decode_ipv4(ip) IPAddr.new(ip,Socket::AF_INET) end |
.decode_ipv6(ipv6_hi, ipv6_lo) ⇒ IPAddr
Decodes an IPv6 address from two 64bit integers.
234 235 236 |
# File 'lib/masscan/parsers/binary.rb', line 234 def self.decode_ipv6(ipv6_hi,ipv6_lo) IPAddr.new((ipv6_hi << 64) | ipv6_lo,Socket::AF_INET6) end |
.decode_reason(reason) ⇒ Array<:fin, :syn, :rst, :psh, :ack, :urg, :ece, :cwr>
Decodes a reason bitflag.
273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/masscan/parsers/binary.rb', line 273 def self.decode_reason(reason) flags = [] flags << :fin if (reason & 0x01) != 0 flags << :syn if (reason & 0x02) != 0 flags << :rst if (reason & 0x04) != 0 flags << :psh if (reason & 0x08) != 0 flags << :ack if (reason & 0x10) != 0 flags << :urg if (reason & 0x20) != 0 flags << :ece if (reason & 0x40) != 0 flags << :cwr if (reason & 0x80) != 0 flags end |
.decode_timestamp(timestamp) ⇒ Time
Decodes a timestamp from an integer.
205 206 207 |
# File 'lib/masscan/parsers/binary.rb', line 205 def self.() Time.at() end |
.lookup_app_protocol(proto) ⇒ Symbol?
Looks up an application protocol number.
332 333 334 |
# File 'lib/masscan/parsers/binary.rb', line 332 def self.lookup_app_protocol(proto) APP_PROTOCOLS[proto] end |
.lookup_ip_protocol(proto) ⇒ :icmp, ...
Looks up an IP protocol number.
260 261 262 |
# File 'lib/masscan/parsers/binary.rb', line 260 def self.lookup_ip_protocol(proto) IP_PROTOCOLS[proto] end |
.open(path) {|file| ... } ⇒ File
Opens a binary file for parsing.
34 35 36 |
# File 'lib/masscan/parsers/binary.rb', line 34 def self.open(path,&block) File.open(path,'rb',&block) end |
.parse(io) {|record| ... } ⇒ Enumerator
Parses masscan binary data.
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 |
# File 'lib/masscan/parsers/binary.rb', line 59 def self.parse(io) return enum_for(__method__,io) unless block_given? pseudo = read_pseudo_record(io) # look for the start time if (match = pseudo.match(/s:(\d+)/)) start_time = (match[1].to_i) end total_records = 0 # read all records loop do # read the TYPE field unless (type = read_multibyte_uint(io)) return end # read the LENGTH field unless (length = read_multibyte_uint(io)) return end if length > BUF_MAX raise(CorruptedFile,"file corrupted") end # read the remainder of the record buffer = io.read(length) if buffer.length < length return end # parse the specific record type record = case type when 1 # STATUS: open parse_status(buffer,:open) when 2 # STATUS: closed parse_status(buffer,:closed) when 3 # BANNER (buffer) when 4 io.getbyte (buffer) when 5 (buffer) when 6 # STATUS: open parse_status2(buffer,:open) when 7 # STATUS: closed parse_status2(buffer,:closed) when 9 (buffer) when 10 # Open6 parse_status6(buffer,:open) when 11 # Closed6 parse_status6(buffer,:closed) when 13 # Banner6 (buffer) when 109 # 'm'.ord # FILEHEADER next else raise(CorruptedFile,"unknown type: #{type.inspect}") end if record start_time ||= record. yield record total_records += 1 end end return total_records end |
.parse_banner3(buffer) ⇒ Buffer
Parses a banner record.
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/masscan/parsers/binary.rb', line 391 def self.(buffer) , ip, port, app_proto, payload = buffer.unpack('L>L>S>S>A*') = () ip = decode_ipv4(ip) app_proto = lookup_app_protocol(app_proto) # defaults ip_proto = :tcp ttl = 0 return Banner.new( protocol: ip_proto, port: port, ttl: ttl, ip: ip, timestamp: , app_protocol: app_proto, payload: payload ) end |
.parse_banner4(buffer) ⇒ Buffer
Parses a banner record.
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'lib/masscan/parsers/binary.rb', line 422 def self.(buffer) if buffer.length < 13 return end , ip, ip_proto, port, app_proto, payload = buffer.unpack('L>L>CS>S>A*') = () ip = decode_ipv4(ip) ip_proto = lookup_ip_protocol(ip_proto) app_proto = lookup_app_protocol(app_proto) # defaults ttl = 0 return Banner.new( protocol: ip_proto, port: port, ttl: ttl, ip: ip, timestamp: , app_protocol: app_proto, payload: payload ) end |
.parse_banner6(buffer) ⇒ Buffer
Parses a banner record.
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/masscan/parsers/binary.rb', line 570 def self.(buffer) , ip_proto, port, app_proto, ttl, ip_version, ipv6_hi, ipv6_lo, payload = buffer.unpack('L>CS>S>CCQ>Q>A*') ||= 0xffffffff protocol ||= 0xff port ||= 0xffff app_proto ||= 0xffff ttl ||= 0xff ip_version ||= 0xff ipv6_hi ||= 0xffffffff_ffffffff ipv6_lo ||= 0xffffffff_ffffffff = () ip_proto = lookup_ip_protocol(ip_proto) app_proto = lookup_app_protocol(app_proto) ipv6 = decode_ipv6(ipv6_hi,ipv6_lo) return Banner.new( protocol: ip_proto, port: port, ttl: ttl, ip: ipv6, timestamp: , app_protocol: app_proto, payload: payload ) end |
.parse_banner9(buffer) ⇒ Buffer
Parses a banner record.
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 |
# File 'lib/masscan/parsers/binary.rb', line 496 def self.(buffer) if buffer.length < 14 return end , ip, ip_proto, port, app_proto, ttl, payload = buffer.unpack('L>L>CS>S>CA*') = () ip = decode_ipv4(ip) ip_proto = lookup_ip_protocol(ip_proto) app_proto = lookup_app_protocol(app_proto) return Banner.new( protocol: ip_proto, port: port, ttl: ttl, ip: ip, timestamp: , app_protocol: app_proto, payload: payload ) end |
.parse_status(buffer, status) ⇒ Status
Parses a status record.
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 |
# File 'lib/masscan/parsers/binary.rb', line 348 def self.parse_status(buffer,status) if buffer.length < 12 return end , ip, port, reason, ttl = buffer.unpack("L>L>S>CC") = () ip = decode_ipv4(ip) reason = decode_reason(reason) # if ARP, there will be a MAC address after the record mac = if ip == 0 && buffer.length >= 12+6 buffer[12+6,6] end protocol = case port when 53, 123, 137, 161 then :udp when 36422, 36412, 2905 then :sctp else :tcp end return Status.new( status: status, protocol: protocol, port: port, reason: reason, ttl: ttl, ip: ip, timestamp: , mac: mac ) end |
.parse_status2(buffer, status) ⇒ Status
Parses a status record.
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
# File 'lib/masscan/parsers/binary.rb', line 460 def self.parse_status2(buffer,status) if buffer.length < 13 return end , ip, ip_proto, port, reason, ttl = buffer.unpack('L>L>CS>CC') = () ip = decode_ipv4(ip) ip_proto = lookup_ip_protocol(ip_proto) reason = decode_reason(reason) mac = if ip == 0 && buffer.length >= 13+6 buffer[13,6] end return Status.new( status: status, protocol: ip_proto, port: port, reason: reason, ttl: ttl, ip: ip, timestamp: , mac: mac ) end |
.parse_status6(buffer, status) ⇒ Status
Parses a status record.
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/masscan/parsers/binary.rb', line 530 def self.parse_status6(buffer,status) , ip_proto, port, reason, ttl, ip_version, ipv6_hi, ipv6_lo = buffer.unpack('L>CS>CCCQ>Q>') ||= 0xffffffff ip_proto ||= 0xff port ||= 0xffff reason ||= 0xff ttl ||= 0xff ip_version ||= 0xff ipv6_hi ||= 0xffffffff_ffffffff ipv6_lo ||= 0xffffffff_ffffffff unless ip_version == 6 raise(CorruptedFile,"expected ip_version to be 6: #{ip_version.inspect}") end = () ip_proto = lookup_ip_protocol(ip_proto) reason = decode_reason(reason) ipv6 = decode_ipv6(ipv6_hi,ipv6_lo) return Status.new( status: status, protocol: ip_proto, port: port, reason: reason, ttl: ttl, ip: ipv6, timestamp: ) end |
.read_multibyte_uint(io) ⇒ Integer?
Reads a multi-byte unsigned integer.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/masscan/parsers/binary.rb', line 178 def self.read_multibyte_uint(io) unless (b = io.getbyte) return end type = b & 0x7f while (b & 0x80) != 0 unless (b = io.getbyte) return end type = (type << 7) | (b & 0x7f) end return type end |
.read_pseudo_record(io) ⇒ String
Reads the "pseudo record" at the beginning of the file.
155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/masscan/parsers/binary.rb', line 155 def self.read_pseudo_record(io) buffer = io.read(PSEUDO_RECORD_SIZE) if buffer.length < PSEUDO_RECORD_SIZE raise(CorruptedFile,"invalid masscan binary format") end unless buffer.start_with?(MASSCAN_MAGIC) raise(CorruptedFile,"unknown file format (expected #{MASSCAN_MAGIC})") end return buffer end |