0x00 前言最近做htb的时候遇到了一个bof的题,但是由于很久之前学过的东西有点想不起,所以就找到了一个pwn的平台的练习。做了几个发现实际难度并不是很大,直到做到这个input的时候,感觉难度一下子就上来了(可能是对于linux理解太菜了,昨天看了一天),所以想进行一下详细的分析,希望可以把这道题所有的知识点都吃透。
本篇文章想从目标c语言开始分析,分析对应的汇编,以及到python和c两种语言的poc编写。
0x01 题目环境 1.题目
2.目标c语言#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;
}
0x02 分析正文发现c中需要满足5个要求,方能得到flag,所以我们也分成5个部分进行分析
1.argv 1.1 目标c语言 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.
1.2 ida对比学习由于直接写在ida中比较方便也比较清楚,所以就直接标注在ida里了。
1.3 python pocimport 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)
效果如下:
1.4 c语言 poc#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);
}
效果如下:
2.stdio 2.1 目标c语言 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,获取标准错误输出,然后进行对比。真正的难点就是去如何构造这个标准错误输出了,输入还是比较好构造的。
2.2 ida对比学习 感觉看了源码之后发现ida看起来也很清楚了
2.3 python pocimport 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更快一点。 效果:
2.4 c语言 poc#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构造了标准错误输出。
3.env 3.1 目标cif(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
这里的重点就是getenv,查了一下详细的说明如下。 该函数返回一个以 null 结尾的字符串,该字符串为被请求环境变量的值。如果该环境变量不存在,则返回 NULL。 我们的目标就是要构造一个环境变量
3.2 ida对比学习
3.3 python pocenvs={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
subprocess.Popen(argv,stdin=r,stderr=r_e,env=envs)
3.4 c语言 pocchar *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp);
c语言也是直接设置就可以。
4.file 4.1 目标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即可。
4.2 ida对比学习
4.3 python pocwith open("\x0a","w") as f:
f.write("\x00\x00\x00\x00")
4.4 c语言 pocFILE* fp = fopen("\x0a", "w");
fwrite(buf, sizeof(buf) , 1, fp );
fclose(fp);
5 network 5.1 目标c语言 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就可以了。
5.2 ida 对比分析
5.3 python pocs = socket.socket()
port = 24444
s.connect(("127.0.0.1", port))
s.send("\xde\xad\xbe\xef")
5.4 c语言 pocruct 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连接的过程。所以就单独开了一个。
0x03 总结其实在做这道题的时候就一直纠结,到底要不要认真的看一下,在没有仔细研究的时候觉得这个东西很难,但是一旦弄懂了之后就会豁然开朗,感谢老婆的安慰和陪伴。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-6-21 21:14
被王嘟嘟编辑
,原因:
上传的附件: