DEFCON (也写做 DEF CON, Defcon, or DC) 是全球最大的计算机安全会议之一,自1993年6月起,每年在美国内华达州的拉斯维加斯举办。 DEFCON的与会者主要有计算机安全领域的专家、记者、律师、政府雇员、安全研究员、学生和黑客等对安全领域有兴趣的成员,涉及的领域主要有软件安全、计算机架构、无线电窃听、硬件修改和其他容易受到攻击的信息领域。会议除了有对前沿技术的分享外,还有多种实践项目,如Wargames,最远距离 Wi-Fi 创建比赛,和计算机冷却系统比赛等等。
这个靶机是以前玩过的,是有一年defcon比赛的靶机,拿过来玩了一下,之后我也会发一下以前玩过的好的靶机。
靶机下载地址:https://www.vulnhub.com/entry/defcon-ctf-010,160/
我使用的是VMware,导入ova文件,NAT方式连接后靶机自动获取IP
攻击机IP:192.168.2.129
靶机IP:192.168.2.167
在kali输入:
如果连接正确,会在终端看到如下内容:
终端在等待输入,我们输入一些信息,比如电影中的这段话:
终端会回答我们的输入:
通过这种方法检查连接正确,网络配置完成后进入正题。
从github下载要使用的二进制文件,并进行反汇编,用到的工具是Binary Ninja,你也可以用自己擅长的工具
打开之前先看一下这个文件:
文件要在x86处理器上运行,是ELF,不会提供我们源代码中变量名称和原始函数的任何信息,需要一点x86汇编基础
当我们用Binary Ninja打开可执行文件时,反汇编程序会找到并显示程序的入口点:
入口点-start是编译器将代码从main函数开始运行的地方,这个文件只需要在下面找到call -start
双击此函数sub_8048a24是由Binary Ninja 标记的,因为调试符号没有告诉我们真实变量信息,会显示main函数:
前几行和后几行代码设置了函数的堆栈框架,不需要关注,重点关心下面三个函数调用:
我们所关心的实际上是在0x08048A4C处压入堆栈的函数地址,这是sub_8048d14函数的一个参数,查看该函数发现该函数的子进程fork将调用地址:0x080489B4。在Binary Ninja中,我们可以点击“p”键来告诉它这个地址是一个函数:
上面就是连接处理程序,这是我们真正要关心的,当我们使用netcat连接到服务时,它会在后台运行,该功能用于处理我们的网络连接,只有3个 基本函数块,接下来我们将一块一块地进行分析
第一块:
在开头,我们可以看到设置堆栈帧的函数起始点,接下来,我们可以看到程序从堆栈指针(esp)中减去0x204 ,这有效地为堆栈上的0x204字节腾出空间用作局部变量。
然后,看到有一个call sub_8048b44, x86上FreeBSD 的调用约定push是以相反的顺序将所有函数参数传递到堆栈上,这意味着第一个参数中的call在push之前。
我已经对接下来的4条指令sub_8048b44(arg_4, message, 0);进行了反汇编,最后一个push在0x080489C9编辑堆栈中,esi包含了Binary Ninja arg_4在0x080489BF 标记的内容,这实际上是我们回调的第一个函数的值, main作为参数传递给了这个函数。
在调用这个函数之后,我们将调整堆栈指针返回到它应该在的位置并有条件地跳转到另一个地址。地址0x08048A18引用第三个函数块,它从函数中很简单地返回(在使用函数epilogue清除我们之前做的堆栈帧之后):
这意味着,如果sub_8048b44返回-1(表示发生错误),我们将跳过第二个函数块中的所有逻辑(如Binary Ninja中的箭头所示),这是一个if声明在汇编中的样子。
这就是第一个和第三个函数块,第二块更有意思:
在这里,我们将进行3个函数调用
第一个将从网络接收(recv)到0x100字节var_20c (Binary Ninja的名字ebp-0x208,这是一个位置在堆栈上的0x208字节)。这是程序获得我们输入的地方!
第二个函数调用需要我们的输入并用snprintf它来进行不同的格式化,它会将这个新格式化的字符串保存到0x12C字节var_10c(Binary Ninja的名字为 ebp-0x108,它是堆栈中0x108字节的位置),这就是程序建立输出的地方。
第三个函数调用将使用此输出字符串并sub_8048b44再次使用它通过网络发送出去,在此之后,我们转到第三个基本块(如上所示)并从函数返回。
我们已经分析了程序的所有代码,现在我们来寻找漏洞,先看一下这个堆栈:
我们的程序中有两个字符串缓冲区:var_20c和var_10c,这是输入点,它的长度是0x100(256)字节,在0x080489EB,我们recv最多可以将0x100字节存入这个内存位置。
一旦我们有了输入点,我们就可以snprintf将字符串转换成特定的格式,我们采用这个新字符串的0x12C(300)字节并将它们存储到var_10c的输出缓冲区中,这里的问题是我们没有0x12C字节的空间 , 我们只有0x100。任何额外的字节都会覆盖掉堆栈下方的其他值。这并不是函数功能所期望的,这样就出现了一个缓冲区溢出漏洞。
那么,我们如何利用这个漏洞来控制这个程序呢?在x86上最基本的方法是接管eip指令指针,这是处理器将执行的下一条指令的地址,如果我们能够控制这个值,我们可以影响下一个程序的执行位置。
为此,我们需要eip用输入数据覆盖堆栈中保存的值。为了达到这个目的,我们需要:
上面的内容加起来是255个字节,如果我们向服务发送255个“A”字符,它将会试图返回地址0x41414141(“A”的ASCII值),但是我们不希望程序崩溃 -我们的目的是拿到flag!所以,我们需要将程序指向有意义的地方。
这就是漏洞利用最难的地方了,我们要做的是将指针指向我们的输入,当我们这样做时,程序会相信我们的输入实际上是编译代码 ,就像其他可执行文件一样。因此,我们可以提供符合我们需要的新代码——shellcode
当我们通过我们的输入向可执行文件提供新代码时,这些代码就是“shellcode”,Shellcode实际上只是我们自己编写的程序包,而不是编译器的输出结果,这里有一段shellcode可以让我们在远程系统上运行一个shell:
这个shellcode只是一个execve 系统调用,系统调用是对内核的直接请求,而不是对系统库或函数的调用,也有一个独立的调用约定 ,这就是我们上面所设置的,具体来说,我们调用AUE_EXECVE 上的0x3B(59)系统调用
上面的代码是专门编写的nasm,但可以做一些小的调整之后与不同的汇编程序一起使用,因为我们需要原始字节而不是完整的可执行文件,所以需要像这样编写:
对于这个靶机,我们还需要一些更重要的东西,下面这个可以用做参考:
既然我们已经有了shellcode,并且知道了如何利用这个漏洞,现在就是实现它了,这是一个python漏洞利用脚本:
在另一个终端中监听主机端口:
现在在端口1337上有一个开放的socket已经LPORT在上面的脚本中指定了),运行脚本,将连接到服务器,接收初始化消息,发送shellcode和堆栈地址,打开保存的寄存器值,并强制在返回时执行代码sub_80489b4。
当shellcode代码执行时,它会尝试连接回LHOST我们指定端口的IP地址,可以像远程系统一样与远程系统进行交互ssh!
为了获得flag,我们需要输入:
服务器会返回flag:
在真实的比赛中将这个flag提交给得分服务器,就会拿到分数,继续进行研究。
现在我们已经利用了这个漏洞,我们将如何修复它以防止自己受到攻击?对于这个服务,直接打补丁很简单:我们将0x12C的值更改为0x100,并防止程序写入敏感的堆栈信息,这样就可以了
使用Binary Ninja让补丁变得非常容易,你只需要切换到0x080489F6(push 0x12C指令的位置 ,告诉snprintf它需要多少空间),点击“h”在十六进制编辑器视图中查看它,并将2c0x080489F7更改为00:
再次点击“h”会回到图形界面,在那里我们可以看到补丁生效了:
现在,你可以转到“文件 - >另存为...”并将修补后的可执行文件保存到磁盘。如果你将scp这个二进制文件替换到服务器并替换那里这个漏洞就没有了,该服务器也安全了!
总的来说这个靶机并不难,通过这样的实战训练提升自己的实战能力是一个不错的方法。
最快的成长方式就是实战中成长,比如你拿到攻击者的样本,立马可以吸收其手法精髓,防御上就可以有的放矢。再比如为了突破,你死磕到底,一回头会发现:卧槽,掌握了各种技巧,而这许多是死磕前绝无法想象到的。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)