pwnable.kr input

思路

  • 首先ssh连接后查看c代码是什么,发现这是一个考验linux基础输入知识的题,意味着没接触过的人要大量恶补linux的知识(说的就是自己)
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;
}
  • 首先由代码可见,我们一共需要过五关,每一次都要成功,最后才能cat flag

首先介绍一个函数int execve(const char *filename, char *const argv[],char *const envp[])作用是启动新的进程,而进程的文件有filename指定,传入的参数为argv,并且有环境变量envp,这个函数将在下面中用到,同时需要注意argv和envp都需要以NULL结尾

第一关

    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"); 
  • 首先我们的argc要等于100,既要有100个参数,要在第“A”(即65)的位置为\x00,在第“B”(66)的位置是“\x20\x0a\x0d”,那么就简单的写入即可
#include
int main()
{
    /*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";
    
}

第二关

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");
  • 这一次由之前做过的fd题可以知道read()的第一个参数表示文件描述符,0代表标准输入,2代表标准错误输出,输入容易控制,但要如何使得错误输出也被控制呢?就需要学会pipe管道和I/O重定向

pipe是为了在两个进程之间通信设置的,单方向的通信,一方面读,一方面写。以下是pipe的定义http://man7.org/linux/man-pages/man2/pipe.2.html

pipe通道是为两个进程通信服务的,但此时只有一个进程,因此我们要fork一个子进程,实现子进程和父进程的通信,以下是fork的原理
https://blog.csdn.net/jason314/article/details/5640969

I/O重定向指的是将已创建的文件描述符指向其他文件,以下是具体原理
http://www.cnblogs.com/weidagang2046/p/io-redirection.html

以下是一个很好的利用pipe通道实现I/O重定向的说明
http://unixwiz.net/techtips/remap-pipe-fds.html

  • 因此我们创建两个pie通道,表示两个进程之间的通信,然后fork处子进程,在子进程中写入,在父进程处进行重定向,将从子进程读取的内容重定向到标准输入和标准错误输出
    /*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.");
    }

    #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.");
    }
    if(pid_child == 0)  //in child
    {
        close(STDIN_READ);
        close(STDERR_READ);//关闭输入
        write(STDIN_WRITE,"\x00\x0a\x00\xff",4);
        write(STDERR_WRITE,"\x00\x0a\x02\xff",4);
    }
    else    //in father
    {
        close(STDERR_WRITE);
        close(STDIN_WRITE);//关闭输出
        dup2(STDIN_READ,0);
        dup2(STDERR_READ,2);
    }
    printf("link\n");
   
}

第三关

 // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
  • 首先是函数getenv,目得是使两个值相等

char *getenv(const char *name)是查找程序环境列表中参数name的值

  • 而环境列表我们一般用不到,但是,作用是将一些源程序所在系统的位置等信息传入,那我们只需要传入环境变量时,将这一等式当作环境变量之一传入即可。
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};//第三关用到的环境变量
execve("/home/input2/input", argv, envp);

第四关

    // 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"); 
  • 简单的打开“\x0a”这个文件,写入信息"\x00\x00\x00\x00"即可
/*4*/
    FILE *fp=fopen("\x0a","wb");
    if(!fp)
    {
        perror("Can not open file.");

    }
    printf("Open file success.\n");
    fwrite("\x00\x00\x00\x00",4,1,fp);
    fclose(fp);
}

第五关

 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;
}
  • 由代码可知把这个input作为一个服务端,绑定的端口值是argv[‘C’]里面存的数值,当然代码用了一下atoi转化字符串为数字,比如存的是char 0,那么绑定的端口就是0号端口。然后验证的是传进来的某连接发送的内容是"\xde\xad\xbe\xef".

  • 所以我们自己写的时候,可以给input指定一个端口,然后我们的程序再连接这个端口,发送"\xde\xad\xbe\xef"就好了。

argv['C'] = "9999"; 
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;

总结

  • 做这道题时会感觉好多都不认识,但坚持下来就能学到很多的东西。

tips:在这道题提交的时候需要将自己的文件用scp上传到/tmp或者在服务器的tmp文件夹中新建文件夹,用vim写,使用“gcc 文件 -o 输出文件名称”来编译,同时还需要软连接,“ln -s /home/input2/flag flag”

#include//fopen perror
#include 
#include //pipe execve
#include //strcmp  
#include //bind
#include // linux socket
#include    
#include    
#include   

int main()
{
    /*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"; 
    argv[100] = NULL;


    char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};//第三关用到的环境变量

    /*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.");
    }

    #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.");
    }
    if(pid_child == 0)  //in child
    {
        close(STDIN_READ);
        close(STDERR_READ);//关闭输入
        write(STDIN_WRITE,"\x00\x0a\x00\xff",4);
        write(STDERR_WRITE,"\x00\x0a\x02\xff",4);
    }
    else    //in father
    {
        close(STDERR_WRITE);
        close(STDIN_WRITE);
        dup2(STDIN_READ,0);
        dup2(STDERR_READ,2);
        execve("/home/input2/input", argv, envp);  
    }
    printf("link\n");

    /*4*/
    FILE *fp=fopen("\x0a","wb");
    if(!fp)
    {
        perror("Can not open file.");

    }
    printf("Open file success.\n");
    fwrite("\x00\x00\x00\x00",4,1,fp);
    fclose(fp);

    /*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;
}
success.png

你可能感兴趣的:(pwnable.kr input)