class RogueOne::Detector
Constants
- GOOGLE_PUBLIC_DNS
Attributes
custom_list[R]
default_list[R]
max_concurrency[R]
record_type[R]
target[R]
verbose[R]
Public Class Methods
new(target:, custom_list: nil, default_list: "alexa", record_type: "A", threshold: nil, verbose: false)
click to toggle source
# File lib/rogue_one/detector.rb, line 23 def initialize(target:, custom_list: nil, default_list: "alexa", record_type: "A", threshold: nil, verbose: false) @target = target @custom_list = custom_list @default_list = default_list @record_type = record_type.upcase.to_sym @threshold = threshold @verbose = verbose @max_concurrency = Etc.nprocessors * 2 @memo = {} @verbose_memo = nil end
Public Instance Methods
report()
click to toggle source
# File lib/rogue_one/detector.rb, line 42 def report inspect { verdict: verdict, landing_pages: landing_pages, results: results, meta: meta }.compact end
Private Instance Methods
bulk_resolve(resolver, domains)
click to toggle source
# File lib/rogue_one/detector.rb, line 147 def bulk_resolve(resolver, domains) results = [] Async do barrier = Async::Barrier.new semaphore = Async::Semaphore.new(max_concurrency, parent: barrier) domains.each do |domain| semaphore.async do addresses = [] begin addresses = resolver.addresses_for(domain, dns_resource_by_record_type, { retries: 1 }).map(&:to_s) rescue Async::DNS::ResolutionFailure # do nothing end results << [domain, addresses] end end end results.to_h end
custom_domains()
click to toggle source
# File lib/rogue_one/detector.rb, line 126 def custom_domains read_domains custom_list end
dns_resource_by_record_type()
click to toggle source
# File lib/rogue_one/detector.rb, line 177 def dns_resource_by_record_type @dns_resource_by_record_type ||= dns_resources.dig(record_type) end
dns_resources()
click to toggle source
# File lib/rogue_one/detector.rb, line 181 def dns_resources { A: Resolv::DNS::Resource::IN::A, AAAA: Resolv::DNS::Resource::IN::AAAA, } end
domains()
click to toggle source
# File lib/rogue_one/detector.rb, line 122 def domains @domains ||= custom_list ? custom_domains : top_100_domains end
inspect()
click to toggle source
# File lib/rogue_one/detector.rb, line 99 def inspect return unless @memo.empty? # read domains outside of the async blocks load_domains normal_resolutions = bulk_resolve(normal_resolver, domains) resolutions = bulk_resolve(target_resolver, domains) results = resolutions.map do |domain, addresses| normal_addresses = normal_resolutions.dig(domain) || [] address = (addresses || []).first [domain, address] if address && !normal_addresses.include?(address) end.compact.to_h @memo = results.values.group_by(&:itself).map { |k, v| [k, v.length] }.to_h @verbose_memo = results if verbose end
landing_pages()
click to toggle source
# File lib/rogue_one/detector.rb, line 76 def landing_pages @memo.map do |ip, count| count > threshold ? ip : nil end.compact.sort end
load_domains()
click to toggle source
# File lib/rogue_one/detector.rb, line 118 def load_domains domains end
meta()
click to toggle source
# File lib/rogue_one/detector.rb, line 67 def meta return nil unless verbose { record_type: record_type, threshold: threshold, } end
normal_resolver()
click to toggle source
# File lib/rogue_one/detector.rb, line 169 def normal_resolver Async::DNS::Resolver.new([[:udp, GOOGLE_PUBLIC_DNS, 53], [:tcp, GOOGLE_PUBLIC_DNS, 53]]) end
occurrences()
click to toggle source
# File lib/rogue_one/detector.rb, line 95 def occurrences @memo.sort_by{ |_, v| -v }.to_h end
read_domains(path)
click to toggle source
# File lib/rogue_one/detector.rb, line 139 def read_domains(path) list = DomainList.new(path) return list.domains if list.valid? raise ArgumentError, "Inputted an invalid list. #{path} is not eixst." unless list.exists? raise ArgumentError, "Inputted an invalid list. Please input a list as an YAML file." unless list.valid_format? end
resolutions()
click to toggle source
# File lib/rogue_one/detector.rb, line 91 def resolutions (@verbose_memo || {}).sort_by { |_, v| v }.to_h end
results()
click to toggle source
# File lib/rogue_one/detector.rb, line 82 def results return nil unless verbose { resolutions: resolutions, occurrences: occurrences } end
rogue_one?()
click to toggle source
# File lib/rogue_one/detector.rb, line 59 def rogue_one? !landing_pages.empty? end
target_resolver()
click to toggle source
# File lib/rogue_one/detector.rb, line 173 def target_resolver Async::DNS::Resolver.new([[:udp, target, 53], [:tcp, target, 53]]) end
threshold()
click to toggle source
# File lib/rogue_one/detector.rb, line 63 def threshold @threshold ||= (domains.length.to_f / 10.0).ceil end
top_100_domains()
click to toggle source
# File lib/rogue_one/detector.rb, line 130 def top_100_domains case default_list when "alexa" read_domains File.expand_path("./data/alexa_100.yml", __dir__) when "fortune" read_domains File.expand_path("./data/fortune_100.yml", __dir__) end end
verdict()
click to toggle source
# File lib/rogue_one/detector.rb, line 55 def verdict rogue_one? ? "rogue one" : "benign one" end