pwnable.kr [Toddler's Bottle]

来源:互联网 发布:json格式 java 编辑:程序博客网 时间:2024/04/29 03:11

这题流程相对较长,考查Linux编程的基本功(笔者做到这题不禁感叹自己基本功还是欠了不少火候)。
在一开始,尝试写Python脚本去完成验证,但stage 2关于stdio的验证却苦无思路。
这里感谢werew在他的writeup中提供的解决思路,这才豁然开朗。
参考链接:https://werewblog.wordpress.com/2016/01/11/pwnable-kr-input/

关于file descriptor maping的内容我另外做了整理,
详见 http://blog.csdn.net/qq_19550513/article/details/61915756

先放上题目源码:

/* ssh input2@pwnable.kr -p2222 (pw:guest) */int main(int argc, char* argv[], char* envp[]){    printf("Welcome to pwnable.kr\n");    printf("Let's see if you know how to give input to program\n");    printf("Just give me correct inputs then you will get the flag :)\n");    // argv    if(argc != 100) return 0;    if(strcmp(argv['A'],"\x00")) return 0;    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;    printf("Stage 1 clear!\n");     // stdio    char buf[4];    read(0, buf, 4);    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;    read(2, buf, 4);        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;    printf("Stage 2 clear!\n");    // env    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;    printf("Stage 3 clear!\n");    // file    FILE* fp = fopen("\x0a", "r");    if(!fp) return 0;    if( fread(buf, 4, 1, fp)!=1 ) return 0;    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;    fclose(fp);    printf("Stage 4 clear!\n");     // network    int sd, cd;    struct sockaddr_in saddr, caddr;    sd = socket(AF_INET, SOCK_STREAM, 0);    if(sd == -1){        printf("socket error, tell admin\n");        return 0;    }    saddr.sin_family = AF_INET;    saddr.sin_addr.s_addr = INADDR_ANY;    saddr.sin_port = htons( atoi(argv['C']) );    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){        printf("bind error, use another port\n");            return 1;    }    listen(sd, 1);    int c = sizeof(struct sockaddr_in);    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);    if(cd < 0){        printf("accept error, tell admin\n");        return 0;    }    if( recv(cd, buf, 4, 0) != 4 ) return 0;    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;    printf("Stage 5 clear!\n");    // here's your flag    system("/bin/cat flag");        return 0;}

一共分为五个验证步骤,先一个个来看。

Stage 1 argv

第一步比较常规,argv需要传递100个参数,即实际为argv[101](argv[0]指向执行的路径,argv[100]为NULL)。

    /* stage 1 */    char *argv[101] = {0};    for(int i = 1; i<100; ++i)        argv[i] = "a";    argv[0] = "/home/input2/input";    argv['A'] = "\x00";    argv['B'] = "\x20\x0a\x0d";    argv[100] = NULL;

Stage 2 stdio

可以根据上面的链接查看我整理的关于file descriptor maping的帖子,个中内容不再赘述。主要原理就是利用fork()创建一个当前进程的复刻,父进程和子进程分开进行两个操作:使用dup2()函数将管道重定向,将所需的内容写入相应的pipe。按题设要求我们需要重定向0-stdin和2-stderr。

然后是关于execve,这里使用该函数创建input进程:
在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。

  • 头文件: unistd.h
  • 函数定义: int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
  • 返回值: 函数执行成功时没有返回值,执行失败时的返回值为-1.
  • 函数说明: execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
    /* stage 2 */    int pipe_stdin[2] = {-1, -1};    int pipe_stderr[2] = {-1, -1};    pid_t pid_child;    if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )    {        perror("Cannot create the pipe.");        exit(1);    }    #define STDIN_READ   pipe_stdin[0]    #define STDIN_WRITE  pipe_stdin[1]    #define STDERR_READ  pipe_stderr[0]    #define STDERR_WRITE pipe_stderr[1]    if ( ( pid_child = fork() ) < 0 )   // do not forget the ()!    {        perror("Cannot create fork child.");        exit(1);    }    if( pid_child == 0 )    {        /* child proc */        sleep(1); //wait to pipe link 0,2        close(STDIN_READ);        close(STDERR_READ);        write(STDIN_WRITE, "\x00\x0a\x00\xff", 4);        write(STDERR_WRITE, "\x00\x0a\x02\xff", 4);    }    else    {        /* parent proc */        close(STDIN_WRITE);        close(STDERR_WRITE);        dup2(STDIN_READ, 0);  //dup to 0-stdin        dup2(STDERR_READ, 2); //dup to 2-stderr        printf("start execve input.\n");        execve("/home/input2/input", argv, envp);  //envp see stage 3        perror("Fail to execute the program");        exit(1);    }    printf("pipe link.\n");

Stage 3 envp

getenv(“\xde\xad\xbe\xef”)意为查看名为”\xde\xad\xbe\xef”的环境变量的值,这里我们只需传递一个环境变量,名为”\xde\xad\xbe\xef”,值为”\xca\xfe\xba\xbe”即可。

/* stage 3 */char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

Stage 4 file

按要求创建文件写入内容即可。需要注意的是对于文件的操作需要在execve函数被调用之前完成,因为input进程不会等待当前进行对该文件的创建和写入,需要保证在执行验证之前完成文件的构造。

    /* stage 4 */      // ! : file open before execve , or the check will fail     FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win    if(!fp)                         //see \x0d\x0a in win and \x0a in linux    {        perror("Cannot open file.");        exit(1);    }    printf("open file success.\n");    fwrite("\x00\x00\x00\x00", 4, 1, fp);    fclose(fp);

Stage 5 network

通过socket完成linux下进程间的通信,和在windows下基本类似。
具体可见我关于windows下的socket通信的简单实例。http://blog.csdn.net/qq_19550513/article/details/54965653
唯一区别在于windows下需要额外对winsock信息 WSAData 利用 WSAStartup() 函数进行初始化操作。

    /* stage 5 */    sleep(2); // wait the server start    int sockfd;    char buf[10] = {0}; // buf to be sent    int len;            // len of avail buf    struct sockaddr_in servaddr;    servaddr.sin_family = AF_INET;      servaddr.sin_port = htons(9999);  // port in argv['C']     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local    if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )      {          perror("socket error.");          exit(1);      }      if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 )    {        perror("connect error.");        exit(1);        }    printf("socket connect.\n");    strcpy(buf, "\xde\xad\xbe\xef");    len = strlen(buf);    send(sockfd, buf, len, 0);    close(sockfd);  

Final Stage

整合后,ssh登录后在/tmp目录下我们自己的目录,利用vi写入c文件,再用gcc编译生成可执行文件。
到这里还没有结束,因为只有在当前目录guest才有写的权限,而flag路径为/home/input2/flag。
这里我们需要一个soft link指向flag文件,在当前目录 ln -s /home/input2/flag flag 即可。注意不能创建hard link,因为guest对flag文件本身是没有读写权限的。
c文件内容如下:

#include <stdio.h>#include <stdlib.h>#include <sys/types.h> #include <unistd.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>int main(){    /* stage 1 */    char *argv[101] = {0};    for(int i = 1; i<100; ++i)        argv[i] = "a";    argv[0] = "/home/input2/input";    argv['A'] = "\x00";    argv['B'] = "\x20\x0a\x0d";    argv['C'] = "9999"; //server port    argv[100] = NULL;    /* stage 3 */    char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};    /* stage 4 */  // ! : file open before execve , or the check will fail     FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win    if(!fp)                         //see \x0d\x0a in win and \x0a in linux    {        perror("Cannot open file.");        exit(1);    }    printf("open file success.\n");    fwrite("\x00\x00\x00\x00", 4, 1, fp);    fclose(fp);    /* stage 2 */    int pipe_stdin[2] = {-1, -1};    int pipe_stderr[2] = {-1, -1};    pid_t pid_child;    if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )    {        perror("Cannot create the pipe.");        exit(1);    }    #define STDIN_READ   pipe_stdin[0]    #define STDIN_WRITE  pipe_stdin[1]    #define STDERR_READ  pipe_stderr[0]    #define STDERR_WRITE pipe_stderr[1]    if ( ( pid_child = fork() ) < 0 )   // do not forget the ()!    {        perror("Cannot create fork child.");        exit(1);    }    if( pid_child == 0 )    {        /*child proc*/        sleep(1); //wait to pipe link 0,2        close(STDIN_READ);        close(STDERR_READ);        write(STDIN_WRITE, "\x00\x0a\x00\xff", 4);        write(STDERR_WRITE, "\x00\x0a\x02\xff", 4);    }    else    {        /*parent proc*/        close(STDIN_WRITE);        close(STDERR_WRITE);        dup2(STDIN_READ, 0);  //dup to 0-stdin        dup2(STDERR_READ, 2); //dup to 2-stderr        printf("start execve input.\n");        execve("/home/input2/input", argv, envp);            perror("Fail to execute the program");            exit(1);    }    printf("pipe link.\n");    /* stage 5 */    sleep(2); // wait the server start    int sockfd;    char buf[10] = {0}; // buf to be sent    int len;            // len of avail buf    struct sockaddr_in servaddr;    servaddr.sin_family = AF_INET;      servaddr.sin_port = htons(9999);  // port in argv['C']     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local    if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )      {          perror("socket error.");          exit(1);      }      if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 )    {        perror("connect error.");        exit(1);        }    printf("socket connect.\n");    strcpy(buf, "\xde\xad\xbe\xef");    len = strlen(buf);    send(sockfd, buf, len, 0);    close(sockfd);      return 0;}

最后的运行情况如下:

input2@ubuntu:/tmp/umiade$ ./setinput open file success.start execve input.Welcome to pwnable.krLet's see if you know how to give input to programJust give me correct inputs then you will get the flag :)Stage 1 clear!pipe link.Stage 2 clear!Stage 3 clear!Stage 4 clear!socket connect.Stage 5 clear!Mommy! I learned how to pass various input in Linux :)
2 0
原创粉丝点击