URI: 
       Automatic matching improvements, additional of a maltego transform for finding like numbers - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit 3a47479fdb9dc463945b69e36fe5a1a71a3f4bc8
   DIR parent 2af809ed64362553e2fd7b309fd8da3490351bb6
  HTML Author: HD Moore <hd_moore@rapid7.com>
       Date:   Sat, 14 Feb 2009 22:04:43 +0000
       
       Automatic matching improvements, additional of a maltego transform for finding like numbers
       
       
       Diffstat:
         M bin/automatch.rb                    |      41 ++++++++++++++++++++-----------
         A bin/link_maltego.rb                 |     159 +++++++++++++++++++++++++++++++
         A docs/maltego/local_transform.png    |       0 
         A docs/maltego/sample_output.png      |       0 
         M lib/warvox/db.rb                    |      66 +++++++++++++++++++------------
       
       5 files changed, 226 insertions(+), 40 deletions(-)
       ---
   DIR diff --git a/bin/automatch.rb b/bin/automatch.rb
       @@ -16,13 +16,14 @@ require 'warvox'
        # 
        
        def usage
       -        $stderr.puts "#{$0} [warvox.db] <db-threshold>"
       +        $stderr.puts "#{$0} [warvox.db] <db-threshold> <fuzz>"
                exit
        end
        
        threads = 2 
        inp     = ARGV.shift || usage
        thresh  = (ARGV.shift() || 800).to_i
       +fuzz    = (ARGV.shift() || 100).to_i
        wdb     = WarVOX::DB.new(inp, thresh)
        
        # Scrub the carriers out of the pool first
       @@ -33,45 +34,57 @@ end
        
        groups = 
        {
       -        "carriers" => car.keys
       +        "carriers" => car.keys,
       +        "unique"   => []
        }
        
        oset = wdb.keys.sort
        iset = oset.dup
        
        
       -
        while(not oset.empty?)
        
       +        s = Time.now
                k = oset.shift
                
       -        found = []
       -        best  = nil
       +        found = {}
                next if not iset.include?(k)
                
                iset.each do |n|
                        next if k == n
                        
                        begin
       -                        res = wdb.find_sig(k,n)
       +                        res = wdb.find_sig(k,n,{ :fuzz => fuzz })
                        rescue ::WarVOX::DB::Error
                        end
                        
                        next if not res
                        next if res[:len] < 5
       -                found << res
       +                if(not found[n] or found[n][:len] < res[:len])
       +                        found[n] = res
       +                end
       +        end
       +
       +        if(found.empty?)
       +                next
                end
                
       -        next if found.empty?
       -        
       -        groups[k] = [ ]
       -        found.each do |f|
       -                groups[k] << [ f[:num2], f[:len] ]
       +        groups[k] = [ [k, 0] ]
       +        found.keys.sort.each do |n|
       +                groups[k] <<  [n, found[n][:len]]
                end
        
       -        $stdout.puts "#{k} " + groups[k].map{|x| "#{x[0]}-#{x[1]}" }.join(" ")
       +        $stdout.puts groups[k].map{|x| "#{x[0]}-#{x[1]}" }.join(" ") 
                $stdout.flush
                
                groups[k].unshift(k)
       -end
        
       +        # Remove matches from the search listing
       +        iset.delete(k)
       +        found.keys.each do |k|
       +                iset.delete(k)
       +        end
       +end
       +iset.each do |k|
       +        puts "#{k}-0"
       +end
   DIR diff --git a/bin/link_maltego.rb b/bin/link_maltego.rb
       @@ -0,0 +1,159 @@
       +#!/usr/bin/env ruby
       +###################
       +
       +#
       +# Load the library path
       +# 
       +base = __FILE__
       +while File.symlink?(base)
       +        base = File.expand_path(File.readlink(base), File.dirname(base))
       +end
       +$:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
       +require 'warvox'
       +require 'rexml/document'
       +
       +#
       +# Script
       +#
       +
       +#
       +# http://ctas.paterva.com/view/Specification
       +#
       +
       +def xml_results_empty
       +        root = REXML::Element.new('MaltegoMessage')
       +        xml2 = root.add_element('MaltegoTransformResponseMessage')
       +        xml2.add_element('Entities')
       +        root
       +end
       +
       +def xml_results_matches(res)
       +        root = REXML::Element.new('MaltegoMessage')
       +        xml2 = root.add_element('MaltegoTransformResponseMessage')
       +        xml3 = xml2.add_element('Entities')
       +        
       +        res.each_key do |k|
       +                num_area = k[0,3]
       +                num_city = k[3,3]
       +                num_last = k[6,4]
       +        
       +                num = num_area + " " + num_city + " " + num_last
       +                
       +                val = REXML::Element.new('Value')
       +                val.add_text(num)
       +
       +                adf = REXML::Element.new('AdditionalFields')
       +                
       +                        adf_area = REXML::Element.new('Field')
       +                        adf_area.add_attribute('Name', 'areacode')
       +                        adf_area.add_text( REXML::Text.new( num_area.to_s ) )
       +                        adf << adf_area
       +
       +                        adf_city = REXML::Element.new('Field')
       +                        adf_city.add_attribute('Name', 'citycode')
       +                        adf_city.add_text( REXML::Text.new( num_city.to_s ) )
       +                        adf << adf_city
       +
       +                        adf_last = REXML::Element.new('Field')
       +                        adf_last.add_attribute('Name', 'lastnumbers')
       +                        adf_last.add_text( REXML::Text.new( num_last.to_s ) )
       +                        adf << adf_last
       +
       +                        adf_info = REXML::Element.new('Field')
       +                        adf_info.add_attribute('Name', 'additional')
       +                        adf_info.add_text( REXML::Text.new( "Sig: " + res[k][:sig].map{|x| "#{x[0]},#{x[1]},#{x[2]}"}.join(" ") ) )
       +                        adf << adf_info
       +                                                                        
       +
       +                wgt = REXML::Element.new('Weight')
       +                wgt.add_text(  REXML::Text.new( [res[k][:len] * 10, 100].min.to_s  ) )
       +                
       +                ent = REXML::Element.new('Entity')
       +                ent.add_attribute('Type', 'PhoneNumber')
       +
       +                ent << val
       +                ent << wgt
       +                ent << adf
       +                
       +                xml3 << ent
       +        end
       +        root
       +end
       +
       +
       +# Only report each percentage once
       +@progress_done = {}
       +
       +def report_progress(pct)
       +        return if @progress_done[pct]
       +        $stderr.puts "%#{pct}"
       +        $stderr.flush
       +        @progress_done[pct] = true
       +end
       +
       +def usage
       +        $stderr.puts "#{$0} [target] [params]"
       +        exit
       +end
       +
       +#
       +# Parse input
       +#
       +
       +params = {}
       +target = ARGV.shift || usage()
       +(ARGV.shift || usage()).split('#').each do |param|
       +        k,v = param.split('=', 2)
       +        params[k] = v
       +end
       +
       +# XXX: Problematic right now
       +# target_number = params['areacode'] + params['citycode'] + params['lastnumbers']
       +
       +target_number = target.scan(/\d+/).join
       +if(target_number.length != 10)
       +        $stderr.puts "D: Only 10 digit US numbers are currently supported"
       +        $stdout.puts xml_results_empty().to_s
       +        exit        
       +end
       +
       +
       +#
       +# Search database
       +#
       +
       +carriers = {}
       +
       +data_root = File.join(File.dirname(base), '..', 'data')
       +wdb       = WarVOX::DB.new(nil)
       +
       +Dir.new(data_root).entries.grep(/\.db$/).each do |db|
       +        $stderr.puts "D: Loading #{db}..."
       +        wdb.import(File.join(data_root, db))
       +end
       +
       +# No matching number
       +if(not wdb[target_number])
       +        $stderr.puts "D: Target #{target_number} (#{target}) is not in the WarVOX database"
       +        $stdout.puts xml_results_empty().to_s
       +        exit
       +end
       +
       +found = {}
       +cnt = 0
       +wdb.each_key do |n|
       +        cnt += 1
       +        
       +        report_progress(((cnt / wdb.keys.length.to_f) * 100).to_i.to_s)
       +
       +        next if target_number == n
       +        begin
       +                res = wdb.find_sig(target_number, n, { :fuzz => 100 })
       +        rescue ::WarVOX::DB::Error
       +        end
       +        next if not res
       +        next if res[:len] < 5
       +        found[n] = res
       +end
       +
       +$stdout.puts xml_results_matches(found).to_s
   DIR diff --git a/docs/maltego/local_transform.png b/docs/maltego/local_transform.png
       Binary files differ.
   DIR diff --git a/docs/maltego/sample_output.png b/docs/maltego/sample_output.png
       Binary files differ.
   DIR diff --git a/lib/warvox/db.rb b/lib/warvox/db.rb
       @@ -12,7 +12,10 @@ class DB < ::Hash
                        self.path      = path
                        self.threshold = threshold
                        self.version   = VERSION
       -                
       +                import(path) if path
       +        end
       +        
       +        def import(path)
                        File.open(path, "r") do |fd|
                                fd.each_line do |line|
                                        line.strip!
       @@ -32,47 +35,36 @@ class DB < ::Hash
                                                self[name] << [s, l.to_i, a.to_i]
                                        end
                                end
       -                end
       +                end        
                end
                
                #
                # Utility methods
                #
                
       -        # Find the largest pattern shared between two samples
       -        def find_sig(num1, num2, opts={})
       -                
       -                fuzz  = opts[:fuzz] || 100
       -                info1 = self[num1]
       -                info2 = self[num2]
       -                
       -                # Make sure both samples exist in the database
       -                if ( not (info1 and info2 and not (info1.empty? or info2.empty?) ) )
       -                        raise Error, "The database must contain both numbers"
       -                end
       -
       -                # Remove the silence prefix from both samples
       -                info1.shift if info1[0][0] == "L"
       -                info2.shift if info2[0][0] == "L"
       +        # Find a signature within a sample
       +        def find_match(pat, sam, opts={})
       +        
       +                fuzz    = opts[:fuzz]    || 100
       +                min_sig = opts[:min_sig] || 2
                        
       -                min_sig = 2
                        idx     = 0
                        fnd     = nil
                        mat     = nil
                        r       = 0
        
       -                while(idx < info1.length-min_sig)
       -                        sig  = info1[idx,info1.length]
       +                while(idx < pat.length-min_sig)
       +                        sig  = pat[idx,pat.length]
                                idx2 = 0
        
       -                        while (idx2 < info2.length)
       +                        while (idx2 < sam.length)
                                        c = 0 
                                        0.upto(sig.length-1) do |si|
       -                                        break if not info2[idx2+si]
       +                                        break if not sam[idx2+si]
                                                break if not ( 
       -                                                sig[si][0] == info2[idx2+si][0] and
       -                                                info2[idx2 + si][1] > sig[si][1]-fuzz and
       -                                                info2[idx2 + si][1] < sig[si][1]+fuzz
       +                                                sig[si][0] == sam[idx2+si][0] and
       +                                                sam[idx2 + si][1] > sig[si][1]-fuzz and
       +                                                sam[idx2 + si][1] < sig[si][1]+fuzz
                                                )
                                                c += 1
                                        end
       @@ -80,13 +72,35 @@ class DB < ::Hash
                                        if (c > r)
                                                r = c
                                                fnd = sig[0, r]
       -                                        mat = info2[idx2, r]
       +                                        mat = sam[idx2, r]
                                        end        
                                        idx2 += 1
                                end
                                idx += 1
                        end
                        
       +                # Return the results
       +                [fnd, mat, r]
       +        end
       +        
       +        # Find the largest pattern shared between two samples
       +        def find_sig(num1, num2, opts={})
       +                
       +                fuzz  = opts[:fuzz] || 100
       +                info1 = self[num1]
       +                info2 = self[num2]
       +                
       +                # Make sure both samples exist in the database
       +                if ( not (info1 and info2 and not (info1.empty? or info2.empty?) ) )
       +                        raise Error, "The database must contain both numbers"
       +                end
       +
       +                # Remove the silence prefix from both samples
       +                info1.shift if info1[0][0] == "L"
       +                info2.shift if info2[0][0] == "L"
       +                
       +                fnd,mat,r = find_match(info1, info2, { :fuzz => fuzz })
       +                
                        return nil if not fnd
                        
                        sig = []