哎,做完这道题,才深知自己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");// argvif(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");// stdiochar 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");// envif(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;printf("Stage 3 clear!\n");// fileFILE* 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");// networkint 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 flagsystem("/bin/cat flag");return 0;
}
很明显当我们把这五关都通过了,flag也就粗来了,让我们开始吧
这里首先先明确一个啦,就是我们很明显这个输入是个很复杂的东西,我们无法在命令行里直接输入参数,当然也可以通过写exp。
这里我用的是execve函数,其原型如下:
intexecve(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//networkexecve("/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
// stdiochar 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
// fileFILE* 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
终于到最后一关啦
// networkint 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];//argvfor(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"; //stdioint 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);//envnew_envp[0] = "\xde\xad\xbe\xef=\xca\xfe\xba\xbe";new_envp[1] = NULL;//fileint 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 ?