最近做htb的时候遇到了一个bof的题,但是由于很久之前学过的东西有点想不起,所以就找到了一个pwn的平台的练习。做了几个发现实际难度并不是很大,直到做到这个input的时候,感觉难度一下子就上来了(可能是对于linux理解太菜了,昨天看了一天),所以想进行一下详细的分析,希望可以把这道题所有的知识点都吃透。
本篇文章想从目标c语言开始分析,分析对应的汇编,以及到python和c两种语言的poc编写。
#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; }
发现c中需要满足5个要求,方能得到flag,所以我们也分成5个部分进行分析
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");
这里c语言描述的是需要至少100个参数,并且argv['A']得是\x00
也就是终止符,argv['B']为\x20\x0a\x0d.
由于直接写在ida中比较方便也比较清楚,所以就直接标注在ida里了。
import subprocess #argv input_path="./input" argv=[] argv.append(input_path) for i in range(1,100): argv.append('A') argv[ord('A')]="" argv[ord('B')]="\x20\x0a\x0d" print(argv[ord('B')]) subprocess.Popen(argv)
效果如下:
#include <stdio.h> #include <unistd.h> int main(){ char *argv[101] = {"/root/Desktop/tools/bof/pwnablekr/input/input", [1 ... 99] = "A", NULL}; argv['A'] = "\x00"; argv['B'] = "\x20\x0a\x0d"; char *envp[]={0,NULL}; execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp); }
效果如下:
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");
这里作为c语言来说理解还是比较简单的,第一个read,读取标准输入,就是从键盘上获取一个标准输入,第二个read,获取标准错误输出,然后进行对比。真正的难点就是去如何构造这个标准错误输出了,输入还是比较好构造的。
感觉看了源码之后发现ida看起来也很清楚了
import subprocess import os,sys #argv input_path="./input" argv=[] argv.append(input_path) for i in range(1,100): argv.append('A') argv[ord('A')]="" argv[ord('B')]="\x20\x0a\x0d" print(argv[ord('B')]) r,w=os.pipe() r_e,w_e=os.pipe() os.write(w, "\x00\x0a\x00\xff") os.write(w_e, "\x00\x0a\x02\xff") subprocess.Popen(argv,stdin=r,stderr=r_e)
相对于c来说,还是觉的解决问题,python更快一点。
效果:
#include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> int main(){ char *argv[101] = {"/root/Desktop/tools/bof/pwnablekr/input/input", [1 ... 99] = "A", NULL}; argv['A'] = "\x00"; argv['B'] = "\x20\x0a\x0d"; char *envp[]={0,NULL}; int fd_0[2]; int fd_2[2]; pid_t child; if (pipe(fd_0)<0||pipe(fd_2)<0) { perror("error"); } write(fd_0[1],"\x00\x0a\x00\xff",4); write(fd_0[1],"\x00\x0a\x02\xff",4); dup2(fd_0[0],0); dup2(fd_0[0],2); execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp); }
效果如下:
这里还是要说一下的,不然的话就没什么意义了。
涉及到知识点:
pipe,linux下的管道问题,这个管道有俩,一个是输入,一个是输出,我们需要做的就是控制这个输出的内容。
根据查到的资料,可以利用先fock一个子进程,在子进程进行输入,然后主进程复制文件流,拿到输出。此时就满足了题目的要求。但是之后发现不需要fock子进程,直接写也是可以的,没什么问题。
最终的实质就是利用pipe构造了标准错误输出。
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0; printf("Stage 3 clear!\n");
这里的重点就是getenv,查了一下详细的说明如下。
该函数返回一个以 null 结尾的字符串,该字符串为被请求环境变量的值。如果该环境变量不存在,则返回 NULL。
我们的目标就是要构造一个环境变量
envs={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'} subprocess.Popen(argv,stdin=r,stderr=r_e,env=envs)
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL}; execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp);
c语言也是直接设置就可以。
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");
这里就是读取一个文件,然后读取的内容为\x00\x00\x00\x00即可。
with open("\x0a","w") as f: f.write("\x00\x00\x00\x00")
FILE* fp = fopen("\x0a", "w"); fwrite(buf, sizeof(buf) , 1, fp ); fclose(fp);
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");
这里的socket就是开了一个argv['C']的端口的socket,只要传进去一个 \xde\xad\xbe\xef就可以了。
s = socket.socket() port = 24444 s.connect(("127.0.0.1", port)) s.send("\xde\xad\xbe\xef")
ruct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons(1111); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 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); } strcpy(bufs, "\xde\xad\xbe\xef"); len = strlen(bufs); send(sockfd, bufs, len, 0); close(sockfd); return 0;
我用c语言写的时候写的有点问题,当程序监听的时候就不会出现socket连接的过程。所以就单独开了一个。
其实在做这道题的时候就一直纠结,到底要不要认真的看一下,在没有仔细研究的时候觉得这个东西很难,但是一旦弄懂了之后就会豁然开朗,感谢老婆的安慰和陪伴。
[公告]看雪论坛2020激励机制上线了:“活跃值”、“能力值”、“雪币”!发帖、回帖不减雪币了!赶紧回帖看看你的活跃值?
最后于 4小时前 被王嘟嘟编辑 ,原因: