project-voip.rb - projectvoip - VoIP honeypot similar to ssh honeypot, using asterisk as the backend.
 (HTM) git clone git://jay.scot/projectvoip
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
       project-voip.rb (6749B)
       ---
            1 #!/usr/bin/ruby
            2 
            3 =begin
            4 
            5 Project VOIP
            6 =============
            7 
            8 
            9 Project VOIP was meant to be a VOIP honeypot but I havent had much time to
           10 develop it so I am uploading everything I have so far here :-) 
           11 
           12 Project VOIP is based on phorensix v.1 by J. Oquendo / sil @ infiltrated dot net.
           13 
           14 Phorensix was scripted in bash and logged all information to a series of files. 
           15 Project VOIP is coded in Ruby has been updated to work with the latest version of
           16 asterisk and also logs all information to a MySQL database. 
           17 
           18 Jay Scott <jay@jayscott.co.uk>
           19 
           20 
           21 What it does
           22 ------------
           23 
           24 -> Logs the following information to a mysql database: 
           25  -> IP Address information
           26    -> Peer(s) AS Number
           27    -> Netblock AS Number
           28    -> Netblock Prefix
           29    -> AS Name
           30    -> AS Country
           31    -> AS Domain name
           32    -> ISP Name
           33  -> Number called
           34  -> SIP Agent
           35  -> SIP Channel used.
           36  -> Traceroute of the IP Address
           37  -> Packet capture of the session (.cap file)
           38  -> Recording of the call  (.wav)
           39 
           40 Installing
           41 ----------
           42 
           43 Install Tshark and ruby gems if not installed already
           44 
           45  - apt-get install tshark rubygems mysql-client libmysqlclient-dev
           46 
           47 Install the ruby gem files for mysql
           48 
           49   gem install mysql
           50 
           51 Use the configs below as a template, changing the values as appropriate
           52 
           53 
           54 Make sure and update the Mysql information!.
           55  
           56 =end
           57 
           58 require 'rubygems'
           59 require 'optparse'
           60 require 'mysql'
           61 
           62 BASE_DATA     = "/opt/project-voip/data"
           63 CRON_FILE     = "/opt/project-voip/data/cron"
           64 MYSQL_HOST    = ""
           65 MYSQL_USER    = ""
           66 MYSQL_PASS    = ""
           67 MYSQL_TABLE   = ""
           68 HONEYPOT_ID   = "1"
           69 
           70 options = {}
           71  
           72 op = OptionParser.new do|opts|
           73 
           74   opts.banner = "Usage: ruby #{File.basename($0)} "
           75   options[:ipaddress] = nil
           76   options[:data] = nil
           77   options[:useragent] = "Unknown"
           78   options[:exten] = "Unknown"
           79   options[:channel] = "Unknown"
           80 
           81   opts.on( '-i', '--ipaddress [IPADDRESS]', 'IP Address' ) do|i|
           82     options[:ipaddress] = i
           83   end
           84 
           85   opts.on( '-e', '--exten [EXTEN]', 'Phone number called ' ) do|e|
           86     options[:exten] = e 
           87   end
           88 
           89   opts.on( '-c', '--channel [CHANNEL]', 'Channel passed by Asterisk' ) do|c|
           90     options[:channel] = c 
           91   end
           92   
           93   opts.on( '-u', '--useragent [USERAGENT]', 'Callers user-agent' ) do|u|
           94     options[:useragent] = u
           95   end
           96   
           97   opts.on( '-w', '--wav [WAV FILE]', 'WAV file location' ) do|w|
           98     options[:wav] = w
           99   end
          100   
          101   opts.on( '-d', '--data', 'Upload Data Files to MySQL.' ) do
          102     options[:data] = true
          103   end
          104 
          105   opts.on( '-h', '--help', 'Display this screen' ) do
          106     puts opts
          107     exit
          108   end
          109 
          110   opts.on("--version", "Show version") do
          111     puts OptionParser::Version.join('.')
          112     exit
          113   end
          114 end
          115  
          116 op.parse!
          117 
          118 # Get the options passed
          119 ip_address      = options[:ipaddress]
          120 data_update     = options[:data]
          121 useragent       = options[:useragent]
          122 called_exten    = options[:exten]
          123 caller_channel  = options[:channel]
          124 wav_file        = options[:wav]
          125 
          126 # No IP.. somethings wrong, exit :p
          127 if !data_update.nil?
          128 
          129   File.open("#{CRON_FILE}").each_line{ |s|
          130     arrayData = s.split(':')
          131     file_id = arrayData[0]
          132     file_cap = arrayData[1].strip
          133     file_wav = arrayData[2].strip
          134     
          135     cf = File.new(file_cap, 'rb')
          136     wf = File.new(file_wav, 'rb')
          137     
          138     cap_data = Mysql.escape_string(cf.sysread(cf.stat.size))
          139     wav_data = Mysql.escape_string(wf.sysread(wf.stat.size))
          140     
          141     begin
          142       con_info = Mysql.real_connect("#{MYSQL_HOST}", "#{MYSQL_USER}", "#{MYSQL_PASS}", "#{MYSQL_TABLE}")
          143     rescue Mysql::Error => e
          144          puts "Error code: #{e.errno}"
          145          puts "Error message: #{e.error}"
          146          puts "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
          147     end
          148     
          149     con_info.query("UPDATE caller SET capture=\"#{cap_data}\", recording=\"#{wav_data}\" WHERE id='#{file_id}'")
          150     
          151     con_info.close
          152   }
          153   
          154   puts "Updated"
          155   File.delete("#{CRON_FILE}")
          156   File.new("#{CRON_FILE}", "w")
          157   exit(1)
          158   
          159 end
          160 
          161 # No IP.. somethings wrong, exit :p
          162 if ip_address.nil?
          163   exit(-1)
          164 end
          165 
          166 # Set the dates we want
          167 date = Time.new
          168 current_date  = date.strftime("%Y%m%d")
          169 current_time  = date.strftime("%H%M%S")
          170 mysql_date    = date.strftime("%H%M%S")
          171 
          172 cap_file  =  "#{BASE_DATA}/#{ip_address}-#{mysql_date}.cap"
          173 
          174 whois_info = Array.new
          175 whois_data = `whois -h whois.pwhois.org #{ip_address}`
          176 whois_info = whois_data.split("\n")
          177 
          178 # ip_address, origin_as, prefix, as_path, as_org_name, org_name, net_name, cache_date, lat, long, city, region, country
          179 whois_info = whois_info.map do |x| 
          180   x.split(':')[1].strip
          181 end
          182 
          183 traceroute_output = `traceroute -w 1 -m 25 -n #{ip_address} &`
          184 packet_capture = `tshark -q -R \"ip.addr == #{ip_address}\" -w #{cap_file} -a duration:20 &`
          185 
          186 # Connect to MySQL DB. 
          187 begin
          188   con_info = Mysql.real_connect("#{MYSQL_HOST}", "#{MYSQL_USER}", "#{MYSQL_PASS}", "#{MYSQL_TABLE}")
          189 rescue Mysql::Error => e
          190      puts "Error code: #{e.errno}"
          191      puts "Error message: #{e.error}"
          192      puts "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
          193 end
          194 
          195 rs_check = con_info.query("SELECT * from information WHERE ip_address='#{ip_address}'")
          196 
          197 # If the IP is unique then create a new record for it. 
          198 if rs_check.num_rows == 0
          199   con_info.query("INSERT INTO information 
          200                 (ip_address, asn, prefix, as_path, as_org_name, org_name, net_name, lat, lon, city, region, country, traceroute) 
          201                 VALUES 
          202                 ('#{ip_address}', '#{whois_info[1]}', '#{whois_info[2]}', '#{whois_info[3]}', '#{whois_info[4]}', '#{whois_info[5]}', '#{whois_info[6]}', '#{whois_info[8]}', '#{whois_info[9]}', '#{whois_info[10]}', '#{whois_info[11]}', '#{whois_info[12]}', '#{traceroute_output}')")
          203 end
          204 
          205 d = DateTime.now
          206 
          207 # Insert the call information
          208 con_info.query("INSERT INTO caller 
          209                 (uid, number, agent, channel, timestamp, honeypot) 
          210                 VALUES 
          211                 ('#{ip_address}','#{called_exten}','#{useragent}','#{caller_channel}', '#{d.to_s}', '#{HONEYPOT_ID}')")
          212 
          213 
          214 # This section dumps the wav and cap file information to a file to be read by 
          215 # cron. We cant upload the wav and cap file at the same time because you dont
          216 # know if the call has finished so the wav and cap are still in progress.
          217 #
          218 # Asterisk cant reliably detect if a call has terminated, if it did we could
          219 # call function to kill tshark and then update the DB with the file data then. 
          220 #
          221 # PROBLEM: The cron could run just after this information has been saved or not
          222 # finished processing and chop the data anyway 
          223 # 
          224 # meh.... need to ditch asterisk and just create my own sip server.
          225 
          226 # Get the ID of the record we just inserted.
          227 
          228 rs_id = con_info.query("SELECT MAX(id) AS MAXID FROM caller")
          229 
          230 while row = rs_id.fetch_row do
          231     last_id = row[0]
          232 end
          233 
          234 con_info.close
          235 
          236 if !File::file?(CRON_FILE)
          237   cronFile = File.new("#{CRON_FILE }", "w")
          238 end
          239 
          240 File.open("#{CRON_FILE}", "a") do |f|
          241   f.puts "#{last_id}:#{cap_file}:#{wav_file}"
          242 end