require 'metasm'
require 'erb'
require 'metasploit/framework/compiler/utils'
require 'metasploit/framework/compiler/headers/windows'
require 'metasploit/framework/obfuscation/crandomizer'

module Metasploit
  module Framework
    module Compiler

      class Windows

        # Returns the binary of a compiled source.
        #
        # @param c_template [String] The C source code to compile.
        # @param type [Symbol] PE type, either :exe or :dll
        # @param cpu [Metasm::CPU] A Metasm cpu object, for example: Metasm::Ia32.new
        # @raise [NotImplementedError] If the type is not supported.
        # @return [String] The compiled code.
        def self.compile_c(c_template, type=:exe, cpu=Metasm::Ia32.new)
          headers = Compiler::Headers::Windows.new
          source_code = Compiler::Utils.normalize_code(c_template, headers)
          pe = Metasm::PE.compile_c(cpu, source_code)

          case type
          when :exe
            pe.encode
          when :dll
            pe.encode('dll')
          else
            raise NotImplementedError
          end
        end

        # Saves the compiled code as a file. This is basically a wrapper of #self.compile.
        #
        # @param out_file [String] The file path to save the binary as.
        # @param c_template [String] The C source code to compile.
        # @param type [Symbol] PE type, either :exe or :dll
        # @param cpu [Metasm::CPU] A Metasm cpu object, for example: Metasm::Ia32.new
        # @return [Integer] The number of bytes written.
        def self.compile_c_to_file(out_file, c_template, type=:exe, cpu=Metasm::Ia32.new)
          pe = self.compile_c(c_template, type)
          File.write(out_file, pe, mode: 'wb')
        end

        # Returns randomized c source code.
        #
        # @param c_template [String]
        #
        # @raise [NotImplementedError] If the type is not supported.
        # @return [String] The compiled code.
        def self.generate_random_c(c_template, opts={})
          weight = opts[:weight] || 80
          headers = Compiler::Headers::Windows.new
          source_code = Compiler::Utils.normalize_code(c_template, headers)

          randomizer = Metasploit::Framework::Obfuscation::CRandomizer::Parser.new(weight)
          randomized_code = randomizer.parse(source_code)
          randomized_code.to_s
        end

        # Returns the binary of a randomized and compiled source code.
        #
        # @param c_template [String]
        #
        # @raise [NotImplementedError] If the type is not supported.
        # @return [String] The compiled code.
        def self.compile_random_c(c_template, opts={})
          type = opts[:type] || :exe
          cpu = opts[:cpu] || Metasm::Ia32.new

          random_c = self.generate_random_c(c_template, opts)
          self.compile_c(random_c, type, cpu)
        end

        # Saves the randomized compiled code as a file. This is basically a wrapper for #self.compile_random_c
        #
        # @param out_file [String] The file path to save the binary as.
        # @param c_template [String] The randomized C source code to compile.
        # @param opts [Hash] Options to pass to #compile_random_c
        # @return [Integer] The number of bytes written.
        def self.compile_random_c_to_file(out_file, c_template, opts={})
          pe = self.compile_random_c(c_template, opts)
          File.write(out_file, pe, mode: 'wb')
        end
      end

    end
  end
end
