真的推荐学 Linux C的IO一定得做做这个,真的能学到很多。

连接远程服务器把源码下下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#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;
}

源码大概意思就是给程序五种输入方法,如果程序从指定方法获得了对应的值那么这一关被判正确并且进入下一关,过去五关就能拿到flag。

第一关是命令行输入,要求命令行有100个参数,然后argv['A']="",argv['B']="0a0d",那就给命令行一百个参数,然后满足一下对应的要求即可,这里我们可以选择用execve去执行这个程序。然后程序构造命令行的参数传给第二个参数就可以通过第一关了。

1
2
3
4
5
6
7
8
9
10
11
int main(){

char *arg[101];
for(int i=0;i<100;i++){
arg[i]=(char *)malloc(0x10);
*arg[i]='\0';
}
arg[100]=NULL;
strcpy(arg['B'],"\x20\x0a\x0d");
execve("./input",arg,0);
}

运行就可以发现第一关过去了,这个源码可以放在自己电脑编译然后测试哦,等到自己能打通了再去考虑服务器的环境。

第二关虽然是stdio,但是它这里不仅要求读取特殊字节,还要从stderr中读入特定字节。这里卡了有点久,最后还是google了一下才得到的一个好方法,这里我也贴一下这位师傅这篇博客,我做这个也是参照这位师傅的博客做的链接,特此感谢!

这里呢选择fork一个子进程,然后父子进程用管道通信,父进程把管道接口定向到stdin和stderr后去执行input。子进程通过管道给父进程发送数据,然后父进程就能从stdin和stderr中得到数据了。

第三关也比较简单,从环境变量中获得对应数据。环境变量其实跟命令行参数输入差不多,execve第三个参数就是环境变量,传入一个指针数组,然后里面给一个xxx=yyy,那么它从环境变量中get(xxx)就会获得yyy。

第四个就更简单了,它从文件读你就对应写一个文件给它好了。

第五个稍微有点东西,是关于socket编程的,基本上也可以超,但是也遇到诸多问题,我一个exp并不能打通,我运行一个exp之后会被挂起,我再运行一个exp才能让第一个exp getflag,盲猜是它数据没接收到,我测试之后发现sleep也不管用,但是这一关已经能打过去了。

但是由于pwnable服务器的特殊机制,我们home 目录没有写的权限,所以得去寻找我们文件能落地的地方,那就是/tmp目录,这里的目录我们有写的权限,但是没有读的权限,所以我们在里面新建一个自己的文件夹,然后把input可执行文件和flag文件链接到这个目录来,最后上传exp执行就可以拿到flag了。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include <arpa/inet.h>
int main(){
FILE *fd=fopen("\x0a","w");
char buf[8];
memset(buf,0,sizeof(buf));
fwrite(buf,1,4,fd);


int pipe_stdin[2]={-1,-1},pipe_stderr[2]={-1,-1};
char *arg[101];
char *env[2];

for(int i=0;i<100;i++){
arg[i]=(char *)malloc(0x10);
*arg[i]='\0';
}
arg[100]=NULL;
strcpy(arg['B'],"\x20\x0a\x0d");
strcpy(arg['C'],"55555");
env[0]=(char *)malloc(0x10);
strcpy(env[0],"\xde\xad\xbe\xef=\xca\xfe\xba\xbe");
env[1]=NULL;
pipe(pipe_stdin);
pipe(pipe_stderr);
int p=fork();
if(p==0){
write(pipe_stdin[1],"\x00\x0a\x00\xff",4);
write(pipe_stderr[1],"\x00\x0a\x02\xff",4);
}
else{
dup2(pipe_stdin[0],0);
dup2(pipe_stderr[0],2);
execve("./input",arg,env);
}
//sleep(1);
int sockfd;
struct sockaddr_in saddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);

saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
saddr.sin_port = htons(55555);
printf("connenct\n");
if(connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
{
perror("Cannot connect to server!");
exit(-1);
}
write(sockfd, "\xde\xad\xbe\xef", 4);
close(sockfd);
}

flag:Mommy! I learned how to pass various input in Linux :)