本文共 7189 字,大约阅读时间需要 23 分钟。
下载程序(注意要用bash,zsh报错zsh: no matches found: input2@pwnable.kr:/home/input2/*)
scp -P 2222 -p input2@pwnable.kr:/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。
在中也有说明:
#includevoid 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如何实现进程通信呢?这里有一份很好的资料可以参考。
通过该文章我们可以知道,进程有自己独立的地址空间,需要通过kernel来交换数据。pipe函数可以在kernel中建立一个半双工的管道用于单向传递数据。一种利用pipe来传递数据的思路如下所示:1. 进程调用pipe创建自身的读写管道2. 调用fork生成子进程并close父进程的写端和子进程的读端建立进程间半双工管道3. 开始传递数据(参考上文链接中的图片)知道了如何在进程之间传递数据之后,只需要重定向子进程的描述符即可。dup2函数可以复制描述符,如下程序所示。#include#include void main(){ dup2(1,3); write(3,"123",3);//程序输出123}
因此,stage2的exp为:
#include#include #include 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#include #include 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最后修改程序路径,上传程序运行即可。#include #include #include #include #include #include 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 input2@pwnable.kr:/tmp/exp/恭喜通关。