pwnable.kr之input
连接到远程服务器:ssh input2@pwnable.kr -p2222
查看题目所给的代码,根据题目的要求我们要给出所有符合条件的输入才能拿到flag,本来想在输入上动点歪脑筋,结果输入有字节数的限制,然后再查看一下程序是否有保护
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
发现有栈溢出和数据执行保护,就绝了走捷径的心思。
查看代码知道有五步要走,为了检测每步是否正确,使用命令:scp -P 2222 input2@pwnable.kr:/home/input2/input /home/countfatcode/Code 把input文件下载到本地。
scp命令详解请看:https://blog.csdn.net/sanbingyutuoniao123/article/details/72420637
第一部分:argv
1 // argv 2 if(argc != 100) return 0; 3 if(strcmp(argv['A'],"\x00")) return 0; 4 if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0; 5 printf("Stage 1 clear!\n");
因为要传入的参数过多,可以用 execve() 函数来实现。
函数详解请看:https://www.cnblogs.com/jxhd1/p/6706701.html
于是可以构造c语言代码:
#include <stdio.h> #include <stdlib.h> int main() { char *argv[]={"input",[1 ... 99]="A",NULL}; argv['A']="\x00"; argv['B']="\x20\x0a\x0d"; execve("input",argv,NULL); return 0; }
编译运行,得到结果:
Welcome to pwnable.kr Let's see if you know how to give input to program Just give me correct inputs then you will get the flag :) Stage 1 clear!
第二部分:stdio
查看代码:
// 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");
第一个 read() 函数的文件描述符是0,表示从终端读入字符,第二个 read() 函数的文件描述符是 2,表示从标准错误输出读入字符,此时就要用到 pipe管道来解决。
pipe详解:https://blog.csdn.net/skyroben/article/details/71513385
因为有两个read,所以要创建两个管道,代码如下:
int fd1[2],fd2[2]; int ret1,ret2; //用来接受pipe函数的返回值 ret1=pipe(fd1); if(ret1==-1) { perror("pipe fd1 error:"); exit(1); } ret2=pipe(fd2); if(ret2==-1) { perror("pipe fd2 error:"); exit(1); }
再用 fork() 创建子进程,代码如下:
pid_t id=fork()
第二部分完整代码如下:
//stdio int fd1[2],fd2[2]; int ret1,ret2; //用来接受pipe函数的返回值 ret1=pipe(fd1); if(ret1==-1) { perror("pipe fd1 error:"); exit(1); } ret2=pipe(fd2); if(ret2==-1) { perror("pipe fd2 error:"); exit(1); } pid_t id=fork(); if(id==0) //子进程 { close(fd1[0]); close(fd2[0]); //关闭子进程读端 write(fd1[1],"\x00\x0a\x00\xff",4); write(fd2[1],"\x00\x0a\x02\xff",4); } else //父进程 { close(fd1[1]); close(fd2[1]); dup2(fd1[0],0); dup2(fd2[0],2);//文件描述符重定向 close(fd1[0]); close(fd2[0]); //env char *env[2]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL}; //file FILE *fp=fopen("\x0a","w"); if(fp==NULL) printf("文件打开失败\n"); else { fwrite("\x00\x00\x00\x00",4,1,fp); fclose(fp); execve("input",argv,env); } }
将一二部分的代码结合起来运行一下,发现正确。
第三部分:env
查看代码:
// env if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0; printf("Stage 3 clear!\n");
getenv() 函数的作用是获取环境变量名为 \xde\xad\xbe\xef 的变量值,这时就要用 execve() 的第三个参数来构造该环境变量,具体代码如下:
//env char *env[2]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL};
结合一二部分的代码运行,发现正确。
第四部分:file
查看代码:
// 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");
这部分是关于文件操作,具体代码如下:
//file FILE *fp=fopen("\x0a","w"); if(fp==NULL) printf("文件打开失败\n"); else { fwrite("\x00\x00\x00\x00",4,1,fp); fclose(fp); execve("input",argv,env); }
再结合第一二三部分的代码运行,正确。
第五部分:network
第五题主要涉及的是socket编程,题目给的代码是:
// 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");
观察知题目提供的是服务器端的代码,所以我们要通过编程实现客户端的代码
sleep(5); struct sockaddr_in server; int sockfd = socket(AF_INET,SOCK_STREAM,0); //创造一个套字节,客户端只有一个套字节 if(sockfd<0) //检查是否创造成功 { perror("Cannot create the socket:"); exit(1); } server.sin_family=AF_INET; //使用IPv4地址 server.sin_addr.s_addr=inet_addr("127.0.0.1"); //具体的IP地址 server.sin_port=htons(55555); //端口 if(connect(sockfd,(struct sockaddr*)&server,sizeof(server))<0) { perror("Problem connecting"); exit(1); } printf("Connected\n"); char buf[4]="\xde\xad\xbe\xef"; write(sockfd,buf,4); close(sockfd); return 0;
最后这题完整的代码如下:
#include <stdio.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdlib.h> #include <unistd.h> int main() { //argv char *argv[]={"./input",[1 ... 99]="A",'\0'}; argv['A']="\x00"; argv['B']="\x20\x0a\x0d"; argv['C']="55555"; //stdio int fd1[2],fd2[2]; int ret1,ret2; //用来接受pipe函数的返回值 ret1=pipe(fd1); if(ret1==-1) { perror("pipe fd1 error:"); exit(1); } ret2=pipe(fd2); if(ret2==-1) { perror("pipe fd2 error:"); exit(1); } pid_t id=fork(); if(id==0) //子进程 { close(fd1[0]); close(fd2[0]); //关闭子进程读端 write(fd1[1],"\x00\x0a\x00\xff",4); write(fd2[1],"\x00\x0a\x02\xff",4); } else //父进程 { close(fd1[1]); close(fd2[1]); dup2(fd1[0],0); dup2(fd2[0],2);//文件描述符重定向 close(fd1[0]); close(fd2[0]); //env char *env[2]={"\xde\xad\xbe\xef=\xca\xfe\xba\xbe",NULL}; //file FILE *fp=fopen("\x0a","w"); if(fp==NULL) printf("文件打开失败\n"); else { fwrite("\x00\x00\x00\x00",4,1,fp); fclose(fp); execve("input",argv,env); } } //network sleep(5); struct sockaddr_in server; int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { perror("Cannot create the socket:"); exit(1); } server.sin_family=AF_INET; server.sin_addr.s_addr=inet_addr("127.0.0.1"); server.sin_port=htons(55555); if(connect(sockfd,(struct sockaddr*)&server,sizeof(server))<0) { perror("Problem connecting"); exit(1); } printf("Connected\n"); char buf[4]="\xde\xad\xbe\xef"; write(sockfd,buf,4); close(sockfd); return 0; }
注意:我们需要用scp命令把代码文件上传到服务器的tmp文件夹,由于tmp文件夹里没有flag文件,还需要用ln命令进行连接,最后编译运行就可以得到flag。