2019.08.18|pwnable记录input(大坑)

发表于 2019-08-18  93 次阅读


文章目录

题目地址:ssh input2@pwnable.kr -p2222

题目考点:linux下的文件、进程通信原理,socket网络编程,C语言的一些琐碎的细节理解。


这个题的源码非常长,所以考点繁杂,像我这样对linux了解不多的pwn萌新是很头秃的,先贴上:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

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;
}

显然,按照题目的要求只要完成stage1~5就能够get flag


Stage1

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

首先了解一下C语言main函数的写法,

int     main(   int   argc   ,   char   *argv[ ]   ,   char   *envp[ ]   ) 

/*

int argc, char **argv 用于运行时,把命令行参数传入主程序。

argc -- 命令行参数总个数,包括 可执行程序名。

argv[i] -- 第 i 个参数。

argv[0] -- 可执行程序名。

*/

envp用来取得系统的环境变量,如:在DOS下,有一个PATH变量。当你在DOS提示符下输入一个命令的时候,DOS会首先在当前目录下找这个命令的执行文件。如果找不到,则到PATH定义的路径下去找,找到则执行,找不到返回Bad   command   or   file   name 。在DOS命令提示符下键入set可查看系统的环境变量。

然后是本关的思路:

1、要求参数要等于100个
2、argv['A'],argv['B']分别要符合要求
        实际上这里的A和B其实分别是其ascii码作为索引值,即参数第64个要为"\x00"而参数第65个要为"\x20\x0a\x0d",这样就完成了第一部分。对于第一部分,在python脚本中是先用99个A初始化,为什么用99个A而不是100个呢,可以看到,在最后的Popen()函数中,其实是将第100个参数加上的,至于为什么是/home/input2/input呢?这是因为,对于命令行参数,argc[0]通常是脚本名的。
注意⚠️这里是有坑的。C语言和python中对于\x00有着不一样的定义:
C语言中’a’和”a”的区别:C的字符串中以字符’\0’(=’\x00’) 作为结束标志,’\0’是一个ASCII码为0的字符,它不会引起任何控制动作,也不是一个可显示的字符。可以说,C语言中\x00相当于空串,会截断字符串。
Python不支持单字符类型,单字符也在Python也是作为一个字符串使用。python字符串其实是一个固定长度的字符数组,所以不用结束标志了。修改字符串其实是另外生成一个新的。也就是说,在python中\x00和空串并不等价。
(https://blog.csdn.net/jason_cuijiahui/article/details/72511610)
这两点需要注意一下,在写exp的时候要用到。

Stage2

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

        本关采用os的pipe()函数,所以我们需要了解一下os的pipe函数:os.pipe()用于创建一个管道,返回一对文件描述符(r,w)分别为读和写。这段c代码中,还有一个需要注意的是read函数,read函数作用是从文件描述如中读取size大小的元素,并送入buffer中,这里我们看到文件描述符是0与2,回想一下pwnable第一关fd时,我们便知道这里的0其实是stdin,2是stderr,所以在python脚本的最初,我们声明了两个pipe管道,stdinr,stdinw和stderrr,stderrw,其作用就是向stdin和stderr写入题目需要的字符串,而如果直接用stdin和stderr是无法直接写入的,所以利用管道的双向读写功能来完成这一部分。

Stage3

// env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");

这里要认识一下getenv()函数的作用,返回一给定的环境变量值,环境变量名可大写或小写。如果指定的变量在环境中未定义,则返回一空串。

这里是要让"\xde\xad\xbe\xef"的环境变量等于"\xca\xfe\xba\xbe",那么在python脚本中就可以通过定义一个字典,然后让"\xde\xad\xbe\xef"对应"\xca\xfe\xba\xbe",在Popen函数的时候,将这个字典当作env的参数就好了。

Stage4

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

        接下来是第四部分,重点解读一下fread函数,该函数原型

size_t fread(void *buffer, size_t size, size_t count FILE *stream)

就是读取stream流中count个元素,每个元素size大小,读取到buffer中,在这里的c代码中,是从fp中读取一个元素,4字节大小到buf中,因此,只要在python脚本中写入4个\x00就好了。

Stage5

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

这里就是socket编程,直接按照C语言的要求连接写入就好了

        前面都是建立socket连接,只要成功建立socket连接就不会报错,后面recv(cd, buf, 4, 0)这里,cd是之前accept返回的套接字描述符,这段socket代码是一个服务端的代码,accept返回的这个cd可以想象成服务端与客户端建立连接的一个钥匙,那么这个recv其实就很好理解了,就是服务端接受的4字节的数据并且放到buf中,第四个参数0是flag一个标志位,一般为0,这里可以不用管他。然后是一个memcmp函数,这个函数就是比较buf与"\xde\xad\xbe\xef"的前4字节的内容是否相等,相等则返回0,因此这部分就是向服务端发送"\xde\xad\xbe\xef"这个字符串即可通过。
最后一步(大坑)
先来介绍一下软链接
ln src dest
这里需要注意的是,第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化;第二,ln的链接又软链接 和硬链接两种,软链接就是ln -s str dest,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接ln src dest,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。
如果我们用ls察看一个目录时,会发现有的文件后面有一个@的符号,那就是一个用ln命令生成的文件,用ls -l命令去察看,就可以看到显示的link的路径了。 
        首先/home/input这个目录下没有写的权限,所以需要把脚本放在/tmp去执行,而且part 4创建文件的操作也需要在tmp下进行,但是读取flag的system("/bin/cat flag")中的/bin/cat flag是使用相对路径的,是无法读取到flag的,所以这里使用了软连接的方法。

但是解决了相对路径的问题后运行还是不能弹出flag,发现是因为/tmp目录下没有读的权限,但是有写的权限,就有建了一个input(随便什么都可以)目录,最后就可以弹出flag了。

软连接命令示例:ln -s ~/flag ./

有两篇写的挺好的WP贴出来:
https://www.jianshu.com/p/032a754b72d8
https://blog.csdn.net/SmalOSnail/article/details/53048109
这两篇都是用python写的exp,其他多数帖子都是C语言写的,表示感觉还是py好用。
把我的exp贴上来吧:
本站文章基于国际协议BY-NA-SA 4.0协议共享;
如未特殊说明,本站文章皆为原创文章,请规范转载。

0

From XDU.Doing and Listening.