Pwnable.kr---input

Pwnable.kr—input

解题思路

接触这个题目的时候,刚刚做完一个和返回地址重写有关的题目,就想这个题目是不是也可以直接跳过中间的一大片,直接system(/bin/ cat flag)。哈哈开个玩笑

题目代码很明显,要求我们一共通过五个关卡,才能执行到system()函数,cat flag;那就只能一个一个来过了,分解目标。

这里使用C来编写脚本文件,调用input。

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

将argv[]在main.c中赋值,然后执行execve()函数调用可执行文件;代码如下:

cahr *argv[101]={"/home/input2/input",[1...99]="A",NULL};
argv['A']="\x00";
argv['B']="\x20\x0a\x0d";
execve("/home/input2/input",argv,NULL);

stage2

    // stdio
    char buf[4];
    read(0, buf, 4);
    puts(buf);
    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");

这里要明白read()函数是从哪里读取的。read(0,,)是从stdin读取,read(2,,)是从stderr读取。这部分也是要借助脚本文件,由于需要从stdin和stderr读取,那么使用管道通信,在脚本文件中分别利用两个管道写入”\x00\x0a\x00\xff”和”\x00\x0a\x02\xff”,然后将这两个管道的读端口重定向到stdin和stderr,然后执行input,此时input就可以read(0,,)和read(2,,)。

代码如下:

int pipe2stdin[2]={-1,-1};
int pipe2stderr[2]={-1,-1};
pid_t childpid;
if(pipe(pipe2stdin)<0||pipe(pipe2stderr)<0){
  perror("cannot create pipe");
  exit(1);
}
if((childpid=fork())<0){
  perror("cannot fork()");
  exit(1);
}
else if(childpid==0){
  close(pipe2stdin[0]);close(pipe2stderr[0]);
  write(pipe2stdin[1],"\x00\x0a\x00\xff");
  write(pipe2stderr[1],"\x00\x0a\x02\xff");
}
else{
  close(pipe2stdin[1]);close(pipe2stderr[1]);
  dup2(pipe2stdin[0],0);close(pipe2stdin[0]);
  dup2(pipe2stderr[0],2);close(pipe2stderr[0]);
  execve("/home/input2/input",argv,NULL);
}

stage3

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

这里就到envp[]上场了,只需要初始化一个envp[],然后赋予相应的值,然后作为exceve()的第三个参数传入就可以了。代码如下:

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

然后修改exceve():

execve("/home/input2/input",argv,envp);

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

有了上边的经验,这部分就比较简单了,这段代码就是从路径名为“/x0a”的文件中读取内容到buf,并和指定值进行比较,相等即可。那么只需要向该文件按照相同的格式(数据项字节数、数据项数)写入数据即可。代码如下:

FILE *fp=fopen("\x0a","w");
if(!fp)return 0;
if(fwrite("\x00\x00\x00\x00",4,1,fp)!=1)return 0;
fclose(fp);

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通信的服务器端代码,其中涉及到绑定(bind)、监听(listen)、接收连接(accept)。这一系列之后就是从cd(客户端的socket)接收数据,并与已给的字符串进行比较,相等即过关。那么我们要做的就是在脚本程序中写入socket通信的客户端代码,并向server发送指定数据即可。代码如下:

int socket=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(55555);
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(connect(sock_cli,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){
  perror("connect");
  exit(1);
}
send(sock_cli,"\xde\xad\xbe\xef",4,0);
close(sock_cli);

stage Final

虽然每一步写完了,但是整个是一个脚本文件,所以还需要整合到一起。这里就需要注意一下脚本文件的执行和input执行的顺序问题,特别注意的就是最后的net部分,需要保证server端已经在侦听并可以accept之后,client端才可以connet,于是要在stage5之前Sleep().

由于input中需要打开flag文件,可是我们并没有读取/home/input2/flag文件的权限;此外,我们的脚本文件也无法写入到/home/input2的目录下。我们需要这样做(我不是很明白为什么):

a.先回到root目录下,cd .. cd ..,然后找到/tmp,在该目录下mkdir自己的文件夹

b.在自己的文件夹下编辑脚本文件,并编译

c.将/home/input2/flag软连接到/tmp/自己文件夹(即当前目录)

ln -s /home/input2/flag newname

d.执行编译好的脚本文件即可

答案

还是给链接吧,我的大部分都是借鉴别人的

优解1

优解2

遇到的问题

Q1

Q: 我尝试通过终端输入一共100个参数(包括文件路径),其中令第65(‘A’)和第66分别为“\x00”,”\x20\x0a\x0d”,但是输入的时候出现了问题,并不是想象的样子。于是我写了一个简单的测试函数想弄明白这个strcmp()函数的第二个参数到底代表的是什么?(不是字符串”\x00”)

void  main(char argv[])
{
    printf("%s\n",argv);
    int mark=strcmp(argv,"\x01");
    if(mark)
        printf("no\n");
    else
        printf("yes\n");
    printf("mark=%d\n",mark);
}

我发现不管怎么输入都无法得到yes或者得到mark=-1,也就是无法输入一个ascii值小于“\x01”的字符串。

A:目前我接受的解释是,“\x01”代表的是十六进制为1的不可见字符,那么我需要输入的就是“\x01”或“\x00”才可以使mark<=0。这两个都是不可见字符,目前我无法通过终端输入。终端输入的“\x01”是字符串\x01与“\x01”是不一样的………………

于是就可以利用脚本输入,这里应该是因为标准输入和程序里边的数据流是不一样的吧………………

Q2

Q:在做到stage3的时候,我认为envp代表的就是一个函数所在的所有的环境变量的键值对。于是我的计划是利用setEnvironVariable()函数向原来envp[]中添加一组环境变量。没有尝试……

看到的WriteUp是像argv[]一样,自定义了envp[],没有理会实际的环境变量…..

Q3

Q:客户端socket程序编写过程中对端口号和IP地址的取值不是很清楚?

A:如果是在localhost上,一般选择1024-65535中的端口号。因为前1024已有专用。由于是和本机通信,所以IP选择127.0.0.1 。这里注意并没有和pwnable的服务器进行通信,这里只有两个本地进程的通信。

收获

  1. if(zero)返回false;if(non_zero)返回true,这里non_zero包含positive和negative。

  2. python中print(r”\x01”)输出“\x01”,r的作用反转义。可以在\x01再加一个\反转义。

  3. execve(参数1,参数2,参数3)的使用:参数1是命令所在的路径;参数2是命令集合;参数3是传递给执行文件的环境变量集。

  4. read(FILE *fd,buf,size),从文件描述符fd所指的文件中读取size字节到buf。其中比较特殊的fd有:1–stdin标准输入流;2–stdout标准输出流;3–stderr标准错误流

  5. dup&dup2:这里主要使用了dup2,其中利用了它数据流的重定向功能。创建了两个管道A、B,管道A的写端口被子进程写入,管道A的读端口在父进程中被重定向为stdin;管道B的写端口被子进程写入,管道B的读端口在父进程中被重定向为stderr。于是当在父进程执行exceve()函数时就继承了这些standard streams。

    often,the descriptors in the child are duplicates onto standard input or output. this child can then exec another program, which inherits the standard streams.

  6. fopen()返回一个文件描述符;文件描述符是文件的唯一标识;函数的第一个参数是文件的pathname;第二个参数是mode,即打开方式,或称流形态,通常有”r”,”w”,”r+”,”w+”等等。

  7. size_t fread(void *buffer, size_t size, size_t count, FILE *stream)。第一个参数–接收数据的内存地址;第二个参数–要读的每个数据项的字节数;第三个参数–要读的数据项的个数;第四个参数–输入流。
  8. fwrite()同上,只是第一个参数表示获取数据的地址,其余含义相同。
  9. main(int argc, char* argv[], char* envp[])其中的envp指的是环境变量,其存储形式是以类似于键值对的形式。
  10. 软连接ln -s filename filename。文件用户数据中存放的内容是另一文件路径的指向。当前文件有自己的inode号以及数据块。有自己的文件属性和权限。可对不存在的文件软连接,可交叉文件系统,for dir, for file。i_nlink不会增加。delete软连接不影响被指向文件;若被指向的文件被delete,则链接变成死链接,dangling link;若被指向的文件被重新创建,恢复软连接。
  11. 硬链接 ln filename filename; link filename filename。相同的inode指向不同的文件名,但是有相同的data block。只能对已经存在的文件创建。不能交叉文件系统。not for dir, for file。delete不影响其他具有相同inode的文件。

仍存在的疑惑

  1. 为什么使用system(/bin/ cat flag)就可以得到答案?但是我并没有看到目录/bin下边有flag文件的存在啊?
  2. 最后socket部分的前边Sleep()的原因?如果不sleep的话服务器那边还没有准备好,就无法建立连接了。所以要保证在客户端发起connect请求之前服务器端就已经准备好Accept/listen了,那么就需要在socket之前sleep()???还需要认真思考这个问题?
  3. 最后clear了5个stages之后,并没有cat到flag。原因在于,我是直接将脚本文件放在/tmp目录下,然后进行编译,同时建立/home/input2/flag文件到当前目录的软连接,结果并得不到flag。然而当我在/tmp目录下mkdir自己的文件夹,并在这个文件夹里边操作的话,就可以cat到flag?
  4. 这个flag的运行机理是什么?为什么system(/bin/ cat flag)可以打开?这个系统调用函数打开的到底是哪个文件?在这个题目中,当我执行脚本文件的时候,脚本文件调用input可执行文件,可执行文件中调用了system()函数,那么system()执行的到底是tmp下的flag,还是/input2下边的flag?如果不建立软连接可以吗?有待测试………….
  5. 字符串数组的初始化?
  6. 字符串数组的定义?
  7. 脚本文件调用input文件时,两个可执行文件在两个进程中还是一个进程,脚本文件sleep()的时候,input继续执行?
  8. 可以创建软连接,不能创建硬链接,权限问题?
  9. 为什么软连接之前没有权限读取flag?软连接之后就有了?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值