【pwnable.kr】input

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/think_ycx/article/details/80962123


下载程序(注意要用bash,zsh报错zsh: no matches found: [email protected]:/home/input2/*)

scp -P 2222 -p  [email protected]:/home/input2/* ./

昨晚阅读了一下pwnable.kr的官网,里面提到"the main purpose of pwnable.kr is 'fun'.    please consider each of the challenges as a game",感觉挺有意思的。ok,记录一下本关的通关过程。

本关一共有五个小关卡,分为5个stage。

第一关

本关对argc、argv[65]、argv[66]做了判断,本来考虑在bash使用python传递参数给程序,但是\x00实在不知道如何传递,参考网上WP编写C程序,调用execve时传递argv给子程序。

  v16 = argv;
  v24 = __readfsqword(0x28u);
  puts("Welcome to pwnable.kr");
  puts("Let's see if you know how to give input to program");
  puts("Just give me correct inputs then you will get the flag :)");
  if ( argc != 100 )
    return 0;
  if ( *argv[65] )
    return 0;
  v4 = __CFADD__(argv, 528LL);
  v5 = argv + 66 == 0LL;
  v6 = 4LL;
  v7 = argv[66];
  v8 = " \n\r";
  do
  {
    if ( !v6 )
      break;
    v4 = (const unsigned __int8)*v7 < *v8;
    v5 = *v7++ == *v8++;
    --v6;
  }
  while ( v5 );
  if ( (!v4 && !v5) != v4 )
    return 0;
  puts("Stage 1 clear!");
argc是arguments count,程序运行时参数的数量。没有参数时argc为1。

argv是argument value,传递给程序的argv长度为argc,长度为argc。argv[0]是程序本身,注意argv[argc]需为NULL。

execve的man文档中也有说明:

#include <unistd.h>
void main(int argc, char **argv, char **env){
    char *arg[101]={"/tmp/input2",[1 ... 99]="A", NULL};
    arg['A'] = "\x00";
    arg['B'] = "\x20\x0a\x0d";
    execve(arg[0],arg,NULL);
}

第二关

程序从0 stdin和2 stderr读取buf并和字符串比较,因此需要想办法重定向子进程的stdin和stderr。dup2函数可以做这件事情。
重定向了stdin和stderr之后,父进程向子进程写入对于数据即可。

  read(0, &buf, 4uLL);
  if ( memcmp(&buf, &unk_400E3D, 4uLL) ) // "\x00\x0a\x00\xff"
    return 0;
  read(2, &buf, 4uLL);
  if ( memcmp(&buf, &unk_400E42, 4uLL) ) // "\x00\x0a\x02\xff"
    return 0;
  puts("Stage 2 clear!");

linux c如何实现进程通信呢?这里有一份很好的资料可以参考。
https://akaedu.github.io/book/ch30s04.html#id2867812
通过该文章我们可以知道,进程有自己独立的地址空间,需要通过kernel来交换数据。
pipe函数可以在kernel中建立一个半双工的管道用于单向传递数据。一种利用pipe来传递数据的思路如下所示:
1. 进程调用pipe创建自身的读写管道
2. 调用fork生成子进程并close父进程的写端和子进程的读端建立进程间半双工管道
3. 开始传递数据(参考上文链接中的图片)

知道了如何在进程之间传递数据之后,只需要重定向子进程的描述符即可。
dup2函数可以复制描述符,如下程序所示。

#include <unistd.h>
#include <stdio.h>
void main(){
    dup2(1,3);
    write(3,"123",3);//程序输出123
}

因此,stage2的exp为:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void main(int argc, char **argv, char **env){
    char *arg[101]={"/tmp/input2",[1 ... 99]="A", NULL};
    arg['A'] = "\x00";
    arg['B'] = "\x20\x0a\x0d";
    
    pid_t pid;
    int pipefd1[2], pipefd2[2];
    //int n;
    //char buf[100]={0};
    if(pipe(pipefd1)<0 || pipe(pipefd2)<0){
        exit(-1);
    }
    pid = fork();
    if(pid == -1){
        printf("fork error");
        return;
    }else if(pid >0){
        close(pipefd1[0]);
        close(pipefd2[0]);
        write(pipefd1[1],"\x00\x0a\x00\xff",4);
        write(pipefd2[1],"\x00\x0a\x02\xff",4);
 //       wait(NULL);
        
    }else if(pid==0){
        close(pipefd1[1]);
        close(pipefd2[1]);

        dup2(pipefd1[0],0); //pipefd1[0]  and  0
        dup2(pipefd2[0],2); //pipefd2[0]  and  2
        //n = read(0,buf,13);
        //write(1, buf, n);
        execve(arg[0],arg,NULL);
    }
}

第三关
本关获取环境变量并调用strcmp(IDA这种伪代码看多了就感觉是这函数了)来比较,因此传递环境变量给子程序即可。

 v11 = getenv(&s2);                            // \xDE\xAD\xBE\xEF
  v12 = 5LL;
  v13 = &unk_400E5B;                            // \xCA\xFE\xBA\xBE
  v14 = v11;
  do
  {
    if ( !v12 )
      break;
    v9 = *v13 < (unsigned __int8)*v14;
    v10 = *v13++ == *v14++;
    --v12;
  }
  while ( v10 );
  if ( (!v9 && !v10) != v9 )
    return 0;
  puts("Stage 3 clear!");
        envp is an array of strings, conventionally of the form key=value, which are
       passed as environment to the new program.  The argv and envp arrays
       must each include a null pointer at the end of the array.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void main(){
...
    char *env[2] = {"\xDE\xAD\xBE\xEF=\xCA\xFE\xBA\xBE", NULL};    
...
        execve(argv[0],argv, env);
    }
}

第四关
本关调用fopen打开文件,读取4byte。

  stream = fopen("\n", "r");
  if ( !stream )
    return 0;
  if ( fread(&buf, 4uLL, 1uLL, stream) != 1 )
    return 0;
  if ( memcmp(&buf, &unk_400E73, 4uLL) )        // \x00\x00\x00\x00
    return 0;
  fclose(stream);
  puts("Stage 4 clear!");
因此调用fopen打开文件,fwrite写入相应数据最后fclose即可。(如果是在windows给linux的共享目录下操作需要拷贝到tmp下运行程序)
    //stage 4
    FILE *fp = fopen("\x0a", "w+");
	if (fp == NULL) {
		perror("Open file recfile");
		exit(-1);
	}
    char *data = "\x00\x00\x00\x00";
    fwrite(data, sizeof(char), 4, fp);
    fclose(fp);第五关


第五关

这关IDA伪代码可以看,不过不太友好,直接看源码吧。程序从argv['C']获取端口绑定并接受4byte,比较成功后get flag。

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

socket编程相关的函数说明参考这里吧,没什么想说的。https://akaedu.github.io/book/ch37s02.html

    // stage 5
      argv['C'] = "7777";
    ...
    sleep(1);
    struct sockaddr_in servaddr;
    int sockfd;
    char *str = "\xde\xad\xbe\xef";
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(atoi(argv['C']));

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    write(sockfd, str, strlen(str));
    close(sockfd);


EXP

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

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

    //stage 3
    char *env[2] = {"\xDE\xAD\xBE\xEF=\xCA\xFE\xBA\xBE", NULL};

    //stage 4
    FILE *fp = fopen("\x0a", "wb");
	if (fp == NULL) {
		perror("Open file recfile");
		exit(-1);
	}
    char *data = "\x00\x00\x00\x00";
    fwrite(data, sizeof(char), 4, fp);
    fclose(fp);

    //stage2    
    pid_t pid;
    int pipefd1[2], pipefd2[2];
    //int n;
    //char buf[100]={0};
    if(pipe(pipefd1)<0 || pipe(pipefd2)<0){
        exit(-1);
    }
    pid = fork();
    if(pid == -1){
        printf("fork error");
        return;
    }else if(pid >0){
        close(pipefd1[0]);
        close(pipefd2[0]);
        write(pipefd1[1],"\x00\x0a\x00\xff",4);
        write(pipefd2[1],"\x00\x0a\x02\xff",4);
    //  wait(NULL);
    
    // stage 5
    sleep(1);
    struct sockaddr_in servaddr;
	int sockfd;
	char *str = "\xde\xad\xbe\xef";

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(atoi(argv['C']));

	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	write(sockfd, str, strlen(str));
	close(sockfd);
        
    }else if(pid==0){
        close(pipefd1[1]);
        close(pipefd2[1]);

        dup2(pipefd1[0],0); //pipefd1[0]  and  0
        dup2(pipefd2[0],2); //pipefd2[0]  and  2

        //n = read(0,buf,13);
        //write(1, buf, n);

        execve(argv[0],argv, env);
    }


}

最后修改程序路径,上传程序运行即可。
scp -P 2222 -p  exp [email protected]:/tmp/exp/

恭喜通关。

猜你喜欢

转载自blog.csdn.net/think_ycx/article/details/80962123
今日推荐