pwnable.kr之input
文章最后更新时间为:2019年03月05日 15:51:25
题目:
ssh连接之后,发现目录有三个文件input
,input.c
,flag
。
熟悉的配方,先查看一下input.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;
}
很多条件需要满足,那么一个一个来解决。
1.argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
从以上可以知道:
- 这里需要100个参数。(程序名属于第一个参数)
- 第66个参数为空字符(A的ascci码为65,下标从0开始)
- 第67个参数为
\x20\x0a\x0d
(B的ascci码为66)
这里我们先用python来生成参数试试:
root@kali:~/桌面/pwn# ./input `python -c 'print "A"*64 + "\x00" + "\x20\x0a\x0d" + "A"*33'`
bash: 警告:命令替换:忽略输入中的 null 字节
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 :)
因为参数中存在\x00,也就是null,所以当程序识别参数时到这里的时候就断了,用python当输入是不行的,这里直接用C语言写:
#include <unistd.h>
int main(){
char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
execve("/home/input/input",argv,NULL);
}
编译运行输出: Stage 1 clear!
2.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");
程序必须从stdin读取\x00\x0a\x00\xff
,从stderr读取\x00\x0a\x02\xff
。对于这可以使用管道完成。可以参考http://unixwiz.net/techtips/remap-pipe-fds.html,我们将使用两个管道:一个用于stdin,一个用于stderr。具体做法如下:
- 创建两个管道pipe2stdin和pipe2stderr
- 创建子进程
子进程
- write
\x00\x0a\x00\xff
to pipe2stdin - write
\x00\x0a\x02\xff
to pipe2stderr
- write
父进程
- 将stdin映射到pipe2stdin
- 将stderr映射到pipe2stderr
- 执行input程序
增加以下代码:
int pipe2stdin[2] = {-1,-1};
int pipe2stderr[2] = {-1,-1};
pid_t childpid;
if ( pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0){ // 创建管道
perror("Cannot create the pipe");
exit(1);
}
if ( ( childpid = fork() ) < 0 ){ // 创建子进程
perror("Cannot fork");
exit(1);
}
if ( childpid == 0 ){
close(pipe2stdin[0]); close(pipe2stderr[0]); // 子进程关闭读管道
write(pipe2stdin[1],"\x00\x0a\x00\xff",4);
write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
}
else {
close(pipe2stdin[1]); close(pipe2stderr[1]); // 关闭写管道
dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // 映射stdin和stderr
close(pipe2stdin[0]); close(pipe2stderr[1]);
execve("/home/input/input",argv,NULL); // 执行程序
}
3.env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
getenv("\xde\xad\xbe\xef")意为查看名为"\xde\xad\xbe\xef"的环境变量的值,这里我们只需传递一个环境变量,名为"\xde\xad\xbe\xef",值为"\xca\xfe\xba\xbe"即可。
char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
execve("/home/input/input",argv,env);
4.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");
We need to open a file and write down 4 null bytes. Once we are done we can close it.
我们需要打开一个文件,并且在文件中写4个null。对于文件的操作需要在execve函数被调用之前完成。
FILE* fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
5.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");
程序变成了一个socket服务器,我们选择端口为55555:
argv['C'] = "55555";
现在,当我们执行程序时,它将侦听端口55555并等待一个传入连接。我们发送给程序的数据的前4个字节等于\xde\xad\xbe\xef
即可。
我们在程序中构建自己的客户端:
int sockfd;
struct sockaddr_in server;
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);
}
char buf[4] = "\xde\xad\xbe\xef";
write(sockfd,buf,4);
close(sockfd);
6.GOGOGO
最后创建c文件时,在/home/input2目录下是无法创建文件的,只有在/tmp目录下才有权限创建文件,而flag路径为/home/input2/flag,这意味着input将无法找到文件flag。为了解决这个问题,我们可以在工作目录中创建一个flag链接,并将其命名为flag。
ln -s /home/input2/flag flag
最终的完成程序为:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main (){
//Stage 1
char *argv[101] = {"/home/input/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "55555";
//Stage 2
int pipe2stdin[2] = {-1,-1};
int pipe2stderr[2] = {-1,-1};
pid_t childpid;
if ( pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0){
perror("Cannot create the pipe");
exit(1);
}
//Stage 4
FILE* fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
if ( ( childpid = fork() ) < 0 ){
perror("Cannot fork");
exit(1);
}
if ( childpid == 0 ){
/* Child process */
close(pipe2stdin[0]); close(pipe2stderr[0]); // Close pipes for reading
write(pipe2stdin[1],"\x00\x0a\x00\xff",4);
write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
}
else {
/* Parent process */
close(pipe2stdin[1]); close(pipe2stderr[1]); // Close pipes for writing
dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // Map to stdin and stderr
close(pipe2stdin[0]); close(pipe2stderr[1]); // Close write end (the fd has been copied before)
// Stage 3
char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
execve("/home/input/input",argv,env); // Execute the program
perror("Fail to execute the program");
exit(1);
}
// Stage 5
sleep(5);
int sockfd;
struct sockaddr_in server;
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 -P2222 /root/test.c [email protected]:/tmp/what
flag
Mommy! I learned how to pass various input in Linux :)
参考:https://werewblog.wordpress.com/2016/01/11/pwnable-kr-input/
学习了,涨姿势啊!!