pwnable.kr —— input题解

哎,做完这道题,才深知自己c语言功底渣到不行,假期一定狂补一下c语言基础语法,不然就太菜了

这道题就很有意思了,完全是考察你的linux编程功底。

我们就直接来看源码吧

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

很明显当我们把这五关都通过了,flag也就粗来了,让我们开始吧

这里首先先明确一个啦,就是我们很明显这个输入是个很复杂的东西,我们无法在命令行里直接输入参数,当然也可以通过写exp。

这里我用的是execve函数,其原型如下:

 int
 execve(const char *path, char *const argv[], char *const envp[]);

所以我们通过execve函数来给源文件input传值
这里我们需要查看一下input文件的位置

可以得知我们的path为"/home/input2/input"

然后接下来一个问题就是,我们将把我们这个写的文件放在服务器的哪里呢?这个服务器对我们的权限做了很大的限制。我在服务器上没有找到能够创建文件的地方,所以我想到的办法是通过scp将原文件都下载到本地,然后再本地上做~(注意,如果下载到本地的话,这个path就会有变化,这里我就暂且先不做更改)

所以我们这个文件暂且命名为input_replace.c

#include<stdio.h>
int main(int argc, char const *argv[])
{
	char **new_agrv;
	char **new_envp;

	//argv

	//stdio

	//env

	//file

	//network

	execve("/home/input2/input",new_argv,new_envp);
	return 0;
}

然后我们就来逐个击破吧~

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

这里也就是让我们在命令行参数输入100个参数,由于我们的输入运行程序占一个,所以我们也就输入99个参数就可以了。但是这里我们要注意一下,因为我们是通过input_replace.c来调用input,所以我们的第一个参数是./input_replace,比直接运行input多了这么一个参数,所以实际上我们需要的是101个参数。

这里我第一次看的时候出现了一个低级错误,我想argv[‘B’]只是一个字节啊,而"\x20\x0a\x0d"是三个字节,怎么比较啊?

哎,还是基本功不扎实。argv是一个 char*类型的数组,为字符串数组,用来存放指向的字符串参数的指针,每一个元素指向一个参数(参数是字符串)

所以第一个我们就很好写了

char * new_argv[101];
for(int i = 0;i<100;i++)
{
	new_argv[i] = "";
}
new_argv['A'] = "\x00";
new_argv['B'] = "\x20\x0a\x0d";
new_argv[100] = NULL;   

运行结果如下:

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

乍一看这个问题,memcmp这个函数面生啊,于是我就查了一下man memcmp了一下这个函数,并且顺便看了一下这个函数可能带来的漏洞。漏洞见这里

不过这个问题原因在于,你要从标准输入读字符进buf中,然后将这两个内存进行对比。从标准输出中读字符进buf,然后比较内存值。

但是,问题就出在,我们无法直接往文件描述符0和2中输入值,所以我们就想办法,先往一个文件中写入需要的内存值,然后将这个文件的文件描述符通过dup2函数来改为我们所需要的

代码如下:

    int stdin_fd = open("first",O_RDWR|O_CREAT,0777);
	int stderr_fd = open("second",O_RDWR|O_CREAT,0777);
	
	write(stdin_fd,"\x00\x0a\x00\xff",4);
	write(stderr_fd,"\x00\x0a\x02\xff",4);	

	lseek(stdin_fd,0,SEEK_SET);//防止出现文件空洞
	lseek(stderr_fd,0,SEEK_SET);

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

这个就很简单了,我们直接设置环境变量即可

char *new_envp[2];
new_envp[0] = "\xde\xad\xbe\xef=\xca\xfe\xba\xbe";
new_envp[1] = NULL;

这两个运行结果如下:

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

这个就很好写了,程序如下:

int file_fd = open("\x0a",O_RDWR|O_CREAT,0777);
write(file_fd,"\x00\x00\x00\x00", 4);

Part five

终于到最后一关啦

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

读这段代码,需要一点点linux网络编程的基础。这段代码的意思就是说:这是一个服务器端程序,完成一个TCP套接字传输。我们绑定的端口号为argv['C']所对应的值,然后我们送这个端口里接收到的数据要和\xde\xad\xbe\xef 相等。
我们要做的就是写一个客户端程序了,来传递服务器端想要的这个值,代码如下:

int sock_clientfd;
	unsigned short port = 1111;//这里要在前面加上new_argv['C'] = 1111;  
	char buf[20]="";
	sock_clientfd = socket(AF_INET,SOCK_STREAM,0);
	if(sock_clientfd < 0)
	{
		perror("socket");
		exit(-1);
	}

	struct sockaddr_in saddr;
	bzero(&saddr,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr);

	if((connect(sock_clientfd,(struct sockaddr *)&saddr,sizeof(saddr))) == 0)
	{
		perror("connect");
		exit(-1);
	}

	int number;
	strcpy(buf,"\xde\xad\xbe\xef");
	number = send(sock_clientfd,buf,strlen(buf),0);
	if(number != 4)
	{
		perror("send");
		exit(-1);
	}

	close(sock_clientfd); 	

按理说是应该可以的,不过由于我们是把程序download到本地上了,所以,我们这里没法得到想要的结果,于是,也不知道对不对。
所以,我去看了一下官解,他是这么做的:

录屏工具:QuickTime Player
在线mov转换成gif格式:https://convertio.co/zh/mov-gif/

到这里,我们的问题算是都解决完了。完整代码如下:

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

char * EXE = "/home/yannie/Desktop/input";

int main(int argc, char *argv[])
{
	
	char *new_envp[2];

	char * new_argv[101];
	
	//argv

	for(int i = 0;i<100;i++)
	{
		new_argv[i] = "";
	}

	new_argv['A'] = "\x00";
	new_argv['B'] = "\x20\x0a\x0d";
	new_argv[100] = NULL; 
 	new_argv['C'] = "1111";   

	//stdio

	int stdin_fd = open("first",O_RDWR);
	int stderr_fd = open("second",O_RDWR);
	
	write(stdin_fd,"\x00\x0a\x00\xff",4);
	write(stderr_fd,"\x00\x0a\x02\xff",4);	

	lseek(stdin_fd,0,SEEK_SET);
	lseek(stderr_fd,0,SEEK_SET);

	dup2(stdin_fd,0);
	dup2(stderr_fd,2);

	//env

	new_envp[0] = "\xde\xad\xbe\xef=\xca\xfe\xba\xbe";
	new_envp[1] = NULL;

	//file
	
	int file_fd = open("\x0a",O_RDWR|O_CREAT,0777);
	write(file_fd,"\x00\x00\x00\x00", 4);

	//network 

	execve(EXE,new_argv,new_envp);
	return 0;
}

之后的问题将这个写的解答的文件放在服务器哪里会比较好呢?

在这里插入图片描述
我们发现我们在/tmp目录被限制了,无法使用ls,vim等命令。于是我们自己创建一个文件夹,这样就可以把我们刚刚写的文件放在这里了,不过需要将EXE改变一下

修改完之后,这里还有一个问题,就是我们的flag文件和我们不在一个目录下,我们访问不到怎么办?这里就需要软连接一下了(我忘记截图了,就截了一张官网的图)

于是此时我们就可以通过2.c来得到我们的flag了

此时我们需要另一个登陆的shell。来完成我们part 5的部分。


答案出来了:Mommy! I learned how to pass various input in Linux ?

感悟:通过这道题真的是能学到很多,也深知自己的基础薄弱。不过还好,还有时间去弥补。得到flag很开心?,进军下一题~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五月的天气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值