思路
- 首先ssh连接后查看c代码是什么,发现这是一个考验linux基础输入知识的题,意味着没接触过的人要大量恶补linux的知识(说的就是自己)
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;
}
- 首先由代码可见,我们一共需要过五关,每一次都要成功,最后才能cat flag
首先介绍一个函数int execve(const char *filename, char *const argv[],char *const envp[])作用是启动新的进程,而进程的文件有filename指定,传入的参数为argv,并且有环境变量envp,这个函数将在下面中用到,同时需要注意argv和envp都需要以NULL结尾
第一关
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");
- 首先我们的argc要等于100,既要有100个参数,要在第“A”(即65)的位置为\x00,在第“B”(66)的位置是“\x20\x0a\x0d”,那么就简单的写入即可
#include
int main()
{
/*1*/
char *argv[101]={0};
for(int i=1;i<100;i++)
argv[i]="a";
argv[0]="/home/input2/input";
argv['A']="\x00";
argv['B']="\x20\x0a\x0d";
}
第二关
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");
- 这一次由之前做过的fd题可以知道read()的第一个参数表示文件描述符,0代表标准输入,2代表标准错误输出,输入容易控制,但要如何使得错误输出也被控制呢?就需要学会pipe管道和I/O重定向
pipe是为了在两个进程之间通信设置的,单方向的通信,一方面读,一方面写。以下是pipe的定义http://man7.org/linux/man-pages/man2/pipe.2.html
pipe通道是为两个进程通信服务的,但此时只有一个进程,因此我们要fork一个子进程,实现子进程和父进程的通信,以下是fork的原理
https://blog.csdn.net/jason314/article/details/5640969
I/O重定向指的是将已创建的文件描述符指向其他文件,以下是具体原理
http://www.cnblogs.com/weidagang2046/p/io-redirection.html
以下是一个很好的利用pipe通道实现I/O重定向的说明
http://unixwiz.net/techtips/remap-pipe-fds.html
- 因此我们创建两个pie通道,表示两个进程之间的通信,然后fork处子进程,在子进程中写入,在父进程处进行重定向,将从子进程读取的内容重定向到标准输入和标准错误输出
/*2*/
int pipe_stdin[2] = {-1, -1};
int pipe_stderr[2] = {-1, -1};
pid_t pid_child;
if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )
{
perror("Cannot create the pipe.");
}
#define STDIN_READ pipe_stdin[0]
#define STDIN_WRITE pipe_stdin[1]
#define STDERR_READ pipe_stderr[0]
#define STDERR_WRITE pipe_stderr[1]
if ( ( pid_child = fork() ) < 0 ) // do not forget the ()!
{
perror("Cannot create fork child.");
}
if(pid_child == 0) //in child
{
close(STDIN_READ);
close(STDERR_READ);//关闭输入
write(STDIN_WRITE,"\x00\x0a\x00\xff",4);
write(STDERR_WRITE,"\x00\x0a\x02\xff",4);
}
else //in father
{
close(STDERR_WRITE);
close(STDIN_WRITE);//关闭输出
dup2(STDIN_READ,0);
dup2(STDERR_READ,2);
}
printf("link\n");
}
第三关
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
- 首先是函数getenv,目得是使两个值相等
char *getenv(const char *name)是查找程序环境列表中参数name的值
- 而环境列表我们一般用不到,但是,作用是将一些源程序所在系统的位置等信息传入,那我们只需要传入环境变量时,将这一等式当作环境变量之一传入即可。
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};//第三关用到的环境变量
execve("/home/input2/input", argv, envp);
第四关
// 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");
- 简单的打开“\x0a”这个文件,写入信息"\x00\x00\x00\x00"即可
/*4*/
FILE *fp=fopen("\x0a","wb");
if(!fp)
{
perror("Can not open file.");
}
printf("Open file success.\n");
fwrite("\x00\x00\x00\x00",4,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");
// here's your flag
system("/bin/cat flag");
return 0;
}
由代码可知把这个input作为一个服务端,绑定的端口值是argv[‘C’]里面存的数值,当然代码用了一下atoi转化字符串为数字,比如存的是char 0,那么绑定的端口就是0号端口。然后验证的是传进来的某连接发送的内容是"\xde\xad\xbe\xef".
所以我们自己写的时候,可以给input指定一个端口,然后我们的程序再连接这个端口,发送"\xde\xad\xbe\xef"就好了。
argv['C'] = "9999";
sleep(2); // wait the server start
int sockfd;
char buf[10] = {0}; // buf to be sent
int len; // len of avail buf
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999); // port in argv['C']
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local
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);
}
printf("socket connect.\n");
strcpy(buf, "\xde\xad\xbe\xef");
len = strlen(buf);
send(sockfd, buf, len, 0);
close(sockfd);
return 0;
总结
- 做这道题时会感觉好多都不认识,但坚持下来就能学到很多的东西。
tips:在这道题提交的时候需要将自己的文件用scp上传到/tmp或者在服务器的tmp文件夹中新建文件夹,用vim写,使用“gcc 文件 -o 输出文件名称”来编译,同时还需要软连接,“ln -s /home/input2/flag flag”
#include//fopen perror
#include
#include //pipe execve
#include //strcmp
#include //bind
#include // linux socket
#include
#include
#include
int main()
{
/*1*/
char *argv[101]={0};
for(int i=1;i<100;i++)
argv[i]="a";
argv[0]="/home/input2/input";
argv['A']="\x00";
argv['B']="\x20\x0a\x0d";
argv['C'] = "9999";
argv[100] = NULL;
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};//第三关用到的环境变量
/*2*/
int pipe_stdin[2] = {-1, -1};
int pipe_stderr[2] = {-1, -1};
pid_t pid_child;
if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )
{
perror("Cannot create the pipe.");
}
#define STDIN_READ pipe_stdin[0]
#define STDIN_WRITE pipe_stdin[1]
#define STDERR_READ pipe_stderr[0]
#define STDERR_WRITE pipe_stderr[1]
if ( ( pid_child = fork() ) < 0 ) // do not forget the ()!
{
perror("Cannot create fork child.");
}
if(pid_child == 0) //in child
{
close(STDIN_READ);
close(STDERR_READ);//关闭输入
write(STDIN_WRITE,"\x00\x0a\x00\xff",4);
write(STDERR_WRITE,"\x00\x0a\x02\xff",4);
}
else //in father
{
close(STDERR_WRITE);
close(STDIN_WRITE);
dup2(STDIN_READ,0);
dup2(STDERR_READ,2);
execve("/home/input2/input", argv, envp);
}
printf("link\n");
/*4*/
FILE *fp=fopen("\x0a","wb");
if(!fp)
{
perror("Can not open file.");
}
printf("Open file success.\n");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
/*5*/
sleep(2); // wait the server start
int sockfd;
char buf[10] = {0}; // buf to be sent
int len; // len of avail buf
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999); // port in argv['C']
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local
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);
}
printf("socket connect.\n");
strcpy(buf, "\xde\xad\xbe\xef");
len = strlen(buf);
send(sockfd, buf, len, 0);
close(sockfd);
return 0;
}