通过POC来学习漏洞的原理

本文介绍的是 easyFTPServer 1.7.0.2 ‘Http’ remote Buffer Overflow 的漏洞执行流程,通过已知的 POC 来推断程序的大概执行流程以及漏洞利用原理。

Poc 和软件的下载地址:

https://pan.baidu.com/s/1dHjKFCX (提取码 5h7h )

0x01 了解 FTP

FTP 服务全称为文件传输协议服务,其工作模式采用 C/S 模式,用户想要通过 FTP 服务访问到共享的文件信息时,首先需要在自己的计算机系统上运行一个 FTP 客户端,这个客户端可以是一个 FTP 应用程序,也可以是操作系统自带的命令行程序,然后在 FTP 客户端中输入用户名和密码来登录 FTP 服务程序,成功登陆后,用户就可以获取远程计算机上共享的文件信息了。

emmmm……,换句话说客户端连接远程FTP服务器需要以下几个步骤

(1)建立 TCP 连接

(2)客户端向 FTP 服务程序发送 USER 命令以标识用户自己的身份,然后服务程序要求客户端输入密码

(3)客户端发送 PASS 命令,同时将用户密码发送给远程 FTP 服务程序

(4)服务端判断并通过认证

(5)客户端开始利用其它 FTP 协议进行文件操作

(6)结束此次连接,用 QUIT 命令退出

0x02 搭建实验环境

FTP 服务端:

win xp sp3(我用的是吾爱破解论坛的虚拟机)

FTP 客户端:

win 7 (要求 Python 环境)

easyFTPServer 1.7.0.2 解压到 xp 上,点击运行,目录下会产生 3 个 XML 配置文件以及名为 anonymous 的文件夹,该程序默认情况下会设置一个名为 anonymous 的初始用户,而 anonymous 文件夹就是该用户使用的文件目录。

好了,我们已经配置完服务端了。

大家可能已经发现了,这个软件除了提供 21 端口的 FTP 服务之外,还提供了 8080 端口的服务,这个 8080 端口就是今天我讲解的重点,我们先访问一下

http://192.168.1.106:8080

emmmm,输入 anonymous,anonymous 之后界面如下

们就可以通过网页对 FTP 服务器上分享的文件进行下载其本质是把 FTP 分享的文件以 web 页面的方式给大家呈现了出来。

0x03 poc 的简单介绍

本文我们不研究怎么写 poc,当然我会在最后给大家介绍一下 寻蛋(egghunter) 这种 exp 开发技术,我打算通过已经写好的 exp 来给大家反向讲解一下这个漏洞的成因及原理,个人认为这样更容易理解,具体怎么样要问各位看官了…… 我会在调试的时候把自己的思路给大家详细说明一下

exp 如下(Python2.7 版本)

我大概讲解一下这个 exp 的内容,本质是构造了一个 get 请求,给 path 参数一个超长的字符串里面有我们覆盖返回地址的跳板地址,而 HOST 后面的字符串是我们构造的 shellcode,Authorization 后接我们的用户名和密码。

这样我们就可以简单的登录并把恶意的数据发送给 192.168.1.106:8080 页面所在的服务端上。

这里的 shellcode 是在 win xp sp3 上弹出计算器,可以通过 metasploit 的 msfvenom 模块进行生成,我就不演示了。

0x04 执行 exp

一切准备就绪,我们开始执行 exp

首先,在 FTP 服务端上用 OD 附加 EasyFTP Server 这款软件,由于这个软件运行的时候有两个进程,我们附加它的FTP服务进程,也就是 ftpbasicsvr 这个进程(这里尽量使用原版 OD )

点击附加

然后我们按 F9 让程序继续执行,即让服务端接着监听 8080 端口的请求。

我们回到客户端,执行之前写好的 exp。

按下回车键,回到服务端看看发生了什么?

emmmmm…… 计算器弹了出来,说明 exp 没有什么问题。

那么问题来了,我想看 shellcode 的详细执行流程啊,而不是仅仅弹出一个计算器。

接着我陷入了沉思:客户端和 FTP 服务端进行通信的时候,本质上是 socket 通信,只不过是端口变成了 8080 罢了,有发送就会有接收,我们在 recv() 下断点不就可以截获从客户端发来的数据了吗?

写过 c/c++ 的人都知道,socket 中的 recv() 函数是在 WS2_32.dll 中的。

在 OD 上直接下断 bp recv,然后重启 FTP 服务器,重新附加进程,客户端重新连接,OD 的状态如下:

emmmmm……

看来我们的想法是对的,程序断在了 recv() 函数的入口,注意看堆栈窗口有一堆 aaaaaaaaaaa,那就是我们构造的 get 请求中 path 的参数,接下来我就要说明一下这个栈溢出漏洞的根本原因了,我们可以看到函数入口这样一条汇编

sub esp 124

就是开辟了一个 292 字节的栈空间(124 是十六进制),get 请求中的 path 参数的值,也就是 exp 中 buf 的值,就存放在这个空间中。

而这个漏洞产生的原因就是没有对 path 参数的值,也就是 buf 的值进行长度的校验,以致于我们可以构造超长的字符串从而覆盖这个处理函数的返回地址进而对程序执行流程进行劫持。

具体怎么实现的,我们拉到这个处理函数的末尾,在平栈的时候下断也就是在 0040b92D 处下断,按 F9 执行:

此时要注意堆栈,有一堆 6161616161 , 而 61 正是 a 的十六进制表示,说明我们构造的超长字符串已经入栈,F8 单步执行到 retn 8,我们看到返回地址被覆盖成了跳板地址(这里的跳板地址是 ntdll.dll 中的 jmp esp,地址可以用 OD 插件或者 msf 的模块进行搜索)

执行完 jmp esp 后,F8 向下执行,进入 寻蛋(egghunter) 部分(这里就可以解释一下 py 脚本中在跳板地址后面为什么有 8 个 \x63,因为 retn 8 嘛,返回的时候跳过了 8 个字节)

来到了我们的寻蛋指令,emmmmm 关于这部分,你只需要知道的是蛋(也就是我们的 shellcode )包含四个字节的标志头. 如果寻蛋开始, 首先它会搜索整个内存直至找到重复两次找到这个标志(如果标志是 `”\x44\x7A\x32\x37”, 那么就搜索 ”\x44\x7A\x32\x37\x44\x7A\x32\x37” )。当找到这个标志, 改变执行流跳转到标志后的 shellcode 执行。

接着我们对着 00a2F196 按 F4,我们看寄存器 edi 的值变为了 003D4960 也就是寻蛋完成了,然后按 F8 执行,跳转到了 003D4960 这段空间

明的你已经发现了,此时执行的 shellcode 正是 py 脚本中 HOST: 后边的那一部分,抛开这个跳转不谈,此时,你或许有这样的疑问

HOST: 后面的那些我们构造的 shellcode 到底存放在哪呢?

我一开始想到的是栈,因为我们构造的 path 参数的值就在栈里面,那么 HOST 这一部分也应该在里面,但我光速否决了,栈顶是 00a2 开头的跟 003D 相差太大。

那只能是堆了……

判断栈地址还是堆地址直接用快捷键 ALT+M 就可以看到了,其实这个方法是后来大佬给我说的,我当时判断的方法是这样的:

我先在数据窗口 ctrl+G 输入 003D0178

003D0178 指向 003D5178(chunk),我们转到 003D5178

发现 003D5178 指向 003D0178,而 shellcode 的起始位置 003D4960 正好处于 003D0688003D5178 之间,所以这段 shellcode 确实在堆中……

emmmm…… 跟大佬的方法一比,还是对 OD 有点不熟练啊……

接下来我又产生了一个问题:FTP 服务端是什么时候把 HOST: 后面的 shellcode 写到堆中的呢?

要解决这个问题,我们要以一个开发者的思维来考虑,当一个 get 请求来的时候,我们肯定会创建一个堆区来保存 path,Host,Authorization 这些字段的值,据我的开发经验 c/c++ 对堆使用的函数

(1)一个是 malloc() ,动态分配,涉及到堆的分配

(2)一个是 HeapCreate() ,创建一个堆,紧接着用 HeapAlloc() 方法分配堆空间

我倾向于第二种,所以我对 HeapCreate() 下了一个断点( bp HeapCreate ),该函数位于 kernel32.dll 中,重新运行,客户端建立连接,发现 OD 并没有断在该函数上……

emmmm…… 难道我想错了?

我接着陷入了沉思: HeapCreate() 返回的句柄会不会是一个全局变量,而且在我附加到进程之前就已经进行初始化了,所以才没有断下来,那么我在 HeapAlloc() 下断不就可以了吗?因为开发者肯定会在数据到服务端的时候才进行堆分配并赋值的!

接着我把目标转向了 HeapAlloc()。这里要注意一下在 OD 直接对 HeapAlloc() 下断是不行的,因为 kernel32.dll 中的 HeapAlloc() 函数执行时紧接着会调用 ntdll.dll 中的 RtlAllocateHeap() 所以我们直接对 RtlAllocateHeap() 下断(bp RtlAllocateHeap),重启服务器,重新建立连接之后

程序断到了 RtlAllocateHeap() 的入口处,紧接着在数据窗口转到 003D4960 ,观察数据窗口,一直按 F9,会产生第一次突变

此时已经把 path 参数的值写入堆中,然后接着按 F9,会产生第二次突变。

exp 中 Host: 的值,也就是弹出计算器的 shellcode 已经分配到堆中了,整个流程也就分析完了。

0x05 小结

该漏洞是通过 http 的 get 请求提交的超长字符串淹没程序的返回地址,进而控制程序流程,再使用寻蛋技术使程序跳向堆中进行执行我们已经构造好的 shellcode。

0x06 关于寻蛋技术

我这里粗略的讲一下寻蛋技术的概要,通过前面的溯源我们应该都清楚了缓冲区溢出是怎么工作的,以及我们怎么劫持一个程序的执行流程,那么问题来了,如果像这个漏洞一样栈的空间不足放不下那么大的 shellcode 怎么办?

很明显我们通过把 shellcode 放到堆里面,换句话说就是布置 shellcode 在不同的内存区域,如果离的很近那么我们直接用 jmp offerset

如果离的远,就像本例一样,一个在栈,一个在堆。那么我们就需要一个新的技术来找到它,这便是寻蛋技术的由来。蛋指的是 shellcode 的前四个字节,就相当于一个标志头。寻蛋开始时,首先它会搜索整个内存(栈/堆/…)直至找到重复两次找到这个标志。

当找到这个标志, 改变执行流跳转到标志后的 shellcode 执行。相信大家结合这个实例一定会对寻蛋技术有一个更深的体会。

原文发布于微信公众号 - 信安之路(xazlsec)

原文发表时间:2018-01-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT大咖说

饿了么资深Android工程师带你领略Kotlin协程的力量

内容来源:2018 年 6 月 28 日,饿了么资深Android工程师张涛在“droidcon上海2018安卓技术大会”进行《领略kotlin协程的力量》演讲...

5184
来自专栏IT技术精选文摘

RPC原理及实现

1 简介 RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明...

7519
来自专栏Golang语言社区

Golang工程经验(下)

线上服务端系统,必须要有降级机制,也最好能够有开关机制。降级机制在于出现异常情况能够舍弃某部分服务保证其他主线服务正常;开关也有着同样的功效,在某些情况下打开开...

5883
来自专栏逻辑熊猫带你玩Python

Python | Debugger和pdb,鸡肋否?

我们知道虽然入门级编程语言最好是C和Python,但是C和Python是有这本质的不同的,那就是C语言是编译型语言,而Python是解释型语言。

2342
来自专栏有趣的Python

慕课网-Linux C语言编程基本原理与实践-学习笔记

个人整理,学习自用。课程内容by慕课网。 Linux C语言编程基本原理与实践 高效的学习带着目的性: 是什么 -> 干什么 -> 怎么用 重识C语言 C语言是...

3616
来自专栏非著名程序员

Android Studio你不知道的调试技巧

? 写代码不可避免有Bug,通常情况下除了日志最直接的调试手段就是debug;那么你的调试技术停留在哪一阶段呢?仅仅是下个断点单步执行吗?或者你知道 Eval...

30210
来自专栏java一日一条

40+个对初学者非常有用的PHP技巧(二)

考虑使用ob_gzhandler?不,别这样做。它没有任何意义。PHP应该是来写应用程序的。不要担心PHP中有关如何优化在服务器和浏览器之间传输的数据。

951
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第二十天 Redis学习【悟空教程】

rpm -e --nodeps java-1.6.0-openjdk-1.6.0.0-1.66.1.13.0.el6.i686

1865
来自专栏互联网大杂烩

拜占庭容错机制

Client会发送一系列请求给各个replicas节点来执行相应的操作,BFT算法保证所有正常的replicas节点执行相同序列的操作。因为所有的replica...

902
来自专栏技术博客

系统上线后WCF服务最近经常死掉的原因分析总结

  最近系统上线完修改完各种bug之后,功能上还算是比较稳定,由于最近用户数的增加,不知为何经常出现无法登录、页面出现错误等异常,后来发现是由于WCF服务时不时...

1243

扫码关注云+社区

领取腾讯云代金券