带外科医生的二进制工具:攻击性安全专业人员的入门知识

随着像Parrot OS和Kali Linux这样的面向安全的Linux发行版的流行,加上其捆绑的攻击性安全工具,以及YouTube和HackForums上有关如何使用它们的指南的不乏,似乎任何人都可以成为“黑客”如今。 触发Wifite或Fern之类的工具来尝试闯入安全性较差的wifi网络并不需要任何技能,甚至不需要知识,但是如果您要依靠少数比您自己知识渊博的人编写的工具,您将无法通过实际的IDS进行现实的红队交战,而将笨拙的,薪水不足的员工留在默认设置下的路由器无法成功。

黑客是一门艺术,它是一种更深入地学习,努力理解事物的实践,直到您比其他所有人都更了解它们为止。 最好的不是依赖于教程和预先构建的工具。 最好的工具是您自己制作的工具。 这两个原则使我构建了Surgeon,即二进制Pwning工具(以前为CaveMan;最后是下载链接)。 它迫使我理解二进制可执行文件,以便可以解码二进制文件并在需要时手动在寄存器中执行计算,同时使我能够完全控制如何编辑可执行文件。 如果您从理论上想知道如何对可执行文件进行后门操作,或者已经准备好动手操作并花费很多时间,上帝都知道您需要花费多少时间进行调试并确保您无法被检测到,那么这是给您的。

本文不是入门级材料,并且需要对汇编和机器代码有现有的了解。

ELF(可执行和可链接文件)格式

ELF是可执行二进制文件,库和核心转储的标准,并且是Linux系统使用的最常见的文件格式之一。 尽管Surgeon也可以使用PE(便携式可执行文件)文件,但本指南将重点介绍ELF文件。 每个ELF文件都以所谓的ELF标头开头,该标头的前四个字节始终为0x7f 0x45 0x4c 0x46。 这是幻数,0x7f,后跟字母ELF的ASCII值。 这个魔术数字告诉可以使用ELF文件的计算机,这一个ELF文件。 ELF标头为50字节或62字节,具体取决于它是32位还是64位程序。 前24个字节始终指示相同的信息,包括体系结构。 魔术数字后面的字节表示体系结构,后面的字节表示字节序等。

ELF标头包含读取和执行文件所需的所有相关信息,包括可执行文件的布局。 您可以在此处找到有关媒介撰稿人James Fisher的精彩且深入的指南 。 ELF标头指向程序标头表和节标头表的偏移量,它告诉我们标头的大小,标头中条目的数量,最重要的是入口点 。 由于所有数据和指令都是由字节组成的,因此我们不能仅从文件的开头开始执行字节。 入口点指向程序中实际的,可执行的指令所在的部分,并且应该开始执行指令。

除ELF标题外,本指南中我们感兴趣的另一部分是节标题表 。 该表基本上是ELF文件的目录,指向各个部分,它们的长度,标志等。这告诉我们在哪里可以找到.data,.bss,.comment和.text部分。作为PLT(过程链接表)和GOT(全局偏移表)等。

不使用参数或仅使用-f <file>参数运行时,Surgeon的默认行为是为我们解析ELF标题和节标题表,并返回相关信息。 (它也以预定义的方式搜寻代码洞穴,这将在下面讨论)。

如果我们在上面看一看,可以看到已解析的标头和部分标头表的一部分。 仅在标记下标有“ X”的部分可以执行; 尝试在标记为不可执行的部分中执行代码将导致程序运行SIGSEGV,并可能触发IDS。 我们还可以看到入口点是0x00000440,它对应于.text节开始的偏移量,正如我们所期望的那样,因为它保存了已编译的指令。

代码洞穴和后门

有了以上信息,我们就可以开始构建二进制文件了。 为了确保无论有效负载将被执行,我们都需要做两件事:

找到某个地方来存储我们希望执行的代码。覆盖入口点以指向我们添加的内容。

一种常见且简单但草率的方法是添加我们自己的节,将其标记为可执行,将条目添加到节头表中,然后将条目指向该处。 但是,这带来了一个问题:我们增加了可执行文件的文件大小。 (当然,如果要保留入口点完整,指向正确的位置,则可以使用JMP将第一条指令覆盖到有效负载中,执行它,然后运行覆盖的指令,然后再跳回到原始执行流程。 )

比方说,例如,在整个参与过程中,您决定将二进制文件的修改版本放到要执行的目标计算机上。 您可以使用社会工程学,也可以使用后门程序,因为该程序是如此常用,受害者几乎不可能停止运行它。 恶意软件扫描程序可以分析二进制文件,发现二进制文件中的字节太多,超出了预期。 它可以进一步分析,请参见标记为可执行的多余部分,并将其标记为隔离。

隐形是这里的游戏名称,有一种更好的方法,它叫做代码洞穴。 代码洞穴本质上是一长串连续的Null Bytes。 有时,由于函数之间的对齐原因,编译器会添加代码洞穴,或者它们可用于为运行时生成的代码或自修改代码分配内存。 无论出于何种原因,我们都可以使用它们来存储我们的代码,而无需覆盖现有代码或增大文件大小。

理想情况下,应该找到一个已经标记为可执行文件的区域,其中包含我们代码的空间。 如果我们找不到一个,可以使用Surgeon修改在节头表中设置的标志,以允许在放入它的节中执行我们的代码。

为了插入我们的代码,我们需要代码中插入。 最简单,最快的方法是通常编写和编译所需的代码,因此您可以挑选出已编译的机器代码并将其插入所需的位置。 执行代码后,您可以选择以下几种:

  1. 您可以让程序继续运行,并且很可能崩溃或出现段错误。 这可能会触发反恶意软件,并向用户(可能是管理员)提示您的操作。
  2. 进行干净的退出,并使syscall进入exit()。 稍微好一点,但这只是微不足道的,因为这样我们的pwned程序仍然不会像预期的那样运行自己的本机代码。 这也会使用户出现错误。
  3. 将执行流程跳回到原始入口点,然后正常运行。 这是理想的行为,如果做得正确,可能会使我们的存在无法察觉。

您提供的任何指令很可能会影响处理器的各种寄存器和状态。 为了正确完成所有操作,您应该查看程序在入口点开始执行时寄存器的外观。 已通过您的代码改变的任何寄存器将需要重置为初始值,当你准备跳回到原来的入口点,以确保功能正常。

在已编译程序中编辑任何内容都是危险的,并且最小的更改可能会产生级联效应,从而急剧改变程序的行为方式。 在这里用脚开枪很容易。

准备? 让我们尝试一下。

一个简单的例子

对于以下示例,我决定使用我编写的一个简单程序来充当CrackMe和一个简单的概念验证,以演示如何触发缓冲区溢出和劫持执行流。 由于它是编译后的ELF可执行文件,因此它也可以完美地演示上述概念验证。 我们的任务很简单:找到合适的位置插入一些代码,将程序的入口点更改为该位置,然后插入一些我们可以执行的机器代码。 我们将插入的机器代码只会做一件事:跳转到原始入口点的指令,因此程序可以正常继续。 如果您想遵循以下代码,请执行以下操作:

#include<stdio.h>
#include<string.h>
void benderdontcare(char* input){
 char name[ 181 ];
 strcpy(name, input);
 printf(“%s? Yeah, I don’t care, meatbag.\n”, name);
}
int main(int argc, char** argv){
 if (argc != 2 ){
 printf(“That’s now how you interact with me, Bender. You need to say something! And if you put spaces in it, put the whole thing in quotes! What am I, an AI language parser? (I’m only 40 percent language parser!)\n”);}
 else {
 int bender = 1 ;
 printf(“I’m Bender, the Magnificent body-stealing robot!\n”);
 if (bender== 2 ){
 printf(“But Inspector 7 said I was perfect!\n”);
 }
 benderdontcare(argv[ 1 ]); 
 printf(“Anything less than immortality is a complete waste of time.\n”);
 return 0 ;}
}

Surgeon的默认操作是在所有部分(而不是仅可执行部分)中搜索长度至少为64个字节的空字节(0x00)的所有部分(-A参数)中的任何代码陷阱。 上面显示了默认输出的摘录,但Surgeon无法找到任何合适的代码陷阱。 由于我们只需要几个字节,因此我们可以指定较少数量的连续null来查找合适的洞穴。

为了缩小可行的结果范围,我们将仅搜索可执行部分,并且仅需要8个字节即可找到一个洞穴。 在程序链接表中可以看出自己的一面:

好的,我们有地方放置我们的说明! 由于我们将只执行JMP,因此我们可以在此处简单地查找语法。 我们很幸运,因为代码洞穴的偏移量为0x3e9,原始入口点为0x440,这意味着它们距离我们的目标只有0x57(十进制87)个字节! 这使我们可以执行短距离跳转,操作码需要最少的字节数2个字节。

根据上述关于x86指令的指南,短跳转的操作码为0xEB,后跟有符号字节值(-128 / + 127),表示要进行的跳转位置。 通常,每当我在代码洞中插入自己的代码时,请确保将第一个字节保留为空字节。 空字节通常用作终止符,尤其是当您必须使用以前是数据而不是指令且不可执行的段时。 尽管并非总是必要的,但最佳实践应始终如一地应用。

结果,我们将从0x3ea而不是0x3e9开始插入代码。 第一个字节是0xEB,告诉处理器执行短跳转。 之后的字节位于偏移量0x3eb。 通常,下一个字节位于偏移量0x3ec处,与我们的目标(原始入口点0x440)相距0x54(十进制84)个字节。 因此,我们需要向前跳转84个字节。 然后,我们需要插入的操作码为0xeb 0x54,偏移量为0x3ea,如下所示:

奏效了吗? 我们仍然可以运行可执行文件,而性能与我们所看到的没有明显区别,因此让我们启动GDB并逐步执行说明! 使用“ info file”命令,我们可以看到入口点位于0x3ea。 但是,当文件实际运行时,它将在其他位置加载。 告诉GDB在0x3ea处放置一个断点将导致它在执行开始时未设置断点。 为了确定我们需要在哪里设置断点,我们需要使用“ starti”命令。 GDB有用地具有starti ,让我们可以在_start或main之前进入第一条指令,并在断点处停止。 矿山停在??中的“ 0xf7fd6c70” ()from /lib/ld-linux.so.2”。此处使用“ info file”命令向我们展示了可以在其中设置断点的真实入口点:“入口点:0x565553ea”。

因此,我们将在0x565553ea处设置一个断点,然后运行该程序。 这将停止我们注入的shellcode的执行,使我们可以在逐步执行操作代码时对其进行监视。 命令“ disas / r 0x565553ea,0x565553ec”将强制GDB向我们返回我们正在寻找的操作码和汇编代码,而不是抱怨在指定地址没有功能。 GDB反映了我们正在寻找的确切更改:原始入口点0x56555440的JMP指令<_start>。

在这里,我们可以看到我们已经成功地将我们自己的shellcode添加到了一个可用的代码洞穴内的一个完整的,已编译的二进制文件中。 该Shellcode完全按预期执行,然后将控制权返回到原始入口点,从而使我们的程序能够按预期运行,而不会向最终用户提示我们的代码是否存在。 我们不必通过更改代码大小的方式添加代码来修改二进制文件,也不必修改未标记为可执行文件的现有部分。 对于我们的文件,这是完美的概念证明。

Surgeon正在进行中,正在添加对PE Windows文件的支持。 您可以在github上免费下载它 。 您和您自己应对您的行为负责。 绝对不应将本条款解释为对任何非法活动的认可; 这是为了增强可能不了解如何执行此操作的Red Team成员的能力,以及需要了解此方法如何对其进行防御的Blue Team成员的能力。 话虽如此,祝您黑客愉快!


From: https://hackernoon.com/pwning-binaries-with-surgeon-a-primer-for-offensive-security-professionals-e33765ab2444

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值