pwnable.kr [Toddler's Bottle]
来源:互联网 发布:json格式 java 编辑:程序博客网 时间:2024/04/29 03:11
这题流程相对较长,考查Linux编程的基本功(笔者做到这题不禁感叹自己基本功还是欠了不少火候)。
在一开始,尝试写Python脚本去完成验证,但stage 2关于stdio的验证却苦无思路。
这里感谢werew在他的writeup中提供的解决思路,这才豁然开朗。
参考链接:https://werewblog.wordpress.com/2016/01/11/pwnable-kr-input/
关于file descriptor maping的内容我另外做了整理,
详见 http://blog.csdn.net/qq_19550513/article/details/61915756
先放上题目源码:
/* ssh input2@pwnable.kr -p2222 (pw:guest) */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;}
一共分为五个验证步骤,先一个个来看。
Stage 1 argv
第一步比较常规,argv需要传递100个参数,即实际为argv[101](argv[0]指向执行的路径,argv[100]为NULL)。
/* stage 1 */ char *argv[101] = {0}; for(int i = 1; i<100; ++i) argv[i] = "a"; argv[0] = "/home/input2/input"; argv['A'] = "\x00"; argv['B'] = "\x20\x0a\x0d"; argv[100] = NULL;
Stage 2 stdio
可以根据上面的链接查看我整理的关于file descriptor maping的帖子,个中内容不再赘述。主要原理就是利用fork()创建一个当前进程的复刻,父进程和子进程分开进行两个操作:使用dup2()函数将管道重定向,将所需的内容写入相应的pipe。按题设要求我们需要重定向0-stdin和2-stderr。
然后是关于execve,这里使用该函数创建input进程:
在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。
- 头文件: unistd.h
- 函数定义: int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
- 返回值: 函数执行成功时没有返回值,执行失败时的返回值为-1.
- 函数说明: execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
/* stage 2 */ int pipe_stdin[2] = {-1, -1}; int pipe_stderr[2] = {-1, -1}; pid_t pid_child; if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 ) { perror("Cannot create the pipe."); exit(1); } #define STDIN_READ pipe_stdin[0] #define STDIN_WRITE pipe_stdin[1] #define STDERR_READ pipe_stderr[0] #define STDERR_WRITE pipe_stderr[1] if ( ( pid_child = fork() ) < 0 ) // do not forget the ()! { perror("Cannot create fork child."); exit(1); } if( pid_child == 0 ) { /* child proc */ sleep(1); //wait to pipe link 0,2 close(STDIN_READ); close(STDERR_READ); write(STDIN_WRITE, "\x00\x0a\x00\xff", 4); write(STDERR_WRITE, "\x00\x0a\x02\xff", 4); } else { /* parent proc */ close(STDIN_WRITE); close(STDERR_WRITE); dup2(STDIN_READ, 0); //dup to 0-stdin dup2(STDERR_READ, 2); //dup to 2-stderr printf("start execve input.\n"); execve("/home/input2/input", argv, envp); //envp see stage 3 perror("Fail to execute the program"); exit(1); } printf("pipe link.\n");
Stage 3 envp
getenv(“\xde\xad\xbe\xef”)意为查看名为”\xde\xad\xbe\xef”的环境变量的值,这里我们只需传递一个环境变量,名为”\xde\xad\xbe\xef”,值为”\xca\xfe\xba\xbe”即可。
/* stage 3 */char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
Stage 4 file
按要求创建文件写入内容即可。需要注意的是对于文件的操作需要在execve函数被调用之前完成,因为input进程不会等待当前进行对该文件的创建和写入,需要保证在执行验证之前完成文件的构造。
/* stage 4 */ // ! : file open before execve , or the check will fail FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win if(!fp) //see \x0d\x0a in win and \x0a in linux { perror("Cannot open file."); exit(1); } printf("open file success.\n"); fwrite("\x00\x00\x00\x00", 4, 1, fp); fclose(fp);
Stage 5 network
通过socket完成linux下进程间的通信,和在windows下基本类似。
具体可见我关于windows下的socket通信的简单实例。http://blog.csdn.net/qq_19550513/article/details/54965653
唯一区别在于windows下需要额外对winsock信息 WSAData 利用 WSAStartup() 函数进行初始化操作。
/* stage 5 */ sleep(2); // wait the server start int sockfd; char buf[10] = {0}; // buf to be sent int len; // len of avail buf struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9999); // port in argv['C'] servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) { perror("socket error."); exit(1); } if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 ) { perror("connect error."); exit(1); } printf("socket connect.\n"); strcpy(buf, "\xde\xad\xbe\xef"); len = strlen(buf); send(sockfd, buf, len, 0); close(sockfd);
Final Stage
整合后,ssh登录后在/tmp目录下我们自己的目录,利用vi写入c文件,再用gcc编译生成可执行文件。
到这里还没有结束,因为只有在当前目录guest才有写的权限,而flag路径为/home/input2/flag。
这里我们需要一个soft link指向flag文件,在当前目录 ln -s /home/input2/flag flag
即可。注意不能创建hard link,因为guest对flag文件本身是没有读写权限的。
c文件内容如下:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h> #include <unistd.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>int main(){ /* stage 1 */ char *argv[101] = {0}; for(int i = 1; i<100; ++i) argv[i] = "a"; argv[0] = "/home/input2/input"; argv['A'] = "\x00"; argv['B'] = "\x20\x0a\x0d"; argv['C'] = "9999"; //server port argv[100] = NULL; /* stage 3 */ char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL}; /* stage 4 */ // ! : file open before execve , or the check will fail FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win if(!fp) //see \x0d\x0a in win and \x0a in linux { perror("Cannot open file."); exit(1); } printf("open file success.\n"); fwrite("\x00\x00\x00\x00", 4, 1, fp); fclose(fp); /* stage 2 */ int pipe_stdin[2] = {-1, -1}; int pipe_stderr[2] = {-1, -1}; pid_t pid_child; if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 ) { perror("Cannot create the pipe."); exit(1); } #define STDIN_READ pipe_stdin[0] #define STDIN_WRITE pipe_stdin[1] #define STDERR_READ pipe_stderr[0] #define STDERR_WRITE pipe_stderr[1] if ( ( pid_child = fork() ) < 0 ) // do not forget the ()! { perror("Cannot create fork child."); exit(1); } if( pid_child == 0 ) { /*child proc*/ sleep(1); //wait to pipe link 0,2 close(STDIN_READ); close(STDERR_READ); write(STDIN_WRITE, "\x00\x0a\x00\xff", 4); write(STDERR_WRITE, "\x00\x0a\x02\xff", 4); } else { /*parent proc*/ close(STDIN_WRITE); close(STDERR_WRITE); dup2(STDIN_READ, 0); //dup to 0-stdin dup2(STDERR_READ, 2); //dup to 2-stderr printf("start execve input.\n"); execve("/home/input2/input", argv, envp); perror("Fail to execute the program"); exit(1); } printf("pipe link.\n"); /* stage 5 */ sleep(2); // wait the server start int sockfd; char buf[10] = {0}; // buf to be sent int len; // len of avail buf struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9999); // port in argv['C'] servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) { perror("socket error."); exit(1); } if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 ) { perror("connect error."); exit(1); } printf("socket connect.\n"); strcpy(buf, "\xde\xad\xbe\xef"); len = strlen(buf); send(sockfd, buf, len, 0); close(sockfd); return 0;}
最后的运行情况如下:
input2@ubuntu:/tmp/umiade$ ./setinput open file success.start execve input.Welcome to pwnable.krLet's see if you know how to give input to programJust give me correct inputs then you will get the flag :)Stage 1 clear!pipe link.Stage 2 clear!Stage 3 clear!Stage 4 clear!socket connect.Stage 5 clear!Mommy! I learned how to pass various input in Linux :)
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle]
- pwnable.kr [Toddler's Bottle] -fd
- Pwnable之[Toddler's Bottle]
- 含有无符号类型的表达式计算
- Firefox、Chrome、IE浏览器开发者工具的使用
- 浅谈viewpager是基本
- vs中debug和release版本的区别
- 网络编程-UDP多线程改进
- pwnable.kr [Toddler's Bottle]
- JAVA集合笔记
- 点击PushButton使得图片在QLabel上进行显示,并且自适应QLabel的控件大小
- NB-IoT是什么鬼
- 理解equals,==,hashcode,
- 网络编程-TCP
- JAVA基础--参数传递问题
- 一个关于闭包的小问题
- 「Caffe-01」利用lmdb获得binaryproto